From d43774a8a1047978f80a57850b08e8d4c914229c Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Sat, 25 Jul 2026 10:33:09 +0330 Subject: [PATCH] improvement --- package-lock.json | 24 ++++++++- src/pages/auth/components/LoginStep1.tsx | 2 +- src/pages/auth/components/LoginStep3.tsx | 1 + src/pages/ticket/Detail.tsx | 18 +++++-- src/pages/ticket/TicketList.tsx | 68 +++++++++++++++++++----- src/shared/SideBar.tsx | 46 ++++++---------- src/shared/components/SupportSubMenu.tsx | 12 ++--- 7 files changed, 115 insertions(+), 56 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6ff6cce..7d25a80 100644 --- a/package-lock.json +++ b/package-lock.json @@ -113,6 +113,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.0", @@ -2412,6 +2413,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -2424,6 +2426,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -3072,6 +3075,7 @@ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -3173,6 +3177,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -3249,6 +3254,7 @@ "integrity": "sha512-rBnTWHCdbYM2lh7hjyXqxk70wvon3p2FyaniZuey5TrcGBpfhVp0OxOa6gxr9Q9YhZFKyfbEnxc24ZnVbbUkCA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.18.1", "@typescript-eslint/types": "8.18.1", @@ -3457,6 +3463,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3812,6 +3819,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -4542,6 +4550,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5456,6 +5465,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.23.2" }, @@ -6786,6 +6796,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -6952,6 +6963,7 @@ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -7087,6 +7099,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -7102,6 +7115,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.25.0" }, @@ -7324,7 +7338,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", @@ -7503,6 +7518,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.7" }, @@ -8406,6 +8422,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -8569,6 +8586,7 @@ "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8776,6 +8794,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -8897,6 +8916,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -9187,6 +9207,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -9248,6 +9269,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "license": "MIT", + "peer": true, "bin": { "rollup": "dist/bin/rollup" }, diff --git a/src/pages/auth/components/LoginStep1.tsx b/src/pages/auth/components/LoginStep1.tsx index 2360d0c..2e1b3bc 100644 --- a/src/pages/auth/components/LoginStep1.tsx +++ b/src/pages/auth/components/LoginStep1.tsx @@ -69,7 +69,7 @@ const LoginStep1: FC = () => { label={t('auth.mobile_or_email')} placeholder={t('auth.enter_mobile_or_email')} type='text' - className='text-right' + dir='ltr' name='phone_email' onChange={formik.handleChange} value={formik.values.phone_email} diff --git a/src/pages/auth/components/LoginStep3.tsx b/src/pages/auth/components/LoginStep3.tsx index d59fdc1..6297e83 100644 --- a/src/pages/auth/components/LoginStep3.tsx +++ b/src/pages/auth/components/LoginStep3.tsx @@ -63,6 +63,7 @@ const LoginStep3: FC = () => { type='password' className='text-right' name='password' + dir='ltr' onChange={formik.handleChange} value={formik.values.password} error_text={formik.touched.password && formik.errors.password ? formik.errors.password : ''} diff --git a/src/pages/ticket/Detail.tsx b/src/pages/ticket/Detail.tsx index 0eb0e3a..9b39631 100644 --- a/src/pages/ticket/Detail.tsx +++ b/src/pages/ticket/Detail.tsx @@ -4,7 +4,7 @@ import Input from '../../components/Input' import Textarea from '../../components/Textarea' import UploadBox from '../../components/UploadBox' import Button from '../../components/Button' -import { CloseCircle, InfoCircle, Paperclip2 } from 'iconsax-react' +import { ArrowRight, CloseCircle, InfoCircle, Paperclip2 } from 'iconsax-react' import { useNavigate, useParams } from 'react-router-dom' import { useAddMessageTicket, useCloseTicket, useGetMessages } from './hooks/useTicketData' import PageLoading from '../../components/PageLoading' @@ -89,9 +89,19 @@ const TicketDetail: FC = () => { return (
-
-
{t('ticket.ticket_number')}
-
{getMessages.data?.data?.ticket?.numericId}
+
+
+
{t('ticket.ticket_number')}
+
{getMessages.data?.data?.ticket?.numericId}
+
+
{ getMessages.isPending ? diff --git a/src/pages/ticket/TicketList.tsx b/src/pages/ticket/TicketList.tsx index fcb534d..8ec5e8b 100644 --- a/src/pages/ticket/TicketList.tsx +++ b/src/pages/ticket/TicketList.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useState } from 'react' +import { FC, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { Eye, MessageRemove, MessageTick, MessageTime } from 'iconsax-react' import Tabs from '../../components/Tabs' @@ -11,23 +11,63 @@ import { TicketItemType } from './types/TicketTypes' import OpenTicket from './components/OpenTicket' import Pagination from '../../components/Pagination' +const VALID_STATUSES = ['ANSWERED', 'PENDING', 'CLOSED'] as const +type TicketStatus = (typeof VALID_STATUSES)[number] | '' + const TicketList: FC = () => { const { t } = useTranslation('global') - const [page, setPage] = useState(1) - const [searchParams] = useSearchParams() - const [customerId, setCustomerId] = useState('') - const [activeTab, setActiveTab] = useState<'ANSWERED' | 'PENDING' | 'CLOSED' | ''>('PENDING') + const [searchParams, setSearchParams] = useSearchParams() + + const customerId = searchParams.get('user') ?? '' + const statusParam = searchParams.get('status') + + const activeTab: TicketStatus = (() => { + if (statusParam && (VALID_STATUSES as readonly string[]).includes(statusParam)) { + return statusParam as (typeof VALID_STATUSES)[number] + } + // Customer deep-link without status → show all statuses for that user + if (customerId && !statusParam) return '' + return 'PENDING' + })() + + const page = Math.max(1, parseInt(searchParams.get('page') || '1', 10) || 1) const getTicket = useGetTickets(activeTab, customerId, page) + // Keep status & page in the URL (replace so back button is not polluted) useEffect(() => { - const urlCustomerId = searchParams.get('user') - if (urlCustomerId) { - setCustomerId(urlCustomerId) - setActiveTab('') - } - }, [searchParams]) + const next = new URLSearchParams(searchParams) + let changed = false + if (!statusParam && !customerId) { + next.set('status', 'PENDING') + changed = true + } + + if (!searchParams.get('page')) { + next.set('page', String(page)) + changed = true + } + + if (changed) { + setSearchParams(next, { replace: true }) + } + }, [customerId, page, searchParams, setSearchParams, statusParam]) + + const updateParams = (updates: { status?: string; page?: number }) => { + const next = new URLSearchParams(searchParams) + + if (updates.status !== undefined) { + if (updates.status === '') next.delete('status') + else next.set('status', updates.status) + } + + if (updates.page !== undefined) { + next.set('page', String(Math.max(1, updates.page))) + } + + setSearchParams(next) + } return (
@@ -75,7 +115,7 @@ const TicketList: FC = () => { value: 'CLOSED' }, ]} - onChange={(value) => setActiveTab(value as 'ANSWERED' | 'PENDING' | 'CLOSED')} + onChange={(value) => updateParams({ status: value, page: 1 })} />
@@ -135,7 +175,7 @@ const TicketList: FC = () => { updateParams({ page: newPage })} totalPages={getTicket.data?.data?.pager?.totalPages} />
@@ -143,4 +183,4 @@ const TicketList: FC = () => { ) } -export default TicketList \ No newline at end of file +export default TicketList diff --git a/src/shared/SideBar.tsx b/src/shared/SideBar.tsx index e7ff639..4b08f0e 100644 --- a/src/shared/SideBar.tsx +++ b/src/shared/SideBar.tsx @@ -1,18 +1,4 @@ -import { - Box, - Category, - DocumentLike, - Headphone, - Home, - Logout, - Money3, - Profile, - Profile2User, - Setting2, - TickSquare, - UserOctagon, - UserTick, -} from "iconsax-react"; +import { Box, Category, DocumentLike, Headphone, Home, Logout, Money3, Profile, Profile2User, Setting2, TickSquare, UserOctagon, UserTick } from "iconsax-react"; import { FC, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { useLocation } from "react-router-dom"; @@ -133,6 +119,21 @@ const SideBar: FC = () => { activeName="" /> + + } + title={t("sidebar.support_section")} + isActive={isGroupActive("support")} + link={Pages.ticket.list} + name="support" + activeNames={SUPPORT_PERMISSION_RESOURCES} + /> + { activeNames={FINANCIAL_PERMISSION_RESOURCES} /> - - } - title={t("sidebar.support_section")} - isActive={isGroupActive("support")} - link={Pages.support.list} - name="support" - activeNames={SUPPORT_PERMISSION_RESOURCES} - /> - {profile?.data?.user?.roles?.some((role: { name: string }) => role.name === "super_admin") && ( {
- +