sms count

This commit is contained in:
hamid zarghami
2025-12-29 16:21:00 +03:30
parent 8bdd838ddc
commit deda1f1603
10 changed files with 229 additions and 10 deletions
+1
View File
@@ -132,6 +132,7 @@ export const Pages = {
},
restaurants: {
list: "/dmenu/restaurants/list",
smsCountList: "/dmenu/sms-count/list",
},
},
};
+4 -1
View File
@@ -872,6 +872,7 @@
},
"restaurant": {
"list_restaurant": "لیست رستوران‌ها",
"list_sms_count": "لیست تعداد پیامک‌های رستوران‌ها",
"name": "نام",
"phone": "شماره تماس",
"address": "آدرس",
@@ -880,7 +881,9 @@
"plan": "پلن",
"created_at": "تاریخ ایجاد",
"active": "فعال",
"inactive": "غیرفعال"
"inactive": "غیرفعال",
"admins": "مدیران",
"sms_count": "تعداد پیامک"
},
"cancel": "لغو"
}
+15
View File
@@ -53,3 +53,18 @@ export const useGetRestaurants = () => {
queryFn: api.getRestaurants,
});
};
export const useGetRestaurantAdmins = (restaurantId: string) => {
return useQuery({
queryKey: ["restaurant-admins", restaurantId],
queryFn: () => api.getRestaurantAdmins(restaurantId),
enabled: !!restaurantId,
});
};
export const useGetRestaurantSmsCount = () => {
return useQuery({
queryKey: ["restaurant-sms-count"],
queryFn: api.getRestaurantSmsCount,
});
};
+31 -8
View File
@@ -1,4 +1,4 @@
import { FC } from 'react'
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useGetRestaurants } from '../hooks/useIconData'
import PageLoading from '../../../components/PageLoading'
@@ -7,14 +7,28 @@ import { RestaurantType } from '../types/Types'
import moment from 'moment-jalaali'
import { Copy } from 'iconsax-react'
import { toast } from 'react-toastify'
import Button from '../../../components/Button'
import RestaurantAdminsModal from './components/RestaurantAdminsModal'
const RestaurantsList: FC = () => {
const { t } = useTranslation('global')
const { data: restaurants, isLoading } = useGetRestaurants()
const [selectedRestaurantId, setSelectedRestaurantId] = useState<string>('')
const [isAdminsModalOpen, setIsAdminsModalOpen] = useState<boolean>(false)
const restaurantsList = (restaurants as { data?: RestaurantType[] })?.data || []
const handleOpenAdminsModal = (restaurantId: string) => {
setSelectedRestaurantId(restaurantId)
setIsAdminsModalOpen(true)
}
const handleCloseAdminsModal = () => {
setIsAdminsModalOpen(false)
setSelectedRestaurantId('')
}
const handleCopyAddress = async (address: string | null) => {
if (!address) return
@@ -87,21 +101,30 @@ const RestaurantsList: FC = () => {
{moment(item.createdAt).format('jYYYY-jMM-jDD HH:mm')}
</div>
</Td>
<Td text={''} />
<Td text={''}>
<div className='flex items-center gap-2'>
<Button
className='w-fit px-4'
onClick={() => handleOpenAdminsModal(item.id)}
>
{t('restaurant.admins')}
</Button>
</div>
</Td>
</tr>
)
})
}
</tbody>
</table>
{restaurantsList.length === 0 && (
<div className="text-center py-12">
<div className="text-gray-500 text-lg">هیچ رستورانی یافت نشد</div>
</div>
)}
</div>
}
<RestaurantAdminsModal
open={isAdminsModalOpen}
close={handleCloseAdminsModal}
restaurantId={selectedRestaurantId}
/>
</div>
)
}
@@ -0,0 +1,53 @@
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { useGetRestaurantSmsCount } from '../hooks/useIconData'
import PageLoading from '../../../components/PageLoading'
import Td from '../../../components/Td'
import { RestaurantSmsCountItem } from '../types/Types'
const SmsCountList: FC = () => {
const { t } = useTranslation('global')
const { data: smsCount, isLoading } = useGetRestaurantSmsCount()
const smsCountList = (smsCount as { data?: RestaurantSmsCountItem[] })?.data || []
return (
<div className='mt-4'>
<div className='flex w-full justify-between items-center'>
<div>
{t('restaurant.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('restaurant.name')} />
<Td text={t('restaurant.sms_count')} />
</tr>
</thead>
<tbody>
{
smsCountList.map((item: RestaurantSmsCountItem) => {
return (
<tr key={item.restaurantId} className='tr'>
<Td text={item.restaurantName} />
<Td text={item.smsCount.toString()} />
</tr>
)
})
}
</tbody>
</table>
</div>
}
</div>
)
}
export default SmsCountList
@@ -0,0 +1,71 @@
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import DefaulModal from '../../../../components/DefaulModal'
import { useGetRestaurantAdmins } from '../../hooks/useIconData'
import PageLoading from '../../../../components/PageLoading'
import Td from '../../../../components/Td'
import { RestaurantAdminType } from '../../types/Types'
import moment from 'moment-jalaali'
interface RestaurantAdminsModalProps {
open: boolean
close: () => void
restaurantId: string
}
const RestaurantAdminsModal: FC<RestaurantAdminsModalProps> = ({ open, close, restaurantId }) => {
const { t } = useTranslation('global')
const { data: adminsData, isLoading } = useGetRestaurantAdmins(restaurantId)
const adminsList = (adminsData as { data?: RestaurantAdminType[] })?.data || []
return (
<DefaulModal
open={open}
close={close}
title_header={t('restaurant.admins')}
isHeader
>
<div className='mt-6'>
{
isLoading ?
<PageLoading />
:
<div className='relative overflow-x-auto rounded-3xl w-full'>
<table className='w-full text-sm'>
<thead className='thead'>
<tr>
<Td text={t('user.name')} />
<Td text={t('user.email')} />
<Td text={t('restaurant.phone')} />
<Td text={t('restaurant.created_at')} />
</tr>
</thead>
<tbody>
{
adminsList.map((admin: RestaurantAdminType) => {
return (
<tr key={admin.id} className='tr'>
<Td text={`${admin.firstName} ${admin.lastName}`} />
<Td text={admin.email} />
<Td text={admin.phone || '-'} />
<Td text={''}>
<div className='dltr text-right'>
{moment(admin.createdAt).format('jYYYY-jMM-jDD HH:mm')}
</div>
</Td>
</tr>
)
})
}
</tbody>
</table>
</div>
}
</div>
</DefaulModal>
)
}
export default RestaurantAdminsModal
+15
View File
@@ -6,6 +6,7 @@ import {
IconsResponse,
ReportsResponse,
RestaurantsResponse,
RestaurantSmsCountResponse,
} from "../types/Types";
export const getIcons = async (): Promise<IconsResponse> => {
@@ -47,3 +48,17 @@ export const getRestaurants = async (): Promise<RestaurantsResponse> => {
const { data } = await axios.get("/admin/dmenu/restaurants");
return data;
};
export const getRestaurantAdmins = async (restaurantId: string) => {
const { data } = await axios.get(
`/admin/dmenu/restaurants/${restaurantId}/admins`
);
return data;
};
export const getRestaurantSmsCount = async (): Promise<
RestaurantSmsCountResponse
> => {
const { data } = await axios.get(`/admin/dmenu/restaurants/sms-count`);
return data;
};
+26
View File
@@ -106,3 +106,29 @@ export type RestaurantType = {
export interface RestaurantsResponse extends IResponse<RestaurantType[]> {
statusCode: number;
}
export type RestaurantAdminType = {
id: string;
firstName: string;
lastName: string;
email: string;
phone: string;
createdAt: string;
updatedAt: string;
};
export interface RestaurantAdminsResponse
extends IResponse<RestaurantAdminType[]> {
statusCode: number;
}
export type RestaurantSmsCountItem = {
restaurantId: string;
restaurantName: string;
smsCount: number;
};
export interface RestaurantSmsCountResponse
extends IResponse<RestaurantSmsCountItem[]> {
statusCode: number;
}
+2
View File
@@ -81,6 +81,7 @@ import IconsList from '../pages/dmenu/icon/List'
import GroupIconList from '../pages/dmenu/icon/GroupList'
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 WarningsList from '../pages/dmenu/reports/List' // TODO: Create this component
const MainRouter: FC = () => {
@@ -175,6 +176,7 @@ const MainRouter: FC = () => {
<Route path={Pages.dmenu.icons.groupList} element={<GroupIconList />} />
<Route path={Pages.dmenu.warnings.list} element={<WarningsList />} />
<Route path={Pages.dmenu.restaurants.list} element={<RestaurantsList />} />
<Route path={Pages.dmenu.restaurants.smsCountList} element={<SmsCountList />} />
</Routes>
</div>
</div>
+11 -1
View File
@@ -2,7 +2,7 @@ import { FC, useEffect } from 'react'
import LogoImage from '../assets/images/logo.svg'
import LogoSmall from '../assets/images/logo-small.svg'
import { useTranslation } from 'react-i18next'
import { Card, Code, CodeCircle, DocumentLike, DocumentText, Element3, Gallery, Headphone, Home2, Logout, Message, Messages3, Money3, NotificationStatus, People, Profile, Receipt21, Setting2, SmsTracking, Teacher, TicketDiscount, UserSquare, Activity, Icon, Category, Warning2, Building } from 'iconsax-react'
import { Card, Code, CodeCircle, DocumentLike, DocumentText, Element3, Gallery, Headphone, Home2, Logout, Message, Messages3, Money3, NotificationStatus, People, Profile, Receipt21, Setting2, SmsTracking, Teacher, TicketDiscount, UserSquare, Activity, Icon, Category, Warning2, Building, Sms } from 'iconsax-react'
import SideBarItem from './SideBarItem'
import { useLocation } from 'react-router-dom'
import { Pages } from '../config/Pages'
@@ -344,6 +344,16 @@ const SideBar: FC = () => {
activeName='dmenu'
/>
</div>
<div className='text-xs text-[#8C90A3]'>
<SideBarItem
icon={<Sms variant={isActive('sms-count') ? 'Bold' : 'Outline'} color={isActive('sms-count') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
title={t('sidebar.sms_count')}
isActive={isActive('sms-count')}
link={Pages.dmenu.restaurants.smsCountList}
activeName='dmenu'
/>
</div>
</div>
</>
}