diff --git a/.env b/.env index e49f527..1cfb95b 100644 --- a/.env +++ b/.env @@ -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' \ No newline at end of file +# VITE_BASE_URL = 'http://10.86.60.88:3500' \ No newline at end of file diff --git a/src/config/Pages.ts b/src/config/Pages.ts index b3dfb67..4357336 100644 --- a/src/config/Pages.ts +++ b/src/config/Pages.ts @@ -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", }, diff --git a/src/config/SideBarSubMenu.ts b/src/config/SideBarSubMenu.ts index f734f50..e771538 100644 --- a/src/config/SideBarSubMenu.ts +++ b/src/config/SideBarSubMenu.ts @@ -8,4 +8,6 @@ export const SideBarItemHasSubMenu = [ "blog", "support", "accessLogs", + "dmenu", + "dkala", ]; diff --git a/src/langs/fa.json b/src/langs/fa.json index 803fcce..590c686 100644 --- a/src/langs/fa.json +++ b/src/langs/fa.json @@ -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": "سرویس", diff --git a/src/pages/dkala/hooks/useIconData.ts b/src/pages/dkala/hooks/useIconData.ts new file mode 100644 index 0000000..e611a2e --- /dev/null +++ b/src/pages/dkala/hooks/useIconData.ts @@ -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({ + 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({ + 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({ + 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({ + 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), + }); +}; diff --git a/src/pages/dkala/icon/GroupList.tsx b/src/pages/dkala/icon/GroupList.tsx new file mode 100644 index 0000000..066d565 --- /dev/null +++ b/src/pages/dkala/icon/GroupList.tsx @@ -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(false) + const [search, setSearch] = useState('') + 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 ( +
+
+
+ {t('icon.group_list')} +
+ + +
+ +
+
+
+ setSearch(value)} + /> +
+
+
+ + { + isLoading ? + + : +
+ + + + + + + { + filteredGroups.map((item: GroupIconType) => { + return ( + + + + ) + }) + } + +
+ + + +
+ + + +
+ handleDelete(item.id)} + isLoading={deleteGroupIcon.isPending} + /> +
+
+
+ } + + +
+ ) +} + +export default GroupIconList diff --git a/src/pages/dkala/icon/List.tsx b/src/pages/dkala/icon/List.tsx new file mode 100644 index 0000000..8c39757 --- /dev/null +++ b/src/pages/dkala/icon/List.tsx @@ -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(false) + const [search, setSearch] = useState('') + 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 ( +
+
+
+ {t('icon.list_icon')} +
+ + +
+ +
+
+
+ setSearch(value)} + /> +
+
+
+ + { + isLoading ? + + : +
+ + + + + + + { + filteredIcons.map((item: IconType) => { + return ( + + + + + ) + }) + } + +
+ + + +
+
+
+ {item.url} { + const target = e.target as HTMLImageElement + target.style.display = 'none' + }} + /> +
+
+ {item.url} +
+
+
+ + +
+ handleDelete(item.id)} + isLoading={deleteIcon.isPending} + /> +
+
+
+ } + + +
+ ) +} + +export default IconsList diff --git a/src/pages/dkala/icon/components/CreateGroupIcon.tsx b/src/pages/dkala/icon/components/CreateGroupIcon.tsx new file mode 100644 index 0000000..32ca1e4 --- /dev/null +++ b/src/pages/dkala/icon/components/CreateGroupIcon.tsx @@ -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 = ({ open, close, onSuccess }) => { + const { t } = useTranslation('global') + const createGroupIcon = useCreateGroupIcon() + + const formik = useFormik({ + 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 ( + +
+ + +
+
+ +
+
+
+
+ ) +} + +export default CreateGroupIcon diff --git a/src/pages/dkala/icon/components/CreateIcon.tsx b/src/pages/dkala/icon/components/CreateIcon.tsx new file mode 100644 index 0000000..0010aa8 --- /dev/null +++ b/src/pages/dkala/icon/components/CreateIcon.tsx @@ -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 = ({ open, close, onSuccess }) => { + const { t } = useTranslation('global') + const [file, setFile] = useState() + 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({ + 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 ( + +
+ setFile(files[0])} + isReset={!open} + accept="image/svg+xml" + /> + +
+ + + +
+ +
+ + + +
+ +
+ +
+ +
+ formik.setFieldValue('subscriptionStartDate', value)} + placeholder={t('select')} + defaulValue={formik.values.subscriptionStartDate} + error_text={formik.touched.subscriptionStartDate && formik.errors.subscriptionStartDate ? formik.errors.subscriptionStartDate : ''} + /> + + formik.setFieldValue('subscriptionEndDate', value)} + placeholder={t('select')} + defaulValue={formik.values.subscriptionEndDate} + error_text={formik.touched.subscriptionEndDate && formik.errors.subscriptionEndDate ? formik.errors.subscriptionEndDate : ''} + /> +
+ +
+ formik.setFieldValue('isActive', value)} + /> +
+
+ + + + ) +} + +export default UpdateShop diff --git a/src/pages/dkala/shop/components/AddShopAdminForm.tsx b/src/pages/dkala/shop/components/AddShopAdminForm.tsx new file mode 100644 index 0000000..7c525c1 --- /dev/null +++ b/src/pages/dkala/shop/components/AddShopAdminForm.tsx @@ -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 = ({ + shopId, + onSuccess, + onClose +}) => { + const { t } = useTranslation('global') + const { mutate: addAdmin, isPending } = useAddAdminShop() + const { data: rolesData } = useGetSystemRoles() + + const rolesList = rolesData?.data || [] + + const formik = useFormik({ + 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 ( +
+
+
+ + +
+ +
+ +