use socket
This commit is contained in:
@@ -2,3 +2,5 @@ VITE_TOKEN_NAME = 'dmnu_a_t'
|
|||||||
VITE_REFRESH_TOKEN_NAME = 'dmnu-a-rt'
|
VITE_REFRESH_TOKEN_NAME = 'dmnu-a-rt'
|
||||||
VITE_BASE_URL = 'https://dmenuplus-api.dev.danakcorp.com'
|
VITE_BASE_URL = 'https://dmenuplus-api.dev.danakcorp.com'
|
||||||
# VITE_BASE_URL = 'http://192.168.1.112:4001'
|
# VITE_BASE_URL = 'http://192.168.1.112:4001'
|
||||||
|
# VITE_SOCKET_URL = 'https://dmenuplus-api.dev.danakcorp.com/pager'
|
||||||
|
VITE_SOCKET_URL = 'http://192.168.99.242:4000/pager'
|
||||||
Generated
+137
-1
@@ -32,6 +32,7 @@
|
|||||||
"react-router-dom": "^7.8.2",
|
"react-router-dom": "^7.8.2",
|
||||||
"react-spinners": "^0.17.0",
|
"react-spinners": "^0.17.0",
|
||||||
"react-toastify": "^11.0.5",
|
"react-toastify": "^11.0.5",
|
||||||
|
"socket.io-client": "^4.8.1",
|
||||||
"swiper": "^11.2.10",
|
"swiper": "^11.2.10",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"yup": "^1.7.0",
|
"yup": "^1.7.0",
|
||||||
@@ -1766,6 +1767,12 @@
|
|||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@socket.io/component-emitter": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@swc/helpers": {
|
"node_modules/@swc/helpers": {
|
||||||
"version": "0.5.17",
|
"version": "0.5.17",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
|
||||||
@@ -2933,6 +2940,45 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/engine.io-client": {
|
||||||
|
"version": "6.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
|
||||||
|
"integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@socket.io/component-emitter": "~3.1.0",
|
||||||
|
"debug": "~4.3.1",
|
||||||
|
"engine.io-parser": "~5.2.1",
|
||||||
|
"ws": "~8.17.1",
|
||||||
|
"xmlhttprequest-ssl": "~2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/engine.io-client/node_modules/debug": {
|
||||||
|
"version": "4.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
|
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/engine.io-parser": {
|
||||||
|
"version": "5.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
|
||||||
|
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/enhanced-resolve": {
|
"node_modules/enhanced-resolve": {
|
||||||
"version": "5.18.3",
|
"version": "5.18.3",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
|
||||||
@@ -4303,7 +4349,6 @@
|
|||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
@@ -4935,6 +4980,68 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/socket.io-client": {
|
||||||
|
"version": "4.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
|
||||||
|
"integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@socket.io/component-emitter": "~3.1.0",
|
||||||
|
"debug": "~4.3.2",
|
||||||
|
"engine.io-client": "~6.6.1",
|
||||||
|
"socket.io-parser": "~4.2.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socket.io-client/node_modules/debug": {
|
||||||
|
"version": "4.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
|
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socket.io-parser": {
|
||||||
|
"version": "4.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
||||||
|
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@socket.io/component-emitter": "~3.1.0",
|
||||||
|
"debug": "~4.3.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socket.io-parser/node_modules/debug": {
|
||||||
|
"version": "4.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
|
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
@@ -5403,6 +5510,35 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.17.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||||
|
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/xmlhttprequest-ssl": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
"react-router-dom": "^7.8.2",
|
"react-router-dom": "^7.8.2",
|
||||||
"react-spinners": "^0.17.0",
|
"react-spinners": "^0.17.0",
|
||||||
"react-toastify": "^11.0.5",
|
"react-toastify": "^11.0.5",
|
||||||
|
"socket.io-client": "^4.8.1",
|
||||||
"swiper": "^11.2.10",
|
"swiper": "^11.2.10",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"yup": "^1.7.0",
|
"yup": "^1.7.0",
|
||||||
|
|||||||
@@ -75,4 +75,7 @@ export const Pages = {
|
|||||||
list: "/reviews/list",
|
list: "/reviews/list",
|
||||||
detail: "/reviews/detail/",
|
detail: "/reviews/detail/",
|
||||||
},
|
},
|
||||||
|
pagers: {
|
||||||
|
list: "/pagers/list",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+2
-1
@@ -65,7 +65,8 @@
|
|||||||
"payment_methods": "روش های پرداخت",
|
"payment_methods": "روش های پرداخت",
|
||||||
"admins": "مدیران",
|
"admins": "مدیران",
|
||||||
"shipment_methods": "روش های ارسال",
|
"shipment_methods": "روش های ارسال",
|
||||||
"reviews": "نظرات"
|
"reviews": "نظرات",
|
||||||
|
"pagers": "پیجرها"
|
||||||
},
|
},
|
||||||
"shipment": {
|
"shipment": {
|
||||||
"dineIn": "سرو در محل",
|
"dineIn": "سرو در محل",
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import { type FC, useEffect, useState, useMemo } from 'react'
|
||||||
|
import { useSocket } from '@/pages/pager/hooks/useSocket'
|
||||||
|
import { getToken } from '@/config/func'
|
||||||
|
import type { PagersType, Pager } from './types/Types'
|
||||||
|
import Table from '@/components/Table'
|
||||||
|
import Filters from '@/components/Filters'
|
||||||
|
import { usePagerFilters } from './hooks/usePagerFilters'
|
||||||
|
import { getPagerTableColumns } from './components/PagerTableColumns'
|
||||||
|
import { usePagerFiltersFields } from './components/PagerFiltersFields'
|
||||||
|
import type { RowDataType } from '@/components/types/TableTypes'
|
||||||
|
|
||||||
|
const PagersList: FC = () => {
|
||||||
|
const token = `Bearer ${getToken()}`
|
||||||
|
const { connect, isConnected, socket } = useSocket(import.meta.env.VITE_SOCKET_URL, token)
|
||||||
|
|
||||||
|
const [pagers, setPagers] = useState<PagersType>()
|
||||||
|
|
||||||
|
const {
|
||||||
|
filters,
|
||||||
|
currentPage,
|
||||||
|
handleFiltersChange,
|
||||||
|
handlePageChange,
|
||||||
|
filteredData,
|
||||||
|
paginatedData,
|
||||||
|
totalPages,
|
||||||
|
} = usePagerFilters()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
connect()
|
||||||
|
}, [connect])
|
||||||
|
|
||||||
|
const handleJoinRestaurant = () => {
|
||||||
|
socket?.emit('get:pagers')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePagersList = (data: PagersType) => {
|
||||||
|
setPagers(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isConnected && socket) {
|
||||||
|
socket.emit('join:restaurant')
|
||||||
|
}
|
||||||
|
}, [isConnected, socket])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (socket) {
|
||||||
|
socket.on('joined', handleJoinRestaurant)
|
||||||
|
socket.on('pagers:list', handlePagersList)
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (socket) {
|
||||||
|
socket.off('joined', handleJoinRestaurant)
|
||||||
|
socket.off('pagers:list', handlePagersList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [socket])
|
||||||
|
|
||||||
|
const pagersList = useMemo(() => {
|
||||||
|
return pagers?.pagers || []
|
||||||
|
}, [pagers])
|
||||||
|
|
||||||
|
const filteredPagers = useMemo(() => {
|
||||||
|
return filteredData(pagersList) as Pager[]
|
||||||
|
}, [pagersList, filteredData])
|
||||||
|
|
||||||
|
const paginatedPagers = useMemo(() => {
|
||||||
|
return paginatedData(filteredPagers) as Pager[]
|
||||||
|
}, [filteredPagers, paginatedData])
|
||||||
|
|
||||||
|
const totalPagesCount = useMemo(() => {
|
||||||
|
return totalPages(filteredPagers.length)
|
||||||
|
}, [filteredPagers.length, totalPages])
|
||||||
|
|
||||||
|
const columns = getPagerTableColumns()
|
||||||
|
const filterFields = usePagerFiltersFields()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='mt-5'>
|
||||||
|
<div className='flex justify-between items-center'>
|
||||||
|
<h1 className='text-lg font-light'>لیست پیجرها</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-8'>
|
||||||
|
<Filters
|
||||||
|
fields={filterFields}
|
||||||
|
onChange={handleFiltersChange}
|
||||||
|
initialValues={filters}
|
||||||
|
searchField="search"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table<Pager & RowDataType>
|
||||||
|
columns={columns}
|
||||||
|
data={paginatedPagers as (Pager & RowDataType)[]}
|
||||||
|
isloading={false}
|
||||||
|
pagination={{
|
||||||
|
currentPage,
|
||||||
|
totalPages: totalPagesCount,
|
||||||
|
onPageChange: handlePageChange,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PagersList
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
|
import type { FieldType } from '@/components/Filters'
|
||||||
|
|
||||||
|
export const usePagerFiltersFields = (): FieldType[] => {
|
||||||
|
return useMemo(() => [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'search',
|
||||||
|
placeholder: 'جستجو',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
name: 'status',
|
||||||
|
placeholder: 'وضعیت',
|
||||||
|
options: [
|
||||||
|
{ label: 'همه', value: '' },
|
||||||
|
{ label: 'فعال', value: 'active' },
|
||||||
|
{ label: 'غیرفعال', value: 'inactive' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
], [])
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import type { ColumnType } from '@/components/types/TableTypes'
|
||||||
|
import type { Pager } from '../types/Types'
|
||||||
|
import { formatOptionalDate } from '@/helpers/func'
|
||||||
|
import Status from '@/components/Status'
|
||||||
|
|
||||||
|
export const getPagerTableColumns = (): ColumnType<Pager>[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'tableNumber',
|
||||||
|
title: 'شماره میز',
|
||||||
|
render: (item: Pager) => {
|
||||||
|
return item.tableNumber || '-'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'message',
|
||||||
|
title: 'پیام',
|
||||||
|
render: (item: Pager) => {
|
||||||
|
return item.message || '-'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'status',
|
||||||
|
title: 'وضعیت',
|
||||||
|
render: (item: Pager) => {
|
||||||
|
const statusLabel = item.status === 'active' ? 'فعال' : 'غیرفعال'
|
||||||
|
const statusVariant = item.status === 'active' ? 'success' : 'error'
|
||||||
|
return (
|
||||||
|
<Status variant={statusVariant} label={statusLabel} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'createdAt',
|
||||||
|
title: 'تاریخ ایجاد',
|
||||||
|
render: (item: Pager) => {
|
||||||
|
return formatOptionalDate(item.createdAt, {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'updatedAt',
|
||||||
|
title: 'آخرین بروزرسانی',
|
||||||
|
render: (item: Pager) => {
|
||||||
|
return formatOptionalDate(item.updatedAt, {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,317 @@
|
|||||||
|
# 🚀 useSocket Hook - راهنمای استفاده
|
||||||
|
|
||||||
|
یک Hook قدرتمند و ساده برای مدیریت Socket.IO در React
|
||||||
|
|
||||||
|
## ✨ ویژگیها
|
||||||
|
|
||||||
|
- ✅ دریافت URL و Token مستقیم از کاربر
|
||||||
|
- ✅ اتصال خودکار
|
||||||
|
- ✅ Reconnection هوشمند
|
||||||
|
- ✅ مدیریت Event Listeners
|
||||||
|
- ✅ TypeScript Support
|
||||||
|
- ✅ مدیریت خطاها
|
||||||
|
- ✅ Clean up خودکار
|
||||||
|
|
||||||
|
## 📦 نصب
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install socket.io-client
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📖 استفاده
|
||||||
|
|
||||||
|
### سادهترین روش (بدون token)
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useSocket } from '@/pages/pager/hooks/useSocket';
|
||||||
|
|
||||||
|
function MyComponent() {
|
||||||
|
const { isConnected, emit, on } = useSocket('http://localhost:3000');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
on('message', (data) => {
|
||||||
|
console.log('پیام:', data);
|
||||||
|
});
|
||||||
|
}, [on]);
|
||||||
|
|
||||||
|
return <div>{isConnected ? 'متصل' : 'قطع'}</div>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### با Token
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function MyComponent() {
|
||||||
|
const token = localStorage.getItem('token') || '';
|
||||||
|
const { isConnected, emit, on } = useSocket('http://localhost:3000', token);
|
||||||
|
|
||||||
|
return <div>{isConnected ? 'متصل' : 'قطع'}</div>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### با تنظیمات کامل
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function MyComponent() {
|
||||||
|
const url = 'http://localhost:3000';
|
||||||
|
const token = 'your-jwt-token';
|
||||||
|
|
||||||
|
const { isConnected, emit, on, error } = useSocket(url, token, {
|
||||||
|
reconnectionAttempts: 10,
|
||||||
|
reconnectionDelay: 2000,
|
||||||
|
query: {
|
||||||
|
userId: '123',
|
||||||
|
room: 'general',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{error && <p>خطا: {error.message}</p>}
|
||||||
|
<p>{isConnected ? 'متصل' : 'قطع'}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 API
|
||||||
|
|
||||||
|
### پارامترها
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
useSocket(url: string, token?: string, options?: UseSocketOptions)
|
||||||
|
```
|
||||||
|
|
||||||
|
| نام | نوع | الزامی | توضیح |
|
||||||
|
|-----|-----|--------|-------|
|
||||||
|
| `url` | string | ✅ | آدرس سرور سوکت |
|
||||||
|
| `token` | string | ❌ | توکن احراز هویت |
|
||||||
|
| `options` | object | ❌ | تنظیمات اضافی |
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| نام | نوع | پیشفرض | توضیح |
|
||||||
|
|-----|-----|---------|-------|
|
||||||
|
| `reconnection` | boolean | true | تلاش مجدد برای اتصال |
|
||||||
|
| `reconnectionAttempts` | number | 5 | تعداد تلاش مجدد |
|
||||||
|
| `reconnectionDelay` | number | 1000 | تاخیر بین تلاشها (ms) |
|
||||||
|
| `query` | object | - | پارامترهای query string |
|
||||||
|
|
||||||
|
### Return Values
|
||||||
|
|
||||||
|
| نام | نوع | توضیح |
|
||||||
|
|-----|-----|-------|
|
||||||
|
| `socket` | Socket \| null | instance سوکت |
|
||||||
|
| `isConnected` | boolean | وضعیت اتصال |
|
||||||
|
| `error` | Error \| null | خطای اتصال |
|
||||||
|
| `emit` | function | ارسال event |
|
||||||
|
| `on` | function | گوش دادن به event |
|
||||||
|
| `off` | function | حذف listener |
|
||||||
|
| `connect` | function | اتصال مجدد |
|
||||||
|
| `disconnect` | function | قطع اتصال |
|
||||||
|
|
||||||
|
## 💡 مثالهای کاربردی
|
||||||
|
|
||||||
|
### 1️⃣ صفحه چت
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function ChatPage() {
|
||||||
|
const [messages, setMessages] = useState([]);
|
||||||
|
const token = localStorage.getItem('token') || '';
|
||||||
|
|
||||||
|
const { isConnected, emit, on } = useSocket(
|
||||||
|
'http://localhost:3000',
|
||||||
|
token
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
on('chat:message', (message) => {
|
||||||
|
setMessages(prev => [...prev, message]);
|
||||||
|
});
|
||||||
|
}, [on]);
|
||||||
|
|
||||||
|
const sendMessage = (text: string) => {
|
||||||
|
emit('chat:send', { text, timestamp: Date.now() });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{messages.map((msg, i) => (
|
||||||
|
<div key={i}>{msg.text}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2️⃣ دریافت اعلانها
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function Notifications() {
|
||||||
|
const [notifications, setNotifications] = useState([]);
|
||||||
|
const token = localStorage.getItem('authToken') || '';
|
||||||
|
|
||||||
|
const { on } = useSocket('http://localhost:4000', token);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
on('notification:new', (notif) => {
|
||||||
|
setNotifications(prev => [notif, ...prev]);
|
||||||
|
});
|
||||||
|
}, [on]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{notifications.map((n, i) => (
|
||||||
|
<div key={i}>{n.message}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3️⃣ صفحه Pager
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function PagerList() {
|
||||||
|
const socketUrl = import.meta.env.VITE_SOCKET_URL;
|
||||||
|
const token = localStorage.getItem('token') || '';
|
||||||
|
|
||||||
|
const { isConnected, emit, on } = useSocket(socketUrl, token);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
on('page:new', (page) => {
|
||||||
|
console.log('پیج جدید:', page);
|
||||||
|
});
|
||||||
|
|
||||||
|
on('page:status', (status) => {
|
||||||
|
console.log('وضعیت:', status);
|
||||||
|
});
|
||||||
|
}, [on]);
|
||||||
|
|
||||||
|
const callPage = (number: string) => {
|
||||||
|
emit('page:call', { number });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={isConnected ? 'text-green-500' : 'text-red-500'}>
|
||||||
|
{isConnected ? 'آنلاین' : 'آفلاین'}
|
||||||
|
</div>
|
||||||
|
<button onClick={() => callPage('101')}>
|
||||||
|
فراخوانی
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4️⃣ استفاده با useAuth
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function MyComponent() {
|
||||||
|
const { token } = useAuth(); // hook شما برای احراز هویت
|
||||||
|
const { isConnected, emit } = useSocket('http://localhost:3000', token);
|
||||||
|
|
||||||
|
return <div>{isConnected ? 'متصل' : 'قطع'}</div>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5️⃣ Custom Hook برای استفاده راحتتر
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// hooks/useAppSocket.ts
|
||||||
|
export const useAppSocket = () => {
|
||||||
|
const token = localStorage.getItem('token') || '';
|
||||||
|
const url = import.meta.env.VITE_SOCKET_URL || 'http://localhost:3000';
|
||||||
|
|
||||||
|
return useSocket(url, token, {
|
||||||
|
reconnectionAttempts: 3,
|
||||||
|
reconnectionDelay: 1500,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// استفاده در کامپوننت
|
||||||
|
function MyComponent() {
|
||||||
|
const { isConnected, emit, on } = useAppSocket();
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎨 نمایش وضعیت اتصال
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function ConnectionStatus() {
|
||||||
|
const { isConnected, error } = useSocket('http://localhost:3000');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`
|
||||||
|
px-3 py-1 rounded-full text-sm font-medium
|
||||||
|
${isConnected
|
||||||
|
? 'bg-green-100 text-green-800'
|
||||||
|
: 'bg-red-100 text-red-800'}
|
||||||
|
`}>
|
||||||
|
{isConnected ? '🟢 متصل' : '🔴 قطع'}
|
||||||
|
{error && ` - خطا: ${error.message}`}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔥 نکات مهم
|
||||||
|
|
||||||
|
1. **Token Security**: توکن رو توی localStorage نگه دار
|
||||||
|
2. **URL از .env**: آدرس سوکت رو توی `.env` بذار
|
||||||
|
3. **Clean Up خودکار**: نگران پاک کردن listeners نباش
|
||||||
|
4. **یک instance در هر کامپوننت**: هر کامپوننت instance جدید میسازه
|
||||||
|
|
||||||
|
## 🐛 رفع مشکلات
|
||||||
|
|
||||||
|
### خطای اتصال
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const { error, isConnected } = useSocket(url, token);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('خطا:', error.message);
|
||||||
|
// نمایش پیام به کاربر
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### قطع و وصل دستی
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const { connect, disconnect, isConnected } = useSocket(url, token);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{isConnected ? (
|
||||||
|
<button onClick={disconnect}>قطع</button>
|
||||||
|
) : (
|
||||||
|
<button onClick={connect}>اتصال</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### چند event یکجا
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
useEffect(() => {
|
||||||
|
on('event1', handler1);
|
||||||
|
on('event2', handler2);
|
||||||
|
on('event3', handler3);
|
||||||
|
}, [on]);
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚙️ تنظیمات محیط (.env)
|
||||||
|
|
||||||
|
```env
|
||||||
|
VITE_SOCKET_URL=http://localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**ساخته شده با ❤️ برای Danak**
|
||||||
|
|
||||||
|
موفق باشی! 🚀
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { useState, useMemo } from "react";
|
||||||
|
import type { FilterValues } from "@/components/Filters";
|
||||||
|
|
||||||
|
const DEFAULT_PAGE = 1;
|
||||||
|
const DEFAULT_LIMIT = 10;
|
||||||
|
|
||||||
|
export const usePagerFilters = () => {
|
||||||
|
const [filters, setFilters] = useState<FilterValues>({});
|
||||||
|
const [currentPage, setCurrentPage] = useState(DEFAULT_PAGE);
|
||||||
|
const [limit] = useState(DEFAULT_LIMIT);
|
||||||
|
|
||||||
|
const handleFiltersChange = (newFilters: FilterValues) => {
|
||||||
|
setFilters(newFilters);
|
||||||
|
setCurrentPage(DEFAULT_PAGE);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageChange = (page: number) => {
|
||||||
|
setCurrentPage(page);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredData = useMemo(() => {
|
||||||
|
return (data: unknown[]) => {
|
||||||
|
let filtered = [...data];
|
||||||
|
|
||||||
|
if (filters.search) {
|
||||||
|
const searchTerm = (filters.search as string).toLowerCase();
|
||||||
|
filtered = filtered.filter((item: unknown) => {
|
||||||
|
const pager = item as { tableNumber?: string; message?: string };
|
||||||
|
return (
|
||||||
|
pager.tableNumber?.toLowerCase().includes(searchTerm) ||
|
||||||
|
pager.message?.toLowerCase().includes(searchTerm)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.status && filters.status !== "") {
|
||||||
|
filtered = filtered.filter((item: unknown) => {
|
||||||
|
const pager = item as { status?: string };
|
||||||
|
return pager.status === filters.status;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
};
|
||||||
|
}, [filters]);
|
||||||
|
|
||||||
|
const paginatedData = useMemo(() => {
|
||||||
|
return (data: unknown[]) => {
|
||||||
|
const startIndex = (currentPage - 1) * limit;
|
||||||
|
return data.slice(startIndex, startIndex + limit);
|
||||||
|
};
|
||||||
|
}, [currentPage, limit]);
|
||||||
|
|
||||||
|
const totalPages = useMemo(() => {
|
||||||
|
return (dataLength: number) => {
|
||||||
|
return Math.ceil(dataLength / limit) || 1;
|
||||||
|
};
|
||||||
|
}, [limit]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
filters,
|
||||||
|
currentPage,
|
||||||
|
handleFiltersChange,
|
||||||
|
handlePageChange,
|
||||||
|
limit,
|
||||||
|
filteredData,
|
||||||
|
paginatedData,
|
||||||
|
totalPages,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
import { useEffect, useRef, useState, useCallback } from "react";
|
||||||
|
import { io, Socket } from "socket.io-client";
|
||||||
|
|
||||||
|
interface UseSocketOptions {
|
||||||
|
reconnection?: boolean;
|
||||||
|
reconnectionAttempts?: number;
|
||||||
|
reconnectionDelay?: number;
|
||||||
|
query?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseSocketReturn {
|
||||||
|
socket: Socket | null;
|
||||||
|
isConnected: boolean;
|
||||||
|
error: Error | null;
|
||||||
|
emit: (event: string, data?: unknown) => void;
|
||||||
|
on: (event: string, callback: (...args: unknown[]) => void) => void;
|
||||||
|
off: (event: string, callback?: (...args: unknown[]) => void) => void;
|
||||||
|
connect: () => void;
|
||||||
|
disconnect: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSocket = (
|
||||||
|
url: string,
|
||||||
|
token?: string,
|
||||||
|
options: UseSocketOptions = {}
|
||||||
|
): UseSocketReturn => {
|
||||||
|
const socketRef = useRef<Socket | null>(null);
|
||||||
|
const coreListenersAttachedRef = useRef(false);
|
||||||
|
const [isConnected, setIsConnected] = useState(false);
|
||||||
|
const [error, setError] = useState<Error | null>(null);
|
||||||
|
const listenersRef = useRef<Map<string, Set<(...args: unknown[]) => void>>>(
|
||||||
|
new Map()
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
reconnection = true,
|
||||||
|
reconnectionAttempts = 5,
|
||||||
|
reconnectionDelay = 1000,
|
||||||
|
query,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
const handleConnect = useCallback(() => {
|
||||||
|
setIsConnected(true);
|
||||||
|
setError(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleDisconnect = useCallback(() => {
|
||||||
|
setIsConnected(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleConnectError = useCallback((err: Error) => {
|
||||||
|
setError(err);
|
||||||
|
setIsConnected(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const attachCoreListeners = useCallback(
|
||||||
|
(socket: Socket) => {
|
||||||
|
if (coreListenersAttachedRef.current) return;
|
||||||
|
socket.on("connect", handleConnect);
|
||||||
|
socket.on("disconnect", handleDisconnect);
|
||||||
|
socket.on("connect_error", handleConnectError);
|
||||||
|
coreListenersAttachedRef.current = true;
|
||||||
|
},
|
||||||
|
[handleConnect, handleDisconnect, handleConnectError]
|
||||||
|
);
|
||||||
|
|
||||||
|
const connect = useCallback(() => {
|
||||||
|
if (socketRef.current?.connected) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!socketRef.current) {
|
||||||
|
const socketOptions: {
|
||||||
|
autoConnect: boolean;
|
||||||
|
reconnection: boolean;
|
||||||
|
reconnectionAttempts: number;
|
||||||
|
reconnectionDelay: number;
|
||||||
|
auth?: { token: string };
|
||||||
|
query?: Record<string, string>;
|
||||||
|
} = {
|
||||||
|
autoConnect: false,
|
||||||
|
reconnection,
|
||||||
|
reconnectionAttempts,
|
||||||
|
reconnectionDelay,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
socketOptions.auth = { token };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query) {
|
||||||
|
socketOptions.query = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
socketRef.current = io(url, socketOptions);
|
||||||
|
attachCoreListeners(socketRef.current);
|
||||||
|
} else {
|
||||||
|
attachCoreListeners(socketRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
socketRef.current.connect();
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err : new Error("خطا در اتصال به سوکت"));
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
url,
|
||||||
|
token,
|
||||||
|
reconnection,
|
||||||
|
reconnectionAttempts,
|
||||||
|
reconnectionDelay,
|
||||||
|
query,
|
||||||
|
attachCoreListeners,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const disconnect = useCallback(() => {
|
||||||
|
if (socketRef.current) {
|
||||||
|
socketRef.current.disconnect();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const emit = useCallback((event: string, data?: unknown) => {
|
||||||
|
if (socketRef.current?.connected) {
|
||||||
|
socketRef.current.emit(event, data);
|
||||||
|
} else {
|
||||||
|
console.warn("سوکت متصل نیست. امکان ارسال داده وجود ندارد.");
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const on = useCallback(
|
||||||
|
(event: string, callback: (...args: unknown[]) => void) => {
|
||||||
|
if (!socketRef.current) return;
|
||||||
|
|
||||||
|
socketRef.current.on(event, callback);
|
||||||
|
|
||||||
|
if (!listenersRef.current.has(event)) {
|
||||||
|
listenersRef.current.set(event, new Set());
|
||||||
|
}
|
||||||
|
listenersRef.current.get(event)!.add(callback);
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const off = useCallback(
|
||||||
|
(event: string, callback?: (...args: unknown[]) => void) => {
|
||||||
|
if (!socketRef.current) return;
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
socketRef.current.off(event, callback);
|
||||||
|
listenersRef.current.get(event)?.delete(callback);
|
||||||
|
} else {
|
||||||
|
socketRef.current.off(event);
|
||||||
|
listenersRef.current.delete(event);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
connect();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
listenersRef.current.forEach((callbacks, event) => {
|
||||||
|
callbacks.forEach((callback) => {
|
||||||
|
socketRef.current?.off(event, callback);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
listenersRef.current.clear();
|
||||||
|
|
||||||
|
if (socketRef.current) {
|
||||||
|
socketRef.current.off("connect", handleConnect);
|
||||||
|
socketRef.current.off("disconnect", handleDisconnect);
|
||||||
|
socketRef.current.off("connect_error", handleConnectError);
|
||||||
|
coreListenersAttachedRef.current = false;
|
||||||
|
socketRef.current.disconnect();
|
||||||
|
socketRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [connect, handleConnect, handleDisconnect, handleConnectError]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
socket: socketRef.current,
|
||||||
|
isConnected,
|
||||||
|
error,
|
||||||
|
emit,
|
||||||
|
on,
|
||||||
|
off,
|
||||||
|
connect,
|
||||||
|
disconnect,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export type Pager = {
|
||||||
|
id: string;
|
||||||
|
tableNumber: string;
|
||||||
|
message: string;
|
||||||
|
status: string;
|
||||||
|
cookieId: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Pagers = {
|
||||||
|
pagers: Pager[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PagersType = Pagers;
|
||||||
@@ -38,6 +38,7 @@ import CreateCoupon from '@/pages/coupon/Create'
|
|||||||
import UpdateCoupon from '@/pages/coupon/Update'
|
import UpdateCoupon from '@/pages/coupon/Update'
|
||||||
import ReviewsList from '@/pages/review/List'
|
import ReviewsList from '@/pages/review/List'
|
||||||
import DetailReview from '@/pages/review/Detail'
|
import DetailReview from '@/pages/review/Detail'
|
||||||
|
import PagersList from '@/pages/pager/List'
|
||||||
const MainRouter: FC = () => {
|
const MainRouter: FC = () => {
|
||||||
|
|
||||||
const { hasSubMenu } = useSharedStore()
|
const { hasSubMenu } = useSharedStore()
|
||||||
@@ -92,6 +93,8 @@ const MainRouter: FC = () => {
|
|||||||
|
|
||||||
<Route path={Pages.reviews.list} element={<ReviewsList />} />
|
<Route path={Pages.reviews.list} element={<ReviewsList />} />
|
||||||
<Route path={Pages.reviews.detail + ':id'} element={<DetailReview />} />
|
<Route path={Pages.reviews.detail + ':id'} element={<DetailReview />} />
|
||||||
|
|
||||||
|
<Route path={Pages.pagers.list} element={<PagersList />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+10
-1
@@ -2,7 +2,7 @@ import { type FC, useEffect } from 'react'
|
|||||||
import LogoImage from '../assets/images/logo.svg'
|
import LogoImage from '../assets/images/logo.svg'
|
||||||
import LogoSmall from '../assets/images/logo-small.svg'
|
import LogoSmall from '../assets/images/logo-small.svg'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { DocumentText, Home2, Logout, Message, NotificationStatus, People, Setting2, TicketDiscount, Chart, Calendar, Security, Card, SecurityUser, TruckFast, Element3, Star1 } from 'iconsax-react'
|
import { DocumentText, Home2, Logout, Message, NotificationStatus, People, Setting2, TicketDiscount, Chart, Calendar, Security, Card, SecurityUser, TruckFast, Element3, Star1, NotificationBing } from 'iconsax-react'
|
||||||
import SideBarItem from './SideBarItem'
|
import SideBarItem from './SideBarItem'
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
import { Pages } from '../config/Pages'
|
import { Pages } from '../config/Pages'
|
||||||
@@ -129,6 +129,15 @@ const SideBar: FC = () => {
|
|||||||
activeName='reviews'
|
activeName='reviews'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<SideBarItem
|
||||||
|
icon={<NotificationBing variant={isActive('pagers') ? 'Bold' : 'Outline'} color={isActive('pagers') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||||
|
title={t('sidebar.pagers')}
|
||||||
|
isActive={isActive('pagers')}
|
||||||
|
link={Pages.pagers.list}
|
||||||
|
name='pagers'
|
||||||
|
activeName='pagers'
|
||||||
|
/>
|
||||||
|
|
||||||
<SideBarItem
|
<SideBarItem
|
||||||
icon={<SecurityUser variant={isActive('admins') ? 'Bold' : 'Outline'} color={isActive('admins') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
icon={<SecurityUser variant={isActive('admins') ? 'Bold' : 'Outline'} color={isActive('admins') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||||
title={t('sidebar.admins')}
|
title={t('sidebar.admins')}
|
||||||
|
|||||||
Reference in New Issue
Block a user