dkala ducatated from dmenu
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
VITE_TOKEN_NAME = 'admin_token'
|
||||
VITE_REFRESH_TOKEN_NAME = 'admin_refresh_token'
|
||||
VITE_BASE_URL = 'https://api.danakcorp.com'
|
||||
# VITE_BASE_URL = 'http://192.168.1.100:4001'
|
||||
# VITE_BASE_URL = 'http://10.86.60.88:3500'
|
||||
@@ -136,6 +136,20 @@ export const Pages = {
|
||||
smsCountList: "/dmenu/sms-count/list",
|
||||
},
|
||||
},
|
||||
dkala: {
|
||||
icons: {
|
||||
list: "/dkala/icons/list",
|
||||
groupList: "/dkala/icons/group/list",
|
||||
},
|
||||
warnings: {
|
||||
list: "/dkala/reports/list",
|
||||
},
|
||||
shops: {
|
||||
list: "/dkala/shops/list",
|
||||
update: "/dkala/shops/update/",
|
||||
smsCountList: "/dkala/sms-count/list",
|
||||
},
|
||||
},
|
||||
subscriptions: {
|
||||
list: "/subscriptions/list",
|
||||
},
|
||||
|
||||
@@ -8,4 +8,6 @@ export const SideBarItemHasSubMenu = [
|
||||
"blog",
|
||||
"support",
|
||||
"accessLogs",
|
||||
"dmenu",
|
||||
"dkala",
|
||||
];
|
||||
|
||||
@@ -76,9 +76,11 @@
|
||||
"accessLogs": "لاگهای دسترسی",
|
||||
"icons": "آیکون ها",
|
||||
"dmenu": "دی منو",
|
||||
"dkala": "دی کالا",
|
||||
"groups_icon": "گروه های آیکون",
|
||||
"reports": "گزارش ها",
|
||||
"restaurants": "رستوران ها",
|
||||
"shops": "فروشگاه ها",
|
||||
"sms_count": "تعداد پیامک",
|
||||
"subscriptions": "اشتراک ها"
|
||||
},
|
||||
@@ -895,6 +897,29 @@
|
||||
"subscription_end_date": "تاریخ پایان اشتراک",
|
||||
"days_remaining": "چند روز مانده"
|
||||
},
|
||||
"shop": {
|
||||
"list_shop": "لیست فروشگاهها",
|
||||
"list_sms_count": "لیست تعداد پیامکهای فروشگاهها",
|
||||
"name": "نام",
|
||||
"phone": "شماره تماس",
|
||||
"address": "آدرس",
|
||||
"domain": "دامنه",
|
||||
"status": "وضعیت",
|
||||
"plan": "پلن",
|
||||
"created_at": "تاریخ ایجاد",
|
||||
"active": "فعال",
|
||||
"inactive": "غیرفعال",
|
||||
"admins": "مدیران",
|
||||
"add_admin": "افزودن مدیر",
|
||||
"sms_count": "تعداد پیامک",
|
||||
"role": "نقش",
|
||||
"slug": "اسلاگ",
|
||||
"established_year": "سال تاسیس",
|
||||
"subscription_id": "شناسه اشتراک",
|
||||
"subscription_start_date": "تاریخ شروع اشتراک",
|
||||
"subscription_end_date": "تاریخ پایان اشتراک",
|
||||
"days_remaining": "چند روز مانده"
|
||||
},
|
||||
"subscription": {
|
||||
"list_subscriptions": "لیست اشتراکها",
|
||||
"service": "سرویس",
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import * as api from "../service/IconService";
|
||||
import {
|
||||
GroupIconsResponse,
|
||||
ReportsResponse,
|
||||
SystemRolesResponse,
|
||||
ShopAdminsResponse,
|
||||
UpdateShopType,
|
||||
} from "../types/Types";
|
||||
|
||||
export const useGetIcons = () => {
|
||||
return useQuery({
|
||||
queryKey: ["dkala-icons"],
|
||||
queryFn: api.getIcons,
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateGroupIcon = () => {
|
||||
return useMutation({
|
||||
mutationFn: api.createGroupIcon,
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetGroupIcons = () => {
|
||||
return useQuery<GroupIconsResponse>({
|
||||
queryKey: ["dkala-group-icons"],
|
||||
queryFn: api.getGroupIcons,
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteGroupIcon = () => {
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => api.deleteGroupIcon(id),
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateIcon = () => {
|
||||
return useMutation({
|
||||
mutationFn: api.createIcon,
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteIcon = () => {
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => api.deleteIcon(id),
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetReports = () => {
|
||||
return useQuery<ReportsResponse>({
|
||||
queryKey: ["dkala-reports"],
|
||||
queryFn: api.getReports,
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetShops = (page: number = 1, limit: number = 10) => {
|
||||
return useQuery({
|
||||
queryKey: ["shops", page, limit],
|
||||
queryFn: () => api.getShops(page, limit),
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetShopAdmins = (shopId: string) => {
|
||||
return useQuery<ShopAdminsResponse>({
|
||||
queryKey: ["shop-admins", shopId],
|
||||
queryFn: () => api.getShopAdmins(shopId),
|
||||
enabled: !!shopId,
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetShopSmsCount = () => {
|
||||
return useQuery({
|
||||
queryKey: ["shop-sms-count"],
|
||||
queryFn: api.getShopSmsCount,
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteShopAdmin = () => {
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
shopId,
|
||||
adminId,
|
||||
}: {
|
||||
shopId: string;
|
||||
adminId: string;
|
||||
}) => api.deleteShopAdmin(shopId, adminId),
|
||||
});
|
||||
};
|
||||
|
||||
export const useAddAdminShop = () => {
|
||||
return useMutation({
|
||||
mutationFn: api.addAdminShop,
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetSystemRoles = () => {
|
||||
return useQuery<SystemRolesResponse>({
|
||||
queryKey: ["dkala-system-roles"],
|
||||
queryFn: api.getSystemRoles,
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateShop = () => {
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
id,
|
||||
params,
|
||||
}: {
|
||||
id: string;
|
||||
params: UpdateShopType;
|
||||
}) => api.updateShop(id, params),
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetShop = (id: string) => {
|
||||
return useQuery({
|
||||
queryKey: ["shop", id],
|
||||
queryFn: () => api.getShop(id),
|
||||
enabled: !!id,
|
||||
});
|
||||
};
|
||||
|
||||
export const useHardDeleteShop = () => {
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => api.hardDeleteShop(id),
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,125 @@
|
||||
import { type FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '../../../components/Button'
|
||||
import { Add } from 'iconsax-react'
|
||||
import CreateGroupIcon from './components/CreateGroupIcon'
|
||||
import { useGetGroupIcons, useDeleteGroupIcon } from '../hooks/useIconData'
|
||||
import Td from '../../../components/Td'
|
||||
import Input from '../../../components/Input'
|
||||
import PageLoading from '../../../components/PageLoading'
|
||||
import TrashWithConfrim from '../../../components/TrashWithConfrim'
|
||||
import { toast } from 'react-toastify'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
import { GroupIconType } from '../types/Types'
|
||||
|
||||
const GroupIconList: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const [showModal, setShowModal] = useState<boolean>(false)
|
||||
const [search, setSearch] = useState<string>('')
|
||||
const { data: groupIcons, isLoading, refetch } = useGetGroupIcons()
|
||||
const deleteGroupIcon = useDeleteGroupIcon()
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setShowModal(false)
|
||||
refetch()
|
||||
}
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
deleteGroupIcon.mutate(id, {
|
||||
onSuccess: () => {
|
||||
refetch()
|
||||
toast.success(t('success'))
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(error.response?.data?.error?.message[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const groups = (groupIcons as unknown as { data?: GroupIconType[] })?.data || []
|
||||
const filteredGroups = groups.filter((group: GroupIconType) =>
|
||||
group.name.toLowerCase().includes(search.toLowerCase())
|
||||
)
|
||||
|
||||
return (
|
||||
<div className='mt-4 min-h-[500px]'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div>
|
||||
{t('icon.group_list')}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className='w-[172px]'
|
||||
onClick={() => setShowModal(true)}
|
||||
>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<Add size={20} color='white' />
|
||||
<div>
|
||||
{t('icon.add_new_group')}
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className='mt-4'>
|
||||
<div className='flex justify-end items-center'>
|
||||
<div className='xl:w-[300px] w-full'>
|
||||
<Input
|
||||
variant='search'
|
||||
placeholder={t('ads.search')}
|
||||
className='bg-white w-full'
|
||||
onChangeSearchFinal={(value) => setSearch(value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
isLoading ?
|
||||
<PageLoading />
|
||||
:
|
||||
<div className='relative overflow-x-auto rounded-3xl mt-9 w-full'>
|
||||
<table className='w-full text-sm'>
|
||||
<thead className='thead'>
|
||||
<tr>
|
||||
<Td text={t('icon.name')} />
|
||||
<Td text={t('icon.icons_count')} />
|
||||
<Td text={t('icon.created_at')} />
|
||||
<Td text={t('')} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
filteredGroups.map((item: GroupIconType) => {
|
||||
return (
|
||||
<tr key={item.id} className='tr'>
|
||||
<Td text={item.name} />
|
||||
<Td text={item.icons?.length?.toString() || '0'} />
|
||||
<Td text={new Date(item.createdAt).toLocaleDateString('fa-IR')} />
|
||||
<Td text={t('')}>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<TrashWithConfrim
|
||||
onDelete={() => handleDelete(item.id)}
|
||||
isLoading={deleteGroupIcon.isPending}
|
||||
/>
|
||||
</div>
|
||||
</Td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
|
||||
<CreateGroupIcon
|
||||
open={showModal}
|
||||
close={handleCloseModal}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default GroupIconList
|
||||
@@ -0,0 +1,144 @@
|
||||
import { useState, type FC } from 'react'
|
||||
import { useGetIcons, useDeleteIcon } from '../hooks/useIconData'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '../../../components/Button'
|
||||
import { Add } from 'iconsax-react'
|
||||
import CreateIcon from './components/CreateIcon'
|
||||
import Td from '../../../components/Td'
|
||||
import Input from '../../../components/Input'
|
||||
import PageLoading from '../../../components/PageLoading'
|
||||
import TrashWithConfrim from '../../../components/TrashWithConfrim'
|
||||
import { toast } from 'react-toastify'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
import { IconType } from '../types/Types'
|
||||
import moment from 'moment-jalaali'
|
||||
|
||||
const IconsList: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const [showModal, setShowModal] = useState<boolean>(false)
|
||||
const [search, setSearch] = useState<string>('')
|
||||
const { data: iconsData, isLoading, refetch } = useGetIcons()
|
||||
const deleteIcon = useDeleteIcon()
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setShowModal(false)
|
||||
refetch()
|
||||
}
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
deleteIcon.mutate(id, {
|
||||
onSuccess: () => {
|
||||
refetch()
|
||||
toast.success(t('success'))
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(error.response?.data?.error?.message[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const icons = (iconsData as unknown as { data?: IconType[] })?.data || []
|
||||
const filteredIcons = icons.filter((icon: IconType) =>
|
||||
icon.url.toLowerCase().includes(search.toLowerCase()) ||
|
||||
icon.group?.name?.toLowerCase().includes(search.toLowerCase())
|
||||
)
|
||||
|
||||
return (
|
||||
<div className='mt-4 min-h-[500px]'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div>
|
||||
{t('icon.list_icon')}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className='w-[172px]'
|
||||
onClick={() => setShowModal(true)}
|
||||
>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<Add size={20} color='white' />
|
||||
<div>
|
||||
{t('icon.add_new_icon')}
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className='mt-4'>
|
||||
<div className='flex justify-end items-center'>
|
||||
<div className='xl:w-[300px] w-full'>
|
||||
<Input
|
||||
variant='search'
|
||||
placeholder={t('ads.search')}
|
||||
className='bg-white w-full'
|
||||
onChangeSearchFinal={(value) => setSearch(value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
isLoading ?
|
||||
<PageLoading />
|
||||
:
|
||||
<div className='relative overflow-x-auto rounded-3xl mt-9 w-full'>
|
||||
<table className='w-full text-sm'>
|
||||
<thead className='thead'>
|
||||
<tr>
|
||||
<Td text={t('icon.url')} />
|
||||
<Td text={t('icon.group')} />
|
||||
<Td text={t('icon.created_at')} />
|
||||
<Td text={''} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
filteredIcons.map((item: IconType) => {
|
||||
return (
|
||||
<tr key={item.id} className='tr'>
|
||||
<Td text={''}>
|
||||
<div className='flex items-center gap-3'>
|
||||
<div className='size-10 rounded-lg overflow-hidden bg-gray-100 flex items-center justify-center'>
|
||||
<img
|
||||
src={item.url}
|
||||
alt={item.url}
|
||||
className='size-full object-contain'
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement
|
||||
target.style.display = 'none'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className='truncate max-w-xs' title={item.url}>
|
||||
{item.url}
|
||||
</div>
|
||||
</div>
|
||||
</Td>
|
||||
<Td text={item.group?.name || '-'} />
|
||||
<Td text={moment(item.createdAt).format('jYYYY-jMM-jDD')} />
|
||||
<Td text={''}>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<TrashWithConfrim
|
||||
onDelete={() => handleDelete(item.id)}
|
||||
isLoading={deleteIcon.isPending}
|
||||
/>
|
||||
</div>
|
||||
</Td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
|
||||
<CreateIcon
|
||||
open={showModal}
|
||||
close={handleCloseModal}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconsList
|
||||
@@ -0,0 +1,98 @@
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '../../../../components/Button'
|
||||
import DefaulModal from '../../../../components/DefaulModal'
|
||||
import Input from '../../../../components/Input'
|
||||
import { useFormik } from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import { toast } from 'react-toastify'
|
||||
import { TickCircle } from 'iconsax-react'
|
||||
import { useCreateGroupIcon } from '../../hooks/useIconData'
|
||||
import { CreateGroupIconType } from '../../types/Types'
|
||||
import { ErrorType } from '../../../../helpers/types'
|
||||
|
||||
interface CreateGroupIconProps {
|
||||
open: boolean
|
||||
close: () => void
|
||||
onSuccess?: () => void
|
||||
}
|
||||
|
||||
const CreateGroupIcon: FC<CreateGroupIconProps> = ({ open, close, onSuccess }) => {
|
||||
const { t } = useTranslation('global')
|
||||
const createGroupIcon = useCreateGroupIcon()
|
||||
|
||||
const formik = useFormik<CreateGroupIconType>({
|
||||
initialValues: {
|
||||
name: ''
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
name: Yup.string()
|
||||
.required(t('errors.required'))
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
createGroupIcon.mutate(values, {
|
||||
onSuccess: () => {
|
||||
formik.resetForm()
|
||||
close()
|
||||
toast.success(t('success'))
|
||||
onSuccess?.()
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(error.response?.data?.error?.message[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const handleClose = () => {
|
||||
formik.resetForm()
|
||||
close()
|
||||
}
|
||||
|
||||
return (
|
||||
<DefaulModal
|
||||
open={open}
|
||||
close={handleClose}
|
||||
title_header={t('icon.add_new_group')}
|
||||
isHeader
|
||||
>
|
||||
<div className='mt-6'>
|
||||
<Input
|
||||
label={t('title')}
|
||||
className='bg-white bg-opacity-30'
|
||||
name='name'
|
||||
value={formik.values.name}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.name && formik.errors.name ? formik.errors.name : ''}
|
||||
/>
|
||||
|
||||
<div className='mt-12 border-t border-border pt-5 flex justify-end'>
|
||||
<div className='flex gap-5 items-center'>
|
||||
<Button
|
||||
className='w-[150px] bg-white bg-opacity-15 text-description'
|
||||
label={t('cancel')}
|
||||
onClick={handleClose}
|
||||
/>
|
||||
|
||||
<Button
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={createGroupIcon.isPending}
|
||||
>
|
||||
<div className='flex gap-2'>
|
||||
<TickCircle
|
||||
size={20}
|
||||
color='white'
|
||||
/>
|
||||
<div>
|
||||
{t('submit')}
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DefaulModal>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateGroupIcon
|
||||
@@ -0,0 +1,139 @@
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '../../../../components/Button'
|
||||
import DefaulModal from '../../../../components/DefaulModal'
|
||||
import UploadBox from '../../../../components/UploadBox'
|
||||
import Select from '../../../../components/Select'
|
||||
import { useFormik } from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import { toast } from 'react-toastify'
|
||||
import { TickCircle } from 'iconsax-react'
|
||||
import { useCreateIcon, useGetGroupIcons } from '../../hooks/useIconData'
|
||||
import { useSingleUpload } from '../../../service/hooks/useServiceData'
|
||||
import { CreateIconType, GroupIconType } from '../../types/Types'
|
||||
import { ErrorType } from '../../../../helpers/types'
|
||||
|
||||
interface CreateIconProps {
|
||||
open: boolean
|
||||
close: () => void
|
||||
onSuccess?: () => void
|
||||
}
|
||||
|
||||
const CreateIcon: FC<CreateIconProps> = ({ open, close, onSuccess }) => {
|
||||
const { t } = useTranslation('global')
|
||||
const [file, setFile] = useState<File>()
|
||||
const createIcon = useCreateIcon()
|
||||
const singleUpload = useSingleUpload()
|
||||
const { data: groupIcons } = useGetGroupIcons()
|
||||
|
||||
const groups = (groupIcons as unknown as { data?: GroupIconType[] })?.data || []
|
||||
const groupOptions = groups.map((group: GroupIconType) => ({
|
||||
value: group.id,
|
||||
label: group.name
|
||||
}))
|
||||
|
||||
const handleCreateIcon = (values: CreateIconType) => {
|
||||
createIcon.mutate(values, {
|
||||
onSuccess: () => {
|
||||
formik.resetForm()
|
||||
setFile(undefined)
|
||||
close()
|
||||
toast.success(t('success'))
|
||||
onSuccess?.()
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(error.response?.data?.error?.message[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const formik = useFormik<CreateIconType>({
|
||||
initialValues: {
|
||||
url: '',
|
||||
groupId: ''
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
groupId: Yup.string()
|
||||
.required(t('errors.required'))
|
||||
}),
|
||||
onSubmit: async (values) => {
|
||||
if (!file) {
|
||||
toast.error(t('errors.required'))
|
||||
return
|
||||
}
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
await singleUpload.mutateAsync(formData, {
|
||||
onSuccess: (data) => {
|
||||
values.url = data?.data?.url
|
||||
handleCreateIcon(values)
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(error.response?.data?.error?.message[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const handleClose = () => {
|
||||
formik.resetForm()
|
||||
setFile(undefined)
|
||||
close()
|
||||
}
|
||||
|
||||
return (
|
||||
<DefaulModal
|
||||
open={open}
|
||||
close={handleClose}
|
||||
title_header={t('icon.add_new_icon')}
|
||||
isHeader
|
||||
>
|
||||
<div className='mt-6'>
|
||||
<UploadBox
|
||||
label={t('icon.url')}
|
||||
onChange={(files) => setFile(files[0])}
|
||||
isReset={!open}
|
||||
accept="image/svg+xml"
|
||||
/>
|
||||
|
||||
<div className='mt-4'>
|
||||
<Select
|
||||
label={t('icon.group')}
|
||||
className='bg-white bg-opacity-30'
|
||||
items={groupOptions}
|
||||
placeholder={t('icon.select_group')}
|
||||
{...formik.getFieldProps('groupId')}
|
||||
error_text={formik.touched.groupId && formik.errors.groupId ? formik.errors.groupId : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-12 border-t border-border pt-5 flex justify-end'>
|
||||
<div className='flex gap-5 items-center'>
|
||||
<Button
|
||||
className='w-[150px] bg-white bg-opacity-15 text-description'
|
||||
label={t('cancel')}
|
||||
onClick={handleClose}
|
||||
/>
|
||||
|
||||
<Button
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={createIcon.isPending || singleUpload.isPending}
|
||||
>
|
||||
<div className='flex gap-2'>
|
||||
<TickCircle
|
||||
size={20}
|
||||
color='white'
|
||||
/>
|
||||
<div>
|
||||
{t('submit')}
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DefaulModal>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateIcon
|
||||
@@ -0,0 +1,144 @@
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useGetReports } from '../hooks/useIconData'
|
||||
import PageLoading from '../../../components/PageLoading'
|
||||
import Td from '../../../components/Td'
|
||||
import { ReportItem } from '../types/Types'
|
||||
import moment from 'moment-jalaali'
|
||||
import { Eye } from 'iconsax-react'
|
||||
import DefaulModal from '../../../components/DefaulModal'
|
||||
|
||||
const WarningsList: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const { data: reports, isLoading } = useGetReports()
|
||||
const [selectedReport, setSelectedReport] = useState<ReportItem | null>(null)
|
||||
const [isModalOpen, setIsModalOpen] = useState<boolean>(false)
|
||||
|
||||
const reportsList = (reports as { data?: ReportItem[] })?.data || []
|
||||
|
||||
const handleViewReport = (report: ReportItem) => {
|
||||
setSelectedReport(report)
|
||||
setIsModalOpen(true)
|
||||
}
|
||||
|
||||
const closeModal = () => {
|
||||
setIsModalOpen(false)
|
||||
setSelectedReport(null)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mt-4'>
|
||||
<div className='flex w-full justify-between items-center'>
|
||||
<div>
|
||||
{t('report.list_report')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
isLoading ?
|
||||
<PageLoading />
|
||||
:
|
||||
<div className='relative overflow-x-auto rounded-3xl mt-9 w-full'>
|
||||
<table className='w-full text-sm '>
|
||||
<thead className='thead'>
|
||||
<tr>
|
||||
<Td text={t('report.subject')} />
|
||||
<Td text={t('report.content')} />
|
||||
<Td text={t('report.phone')} />
|
||||
<Td text={t('report.scope')} />
|
||||
<Td text={t('report.status')} />
|
||||
<Td text={t('report.date')} />
|
||||
<Td text={t('report.detail')} />
|
||||
<Td text={''} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
reportsList.map((item: ReportItem) => {
|
||||
return (
|
||||
<tr key={item.id} className='tr'>
|
||||
<Td text={item.subject} />
|
||||
<Td text={item.content?.substring(0, 50) + (item.content?.length > 50 ? '...' : '')} />
|
||||
<Td text={item.phone || '-'} />
|
||||
<Td text={item.scope} />
|
||||
<Td text={item.status} />
|
||||
<Td text={''}>
|
||||
<div className='dltr text-right'>
|
||||
{moment(item.createdAt).format('jYYYY-jMM-jDD HH:mm')}
|
||||
</div>
|
||||
</Td>
|
||||
<Td text={''}>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<Eye
|
||||
size={20}
|
||||
color='#8C90A3'
|
||||
className='cursor-pointer'
|
||||
onClick={() => handleViewReport(item)}
|
||||
/>
|
||||
</div>
|
||||
</Td>
|
||||
<Td text={''} />
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{reportsList.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<div className="text-gray-500 text-lg">هیچ گزارشی یافت نشد</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
|
||||
{/* مودال نمایش متن کامل گزارش */}
|
||||
<DefaulModal
|
||||
open={isModalOpen}
|
||||
close={closeModal}
|
||||
isHeader={true}
|
||||
title_header={t('report.detail')}
|
||||
width={600}
|
||||
>
|
||||
{selectedReport && (
|
||||
<div className="space-y-6 mt-6">
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-600 mb-2">{t('report.subject')}</div>
|
||||
<div className="text-base text-gray-900">{selectedReport.subject}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-600 mb-2">{t('report.content')}</div>
|
||||
<div className="text-base text-gray-900 whitespace-pre-wrap">{selectedReport.content}</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{selectedReport.phone && (
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-600 mb-2">{t('report.phone')}</div>
|
||||
<div className="text-base text-gray-900">{selectedReport.phone}</div>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-600 mb-2">{t('report.scope')}</div>
|
||||
<div className="text-base text-gray-900">{selectedReport.scope}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-600 mb-2">{t('report.status')}</div>
|
||||
<div className="text-base text-gray-900">{selectedReport.status}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-600 mb-2">{t('report.date')}</div>
|
||||
<div className="text-base text-gray-900 dltr text-right">
|
||||
{moment(selectedReport.createdAt).format('jYYYY-jMM-jDD HH:mm')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DefaulModal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default WarningsList
|
||||
@@ -0,0 +1,121 @@
|
||||
import axios from "../../../config/axios";
|
||||
import {
|
||||
CreateGroupIconType,
|
||||
CreateIconType,
|
||||
GroupIconsResponse,
|
||||
IconsResponse,
|
||||
ReportsResponse,
|
||||
ShopsResponse,
|
||||
ShopResponse,
|
||||
ShopSmsCountResponse,
|
||||
AddAdminShopType,
|
||||
SystemRolesResponse,
|
||||
ShopAdminsResponse,
|
||||
UpdateShopType,
|
||||
} from "../types/Types";
|
||||
|
||||
export const getIcons = async (): Promise<IconsResponse> => {
|
||||
const { data } = await axios.get("/admin/dkala/icons");
|
||||
return data;
|
||||
};
|
||||
|
||||
export const createGroupIcon = async (params: CreateGroupIconType) => {
|
||||
const { data } = await axios.post("/admin/dkala/icons/groups", params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getGroupIcons = async (): Promise<GroupIconsResponse> => {
|
||||
const { data } = await axios.get("/admin/dkala/icons/groups");
|
||||
return data;
|
||||
};
|
||||
|
||||
export const deleteGroupIcon = async (id: string) => {
|
||||
const { data } = await axios.delete(`/admin/dkala/icons/groups/${id}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const createIcon = async (params: CreateIconType) => {
|
||||
const { data } = await axios.post("/admin/dkala/icons", params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const deleteIcon = async (id: string) => {
|
||||
const { data } = await axios.delete(`/admin/dkala/icons/${id}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getReports = async (): Promise<ReportsResponse> => {
|
||||
const { data } = await axios.get("/admin/dkala/contacts");
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getShops = async (
|
||||
page: number = 1,
|
||||
limit: number = 10
|
||||
): Promise<ShopsResponse> => {
|
||||
const { data } = await axios.get(
|
||||
`/admin/dkala/shops?page=${page}&limit=${limit}`
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getShopAdmins = async (
|
||||
shopId: string
|
||||
): Promise<ShopAdminsResponse> => {
|
||||
const { data } = await axios.get(
|
||||
`/admin/dkala/shops/${shopId}/admins`
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getShopSmsCount = async (): Promise<
|
||||
ShopSmsCountResponse
|
||||
> => {
|
||||
const { data } = await axios.get(`/admin/dkala/shops/sms-count`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const deleteShopAdmin = async (
|
||||
shopId: string,
|
||||
adminId: string
|
||||
) => {
|
||||
const { data } = await axios.delete(
|
||||
`/admin/dkala/shops/${shopId}/admins/${adminId}`
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const addAdminShop = async (params: AddAdminShopType) => {
|
||||
const { data } = await axios.post(
|
||||
`/admin/dkala/shops/${params.shopId}/admins`,
|
||||
params
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getSystemRoles = async (): Promise<SystemRolesResponse> => {
|
||||
const { data } = await axios.get(`/admin/dkala/system-roles`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const updateShop = async (
|
||||
id: string,
|
||||
params: UpdateShopType
|
||||
) => {
|
||||
const { data } = await axios.patch(`/admin/dkala/shops/${id}`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getShop = async (
|
||||
id: string
|
||||
): Promise<ShopResponse> => {
|
||||
const { data } = await axios.get(`/admin/dkala/shops/${id}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const hardDeleteShop = async (id: string) => {
|
||||
const { data } = await axios.delete(
|
||||
`/admin/dkala/shops/${id}/hard-delete`
|
||||
);
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,175 @@
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useGetShops, useHardDeleteShop } from '../hooks/useIconData'
|
||||
import PageLoading from '../../../components/PageLoading'
|
||||
import Td from '../../../components/Td'
|
||||
import { ShopType, ShopsResponse } from '../types/Types'
|
||||
import moment from 'moment-jalaali'
|
||||
import { Edit } from 'iconsax-react'
|
||||
import { toast } from 'react-toastify'
|
||||
import Button from '../../../components/Button'
|
||||
import ShopAdminsModal from './components/ShopAdminsModal'
|
||||
import Pagination from '../../../components/Pagination'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Pages } from '../../../config/Pages'
|
||||
import TrashWithConfrim from '../../../components/TrashWithConfrim'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
|
||||
const ShopsList: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const queryClient = useQueryClient()
|
||||
const [page, setPage] = useState<number>(1)
|
||||
const limit = 10
|
||||
const { data: shops, isLoading } = useGetShops(page, limit)
|
||||
const [selectedShopId, setSelectedShopId] = useState<string>('')
|
||||
const [isAdminsModalOpen, setIsAdminsModalOpen] = useState<boolean>(false)
|
||||
const deleteShop = useHardDeleteShop()
|
||||
|
||||
const shopsList = (shops as ShopsResponse | undefined)?.data?.shops || []
|
||||
const totalPages = (shops as ShopsResponse | undefined)?.data?.pager?.totalPages || 1
|
||||
|
||||
const handleOpenAdminsModal = (shopId: string) => {
|
||||
setSelectedShopId(shopId)
|
||||
setIsAdminsModalOpen(true)
|
||||
}
|
||||
|
||||
const handleCloseAdminsModal = () => {
|
||||
setIsAdminsModalOpen(false)
|
||||
setSelectedShopId('')
|
||||
}
|
||||
|
||||
const calculateDaysRemaining = (endDate: string): string => {
|
||||
const end = moment(endDate)
|
||||
const now = moment()
|
||||
const diff = end.diff(now, 'days')
|
||||
if (diff < 0) {
|
||||
return 'منقضی شده'
|
||||
}
|
||||
return `${diff} روز`
|
||||
}
|
||||
|
||||
const formatJalaliDate = (date: string): string => {
|
||||
return moment(date).format('jYYYY-jMM-jDD')
|
||||
}
|
||||
|
||||
const handleDeleteShop = (shopId: string) => {
|
||||
deleteShop.mutate(shopId, {
|
||||
onSuccess: () => {
|
||||
toast.success('فروشگاه با موفقیت حذف شد')
|
||||
queryClient.invalidateQueries({ queryKey: ['shops'] })
|
||||
},
|
||||
onError: () => {
|
||||
toast.error('خطا در حذف فروشگاه')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mt-4'>
|
||||
<div className='flex w-full justify-between items-center'>
|
||||
<div>
|
||||
{t('shop.list_shop')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
isLoading ?
|
||||
<PageLoading />
|
||||
:
|
||||
<div className='relative overflow-x-auto rounded-3xl mt-9 w-full'>
|
||||
<table className='w-full text-sm '>
|
||||
<thead className='thead'>
|
||||
<tr>
|
||||
<Td text={t('shop.name')} />
|
||||
<Td text={t('shop.phone')} />
|
||||
<Td text={t('shop.slug')} />
|
||||
<Td text={t('shop.plan')} />
|
||||
<Td text={t('shop.status')} />
|
||||
<Td text={t('shop.subscription_start_date')} />
|
||||
<Td text={t('shop.subscription_end_date')} />
|
||||
<Td text={t('shop.days_remaining')} />
|
||||
<Td text={''} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
shopsList.map((item: ShopType) => {
|
||||
return (
|
||||
<tr key={item.id} className='tr'>
|
||||
<Td text={item.name} />
|
||||
<Td text={item.phone || '-'} />
|
||||
<Td text={item?.slug || '-'} />
|
||||
<Td text={item.plan || '-'} />
|
||||
<Td text={''}>
|
||||
<div className={`w-fit px-3 py-1 rounded-2xl text-xs ${item.isActive
|
||||
? 'bg-green-100 text-green-700'
|
||||
: 'bg-red-100 text-red-700'
|
||||
}`}>
|
||||
{item.isActive ? t('shop.active') : t('shop.inactive')}
|
||||
</div>
|
||||
</Td>
|
||||
<Td text={''}>
|
||||
<div className='dltr text-right'>
|
||||
{formatJalaliDate(item.subscriptionStartDate)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td text={''}>
|
||||
<div className='dltr text-right'>
|
||||
{formatJalaliDate(item.subscriptionEndDate)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td text={''}>
|
||||
<div className='dltr text-right'>
|
||||
{calculateDaysRemaining(item.subscriptionEndDate)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td text={''}>
|
||||
<div className='flex items-center gap-4'>
|
||||
<TrashWithConfrim
|
||||
onDelete={() => handleDeleteShop(item.id)}
|
||||
isLoading={deleteShop.isPending}
|
||||
/>
|
||||
<Link
|
||||
to={Pages.dkala.shops.update + item.id}
|
||||
>
|
||||
<Edit
|
||||
size={20}
|
||||
color='#8C90A3'
|
||||
/>
|
||||
</Link>
|
||||
<Button
|
||||
className='w-fit px-4'
|
||||
onClick={() => handleOpenAdminsModal(item.id)}
|
||||
>
|
||||
{t('shop.admins')}
|
||||
</Button>
|
||||
</div>
|
||||
</Td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{totalPages > 1 && (
|
||||
<Pagination
|
||||
currentPage={page}
|
||||
totalPages={totalPages}
|
||||
onPageChange={setPage}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
|
||||
<ShopAdminsModal
|
||||
open={isAdminsModalOpen}
|
||||
close={handleCloseAdminsModal}
|
||||
shopId={selectedShopId}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ShopsList
|
||||
@@ -0,0 +1,53 @@
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useGetShopSmsCount } from '../hooks/useIconData'
|
||||
import PageLoading from '../../../components/PageLoading'
|
||||
import Td from '../../../components/Td'
|
||||
import { ShopSmsCountItem } from '../types/Types'
|
||||
|
||||
const SmsCountList: FC = () => {
|
||||
const { t } = useTranslation('global')
|
||||
const { data: smsCount, isLoading } = useGetShopSmsCount()
|
||||
|
||||
const smsCountList = (smsCount as { data?: ShopSmsCountItem[] })?.data || []
|
||||
|
||||
return (
|
||||
<div className='mt-4'>
|
||||
<div className='flex w-full justify-between items-center'>
|
||||
<div>
|
||||
{t('shop.list_sms_count')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
isLoading ?
|
||||
<PageLoading />
|
||||
:
|
||||
<div className='relative overflow-x-auto rounded-3xl mt-9 w-full'>
|
||||
<table className='w-full text-sm '>
|
||||
<thead className='thead'>
|
||||
<tr>
|
||||
<Td text={t('shop.name')} />
|
||||
<Td text={t('shop.sms_count')} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
smsCountList.map((item: ShopSmsCountItem) => {
|
||||
return (
|
||||
<tr key={item.shopId} className='tr'>
|
||||
<Td text={item.shopName} />
|
||||
<Td text={item.smsCount.toString()} />
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SmsCountList
|
||||
@@ -0,0 +1,202 @@
|
||||
import { useFormik } from 'formik'
|
||||
import { FC, useEffect } from 'react'
|
||||
import { UpdateShopType, ShopResponse } from '../types/Types'
|
||||
import { useParams, useNavigate } from 'react-router-dom'
|
||||
import { useGetShop, useUpdateShop } from '../hooks/useIconData'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Input from '../../../components/Input'
|
||||
import Select from '../../../components/Select'
|
||||
import SwitchComponent from '../../../components/Switch'
|
||||
import Button from '../../../components/Button'
|
||||
import DatePickerComponent from '../../../components/DatePicker'
|
||||
import PageLoading from '../../../components/PageLoading'
|
||||
import { toast } from 'react-toastify'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
import { TickCircle } from 'iconsax-react'
|
||||
import { Pages } from '../../../config/Pages'
|
||||
import moment from 'moment-jalaali'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
|
||||
const UpdateShop: FC = () => {
|
||||
|
||||
const { id } = useParams()
|
||||
const { t } = useTranslation('global')
|
||||
const navigate = useNavigate()
|
||||
const queryClient = useQueryClient()
|
||||
const { data: shopData, isLoading } = useGetShop(id || '')
|
||||
const updateShop = useUpdateShop()
|
||||
|
||||
const shop = (shopData as ShopResponse | undefined)?.data
|
||||
|
||||
const formik = useFormik<UpdateShopType>({
|
||||
initialValues: {
|
||||
name: undefined,
|
||||
slug: undefined,
|
||||
establishedYear: undefined,
|
||||
phone: undefined,
|
||||
plan: undefined,
|
||||
subscriptionId: undefined,
|
||||
subscriptionEndDate: undefined,
|
||||
subscriptionStartDate: undefined,
|
||||
isActive: undefined,
|
||||
},
|
||||
onSubmit: (values) => {
|
||||
if (!id) return
|
||||
|
||||
const params: UpdateShopType = {
|
||||
...values,
|
||||
subscriptionStartDate: values.subscriptionStartDate
|
||||
? moment(values.subscriptionStartDate, 'jYYYY/jMM/jDD').format('YYYY-MM-DD')
|
||||
: undefined,
|
||||
subscriptionEndDate: values.subscriptionEndDate
|
||||
? moment(values.subscriptionEndDate, 'jYYYY/jMM/jDD').format('YYYY-MM-DD')
|
||||
: undefined,
|
||||
}
|
||||
|
||||
updateShop.mutate(
|
||||
{ id, params },
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success(t('success'))
|
||||
queryClient.invalidateQueries({ queryKey: ['shops'] })
|
||||
queryClient.invalidateQueries({ queryKey: ['shop', id] })
|
||||
navigate(Pages.dkala.shops.list)
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(error.response?.data?.error?.message?.[0] || 'خطا در بروزرسانی فروشگاه')
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (shop) {
|
||||
formik.setValues({
|
||||
name: shop.name || undefined,
|
||||
slug: shop.slug || undefined,
|
||||
establishedYear: shop.establishedYear || undefined,
|
||||
phone: shop.phone || undefined,
|
||||
plan: shop.plan || undefined,
|
||||
subscriptionId: shop.subscriptionId || undefined,
|
||||
subscriptionEndDate: shop.subscriptionEndDate || undefined,
|
||||
subscriptionStartDate: shop.subscriptionStartDate || undefined,
|
||||
isActive: shop.isActive,
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [shop])
|
||||
|
||||
const planOptions = [
|
||||
{ value: 'base', label: 'پایه' },
|
||||
{ value: 'premium', label: 'پرمیوم' },
|
||||
]
|
||||
|
||||
if (isLoading) {
|
||||
return <PageLoading />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='w-full mt-4'>
|
||||
<div className='flex w-full justify-between items-center'>
|
||||
<div>
|
||||
{t('shop.list_shop')}
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
className='px-5'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={updateShop.isPending}
|
||||
>
|
||||
<div className='flex gap-2'>
|
||||
<TickCircle
|
||||
className='size-5'
|
||||
color='#fff'
|
||||
/>
|
||||
<div>{t('save')}</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-6 mt-10'>
|
||||
<div className='flex-1'>
|
||||
<div className='flex-1 bg-white py-8 xl:px-10 px-4 rounded-3xl'>
|
||||
<div className='rowTwoInput'>
|
||||
<Input
|
||||
label={t('shop.name')}
|
||||
{...formik.getFieldProps('name')}
|
||||
error_text={formik.touched.name && formik.errors.name ? formik.errors.name : ''}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label={t('shop.slug')}
|
||||
{...formik.getFieldProps('slug')}
|
||||
error_text={formik.touched.slug && formik.errors.slug ? formik.errors.slug : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-8 rowTwoInput'>
|
||||
<Input
|
||||
label={t('shop.phone')}
|
||||
{...formik.getFieldProps('phone')}
|
||||
error_text={formik.touched.phone && formik.errors.phone ? formik.errors.phone : ''}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label={t('shop.established_year')}
|
||||
type='number'
|
||||
{...formik.getFieldProps('establishedYear')}
|
||||
error_text={formik.touched.establishedYear && formik.errors.establishedYear ? formik.errors.establishedYear : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-8 rowTwoInput'>
|
||||
<Select
|
||||
label={t('shop.plan')}
|
||||
items={planOptions}
|
||||
{...formik.getFieldProps('plan')}
|
||||
placeholder={t('select')}
|
||||
error_text={formik.touched.plan && formik.errors.plan ? formik.errors.plan : ''}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label={t('shop.subscription_id')}
|
||||
{...formik.getFieldProps('subscriptionId')}
|
||||
error_text={formik.touched.subscriptionId && formik.errors.subscriptionId ? formik.errors.subscriptionId : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-8 rowTwoInput'>
|
||||
<DatePickerComponent
|
||||
label={t('shop.subscription_start_date')}
|
||||
onChange={(value) => formik.setFieldValue('subscriptionStartDate', value)}
|
||||
placeholder={t('select')}
|
||||
defaulValue={formik.values.subscriptionStartDate}
|
||||
error_text={formik.touched.subscriptionStartDate && formik.errors.subscriptionStartDate ? formik.errors.subscriptionStartDate : ''}
|
||||
/>
|
||||
|
||||
<DatePickerComponent
|
||||
label={t('shop.subscription_end_date')}
|
||||
onChange={(value) => formik.setFieldValue('subscriptionEndDate', value)}
|
||||
placeholder={t('select')}
|
||||
defaulValue={formik.values.subscriptionEndDate}
|
||||
error_text={formik.touched.subscriptionEndDate && formik.errors.subscriptionEndDate ? formik.errors.subscriptionEndDate : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-8'>
|
||||
<SwitchComponent
|
||||
label={t('shop.status')}
|
||||
active={formik.values.isActive || false}
|
||||
onChange={(value) => formik.setFieldValue('isActive', value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UpdateShop
|
||||
@@ -0,0 +1,124 @@
|
||||
import { FC } from 'react'
|
||||
import { useFormik } from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { AddAdminShopType } from '../../types/Types'
|
||||
import Input from '../../../../components/Input'
|
||||
import Select from '../../../../components/Select'
|
||||
import Button from '../../../../components/Button'
|
||||
import { useAddAdminShop, useGetSystemRoles } from '../../hooks/useIconData'
|
||||
import { SystemRoleType } from '../../types/Types'
|
||||
import { ErrorType } from '../../../../helpers/types'
|
||||
import { toast } from 'react-toastify'
|
||||
import { TickCircle } from 'iconsax-react'
|
||||
|
||||
interface AddShopAdminFormProps {
|
||||
shopId: string
|
||||
onSuccess: () => void
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const AddShopAdminForm: FC<AddShopAdminFormProps> = ({
|
||||
shopId,
|
||||
onSuccess,
|
||||
onClose
|
||||
}) => {
|
||||
const { t } = useTranslation('global')
|
||||
const { mutate: addAdmin, isPending } = useAddAdminShop()
|
||||
const { data: rolesData } = useGetSystemRoles()
|
||||
|
||||
const rolesList = rolesData?.data || []
|
||||
|
||||
const formik = useFormik<AddAdminShopType>({
|
||||
initialValues: {
|
||||
shopId: shopId,
|
||||
phone: '',
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
roleId: ''
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
phone: Yup.string().required(t('errors.required')),
|
||||
firstName: Yup.string().required(t('errors.required')),
|
||||
lastName: Yup.string().required(t('errors.required')),
|
||||
roleId: Yup.string().required(t('errors.required'))
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
addAdmin(values, {
|
||||
onSuccess: () => {
|
||||
toast.success(t('success'))
|
||||
formik.resetForm()
|
||||
onSuccess()
|
||||
onClose()
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(error.response?.data?.error?.message[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<form onSubmit={formik.handleSubmit} className='mt-6'>
|
||||
<div className='flex flex-col gap-6'>
|
||||
<div className='rowTwoInput'>
|
||||
<Input
|
||||
label={t('user.name')}
|
||||
{...formik.getFieldProps('firstName')}
|
||||
error_text={formik.touched.firstName && formik.errors.firstName ? formik.errors.firstName : ''}
|
||||
/>
|
||||
<Input
|
||||
label={t('user.family')}
|
||||
{...formik.getFieldProps('lastName')}
|
||||
error_text={formik.touched.lastName && formik.errors.lastName ? formik.errors.lastName : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='rowTwoInput'>
|
||||
<Input
|
||||
label={t('shop.phone')}
|
||||
{...formik.getFieldProps('phone')}
|
||||
error_text={formik.touched.phone && formik.errors.phone ? formik.errors.phone : ''}
|
||||
/>
|
||||
<Select
|
||||
label={t('user.role')}
|
||||
placeholder={t('select')}
|
||||
items={rolesList.map((item: SystemRoleType) => ({
|
||||
value: item.id,
|
||||
label: item.name
|
||||
}))}
|
||||
{...formik.getFieldProps('roleId')}
|
||||
error_text={formik.touched.roleId && formik.errors.roleId ? formik.errors.roleId : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-4 justify-end mt-4'>
|
||||
<Button
|
||||
type='button'
|
||||
onClick={onClose}
|
||||
className='px-6 bg-gray-200 text-gray-700 hover:bg-gray-300'
|
||||
>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type='submit'
|
||||
isLoading={isPending}
|
||||
className='px-6'
|
||||
>
|
||||
<div className='flex gap-2'>
|
||||
<TickCircle
|
||||
className='size-5'
|
||||
color='white'
|
||||
/>
|
||||
<div className='text-sm'>
|
||||
{t('shop.add_admin')}
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
export default AddShopAdminForm
|
||||
@@ -0,0 +1,129 @@
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import DefaulModal from '../../../../components/DefaulModal'
|
||||
import { useDeleteShopAdmin, useGetShopAdmins } from '../../hooks/useIconData'
|
||||
import PageLoading from '../../../../components/PageLoading'
|
||||
import Td from '../../../../components/Td'
|
||||
import { ShopAdminType } from '../../types/Types'
|
||||
import moment from 'moment-jalaali'
|
||||
import TrashWithConfrim from '../../../../components/TrashWithConfrim'
|
||||
import { toast } from 'react-toastify'
|
||||
import { ErrorType } from '../../../../helpers/types'
|
||||
import Button from '../../../../components/Button'
|
||||
import AddShopAdminForm from './AddShopAdminForm'
|
||||
|
||||
interface ShopAdminsModalProps {
|
||||
open: boolean
|
||||
close: () => void
|
||||
shopId: string
|
||||
}
|
||||
|
||||
const ShopAdminsModal: FC<ShopAdminsModalProps> = ({ open, close, shopId }) => {
|
||||
const { t } = useTranslation('global')
|
||||
const { data: adminsData, isLoading, refetch } = useGetShopAdmins(shopId)
|
||||
const [showAddForm, setShowAddForm] = useState(false)
|
||||
|
||||
const adminsList = adminsData?.data || []
|
||||
|
||||
const { mutate: deleteAdmin, isPending } = useDeleteShopAdmin()
|
||||
|
||||
const handleDeleteAdmin = (shopId: string, adminId: string) => {
|
||||
deleteAdmin({ shopId, adminId }, {
|
||||
onSuccess: () => {
|
||||
refetch()
|
||||
toast.success(t('success'))
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(error.response?.data?.error?.message[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleAddAdmin = () => {
|
||||
setShowAddForm(true)
|
||||
}
|
||||
|
||||
const handleFormSuccess = () => {
|
||||
refetch()
|
||||
setShowAddForm(false)
|
||||
}
|
||||
|
||||
const handleFormClose = () => {
|
||||
setShowAddForm(false)
|
||||
}
|
||||
return (
|
||||
<DefaulModal
|
||||
open={open}
|
||||
close={close}
|
||||
title_header={t('shop.admins')}
|
||||
isHeader
|
||||
>
|
||||
<div className='mt-6'>
|
||||
{showAddForm ? (
|
||||
<AddShopAdminForm
|
||||
shopId={shopId}
|
||||
onSuccess={handleFormSuccess}
|
||||
onClose={handleFormClose}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<div className='flex justify-end'>
|
||||
<Button
|
||||
onClick={handleAddAdmin}
|
||||
className='w-fit px-5'
|
||||
>
|
||||
{t('shop.add_admin')}
|
||||
</Button>
|
||||
</div>
|
||||
{
|
||||
isLoading ?
|
||||
<PageLoading />
|
||||
:
|
||||
<div className='relative overflow-x-auto rounded-3xl w-full mt-6'>
|
||||
<table className='w-full text-sm'>
|
||||
<thead className='thead'>
|
||||
<tr>
|
||||
<Td text={t('user.name')} />
|
||||
<Td text={t('shop.phone')} />
|
||||
<Td text={t('shop.role')} />
|
||||
<Td text={t('shop.created_at')} />
|
||||
<Td text={''} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
adminsList.map((admin: ShopAdminType) => {
|
||||
return (
|
||||
<tr key={admin.id} className='tr'>
|
||||
<Td text={`${admin.firstName} ${admin.lastName}`} />
|
||||
<Td text={admin.phone || '-'} />
|
||||
<Td text={admin.roles[0].role?.name || '-'} />
|
||||
<Td text={''}>
|
||||
<div className='dltr text-right'>
|
||||
{moment(admin.createdAt).format('jYYYY-jMM-jDD HH:mm')}
|
||||
</div>
|
||||
</Td>
|
||||
<Td text=''>
|
||||
<div className='flex gap-2'>
|
||||
<TrashWithConfrim
|
||||
onDelete={() => handleDeleteAdmin(shopId, admin.id)}
|
||||
isLoading={isPending}
|
||||
/>
|
||||
</div>
|
||||
</Td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</DefaulModal>
|
||||
)
|
||||
}
|
||||
|
||||
export default ShopAdminsModal
|
||||
@@ -0,0 +1,219 @@
|
||||
import { IResponse } from "../../../types/response.types";
|
||||
|
||||
export type CreateGroupIconType = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type GroupIconType = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
name: string;
|
||||
icons: unknown[];
|
||||
};
|
||||
|
||||
export interface GroupIconsResponse extends IResponse<GroupIconType[]> {
|
||||
statusCode: number;
|
||||
}
|
||||
|
||||
export type CreateIconType = {
|
||||
url: string;
|
||||
groupId: string;
|
||||
};
|
||||
|
||||
export type IconGroupType = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type IconType = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
url: string;
|
||||
group: IconGroupType;
|
||||
};
|
||||
|
||||
export interface IconsResponse extends IResponse<IconType[]> {
|
||||
statusCode: number;
|
||||
}
|
||||
|
||||
export type ReportItem = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
subject: string;
|
||||
content: string;
|
||||
phone: string | null;
|
||||
scope: string;
|
||||
status: string;
|
||||
};
|
||||
|
||||
export interface ReportsResponse extends IResponse<ReportItem[]> {
|
||||
statusCode: number;
|
||||
}
|
||||
|
||||
export type ScoreType = {
|
||||
scoreAmount: string;
|
||||
scoreCredit: string;
|
||||
birthdayScore: string;
|
||||
purchaseScore: string;
|
||||
referrerScore: string;
|
||||
registerScore: string;
|
||||
purchaseAmount: string;
|
||||
marriageDateScore: string;
|
||||
};
|
||||
|
||||
export type ShopType = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
name: string;
|
||||
slug: string;
|
||||
logo: string | null;
|
||||
address: string | null;
|
||||
menuColor: string | null;
|
||||
latitude: number | null;
|
||||
longitude: number | null;
|
||||
serviceArea: string | null;
|
||||
isActive: boolean;
|
||||
establishedYear: number;
|
||||
phone: string;
|
||||
instagram: string | null;
|
||||
telegram: string | null;
|
||||
whatsapp: string | null;
|
||||
description: string | null;
|
||||
seoTitle: string | null;
|
||||
seoDescription: string | null;
|
||||
tagNames: string | null;
|
||||
images: string | null;
|
||||
vat: number;
|
||||
domain: string;
|
||||
score: ScoreType | null;
|
||||
plan: string;
|
||||
subscriptionId: string;
|
||||
subscriptionEndDate: string;
|
||||
subscriptionStartDate: string;
|
||||
};
|
||||
|
||||
export type PagerType = {
|
||||
page: number;
|
||||
limit: number;
|
||||
totalItems: number;
|
||||
totalPages: number;
|
||||
prevPage: boolean;
|
||||
nextPage: string | false;
|
||||
};
|
||||
|
||||
export type ShopsData = {
|
||||
pager: PagerType;
|
||||
shops: ShopType[];
|
||||
};
|
||||
|
||||
export interface ShopsResponse {
|
||||
statusCode: number;
|
||||
success: boolean;
|
||||
data: ShopsData;
|
||||
}
|
||||
|
||||
export interface ShopResponse extends IResponse<ShopType> {
|
||||
statusCode: number;
|
||||
}
|
||||
|
||||
export type ShopAdminRoleType = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
name: string;
|
||||
isSystem: boolean;
|
||||
shop: string | null;
|
||||
};
|
||||
|
||||
export type ShopAdminRoleItemType = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
admin: string;
|
||||
role: ShopAdminRoleType;
|
||||
shop: string;
|
||||
};
|
||||
|
||||
export type ShopAdminType = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
phone: string;
|
||||
roles: ShopAdminRoleItemType[];
|
||||
};
|
||||
|
||||
export interface ShopAdminsResponse
|
||||
extends IResponse<ShopAdminType[]> {
|
||||
statusCode: number;
|
||||
}
|
||||
|
||||
export type ShopSmsCountItem = {
|
||||
shopId: string;
|
||||
shopName: string;
|
||||
smsCount: number;
|
||||
};
|
||||
|
||||
export interface ShopSmsCountResponse
|
||||
extends IResponse<ShopSmsCountItem[]> {
|
||||
statusCode: number;
|
||||
}
|
||||
|
||||
export type AddAdminShopType = {
|
||||
shopId: string;
|
||||
phone: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
roleId: string;
|
||||
};
|
||||
|
||||
export type PermissionType = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
name: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
export type SystemRoleType = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
name: string;
|
||||
isSystem: boolean;
|
||||
shop: string | null;
|
||||
permissions: PermissionType[];
|
||||
};
|
||||
|
||||
export interface SystemRolesResponse extends IResponse<SystemRoleType[]> {
|
||||
statusCode: number;
|
||||
}
|
||||
|
||||
export type UpdateShopType = {
|
||||
name?: string;
|
||||
slug?: string;
|
||||
establishedYear?: number;
|
||||
phone?: string;
|
||||
plan?: string;
|
||||
subscriptionId?: string;
|
||||
subscriptionEndDate?: string;
|
||||
subscriptionStartDate?: string;
|
||||
isActive?: boolean;
|
||||
};
|
||||
@@ -83,6 +83,12 @@ import WarningsList from '../pages/dmenu/report/List.tsx'
|
||||
import RestaurantsList from '../pages/dmenu/restaurant/List.tsx'
|
||||
import SmsCountList from '../pages/dmenu/restaurant/SmsCountList.tsx'
|
||||
import UpdateRestaurant from '../pages/dmenu/restaurant/Update.tsx'
|
||||
import DkalaIconsList from '../pages/dkala/icon/List'
|
||||
import DkalaGroupIconList from '../pages/dkala/icon/GroupList'
|
||||
import DkalaWarningsList from '../pages/dkala/report/List.tsx'
|
||||
import ShopsList from '../pages/dkala/shop/List.tsx'
|
||||
import DkalaSmsCountList from '../pages/dkala/shop/SmsCountList.tsx'
|
||||
import UpdateShop from '../pages/dkala/shop/Update.tsx'
|
||||
import SubscriptionsList from '../pages/subscriptions/List.tsx'
|
||||
// import WarningsList from '../pages/dmenu/reports/List' // TODO: Create this component
|
||||
const MainRouter: FC = () => {
|
||||
@@ -181,6 +187,13 @@ const MainRouter: FC = () => {
|
||||
<Route path={Pages.dmenu.restaurants.smsCountList} element={<SmsCountList />} />
|
||||
<Route path={Pages.dmenu.restaurants.update + ':id'} element={<UpdateRestaurant />} />
|
||||
|
||||
<Route path={Pages.dkala.icons.list} element={<DkalaIconsList />} />
|
||||
<Route path={Pages.dkala.icons.groupList} element={<DkalaGroupIconList />} />
|
||||
<Route path={Pages.dkala.warnings.list} element={<DkalaWarningsList />} />
|
||||
<Route path={Pages.dkala.shops.list} element={<ShopsList />} />
|
||||
<Route path={Pages.dkala.shops.smsCountList} element={<DkalaSmsCountList />} />
|
||||
<Route path={Pages.dkala.shops.update + ':id'} element={<UpdateShop />} />
|
||||
|
||||
<Route path={Pages.subscriptions.list} element={<SubscriptionsList />} />
|
||||
</Routes>
|
||||
</div>
|
||||
|
||||
@@ -366,6 +366,70 @@ const SideBar: FC = () => {
|
||||
</>
|
||||
}
|
||||
|
||||
{
|
||||
adminPermissions?.data?.permissions?.some((permission: { name: string }) => permission.name === 'dkala') &&
|
||||
<>
|
||||
<div className={clx(
|
||||
'mt-10 px-12 text-header text-sm font-normal',
|
||||
hasSubMenu && 'px-2 text-center'
|
||||
)}>
|
||||
{t('sidebar.dkala')}
|
||||
</div>
|
||||
<div className='flex-1 flex flex-col justify-end'>
|
||||
|
||||
<div className='text-xs text-[#8C90A3]'>
|
||||
<SideBarItem
|
||||
icon={<Icon variant={isActive('dkala/icons') ? 'Bold' : 'Outline'} color={isActive('dkala/icons') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
title={t('sidebar.icons')}
|
||||
isActive={isActive('dkala/icons')}
|
||||
link={Pages.dkala.icons.list}
|
||||
activeName='dkala'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='text-xs text-[#8C90A3]'>
|
||||
<SideBarItem
|
||||
icon={<Category variant={isActive('dkala/group') ? 'Bold' : 'Outline'} color={isActive('dkala/group') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
title={t('sidebar.groups_icon')}
|
||||
isActive={isActive('dkala/icons/group')}
|
||||
link={Pages.dkala.icons.groupList}
|
||||
activeName='dkala'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='text-xs text-[#8C90A3]'>
|
||||
<SideBarItem
|
||||
icon={<Warning2 variant={isActive('dkala/reports') ? 'Bold' : 'Outline'} color={isActive('dkala/reports') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
title={t('sidebar.reports')}
|
||||
isActive={isActive('dkala/reports')}
|
||||
link={Pages.dkala.warnings.list}
|
||||
activeName='dkala'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='text-xs text-[#8C90A3]'>
|
||||
<SideBarItem
|
||||
icon={<Building variant={isActive('dkala/shops') ? 'Bold' : 'Outline'} color={isActive('dkala/shops') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
title={t('sidebar.shops')}
|
||||
isActive={isActive('dkala/shops')}
|
||||
link={Pages.dkala.shops.list}
|
||||
activeName='dkala'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='text-xs text-[#8C90A3]'>
|
||||
<SideBarItem
|
||||
icon={<Sms variant={isActive('dkala/sms-count') ? 'Bold' : 'Outline'} color={isActive('dkala/sms-count') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
title={t('sidebar.sms_count')}
|
||||
isActive={isActive('dkala/sms-count')}
|
||||
link={Pages.dkala.shops.smsCountList}
|
||||
activeName='dkala'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
<div className='flex-1 flex flex-col justify-end mt-14'>
|
||||
<div className='text-xs text-[#8C90A3]'>
|
||||
<SideBarItem
|
||||
|
||||
Reference in New Issue
Block a user