diff --git a/package-lock.json b/package-lock.json index 0b3d63e..4b04315 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@tailwindcss/vite": "^4.1.14", "@tanstack/react-query": "^5.90.3", "axios": "^1.12.2", + "formik": "^2.4.6", "iconsax-react": "^0.0.8", "moment-jalaali": "^0.10.4", "react": "^19.2.0", @@ -23,10 +24,12 @@ "react-multi-date-picker": "^4.5.2", "react-router-dom": "^7.9.4", "react-spinners": "^0.17.0", + "react-toastify": "^11.0.5", "swiper": "^12.0.2", "tailwind-merge": "^3.3.1", "tailwindcss": "^4.1.14", "vite-tsconfig-paths": "^5.1.4", + "yup": "^1.7.1", "zustand": "^5.0.8" }, "devDependencies": { @@ -2102,6 +2105,18 @@ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz", + "integrity": "sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==", + "license": "MIT", + "dependencies": { + "hoist-non-react-statics": "^3.3.0" + }, + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -2133,7 +2148,6 @@ "version": "19.2.2", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", - "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -2768,7 +2782,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true, "license": "MIT" }, "node_modules/debug": { @@ -2795,6 +2808,15 @@ "dev": true, "license": "MIT" }, + "node_modules/deepmerge": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", + "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -3307,6 +3329,31 @@ "node": ">= 6" } }, + "node_modules/formik": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.6.tgz", + "integrity": "sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==", + "funding": [ + { + "type": "individual", + "url": "https://opencollective.com/formik" + } + ], + "license": "Apache-2.0", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.1", + "deepmerge": "^2.1.1", + "hoist-non-react-statics": "^3.3.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "react-fast-compare": "^2.0.1", + "tiny-warning": "^1.0.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3483,6 +3530,15 @@ "node": ">= 0.4" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/iconsax-react": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/iconsax-react/-/iconsax-react-0.0.8.tgz", @@ -3921,6 +3977,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -4277,6 +4345,12 @@ "react-is": "^16.13.1" } }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==", + "license": "MIT" + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -4368,6 +4442,12 @@ "react-dom": ">=16.8.0" } }, + "node_modules/react-fast-compare": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", + "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==", + "license": "MIT" + }, "node_modules/react-infinite-scroll-component": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz", @@ -4467,6 +4547,19 @@ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/react-toastify": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz", + "integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -4721,6 +4814,18 @@ "node": ">=8" } }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==", + "license": "MIT" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -4779,6 +4884,12 @@ "node": ">=8.0" } }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", + "license": "MIT" + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -4831,6 +4942,18 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -5094,6 +5217,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yup": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.7.1.tgz", + "integrity": "sha512-GKHFX2nXul2/4Dtfxhozv701jLQHdf6J34YDh2cEkpqoo8le5Mg6/LrdseVLrFarmFygZTlfIhHx/QKfb/QWXw==", + "license": "MIT", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, "node_modules/zustand": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz", diff --git a/package.json b/package.json index d3ea02e..574ea9d 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@tailwindcss/vite": "^4.1.14", "@tanstack/react-query": "^5.90.3", "axios": "^1.12.2", + "formik": "^2.4.6", "iconsax-react": "^0.0.8", "moment-jalaali": "^0.10.4", "react": "^19.2.0", @@ -25,10 +26,12 @@ "react-multi-date-picker": "^4.5.2", "react-router-dom": "^7.9.4", "react-spinners": "^0.17.0", + "react-toastify": "^11.0.5", "swiper": "^12.0.2", "tailwind-merge": "^3.3.1", "tailwindcss": "^4.1.14", "vite-tsconfig-paths": "^5.1.4", + "yup": "^1.7.1", "zustand": "^5.0.8" }, "devDependencies": { diff --git a/src/App.tsx b/src/App.tsx index f6c0015..70d6589 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,20 +2,47 @@ import { type FC } from 'react' import '@/assets/fonts/irancell/style.css' import 'swiper/swiper-bundle.css'; import 'react-loading-skeleton/dist/skeleton.css' +import 'react-toastify/dist/ReactToastify.css' import { BrowserRouter } from 'react-router-dom' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { ToastContainer } from 'react-toastify' // import { getToken } from './config/func' import MainRouter from './router/MainRouter' // import AuthRouter from './router/AuthRouter' +// ایجاد QueryClient برای React Query +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + retry: 1, + staleTime: 5 * 60 * 1000, // 5 minutes + }, + }, +}) + const App: FC = () => { // const isLoggedIn = getToken() return ( - - {/* {isLoggedIn ? : } */} - - + + + {/* {isLoggedIn ? : } */} + + + + ) } diff --git a/src/components/Tabs.tsx b/src/components/Tabs.tsx index a8e188a..62086b8 100644 --- a/src/components/Tabs.tsx +++ b/src/components/Tabs.tsx @@ -1,20 +1,25 @@ import { clx } from '@/helpers/utils' -import { type FC } from 'react' +import { type FC, type ReactNode } from 'react' type TabItem = { label: string value: string + icon?: ReactNode } type Props = { items: TabItem[] - activeTab: string - onTabChange: (tab: string) => void + active?: string + activeTab?: string + onChange?: (tab: string) => void + onTabChange?: (tab: string) => void } const Tabs: FC = (props) => { - const { items, activeTab, onTabChange } = props + const { items, activeTab, active, onTabChange, onChange } = props + const currentActive = active || activeTab || '' + const handleChange = onChange || onTabChange || (() => { }) return ( @@ -23,10 +28,11 @@ const Tabs: FC = (props) => { { items.map((item) => { return ( -
onTabChange(item.value)} key={item.value} className={clx( - 'h-[32px] flex items-center justify-center w-[122px] cursor-pointer rounded-full', - activeTab === item.value && 'bg-primary' +
handleChange(item.value)} key={item.value} className={clx( + 'h-[32px] flex items-center gap-2 justify-center min-w-[122px] px-3 cursor-pointer rounded-full', + currentActive === item.value && 'bg-primary' )}> + {item.icon} {item.label}
) diff --git a/src/config/Paths.tsx b/src/config/Paths.tsx index 165ae24..952d10c 100644 --- a/src/config/Paths.tsx +++ b/src/config/Paths.tsx @@ -35,4 +35,10 @@ export const Paths = { auth: { login: '/auth/login', }, + ticket: { + list: "/tickets/list", + create: "/tickets/create", + detail: "/tickets/messages/", + category: "/tickets/category", + }, } \ No newline at end of file diff --git a/src/pages/ticket/Category.tsx b/src/pages/ticket/Category.tsx new file mode 100644 index 0000000..c325f28 --- /dev/null +++ b/src/pages/ticket/Category.tsx @@ -0,0 +1,196 @@ +import { type FC } from 'react' +import Select from '@/components/Select' +import Input from '@/components/Input' +import Td from '@/components/Td' +import SwitchComponent from '@/components/Switch' +import { useFormik } from 'formik' +import type { CategoriesItemType, CreateCategoryType } from './types/TicketTypes' +import * as Yup from 'yup' +import Button from '@/components/Button' +import { useCreateCategory, useGetCategories } from './hooks/useTicketData' +import Textarea from '@/components/Textarea' +import type { ErrorType } from '@/helpers/types' +import { toast } from 'react-toastify' + +const TicketCategory: FC = () => { + + // TODO: این hook باید بعداً وصل شود - مربوط به users است + // const getGroups = useGetGroups() + const createCategory = useCreateCategory() + const getCategories = useGetCategories() + + const formik = useFormik({ + initialValues: { + title: '', + description: '', + userGroupId: '', + isActive: true + }, + validationSchema: Yup.object({ + title: Yup.string().required('این فیلد الزامی است'), + userGroupId: Yup.string().required('این فیلد الزامی است'), + description: Yup.string().required('این فیلد الزامی است'), + }), + onSubmit: (values: CreateCategoryType) => { + createCategory.mutate(values, { + onSuccess: () => { + formik.resetForm() + }, + onError: (error: ErrorType) => { + toast.error(error.response?.data?.error.message[0]) + } + }) + } + }) + + return ( +
+
+ دسته‌بندی تیکت‌ها +
+ +
+
+
+
+ +
+
+
+
+ + + + + + + { + getCategories.data?.data?.ticketCategories?.map((item: CategoriesItemType) => ( + + + )) + } + +
+ +
+ +
+
+
+
+ +
+
+ افزودن دسته‌بندی +
+ +
+
+ وضعیت دسته‌بندی +
+ + formik.setFieldValue('isActive', value)} + /> +
+ +
+ +
+ +
+