Compare commits
71 Commits
1d92a954d0
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b96ecf9d9 | |||
| 4bb164be0f | |||
| 2f71b4c96c | |||
| cabf248c0e | |||
| 1990e73368 | |||
| 185961c162 | |||
| b60a38e21d | |||
| f53b5d8fe1 | |||
| f5da44d9cf | |||
| 77475f09cd | |||
| 9413cf03c9 | |||
| b2f9166c22 | |||
| 7fedfdfbfd | |||
| 71e7d4eac2 | |||
| c9713a93ac | |||
| 42528f4d5c | |||
| 52bb36ac3c | |||
| f7871db066 | |||
| 153547d098 | |||
| 09feb73331 | |||
| 9a7858a4ec | |||
| df2f800b54 | |||
| 3537094e7c | |||
| fa55fd0f02 | |||
| d43774a8a1 | |||
| 13781df281 | |||
| 086db34c12 | |||
| b5b8344888 | |||
| 76a68adc1c | |||
| 24171dbaf8 | |||
| 9aadb8e6ad | |||
| 96d750fc88 | |||
| f1173adc6d | |||
| a2d2234a28 | |||
| 81353fc281 | |||
| 402354e122 | |||
| 9004413a33 | |||
| 6b5ac30f32 | |||
| 09adc8bdd0 | |||
| bddf675d66 | |||
| dd9bc0ddca | |||
| 65154a1aab | |||
| 2af7323a6a | |||
| 70e159cefc | |||
| 6609666279 | |||
| a3ce347f02 | |||
| d3fd21177b | |||
| feed194673 | |||
| f2cfbf6a5b | |||
| 94639858f2 | |||
| bd65d28ea3 | |||
| ee792b69bf | |||
| 89d9ad7557 | |||
| 3eae768d5a | |||
| b847dbca69 | |||
| 44fd61c039 | |||
| 2b338d53b1 | |||
| 1fb0091aa2 | |||
| a335f2b66c | |||
| 1d56aae0cb | |||
| ac3e8f5af9 | |||
| 8cd652fece | |||
| 6206b4be17 | |||
| d5d161c7f9 | |||
| 1413744b6f | |||
| 443d97f898 | |||
| a4b263cfd7 | |||
| 59048e9b5f | |||
| f654ec61ef | |||
| 46ac8ca0ca | |||
| a8830ea70d |
@@ -69,7 +69,7 @@ export const createCustomer = async (params: CreateCustomerType) => {
|
|||||||
|
|
||||||
**List** — header + optional filters + `PageLoading` while pending + `<table>` with `Td` + `Pagination`. Link actions via `Pages.{feature}.*`.
|
**List** — header + optional filters + `PageLoading` while pending + `<table>` with `Td` + `Pagination`. Link actions via `Pages.{feature}.*`.
|
||||||
|
|
||||||
**Create / Update** — Formik + Yup validation, `toast.success(t('success'))` on success, `toast.error(error.response?.data?.error.message[0])` on error (`ErrorType` from `src/helpers/types.ts`). Navigate back to list with `useNavigate()` + `Pages.*`.
|
**Create / Update** — Formik + Yup validation, `toast(t('success'), 'success')` on success, `toast(error.response?.data?.error.message[0], 'error')` on error (`ErrorType` from `src/helpers/types.ts`). Import `toast` from `src/components/Toast`. Navigate back to list with `useNavigate()` + `Pages.*`.
|
||||||
|
|
||||||
**Delete** — small component using `ModalConfrim` + mutation hook; call `refetch()` or rely on query invalidation.
|
**Delete** — small component using `ModalConfrim` + mutation hook; call `refetch()` or rely on query invalidation.
|
||||||
|
|
||||||
@@ -87,10 +87,12 @@ Dynamic routes use string concat: `Pages.customer.update + ":id"`.
|
|||||||
|
|
||||||
## Sidebar & submenu
|
## Sidebar & submenu
|
||||||
|
|
||||||
- Main items: `SideBarItem` in `SideBar.tsx` with `link`, `isActive`, `activeName`
|
- Main sidebar shows **group** items (`menu`, `financial`, `other`, `content`, …) via `SideBarItem` with `name` matching the group key
|
||||||
- Features with sub-nav: add segment to `SideBarItemHasSubMenu` in `src/config/SideBarSubMenu.ts`, create `{Feature}SubMenu.tsx` in `src/shared/components/`, render it in the `subMenuName === "..."` block at bottom of `SideBar.tsx`
|
- Each group opens a side panel: `{Group}SubMenu.tsx` in `src/shared/components/` (e.g. `MenuSubMenu.tsx`)
|
||||||
- Submenu items use `SubMenuItem` with `Pages.*` links
|
- Map URL → group in `getSubMenuNameFromPath` (`src/config/SideBarSubMenu.ts`)
|
||||||
- `subMenuName` is derived from first URL segment (`location.pathname.split("/")[1]`)
|
- Flat links inside a group: `SubMenuItem`
|
||||||
|
- Nested feature menus inside a group: `SubMenuAccordion` (arrow open/close) wrapping `SubMenuItem` children
|
||||||
|
- `subMenuName` comes from the group key, not the feature path segment
|
||||||
|
|
||||||
## Conventions
|
## Conventions
|
||||||
|
|
||||||
|
|||||||
@@ -58,10 +58,10 @@ import CouponsList from "../pages/coupons/List";
|
|||||||
|
|
||||||
**Multi-page feature** — also:
|
**Multi-page feature** — also:
|
||||||
|
|
||||||
1. Add `"coupons"` to `SideBarItemHasSubMenu` in `src/config/SideBarSubMenu.ts`
|
1. Add the feature’s URL segment → group mapping in `getSubMenuNameFromPath` / `pathSegmentToSubMenu` (`src/config/SideBarSubMenu.ts`)
|
||||||
2. Create `src/shared/components/CouponsSubMenu.tsx` (copy `CustomerSubMenu.tsx`)
|
2. Add links under the matching group SubMenu (e.g. `MenuSubMenu.tsx`) using `SubMenuItem`
|
||||||
3. Import and add branch in `SideBar.tsx`: `subMenuName === "coupons" ? <CouponsSubMenu /> : ...`
|
3. If the feature has nested pages, wrap them in `SubMenuAccordion` (arrow open/close) inside that group SubMenu
|
||||||
4. Point main sidebar link to list path; first URL segment must match `activeName` / submenu key
|
4. Do not add a new top-level sidebar group unless the feature is a new section (reseller/dmenu-style)
|
||||||
|
|
||||||
## 5. Translations
|
## 5. Translations
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
VITE_TOKEN_NAME = 'admin_token'
|
VITE_TOKEN_NAME = 'admin_token'
|
||||||
VITE_REFRESH_TOKEN_NAME = 'admin_refresh_token'
|
VITE_REFRESH_TOKEN_NAME = 'admin_refresh_token'
|
||||||
VITE_BASE_URL = 'https://api.danakcorp.com'
|
VITE_BASE_URL = 'https://api.danakcorp.com'
|
||||||
#VITE_BASE_URL = 'http://localhost:3500'
|
|
||||||
|
# VITE_BASE_URL = 'http://192.168.99.131:3500'
|
||||||
Binary file not shown.
Generated
+23
-15
@@ -32,7 +32,6 @@
|
|||||||
"react-otp-input": "^3.1.1",
|
"react-otp-input": "^3.1.1",
|
||||||
"react-router-dom": "^7.1.0",
|
"react-router-dom": "^7.1.0",
|
||||||
"react-spinners": "^0.15.0",
|
"react-spinners": "^0.15.0",
|
||||||
"react-toastify": "^11.0.2",
|
|
||||||
"swiper": "^11.2.1",
|
"swiper": "^11.2.1",
|
||||||
"tailwind-merge": "^2.5.5",
|
"tailwind-merge": "^2.5.5",
|
||||||
"vite-plugin-pwa": "^0.21.1",
|
"vite-plugin-pwa": "^0.21.1",
|
||||||
@@ -114,6 +113,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz",
|
||||||
"integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==",
|
"integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
"@babel/code-frame": "^7.26.0",
|
"@babel/code-frame": "^7.26.0",
|
||||||
@@ -2413,6 +2413,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||||
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
|
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
},
|
},
|
||||||
@@ -2425,6 +2426,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||||
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
|
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0",
|
"loose-envify": "^1.1.0",
|
||||||
"scheduler": "^0.23.0"
|
"scheduler": "^0.23.0"
|
||||||
@@ -3073,6 +3075,7 @@
|
|||||||
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
|
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.20.7",
|
"@babel/parser": "^7.20.7",
|
||||||
"@babel/types": "^7.20.7",
|
"@babel/types": "^7.20.7",
|
||||||
@@ -3174,6 +3177,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
|
||||||
"integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
|
"integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
@@ -3250,6 +3254,7 @@
|
|||||||
"integrity": "sha512-rBnTWHCdbYM2lh7hjyXqxk70wvon3p2FyaniZuey5TrcGBpfhVp0OxOa6gxr9Q9YhZFKyfbEnxc24ZnVbbUkCA==",
|
"integrity": "sha512-rBnTWHCdbYM2lh7hjyXqxk70wvon3p2FyaniZuey5TrcGBpfhVp0OxOa6gxr9Q9YhZFKyfbEnxc24ZnVbbUkCA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.18.1",
|
"@typescript-eslint/scope-manager": "8.18.1",
|
||||||
"@typescript-eslint/types": "8.18.1",
|
"@typescript-eslint/types": "8.18.1",
|
||||||
@@ -3458,6 +3463,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -3813,6 +3819,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001688",
|
"caniuse-lite": "^1.0.30001688",
|
||||||
"electron-to-chromium": "^1.5.73",
|
"electron-to-chromium": "^1.5.73",
|
||||||
@@ -4543,6 +4550,7 @@
|
|||||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -5457,6 +5465,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.23.2"
|
"@babel/runtime": "^7.23.2"
|
||||||
},
|
},
|
||||||
@@ -6787,6 +6796,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.8",
|
"nanoid": "^3.3.8",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
@@ -6953,6 +6963,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.4.0",
|
"loose-envify": "^1.4.0",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
@@ -7088,6 +7099,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
|
||||||
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
|
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -7103,6 +7115,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
|
||||||
"integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==",
|
"integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.25.0"
|
"scheduler": "^0.25.0"
|
||||||
},
|
},
|
||||||
@@ -7298,19 +7311,6 @@
|
|||||||
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-toastify": {
|
|
||||||
"version": "11.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.2.tgz",
|
|
||||||
"integrity": "sha512-GjHuGaiXMvbls3ywqv8XdWONwrcO4DXCJIY1zVLkHU73gEElKvTTXNI5Vom3s/k/M8hnkrfsqgBSX3OwmlonbA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"clsx": "^2.1.1"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^18 || ^19",
|
|
||||||
"react-dom": "^18 || ^19"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/read-cache": {
|
"node_modules/read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
@@ -7338,7 +7338,8 @@
|
|||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/reflect.getprototypeof": {
|
"node_modules/reflect.getprototypeof": {
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
@@ -7517,6 +7518,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz",
|
||||||
"integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==",
|
"integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.7"
|
"@types/estree": "1.0.7"
|
||||||
},
|
},
|
||||||
@@ -8420,6 +8422,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -8583,6 +8586,7 @@
|
|||||||
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
|
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -8790,6 +8794,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
||||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.4.4",
|
"fdir": "^6.4.4",
|
||||||
@@ -8911,6 +8916,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -9201,6 +9207,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"fast-uri": "^3.0.1",
|
"fast-uri": "^3.0.1",
|
||||||
@@ -9262,6 +9269,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz",
|
||||||
"integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
|
"integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"rollup": "dist/bin/rollup"
|
"rollup": "dist/bin/rollup"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -34,7 +34,6 @@
|
|||||||
"react-otp-input": "^3.1.1",
|
"react-otp-input": "^3.1.1",
|
||||||
"react-router-dom": "^7.1.0",
|
"react-router-dom": "^7.1.0",
|
||||||
"react-spinners": "^0.15.0",
|
"react-spinners": "^0.15.0",
|
||||||
"react-toastify": "^11.0.2",
|
|
||||||
"swiper": "^11.2.1",
|
"swiper": "^11.2.1",
|
||||||
"tailwind-merge": "^2.5.5",
|
"tailwind-merge": "^2.5.5",
|
||||||
"vite-plugin-pwa": "^0.21.1",
|
"vite-plugin-pwa": "^0.21.1",
|
||||||
|
|||||||
+41
-54
@@ -1,30 +1,30 @@
|
|||||||
import { FC, useEffect, useState } from 'react'
|
import { QueryCache, QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { BrowserRouter } from 'react-router-dom'
|
import i18next from "i18next";
|
||||||
import 'swiper/swiper-bundle.css';
|
|
||||||
import 'rc-rate/assets/index.css';
|
|
||||||
import "quill/dist/quill.snow.css";
|
import "quill/dist/quill.snow.css";
|
||||||
import "react-multi-date-picker/styles/layouts/mobile.css"
|
import "rc-rate/assets/index.css";
|
||||||
import i18next from 'i18next'
|
import { FC, useEffect, useState } from "react";
|
||||||
import { I18nextProvider } from 'react-i18next'
|
import { I18nextProvider } from "react-i18next";
|
||||||
import { QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
import "react-multi-date-picker/styles/layouts/mobile.css";
|
||||||
import { ToastContainer } from 'react-toastify'
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import FaJson from './langs/fa.json'
|
import "swiper/swiper-bundle.css";
|
||||||
import { IApiErrorRepsonse } from './types/error.types'
|
import ToastContainer from "./components/Toast";
|
||||||
import MainRouter from './router/Main'
|
import { Pages } from "./config/Pages";
|
||||||
import AuthRouter from './router/Auth'
|
import { getRefreshToken, getToken, removeRefreshToken, removeToken, setRefreshToken, setToken } from "./config/func";
|
||||||
import { Pages } from './config/Pages'
|
import FaJson from "./langs/fa.json";
|
||||||
import { getRefreshToken, setToken, setRefreshToken, removeToken, removeRefreshToken, getToken } from './config/func';
|
import { refreshToken } from "./pages/auth/service/AuthService";
|
||||||
import { refreshToken } from './pages/auth/service/AuthService';
|
import AuthRouter from "./router/Auth";
|
||||||
|
import MainRouter from "./router/Main";
|
||||||
|
import { IApiErrorRepsonse } from "./types/error.types";
|
||||||
|
|
||||||
i18next.init({
|
i18next.init({
|
||||||
interpolation: { escapeValue: false },
|
interpolation: { escapeValue: false },
|
||||||
lng: 'fa',
|
lng: "fa",
|
||||||
resources: {
|
resources: {
|
||||||
fa: {
|
fa: {
|
||||||
global: FaJson
|
global: FaJson,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@@ -40,7 +40,7 @@ const queryClient = new QueryClient({
|
|||||||
// Use a flag to track if refresh is in progress
|
// Use a flag to track if refresh is in progress
|
||||||
if (window.isRefreshingToken) {
|
if (window.isRefreshingToken) {
|
||||||
// Wait for the refresh to complete
|
// Wait for the refresh to complete
|
||||||
await new Promise(resolve => {
|
await new Promise((resolve) => {
|
||||||
const checkComplete = setInterval(() => {
|
const checkComplete = setInterval(() => {
|
||||||
if (!window.isRefreshingToken) {
|
if (!window.isRefreshingToken) {
|
||||||
clearInterval(checkComplete);
|
clearInterval(checkComplete);
|
||||||
@@ -56,7 +56,7 @@ const queryClient = new QueryClient({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const refreshTokenValue = await getRefreshToken();
|
const refreshTokenValue = await getRefreshToken();
|
||||||
const { data } = await refreshToken({ refreshToken: refreshTokenValue || '' });
|
const { data } = await refreshToken({ refreshToken: refreshTokenValue || "" });
|
||||||
|
|
||||||
if (data?.accessToken?.token) {
|
if (data?.accessToken?.token) {
|
||||||
// Save the new token
|
// Save the new token
|
||||||
@@ -71,67 +71,54 @@ const queryClient = new QueryClient({
|
|||||||
queryClient.invalidateQueries();
|
queryClient.invalidateQueries();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid token response');
|
throw new Error("Invalid token response");
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
window.isRefreshingToken = false;
|
window.isRefreshingToken = false;
|
||||||
}
|
}
|
||||||
} catch (refreshError: unknown) {
|
} catch (refreshError: unknown) {
|
||||||
console.error('Token refresh failed:', refreshError);
|
console.error("Token refresh failed:", refreshError);
|
||||||
// Clear tokens and redirect to login
|
// Clear tokens and redirect to login
|
||||||
await removeToken();
|
await removeToken();
|
||||||
await removeRefreshToken();
|
await removeRefreshToken();
|
||||||
window.location.href = '/auth/login';
|
window.location.href = "/auth/login";
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: {
|
queries: {
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
retry: false
|
retry: false,
|
||||||
// staleTime: 86400000 // 1 day in milliseconds
|
// staleTime: 86400000 // 1 day in milliseconds
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const App: FC = () => {
|
const App: FC = () => {
|
||||||
|
const [isLogin, setIsLogin] = useState<"checking" | "isLogin" | "isNotLogin">("checking");
|
||||||
const [isLogin, setIsLogin] = useState<'checking' | 'isLogin' | 'isNotLogin'>('checking')
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const token = getToken()
|
const token = getToken();
|
||||||
if (token) {
|
if (token) {
|
||||||
setIsLogin('isLogin')
|
setIsLogin("isLogin");
|
||||||
} else {
|
} else {
|
||||||
setIsLogin('isNotLogin')
|
setIsLogin("isNotLogin");
|
||||||
if (window.location.href.split('auth').length === 1) {
|
if (window.location.href.split("auth").length === 1) {
|
||||||
window.location.href = Pages.auth.login
|
window.location.href = Pages.auth.login;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}, []);
|
||||||
}, [])
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<I18nextProvider i18n={i18next}>
|
<I18nextProvider i18n={i18next}>
|
||||||
{
|
{isLogin === "checking" ? null : isLogin === "isLogin" ? <MainRouter /> : <AuthRouter />}
|
||||||
isLogin === 'checking' ?
|
|
||||||
null
|
|
||||||
:
|
|
||||||
isLogin === 'isLogin' ?
|
|
||||||
<MainRouter />
|
|
||||||
:
|
|
||||||
<AuthRouter />
|
|
||||||
}
|
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
</I18nextProvider>
|
</I18nextProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default App
|
export default App;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { FC, Fragment, ReactNode, useEffect } from 'react'
|
import { FC, Fragment, ReactNode, useEffect } from 'react'
|
||||||
|
import { clx } from '../helpers/utils'
|
||||||
import HeaderModal from './HeaderModal'
|
import HeaderModal from './HeaderModal'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -8,6 +9,8 @@ interface Props {
|
|||||||
isHeader?: boolean,
|
isHeader?: boolean,
|
||||||
title_header?: string,
|
title_header?: string,
|
||||||
width?: number
|
width?: number
|
||||||
|
/** Raise above popovers (z-80) when confirm opens from nested panels */
|
||||||
|
elevated?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const DefaulModal: FC<Props> = (props: Props) => {
|
const DefaulModal: FC<Props> = (props: Props) => {
|
||||||
@@ -26,7 +29,14 @@ const DefaulModal: FC<Props> = (props: Props) => {
|
|||||||
{
|
{
|
||||||
props.open && (
|
props.open && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div style={{ maxWidth: props.width }} className='xl:justify-center xl:items-center items-end flex overflow-hidden fixed inset-0 z-[60] h-auto top-0 bottom-0 m-auto outline-none focus:outline-none xl:max-w-xl mx-auto'>
|
<div
|
||||||
|
style={{ maxWidth: props.width }}
|
||||||
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
|
className={clx(
|
||||||
|
'xl:justify-center xl:items-center items-end flex overflow-hidden fixed inset-0 h-auto top-0 bottom-0 m-auto outline-none focus:outline-none xl:max-w-xl mx-auto',
|
||||||
|
props.elevated ? 'z-[100]' : 'z-[60]',
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className='relative max-h-[85vh] bottom-0 left-0 flex xl:items-center items-end w-full xl:my-6 xl:p-2'>
|
<div className='relative max-h-[85vh] bottom-0 left-0 flex xl:items-center items-end w-full xl:my-6 xl:p-2'>
|
||||||
<div className='border-0 max-h-[85vh] p-5 lg:min-w-full overflow-y-auto rounded-3xl rounded-b-none xl:rounded-b-3xl relative flex flex-col w-full modalGlass2 outline-none focus:outline-none'>
|
<div className='border-0 max-h-[85vh] p-5 lg:min-w-full overflow-y-auto rounded-3xl rounded-b-none xl:rounded-b-3xl relative flex flex-col w-full modalGlass2 outline-none focus:outline-none'>
|
||||||
|
|
||||||
@@ -46,7 +56,14 @@ const DefaulModal: FC<Props> = (props: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div onClick={props.close} className='fixed size-full top-0 bottom-0 right-0 modalGlass inset-0 z-50 '></div>
|
<div
|
||||||
|
onClick={props.close}
|
||||||
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
|
className={clx(
|
||||||
|
'fixed size-full top-0 bottom-0 right-0 modalGlass inset-0',
|
||||||
|
props.elevated ? 'z-[90]' : 'z-50',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { FC, useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Button from "./Button";
|
||||||
|
import DefaulModal from "./DefaulModal";
|
||||||
|
import Input from "./Input";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isOpen: boolean;
|
||||||
|
close: () => void;
|
||||||
|
onConfirm: () => void;
|
||||||
|
isLoading?: boolean;
|
||||||
|
itemName: string;
|
||||||
|
message?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DeleteNameConfirmModal: FC<Props> = ({
|
||||||
|
isOpen,
|
||||||
|
close,
|
||||||
|
onConfirm,
|
||||||
|
isLoading = false,
|
||||||
|
itemName,
|
||||||
|
message,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation("global");
|
||||||
|
const [typedName, setTypedName] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
setTypedName("");
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
const isNameMatched = typedName.trim() === itemName.trim();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DefaulModal
|
||||||
|
open={isOpen}
|
||||||
|
close={close}
|
||||||
|
title_header={t("confrim.subject")}
|
||||||
|
isHeader
|
||||||
|
>
|
||||||
|
<div className="mt-6">
|
||||||
|
<div className="text-sm text-center">
|
||||||
|
{message ?? t("taskmanager.delete_confirm_message", { name: itemName })}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-5">
|
||||||
|
<Input
|
||||||
|
value={typedName}
|
||||||
|
onChange={(e) => setTypedName(e.target.value)}
|
||||||
|
placeholder={t("taskmanager.delete_confirm_placeholder")}
|
||||||
|
className="text-center"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-4 justify-center mt-10">
|
||||||
|
<Button
|
||||||
|
label={t("confrim.yes")}
|
||||||
|
onClick={onConfirm}
|
||||||
|
isLoading={isLoading}
|
||||||
|
disabled={!isNameMatched}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
label={t("confrim.cancel")}
|
||||||
|
className="bg-transparent text-black border border-primary"
|
||||||
|
onClick={close}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DefaulModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteNameConfirmModal;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { SearchNormal } from "iconsax-react";
|
import { SearchNormal } from "iconsax-react";
|
||||||
import { FC, InputHTMLAttributes, useEffect, useState } from "react";
|
import { FC, InputHTMLAttributes, useEffect, useRef, useState } from "react";
|
||||||
import EyeIcon from "../assets/images/eye.svg";
|
import EyeIcon from "../assets/images/eye.svg";
|
||||||
import { clx } from "../helpers/utils";
|
import { clx } from "../helpers/utils";
|
||||||
import Error from "./Error";
|
import Error from "./Error";
|
||||||
@@ -30,6 +30,8 @@ const Input: FC<Props> = (props: Props) => {
|
|||||||
|
|
||||||
const [showPassword, setShowPassword] = useState<boolean>(false);
|
const [showPassword, setShowPassword] = useState<boolean>(false);
|
||||||
const [search, setSearch] = useState<string>("");
|
const [search, setSearch] = useState<string>("");
|
||||||
|
const onChangeSearchFinalRef = useRef(props.onChangeSearchFinal);
|
||||||
|
onChangeSearchFinalRef.current = props.onChangeSearchFinal;
|
||||||
|
|
||||||
const inputClass = clx(
|
const inputClass = clx(
|
||||||
"w-full h-10 text-black block px-4 text-xs rounded-xl border border-border",
|
"w-full h-10 text-black block px-4 text-xs rounded-xl border border-border",
|
||||||
@@ -68,13 +70,13 @@ const Input: FC<Props> = (props: Props) => {
|
|||||||
}, [props.value]);
|
}, [props.value]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.variant === "search" && props.onChangeSearchFinal) {
|
if (props.variant !== "search" || !onChangeSearchFinalRef.current) return;
|
||||||
|
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
props.onChangeSearchFinal?.(search);
|
onChangeSearchFinalRef.current?.(search);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
return () => clearTimeout(timeout);
|
return () => clearTimeout(timeout);
|
||||||
}
|
}, [search, props.variant]);
|
||||||
}, [search, props]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { FC, useState } from 'react'
|
import { FC, useState } from 'react'
|
||||||
import DefaulModal from './DefaulModal'
|
import { createPortal } from 'react-dom'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Button from './Button'
|
import Button from './Button'
|
||||||
|
import DefaulModal from './DefaulModal'
|
||||||
import Textarea from './Textarea'
|
import Textarea from './Textarea'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -18,12 +19,13 @@ const ModalConfrim: FC<Props> = (props: Props) => {
|
|||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
const [description, setDescription] = useState<string>('')
|
const [description, setDescription] = useState<string>('')
|
||||||
|
|
||||||
return (
|
return createPortal(
|
||||||
<DefaulModal
|
<DefaulModal
|
||||||
open={props.isOpen}
|
open={props.isOpen}
|
||||||
close={props.close}
|
close={props.close}
|
||||||
title_header={t('confrim.subject')}
|
title_header={t('confrim.subject')}
|
||||||
isHeader
|
isHeader
|
||||||
|
elevated
|
||||||
>
|
>
|
||||||
<div className='mt-6'>
|
<div className='mt-6'>
|
||||||
<div className='text-sm text-center'>
|
<div className='text-sm text-center'>
|
||||||
@@ -60,7 +62,8 @@ const ModalConfrim: FC<Props> = (props: Props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DefaulModal>
|
</DefaulModal>,
|
||||||
|
document.body,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,17 @@ interface PaginationProps {
|
|||||||
currentPage: number;
|
currentPage: number;
|
||||||
totalPages: number;
|
totalPages: number;
|
||||||
onPageChange: (page: number) => void;
|
onPageChange: (page: number) => void;
|
||||||
|
limit?: number;
|
||||||
|
onLimitChange?: (limit: number) => void;
|
||||||
|
limitOptions?: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const Pagination: React.FC<PaginationProps> = ({
|
const Pagination: React.FC<PaginationProps> = ({ currentPage, totalPages, onPageChange, limit = 10, onLimitChange, limitOptions = [10, 20, 50] }) => {
|
||||||
currentPage,
|
const handleLimitChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
totalPages,
|
onLimitChange?.(Number(event.target.value));
|
||||||
onPageChange,
|
onPageChange(1);
|
||||||
}) => {
|
};
|
||||||
|
|
||||||
const getPageNumbers = () => {
|
const getPageNumbers = () => {
|
||||||
const pageNumbers: (number | string)[] = [];
|
const pageNumbers: (number | string)[] = [];
|
||||||
const maxVisiblePages = 5;
|
const maxVisiblePages = 5;
|
||||||
@@ -56,7 +60,29 @@ const Pagination: React.FC<PaginationProps> = ({
|
|||||||
const pageNumbers = getPageNumbers();
|
const pageNumbers = getPageNumbers();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-2 text-xs justify-center items-center mt-4">
|
<div className="flex flex-wrap gap-4 text-xs justify-center items-center mt-4">
|
||||||
|
{onLimitChange && (
|
||||||
|
<label className="flex items-center gap-2 text-gray-600">
|
||||||
|
<span>تعداد در صفحه</span>
|
||||||
|
<select
|
||||||
|
aria-label="تعداد آیتم در صفحه"
|
||||||
|
className="h-8 rounded-md border border-[#EAECF5] bg-white px-2 outline-none focus:border-primary"
|
||||||
|
value={limit}
|
||||||
|
onChange={handleLimitChange}
|
||||||
|
>
|
||||||
|
{limitOptions.map((option) => (
|
||||||
|
<option
|
||||||
|
key={option}
|
||||||
|
value={option}
|
||||||
|
>
|
||||||
|
{option}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
{/* دکمه قبلی */}
|
{/* دکمه قبلی */}
|
||||||
{/* <button
|
{/* <button
|
||||||
className={`px-3 py-1 rounded-md ${currentPage === 1
|
className={`px-3 py-1 rounded-md ${currentPage === 1
|
||||||
@@ -74,19 +100,19 @@ const Pagination: React.FC<PaginationProps> = ({
|
|||||||
typeof page === "number" ? (
|
typeof page === "number" ? (
|
||||||
<button
|
<button
|
||||||
key={`page-${page}`}
|
key={`page-${page}`}
|
||||||
className={`size-8 rounded-md ${currentPage === page
|
className={`size-8 rounded-md ${currentPage === page ? "bg-primary text-white" : "text-primary bg-[#EAECF5] hover:bg-gray-100"}`}
|
||||||
? "bg-primary text-white"
|
|
||||||
: "text-primary bg-[#EAECF5] hover:bg-gray-100"
|
|
||||||
}`}
|
|
||||||
onClick={() => onPageChange(page)}
|
onClick={() => onPageChange(page)}
|
||||||
>
|
>
|
||||||
{page}
|
{page}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<span key={`ellipsis-${index}`} className="px-3 py-1 text-gray-500">
|
<span
|
||||||
|
key={`ellipsis-${index}`}
|
||||||
|
className="px-3 py-1 text-gray-500"
|
||||||
|
>
|
||||||
{page}
|
{page}
|
||||||
</span>
|
</span>
|
||||||
)
|
),
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* دکمه بعدی */}
|
{/* دکمه بعدی */}
|
||||||
@@ -101,6 +127,7 @@ const Pagination: React.FC<PaginationProps> = ({
|
|||||||
بعدی
|
بعدی
|
||||||
</button> */}
|
</button> */}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
'use client'
|
||||||
|
import { CloseCircle, InfoCircle, TickCircle } from 'iconsax-react';
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
interface Toast {
|
||||||
|
id: string;
|
||||||
|
message: string;
|
||||||
|
type?: 'success' | 'error' | 'info';
|
||||||
|
}
|
||||||
|
|
||||||
|
let addToast: (toast: Toast) => void = () => {};
|
||||||
|
|
||||||
|
const ToastContainer: React.FC = () => {
|
||||||
|
const [toasts, setToasts] = useState<Toast[]>([]);
|
||||||
|
|
||||||
|
const showToast = (toastItem: Toast) => {
|
||||||
|
setToasts((prev) => [...prev, toastItem]);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setToasts((prev) => prev.filter((t) => t.id !== toastItem.id));
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
addToast = showToast;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed top-4 text-sm right-0 left-0 mx-auto w-fit z-50 flex flex-col gap-2">
|
||||||
|
{toasts.map((toastItem) => (
|
||||||
|
<div
|
||||||
|
key={toastItem.id}
|
||||||
|
className="px-4 flex items-center gap-2 backdrop-blur-2xl h-16 min-w-[300px] rounded-2xl shadow-md transition-transform bg-white/70 text-black"
|
||||||
|
>
|
||||||
|
{toastItem.type === 'success' ? (
|
||||||
|
<TickCircle className="size-5" color="green" />
|
||||||
|
) : toastItem.type === 'error' ? (
|
||||||
|
<CloseCircle className="size-5" color="red" />
|
||||||
|
) : (
|
||||||
|
<InfoCircle className="size-5" color="blue" />
|
||||||
|
)}
|
||||||
|
{toastItem.message}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toast = (message: string | undefined, type: 'success' | 'error' | 'info' = 'info') => {
|
||||||
|
if (!message) return;
|
||||||
|
addToast({ id: `${Date.now()}-${Math.random()}`, message, type });
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ToastContainer;
|
||||||
@@ -36,6 +36,9 @@ export const Pages = {
|
|||||||
create: "/tickets/create",
|
create: "/tickets/create",
|
||||||
detail: "/tickets/messages/",
|
detail: "/tickets/messages/",
|
||||||
category: "/tickets/category",
|
category: "/tickets/category",
|
||||||
|
masters: "/tickets/masters",
|
||||||
|
mastersCreate: "/tickets/masters/create",
|
||||||
|
mastersUpdate: "/tickets/masters/update/",
|
||||||
},
|
},
|
||||||
announcement: {
|
announcement: {
|
||||||
list: "/announcement",
|
list: "/announcement",
|
||||||
|
|||||||
@@ -1,12 +1,53 @@
|
|||||||
export const SideBarItemHasSubMenu = [
|
export const SideBarItemHasSubMenu = [
|
||||||
"services",
|
"services",
|
||||||
"customers",
|
"customers",
|
||||||
"receipts",
|
"representatives",
|
||||||
"users",
|
"users",
|
||||||
"tickets",
|
|
||||||
"learning",
|
|
||||||
"blog",
|
|
||||||
"support",
|
|
||||||
"accessLogs",
|
|
||||||
"taskmanager",
|
"taskmanager",
|
||||||
|
"content",
|
||||||
|
"financial",
|
||||||
|
"support",
|
||||||
|
"other",
|
||||||
|
"products",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const pathSegmentToSubMenu: Record<string, string> = {
|
||||||
|
services: "services",
|
||||||
|
customers: "customers",
|
||||||
|
representatives: "representatives",
|
||||||
|
reseller: "representatives",
|
||||||
|
users: "users",
|
||||||
|
ads: "content",
|
||||||
|
announcement: "content",
|
||||||
|
blog: "content",
|
||||||
|
learning: "content",
|
||||||
|
sliders: "content",
|
||||||
|
payments: "financial",
|
||||||
|
receipts: "financial",
|
||||||
|
transactions: "financial",
|
||||||
|
discounts: "financial",
|
||||||
|
"card-bank": "financial",
|
||||||
|
support: "support",
|
||||||
|
tickets: "support",
|
||||||
|
feedback: "other",
|
||||||
|
"access-logs": "other",
|
||||||
|
criticisms: "other",
|
||||||
|
messages: "other",
|
||||||
|
dmenu: "products",
|
||||||
|
dkala: "products",
|
||||||
|
dpage: "products",
|
||||||
|
dmail: "products",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSubMenuNameFromPath = (pathname: string): string => {
|
||||||
|
if (pathname.startsWith("/customers/subscriptions")) {
|
||||||
|
return "customers";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathname.startsWith("/workspace") || pathname.startsWith("/project") || pathname.startsWith("/taskmanager")) {
|
||||||
|
return "taskmanager";
|
||||||
|
}
|
||||||
|
|
||||||
|
const segment = pathname.split("/")[1] ?? "";
|
||||||
|
return pathSegmentToSubMenu[segment] ?? "";
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,278 @@
|
|||||||
|
export enum PermissionGroupEnum {
|
||||||
|
SERVICES = "services",
|
||||||
|
CUSTOMERS = "customers",
|
||||||
|
USERS = "users",
|
||||||
|
FINANCIAL = "financial",
|
||||||
|
CONTENT = "content",
|
||||||
|
SUPPORT = "support",
|
||||||
|
OTHER = "other",
|
||||||
|
PRODUCTS = "products",
|
||||||
|
REPRESENTATIVES = "representatives",
|
||||||
|
TASK_MANAGER = "task_manager",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PERMISSION_GROUP_ORDER: PermissionGroupEnum[] = [
|
||||||
|
PermissionGroupEnum.SERVICES,
|
||||||
|
PermissionGroupEnum.CUSTOMERS,
|
||||||
|
PermissionGroupEnum.USERS,
|
||||||
|
PermissionGroupEnum.FINANCIAL,
|
||||||
|
PermissionGroupEnum.CONTENT,
|
||||||
|
PermissionGroupEnum.SUPPORT,
|
||||||
|
PermissionGroupEnum.OTHER,
|
||||||
|
PermissionGroupEnum.PRODUCTS,
|
||||||
|
PermissionGroupEnum.REPRESENTATIVES,
|
||||||
|
PermissionGroupEnum.TASK_MANAGER,
|
||||||
|
];
|
||||||
|
|
||||||
|
export enum PermissionEnum {
|
||||||
|
SERVICES_CREATE = "services_create",
|
||||||
|
SERVICES_READ = "services_read",
|
||||||
|
SERVICES_UPDATE = "services_update",
|
||||||
|
SERVICES_DELETE = "services_delete",
|
||||||
|
|
||||||
|
CUSTOMERS_CREATE = "customers_create",
|
||||||
|
CUSTOMERS_READ = "customers_read",
|
||||||
|
CUSTOMERS_UPDATE = "customers_update",
|
||||||
|
CUSTOMERS_DELETE = "customers_delete",
|
||||||
|
|
||||||
|
AGENTS_CREATE = "agents_create",
|
||||||
|
AGENTS_READ = "agents_read",
|
||||||
|
AGENTS_UPDATE = "agents_update",
|
||||||
|
AGENTS_DELETE = "agents_delete",
|
||||||
|
|
||||||
|
DEVELOPERS_CREATE = "developers_create",
|
||||||
|
DEVELOPERS_READ = "developers_read",
|
||||||
|
DEVELOPERS_UPDATE = "developers_update",
|
||||||
|
DEVELOPERS_DELETE = "developers_delete",
|
||||||
|
|
||||||
|
INVOICES_CREATE = "invoices_create",
|
||||||
|
INVOICES_READ = "invoices_read",
|
||||||
|
INVOICES_UPDATE = "invoices_update",
|
||||||
|
INVOICES_DELETE = "invoices_delete",
|
||||||
|
|
||||||
|
TRANSACTIONS_CREATE = "transactions_create",
|
||||||
|
TRANSACTIONS_READ = "transactions_read",
|
||||||
|
TRANSACTIONS_UPDATE = "transactions_update",
|
||||||
|
TRANSACTIONS_DELETE = "transactions_delete",
|
||||||
|
|
||||||
|
DISCOUNTS_CREATE = "discounts_create",
|
||||||
|
DISCOUNTS_READ = "discounts_read",
|
||||||
|
DISCOUNTS_UPDATE = "discounts_update",
|
||||||
|
DISCOUNTS_DELETE = "discounts_delete",
|
||||||
|
|
||||||
|
ADMINS_CREATE = "admins_create",
|
||||||
|
ADMINS_READ = "admins_read",
|
||||||
|
ADMINS_UPDATE = "admins_update",
|
||||||
|
ADMINS_DELETE = "admins_delete",
|
||||||
|
|
||||||
|
TICKETS_CREATE = "tickets_create",
|
||||||
|
TICKETS_READ = "tickets_read",
|
||||||
|
TICKETS_UPDATE = "tickets_update",
|
||||||
|
TICKETS_DELETE = "tickets_delete",
|
||||||
|
|
||||||
|
CRITICISMS_CREATE = "criticisms_create",
|
||||||
|
CRITICISMS_READ = "criticisms_read",
|
||||||
|
CRITICISMS_UPDATE = "criticisms_update",
|
||||||
|
CRITICISMS_DELETE = "criticisms_delete",
|
||||||
|
|
||||||
|
CONTACTS_US_CREATE = "contacts_us_create",
|
||||||
|
CONTACTS_US_READ = "contacts_us_read",
|
||||||
|
CONTACTS_US_UPDATE = "contacts_us_update",
|
||||||
|
CONTACTS_US_DELETE = "contacts_us_delete",
|
||||||
|
|
||||||
|
ADVERTISEMENTS_CREATE = "advertisements_create",
|
||||||
|
ADVERTISEMENTS_READ = "advertisements_read",
|
||||||
|
ADVERTISEMENTS_UPDATE = "advertisements_update",
|
||||||
|
ADVERTISEMENTS_DELETE = "advertisements_delete",
|
||||||
|
|
||||||
|
ANNOUNCEMENTS_CREATE = "announcements_create",
|
||||||
|
ANNOUNCEMENTS_READ = "announcements_read",
|
||||||
|
ANNOUNCEMENTS_UPDATE = "announcements_update",
|
||||||
|
ANNOUNCEMENTS_DELETE = "announcements_delete",
|
||||||
|
|
||||||
|
BLOGS_CREATE = "blogs_create",
|
||||||
|
BLOGS_READ = "blogs_read",
|
||||||
|
BLOGS_UPDATE = "blogs_update",
|
||||||
|
BLOGS_DELETE = "blogs_delete",
|
||||||
|
|
||||||
|
BLOG_CATEGORIES_CREATE = "blog_categories_create",
|
||||||
|
BLOG_CATEGORIES_READ = "blog_categories_read",
|
||||||
|
BLOG_CATEGORIES_UPDATE = "blog_categories_update",
|
||||||
|
BLOG_CATEGORIES_DELETE = "blog_categories_delete",
|
||||||
|
|
||||||
|
BLOG_COMMENTS_CREATE = "blog_comments_create",
|
||||||
|
BLOG_COMMENTS_READ = "blog_comments_read",
|
||||||
|
BLOG_COMMENTS_UPDATE = "blog_comments_update",
|
||||||
|
BLOG_COMMENTS_DELETE = "blog_comments_delete",
|
||||||
|
|
||||||
|
SLIDERS_CREATE = "sliders_create",
|
||||||
|
SLIDERS_READ = "sliders_read",
|
||||||
|
SLIDERS_UPDATE = "sliders_update",
|
||||||
|
SLIDERS_DELETE = "sliders_delete",
|
||||||
|
|
||||||
|
LEARNINGS_CREATE = "learnings_create",
|
||||||
|
LEARNINGS_READ = "learnings_read",
|
||||||
|
LEARNINGS_UPDATE = "learnings_update",
|
||||||
|
LEARNINGS_DELETE = "learnings_delete",
|
||||||
|
|
||||||
|
SETTINGS_CREATE = "settings_create",
|
||||||
|
SETTINGS_READ = "settings_read",
|
||||||
|
SETTINGS_UPDATE = "settings_update",
|
||||||
|
SETTINGS_DELETE = "settings_delete",
|
||||||
|
|
||||||
|
BANK_ACCOUNTS_CREATE = "bank_accounts_create",
|
||||||
|
BANK_ACCOUNTS_READ = "bank_accounts_read",
|
||||||
|
BANK_ACCOUNTS_UPDATE = "bank_accounts_update",
|
||||||
|
BANK_ACCOUNTS_DELETE = "bank_accounts_delete",
|
||||||
|
|
||||||
|
PAYMENTS_CREATE = "payments_create",
|
||||||
|
PAYMENTS_READ = "payments_read",
|
||||||
|
PAYMENTS_UPDATE = "payments_update",
|
||||||
|
PAYMENTS_DELETE = "payments_delete",
|
||||||
|
|
||||||
|
SUPPORT_PLAN_CREATE = "support_plan_create",
|
||||||
|
SUPPORT_PLAN_READ = "support_plan_read",
|
||||||
|
SUPPORT_PLAN_UPDATE = "support_plan_update",
|
||||||
|
SUPPORT_PLAN_DELETE = "support_plan_delete",
|
||||||
|
|
||||||
|
RESELLER_CREATE = "reseller_create",
|
||||||
|
RESELLER_READ = "reseller_read",
|
||||||
|
RESELLER_UPDATE = "reseller_update",
|
||||||
|
RESELLER_DELETE = "reseller_delete",
|
||||||
|
|
||||||
|
WORKSPACE_CREATE = "workspace_create",
|
||||||
|
WORKSPACE_READ = "workspace_read",
|
||||||
|
WORKSPACE_UPDATE = "workspace_update",
|
||||||
|
WORKSPACE_DELETE = "workspace_delete",
|
||||||
|
|
||||||
|
PROJECT_CREATE = "project_create",
|
||||||
|
PROJECT_READ = "project_read",
|
||||||
|
PROJECT_UPDATE = "project_update",
|
||||||
|
PROJECT_DELETE = "project_delete",
|
||||||
|
|
||||||
|
TASK_CREATE = "task_create",
|
||||||
|
TASK_READ = "task_read",
|
||||||
|
TASK_UPDATE = "task_update",
|
||||||
|
TASK_DELETE = "task_delete",
|
||||||
|
|
||||||
|
TASK_PHASE_CREATE = "task_phase_create",
|
||||||
|
TASK_PHASE_READ = "task_phase_read",
|
||||||
|
TASK_PHASE_UPDATE = "task_phase_update",
|
||||||
|
TASK_PHASE_DELETE = "task_phase_delete",
|
||||||
|
|
||||||
|
TICKET_CATEGORIES_CREATE = "ticket_categories_create",
|
||||||
|
TICKET_CATEGORIES_READ = "ticket_categories_read",
|
||||||
|
TICKET_CATEGORIES_UPDATE = "ticket_categories_update",
|
||||||
|
TICKET_CATEGORIES_DELETE = "ticket_categories_delete",
|
||||||
|
|
||||||
|
TICKET_MASTERS_CREATE = "ticket_masters_create",
|
||||||
|
TICKET_MASTERS_READ = "ticket_masters_read",
|
||||||
|
TICKET_MASTERS_UPDATE = "ticket_masters_update",
|
||||||
|
TICKET_MASTERS_DELETE = "ticket_masters_delete",
|
||||||
|
|
||||||
|
VIEW_ALL_WORKSPACES = "view_all_workspaces",
|
||||||
|
VIEW_ALL_PROJECTS = "view_all_projects",
|
||||||
|
|
||||||
|
LOGS = "logs",
|
||||||
|
MANAGE_SSO_CLIENTS = "manage_sso_clients",
|
||||||
|
DMENU = "dmenu",
|
||||||
|
DKALA = "dkala",
|
||||||
|
DPAGE = "dpage",
|
||||||
|
DMAIL = "dmail",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CrudAction = "create" | "read" | "update" | "delete";
|
||||||
|
|
||||||
|
const CRUD_ACTIONS: readonly CrudAction[] = ["create", "read", "update", "delete"];
|
||||||
|
|
||||||
|
export const CONTENT_PERMISSION_RESOURCES = [
|
||||||
|
"blogs",
|
||||||
|
"blog_categories",
|
||||||
|
"blog_comments",
|
||||||
|
"sliders",
|
||||||
|
"learnings",
|
||||||
|
"advertisements",
|
||||||
|
"announcements",
|
||||||
|
] as const;
|
||||||
|
export const FINANCIAL_PERMISSION_RESOURCES = ["payments", "invoices", "transactions", "discounts", "bank_accounts"] as const;
|
||||||
|
export const SUPPORT_PERMISSION_RESOURCES = [
|
||||||
|
"support_plan",
|
||||||
|
"tickets",
|
||||||
|
"ticket_categories",
|
||||||
|
"ticket_masters",
|
||||||
|
] as const;
|
||||||
|
export const TASK_MANAGER_PERMISSION_RESOURCES = [
|
||||||
|
"workspace",
|
||||||
|
"project",
|
||||||
|
"task",
|
||||||
|
"task_phase",
|
||||||
|
"view_all_workspaces",
|
||||||
|
"view_all_projects",
|
||||||
|
] as const;
|
||||||
|
export const USERS_SIDEBAR_PERMISSION_RESOURCES = ["admins"] as const;
|
||||||
|
export const PRODUCT_PERMISSION_RESOURCES = ["dmenu", "dkala", "dpage", "dmail"] as const;
|
||||||
|
|
||||||
|
type PermissionEntry = { name: string };
|
||||||
|
|
||||||
|
export const getPermissionNames = (permissions: PermissionEntry[] | undefined): string[] =>
|
||||||
|
permissions?.map((permission) => permission.name) ?? [];
|
||||||
|
|
||||||
|
export const hasPermission = (
|
||||||
|
permissions: PermissionEntry[] | undefined,
|
||||||
|
resourceOrPermission: string,
|
||||||
|
): boolean => {
|
||||||
|
const names = getPermissionNames(permissions);
|
||||||
|
|
||||||
|
if (names.includes(resourceOrPermission)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CRUD_ACTIONS.some((action) => names.includes(`${resourceOrPermission}_${action}`));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hasActionPermission = (
|
||||||
|
permissions: PermissionEntry[] | undefined,
|
||||||
|
resource: string,
|
||||||
|
action: CrudAction,
|
||||||
|
): boolean => {
|
||||||
|
const names = getPermissionNames(permissions);
|
||||||
|
|
||||||
|
if (names.includes(resource)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return names.includes(`${resource}_${action}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const canCreate = (permissions: PermissionEntry[] | undefined, resource: string) =>
|
||||||
|
hasActionPermission(permissions, resource, "create");
|
||||||
|
|
||||||
|
export const canRead = (permissions: PermissionEntry[] | undefined, resource: string) =>
|
||||||
|
hasActionPermission(permissions, resource, "read");
|
||||||
|
|
||||||
|
export const canUpdate = (permissions: PermissionEntry[] | undefined, resource: string) =>
|
||||||
|
hasActionPermission(permissions, resource, "update");
|
||||||
|
|
||||||
|
export const canDelete = (permissions: PermissionEntry[] | undefined, resource: string) =>
|
||||||
|
hasActionPermission(permissions, resource, "delete");
|
||||||
|
|
||||||
|
export const hasAnyPermission = (
|
||||||
|
permissions: PermissionEntry[] | undefined,
|
||||||
|
resources: readonly string[],
|
||||||
|
): boolean => resources.some((resource) => hasPermission(permissions, resource));
|
||||||
|
|
||||||
|
export const canAccessMenuItem = (
|
||||||
|
permissions: PermissionEntry[] | undefined,
|
||||||
|
activeName?: string,
|
||||||
|
activeNames?: readonly string[],
|
||||||
|
): boolean => {
|
||||||
|
if (activeNames && activeNames.length > 0) {
|
||||||
|
return hasAnyPermission(permissions, activeNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!activeName) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasPermission(permissions, activeName);
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import {
|
||||||
|
canCreate,
|
||||||
|
canDelete,
|
||||||
|
canRead,
|
||||||
|
canUpdate,
|
||||||
|
CrudAction,
|
||||||
|
hasActionPermission,
|
||||||
|
hasAnyPermission,
|
||||||
|
hasPermission,
|
||||||
|
} from "../helpers/permissions";
|
||||||
|
import { useGetAdminPermissions } from "../pages/users/hooks/useUserData";
|
||||||
|
|
||||||
|
export const usePermissions = () => {
|
||||||
|
const { data, isPending, isLoading } = useGetAdminPermissions();
|
||||||
|
const permissions = data?.data?.permissions;
|
||||||
|
|
||||||
|
return {
|
||||||
|
permissions,
|
||||||
|
isPending: isPending || isLoading,
|
||||||
|
can: (resource: string, action: CrudAction) => hasActionPermission(permissions, resource, action),
|
||||||
|
canCreate: (resource: string) => canCreate(permissions, resource),
|
||||||
|
canRead: (resource: string) => canRead(permissions, resource),
|
||||||
|
canUpdate: (resource: string) => canUpdate(permissions, resource),
|
||||||
|
canDelete: (resource: string) => canDelete(permissions, resource),
|
||||||
|
has: (resourceOrPermission: string) => hasPermission(permissions, resourceOrPermission),
|
||||||
|
hasAny: (resources: readonly string[]) => hasAnyPermission(permissions, resources),
|
||||||
|
};
|
||||||
|
};
|
||||||
+74
-7
@@ -35,7 +35,16 @@
|
|||||||
"enter_password_step3": "رمز عبور را وارد کنید",
|
"enter_password_step3": "رمز عبور را وارد کنید",
|
||||||
"enter_your_password": "رمز عبور خود را وارد کنید.",
|
"enter_your_password": "رمز عبور خود را وارد کنید.",
|
||||||
"forgot_password": "فراموشی رمز عبور",
|
"forgot_password": "فراموشی رمز عبور",
|
||||||
"login_with_otp": "ورود با رمز عبور یکبار مصرف"
|
"forgot_password_description": "ایمیل خود را وارد کنید تا کد بازیابی برای شما ارسال شود.",
|
||||||
|
"reset_password": "بازیابی رمز عبور",
|
||||||
|
"new_password": "رمز عبور جدید",
|
||||||
|
"confirm_password": "تکرار رمز عبور",
|
||||||
|
"password_mismatch": "رمز عبور و تکرار آن یکسان نیستند",
|
||||||
|
"change_email": "تغییر ایمیل",
|
||||||
|
"resend_code": "ارسال مجدد کد",
|
||||||
|
"back_to_login": "بازگشت به صفحه ورود",
|
||||||
|
"login_with_otp": "ورود با رمز عبور یکبار مصرف",
|
||||||
|
"otp_sent": "رمز یک بار مصرف برای شما ارسال شد"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"required": "این فیلد اجباری می باشد",
|
"required": "این فیلد اجباری می باشد",
|
||||||
@@ -59,7 +68,7 @@
|
|||||||
"mainpage": "صفحه اصلی",
|
"mainpage": "صفحه اصلی",
|
||||||
"myservice": "سرویس های من",
|
"myservice": "سرویس های من",
|
||||||
"other_service": "سایر سرویس ها",
|
"other_service": "سایر سرویس ها",
|
||||||
"receipt_list": "لیست صورتحساب",
|
"receipt_list": "صورتحساب ها",
|
||||||
"transactions": "تراکنش ها",
|
"transactions": "تراکنش ها",
|
||||||
"other": "سایر",
|
"other": "سایر",
|
||||||
"ticket": "تیکت ها",
|
"ticket": "تیکت ها",
|
||||||
@@ -72,8 +81,9 @@
|
|||||||
"payments": "پرداخت ها",
|
"payments": "پرداخت ها",
|
||||||
"sliders": "اسلایدر",
|
"sliders": "اسلایدر",
|
||||||
"support": "پلن پشتیبانی",
|
"support": "پلن پشتیبانی",
|
||||||
|
"support_section": "پشتیبانی",
|
||||||
"feedback": "گزارش ها",
|
"feedback": "گزارش ها",
|
||||||
"accessLogs": "لاگهای دسترسی",
|
"accessLogs": "لگ دسترسی",
|
||||||
"icons": "آیکون ها",
|
"icons": "آیکون ها",
|
||||||
"dmenu": "دی منو",
|
"dmenu": "دی منو",
|
||||||
"dkala": "دی کالا",
|
"dkala": "دی کالا",
|
||||||
@@ -86,12 +96,13 @@
|
|||||||
"sms_count": "تعداد پیامک",
|
"sms_count": "تعداد پیامک",
|
||||||
"subscriptions": "اشتراک ها",
|
"subscriptions": "اشتراک ها",
|
||||||
"financial": "مالی",
|
"financial": "مالی",
|
||||||
|
"products": "محصولات",
|
||||||
"dmail": "دی میل",
|
"dmail": "دی میل",
|
||||||
"domain": "دامنه",
|
"domain": "دامنه",
|
||||||
"reseller": "نماینده",
|
"reseller": "نماینده",
|
||||||
"resellers": "نمایندگان",
|
"resellers": "نمایندگان",
|
||||||
"withdraw": "درخواست های برداشت",
|
"withdraw": "درخواست های برداشت",
|
||||||
"taskmanager": "مدیریت تسک ها"
|
"taskmanager": "مدیریت تسک"
|
||||||
},
|
},
|
||||||
"reseller": {
|
"reseller": {
|
||||||
"list": "لیست نمایندکان",
|
"list": "لیست نمایندکان",
|
||||||
@@ -129,7 +140,10 @@
|
|||||||
"service_category": "دسته بندی سرویس ها",
|
"service_category": "دسته بندی سرویس ها",
|
||||||
"customer_list": "لیست مشتریان",
|
"customer_list": "لیست مشتریان",
|
||||||
"create_customer": "افزودن مشتری",
|
"create_customer": "افزودن مشتری",
|
||||||
|
"representative_list": "لیست نمایندگان",
|
||||||
"ticket_list": "لیست تیکت ها",
|
"ticket_list": "لیست تیکت ها",
|
||||||
|
"ticket_category": "دسته بندی تیکت ها",
|
||||||
|
"ticket_masters": "مستر",
|
||||||
"send_ticket": "ارسال تیکت",
|
"send_ticket": "ارسال تیکت",
|
||||||
"category": "دسته بندی",
|
"category": "دسته بندی",
|
||||||
"user_list": "لیست کاربران",
|
"user_list": "لیست کاربران",
|
||||||
@@ -141,9 +155,15 @@
|
|||||||
"plan_users": "کاربران پلن",
|
"plan_users": "کاربران پلن",
|
||||||
"plan_list": "لیست پلن ها",
|
"plan_list": "لیست پلن ها",
|
||||||
"guide": "راهنمای سرویس",
|
"guide": "راهنمای سرویس",
|
||||||
|
"blog_list": "لیست بلاگ",
|
||||||
|
"blog_comments": "نظرات بلاگ",
|
||||||
|
"blog_category": "دسته بندی بلاگ",
|
||||||
|
"learning_list": "لیست آموزش ها",
|
||||||
|
"learning_category": "دسته بندی آموزش ها",
|
||||||
"access_logs_list": "لیست لاگها",
|
"access_logs_list": "لیست لاگها",
|
||||||
"access_logs_stats": "آمار لاگها",
|
"access_logs_stats": "آمار لاگها",
|
||||||
"access_logs_errors": "لاگهای خطا",
|
"access_logs_errors": "لاگهای خطا",
|
||||||
|
"access_logs_permissions": "لاگهای مجوز",
|
||||||
"taskmanager_workspace": "فضای کاری",
|
"taskmanager_workspace": "فضای کاری",
|
||||||
"taskmanager_workspace_list": "لیست فضاهای کاری",
|
"taskmanager_workspace_list": "لیست فضاهای کاری",
|
||||||
"taskmanager_create_workspace": "ساخت فضای کاری جدید",
|
"taskmanager_create_workspace": "ساخت فضای کاری جدید",
|
||||||
@@ -349,7 +369,19 @@
|
|||||||
"assign_to": "کارشناس پاسخگویی",
|
"assign_to": "کارشناس پاسخگویی",
|
||||||
"user_plan": "پلن کاربر",
|
"user_plan": "پلن کاربر",
|
||||||
"refer_ticket_success": "تیکت با موفقیت ارجاع شد",
|
"refer_ticket_success": "تیکت با موفقیت ارجاع شد",
|
||||||
"refer_ticket_error": "تیکت ارجاع نشد"
|
"refer_ticket_error": "تیکت ارجاع نشد",
|
||||||
|
"create_task": "ساخت تسک",
|
||||||
|
"create_task_modal_title": "ساخت تسک از تیکت",
|
||||||
|
"create_task_success": "تسک با موفقیت ساخته شد",
|
||||||
|
"select_project": "انتخاب پروژه",
|
||||||
|
"select_column": "انتخاب ستون",
|
||||||
|
"column": "ستون",
|
||||||
|
"select_workspace_required": "فضای کاری را انتخاب کنید",
|
||||||
|
"select_project_required": "پروژه را انتخاب کنید",
|
||||||
|
"select_column_required": "ستون را انتخاب کنید",
|
||||||
|
"masters": "مستر",
|
||||||
|
"add_master": "افزودن مستر",
|
||||||
|
"edit_master": "ویرایش مستر"
|
||||||
},
|
},
|
||||||
"active": "فعال",
|
"active": "فعال",
|
||||||
"inactive": "غیرفعال",
|
"inactive": "غیرفعال",
|
||||||
@@ -708,6 +740,18 @@
|
|||||||
"submit_role": "ثبت نقش",
|
"submit_role": "ثبت نقش",
|
||||||
"title_role": "عنوان نقش",
|
"title_role": "عنوان نقش",
|
||||||
"permissions": "دسترسی ها",
|
"permissions": "دسترسی ها",
|
||||||
|
"permission_groups": {
|
||||||
|
"services": "سرویسها",
|
||||||
|
"customers": "مشتریان",
|
||||||
|
"users": "کاربران",
|
||||||
|
"financial": "مالی",
|
||||||
|
"content": "محتوا",
|
||||||
|
"support": "پشتیبانی",
|
||||||
|
"other": "سایر",
|
||||||
|
"products": "محصولات",
|
||||||
|
"representatives": "نمایندگان",
|
||||||
|
"task_manager": "مدیریت وظایف"
|
||||||
|
},
|
||||||
"status_role": "وضعیت نقش",
|
"status_role": "وضعیت نقش",
|
||||||
"error_count_permission": "حداقل به دسترسی رو باید انتخاب کنید",
|
"error_count_permission": "حداقل به دسترسی رو باید انتخاب کنید",
|
||||||
"is_admin": "آیا ادمین است؟",
|
"is_admin": "آیا ادمین است؟",
|
||||||
@@ -717,6 +761,7 @@
|
|||||||
"group_add": "ساخت گروه",
|
"group_add": "ساخت گروه",
|
||||||
"submit_group": "ثبت گروه",
|
"submit_group": "ثبت گروه",
|
||||||
"error_count_users": "حداقل یک کاربر باید انتخاب شود",
|
"error_count_users": "حداقل یک کاربر باید انتخاب شود",
|
||||||
|
"error_count_roles": "حداقل یک نقش باید انتخاب شود",
|
||||||
"list_user_group": "لیست گروه های کاربری",
|
"list_user_group": "لیست گروه های کاربری",
|
||||||
"groups": "گروه ها",
|
"groups": "گروه ها",
|
||||||
"group_name": "نام گروه",
|
"group_name": "نام گروه",
|
||||||
@@ -753,7 +798,10 @@
|
|||||||
"confrim_email": "تایید ایمیل",
|
"confrim_email": "تایید ایمیل",
|
||||||
"confrim": "تایید صورتحساب ",
|
"confrim": "تایید صورتحساب ",
|
||||||
"link_email": "یک لینک برای تایید ایمیل برای شما ارسال شد.",
|
"link_email": "یک لینک برای تایید ایمیل برای شما ارسال شد.",
|
||||||
"email_not_verified": "ایمیل تایید نشده"
|
"email_not_verified": "ایمیل تایید نشده",
|
||||||
|
"resend_email": "ارسال مجدد تایید",
|
||||||
|
"first_name": "نام",
|
||||||
|
"last_name": "نام خانوادگی"
|
||||||
},
|
},
|
||||||
"email": "ایمیل",
|
"email": "ایمیل",
|
||||||
"customer": {
|
"customer": {
|
||||||
@@ -994,18 +1042,24 @@
|
|||||||
"project_start_date": "تاریخ شروع پروژه",
|
"project_start_date": "تاریخ شروع پروژه",
|
||||||
"employer_name": "نام کارفرما",
|
"employer_name": "نام کارفرما",
|
||||||
"project_status": "وضعیت فضای کار",
|
"project_status": "وضعیت فضای کار",
|
||||||
|
"project_settings": "تنظیمات پروژه",
|
||||||
"background": "پس زمینه",
|
"background": "پس زمینه",
|
||||||
"background_image": "تصویر پس زمینه",
|
"background_image": "تصویر پس زمینه",
|
||||||
"upload_background": "تصویر مورد نظر را دراپ کنید",
|
"upload_background": "تصویر مورد نظر را دراپ کنید",
|
||||||
"select_date": "انتخاب تاریخ",
|
"select_date": "انتخاب تاریخ",
|
||||||
"projects": "پروژهها",
|
"projects": "پروژهها",
|
||||||
"delete_project": "پاک کردن",
|
"delete_project": "پاک کردن",
|
||||||
|
"project_deleted": "پروژه با موفقیت حذف شد",
|
||||||
|
"delete_confirm_message": "برای حذف «{{name}}»، نام آن را در کادر زیر وارد کنید.",
|
||||||
|
"delete_confirm_placeholder": "نام را وارد کنید",
|
||||||
"add_new_column": "اضافه کردن ستون جدید",
|
"add_new_column": "اضافه کردن ستون جدید",
|
||||||
"add_column": "اضافه کردن",
|
"add_column": "اضافه کردن",
|
||||||
"column_name": "نام ستون",
|
"column_name": "نام ستون",
|
||||||
"delete_column": "حذف ستون",
|
"delete_column": "حذف ستون",
|
||||||
|
"edit_column": "ویرایش نام",
|
||||||
"delete_column_confirm": "آیا از حذف این ستون اطمینان دارید؟",
|
"delete_column_confirm": "آیا از حذف این ستون اطمینان دارید؟",
|
||||||
"column_deleted": "ستون با موفقیت حذف شد",
|
"column_deleted": "ستون با موفقیت حذف شد",
|
||||||
|
"column_updated": "ستون با موفقیت ویرایش شد",
|
||||||
"add_new_task": "اضافه کردن تسک",
|
"add_new_task": "اضافه کردن تسک",
|
||||||
"add_task": "اضافه کردن",
|
"add_task": "اضافه کردن",
|
||||||
"task_title": "عنوان تسک",
|
"task_title": "عنوان تسک",
|
||||||
@@ -1034,7 +1088,20 @@
|
|||||||
"or": "یا",
|
"or": "یا",
|
||||||
"insert": "درج",
|
"insert": "درج",
|
||||||
"start_date": "تاریخ شروع",
|
"start_date": "تاریخ شروع",
|
||||||
"end_date": "تاریخ پایان"
|
"end_date": "تاریخ پایان",
|
||||||
|
"time_tracking": "ثبت زمان",
|
||||||
|
"start_tracking": "شروع",
|
||||||
|
"stop_tracking": "پایان",
|
||||||
|
"time_history": "سابقه",
|
||||||
|
"no_time_entries": "هنوز زمانی ثبت نشده",
|
||||||
|
"tracking_active": "در حال اجرا",
|
||||||
|
"comments": "کامنتها",
|
||||||
|
"add_comment": "ثبت کامنت",
|
||||||
|
"comment_placeholder": "کامنت خود را بنویسید...",
|
||||||
|
"no_comments": "هنوز کامنتی ثبت نشده",
|
||||||
|
"unknown_user": "کاربر",
|
||||||
|
"related_ticket": "تیکت مرتبط",
|
||||||
|
"view_ticket": "مشاهده تیکت"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import moment from 'moment-jalaali'
|
|||||||
import ToggleStatusAds from './components/ToggleStatus'
|
import ToggleStatusAds from './components/ToggleStatus'
|
||||||
import PageLoading from '../../components/PageLoading'
|
import PageLoading from '../../components/PageLoading'
|
||||||
import Pagination from '../../components/Pagination'
|
import Pagination from '../../components/Pagination'
|
||||||
|
import { usePermissions } from '../../hooks/usePermissions'
|
||||||
const AddList: FC = () => {
|
const AddList: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
@@ -23,6 +24,7 @@ const AddList: FC = () => {
|
|||||||
const [page, setPage] = useState<number>(1)
|
const [page, setPage] = useState<number>(1)
|
||||||
const getAds = useGetAdsList(page, search, isActive, since)
|
const getAds = useGetAdsList(page, search, isActive, since)
|
||||||
const deleteAds = useDeleteAds()
|
const deleteAds = useDeleteAds()
|
||||||
|
const { canCreate, canUpdate, canDelete } = usePermissions()
|
||||||
|
|
||||||
const handleDelete = (id: string) => {
|
const handleDelete = (id: string) => {
|
||||||
deleteAds.mutate(id, {
|
deleteAds.mutate(id, {
|
||||||
@@ -39,6 +41,7 @@ const AddList: FC = () => {
|
|||||||
{t('ads.ads')}
|
{t('ads.ads')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{canCreate('advertisements') && (
|
||||||
<Link to={Pages.ads.create}>
|
<Link to={Pages.ads.create}>
|
||||||
<Button
|
<Button
|
||||||
className='w-[172px]'
|
className='w-[172px]'
|
||||||
@@ -51,6 +54,7 @@ const AddList: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className='mt-4'>
|
<div className='mt-4'>
|
||||||
<div className='flex flex-col xl:flex-row justify-between items-center xl:items-end'>
|
<div className='flex flex-col xl:flex-row justify-between items-center xl:items-end'>
|
||||||
@@ -130,10 +134,14 @@ const AddList: FC = () => {
|
|||||||
</Td>
|
</Td>
|
||||||
<Td text={''}>
|
<Td text={''}>
|
||||||
<div className='flex gap-3'>
|
<div className='flex gap-3'>
|
||||||
|
{canUpdate('advertisements') && (
|
||||||
<Link to={Pages.ads.update + item.id}>
|
<Link to={Pages.ads.update + item.id}>
|
||||||
<Eye size={20} color='#8C90A3' />
|
<Eye size={20} color='#8C90A3' />
|
||||||
</Link>
|
</Link>
|
||||||
|
)}
|
||||||
|
{canDelete('advertisements') && (
|
||||||
<Trash onClick={() => handleDelete(item.id)} size={20} color='#8C90A3' />
|
<Trash onClick={() => handleDelete(item.id)} size={20} color='#8C90A3' />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Td>
|
</Td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import CheckBoxComponent from '../../components/CheckBoxComponent'
|
|||||||
import { useSingleUpload } from '../service/hooks/useServiceData'
|
import { useSingleUpload } from '../service/hooks/useServiceData'
|
||||||
import { useCreateAds } from './hooks/useAdsData'
|
import { useCreateAds } from './hooks/useAdsData'
|
||||||
import { ErrorType } from '../../helpers/types'
|
import { ErrorType } from '../../helpers/types'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../components/Toast';
|
||||||
import { clx } from '../../helpers/utils'
|
import { clx } from '../../helpers/utils'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { Pages } from '../../config/Pages'
|
import { Pages } from '../../config/Pages'
|
||||||
@@ -54,21 +54,21 @@ const CreateAd: FC = () => {
|
|||||||
values.imageUrl = data?.data?.url
|
values.imageUrl = data?.data?.url
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
createAds.mutate(values, {
|
createAds.mutate(values, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
navigate(Pages.ads.list)
|
navigate(Pages.ads.list)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
toast.error(t('ads.file_error'))
|
toast(t('ads.file_error'), 'error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import CheckBoxComponent from '../../components/CheckBoxComponent'
|
|||||||
import { useSingleUpload } from '../service/hooks/useServiceData'
|
import { useSingleUpload } from '../service/hooks/useServiceData'
|
||||||
import { useGetDetailAds, useUpdateAds } from './hooks/useAdsData'
|
import { useGetDetailAds, useUpdateAds } from './hooks/useAdsData'
|
||||||
import { ErrorType } from '../../helpers/types'
|
import { ErrorType } from '../../helpers/types'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../components/Toast';
|
||||||
import { clx } from '../../helpers/utils'
|
import { clx } from '../../helpers/utils'
|
||||||
import { useNavigate, useParams } from 'react-router-dom'
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
import { Pages } from '../../config/Pages'
|
import { Pages } from '../../config/Pages'
|
||||||
@@ -56,18 +56,18 @@ const UpdateAds: FC = () => {
|
|||||||
values.imageUrl = data?.data?.url
|
values.imageUrl = data?.data?.url
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAds.mutate(values, {
|
updateAds.mutate(values, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
navigate(Pages.ads.list)
|
navigate(Pages.ads.list)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import * as Yup from 'yup'
|
|||||||
import { ServiceItemType } from '../service/types/ServiceTypes'
|
import { ServiceItemType } from '../service/types/ServiceTypes'
|
||||||
import { useCreateAnnoncement, useGetCustomersService } from './hooks/useAnnoncementData'
|
import { useCreateAnnoncement, useGetCustomersService } from './hooks/useAnnoncementData'
|
||||||
import { ErrorType } from '../../helpers/types'
|
import { ErrorType } from '../../helpers/types'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../components/Toast';
|
||||||
import moment from 'moment-jalaali'
|
import moment from 'moment-jalaali'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { Pages } from '../../config/Pages'
|
import { Pages } from '../../config/Pages'
|
||||||
@@ -57,11 +57,11 @@ const Create: FC = () => {
|
|||||||
createAnnoncement.mutate(values, {
|
createAnnoncement.mutate(values, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
formik.resetForm()
|
formik.resetForm()
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
navigate(Pages.announcement.list)
|
navigate(Pages.announcement.list)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ import { Add, Edit, Trash } from 'iconsax-react'
|
|||||||
import Td from '../../components/Td'
|
import Td from '../../components/Td'
|
||||||
import moment from 'moment-jalaali'
|
import moment from 'moment-jalaali'
|
||||||
import Pagination from '../../components/Pagination'
|
import Pagination from '../../components/Pagination'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../components/Toast';
|
||||||
import { ErrorType } from '../../helpers/types'
|
import { ErrorType } from '../../helpers/types'
|
||||||
|
import { usePermissions } from '../../hooks/usePermissions'
|
||||||
|
|
||||||
const AnnouncementtList: FC = () => {
|
const AnnouncementtList: FC = () => {
|
||||||
|
|
||||||
@@ -18,15 +19,16 @@ const AnnouncementtList: FC = () => {
|
|||||||
const [page, setPage] = useState<number>(1)
|
const [page, setPage] = useState<number>(1)
|
||||||
const getAnnoncements = useGetAnnoncements(page, '', '')
|
const getAnnoncements = useGetAnnoncements(page, '', '')
|
||||||
const deleteAnnoncement = useDeleteAnnoncement()
|
const deleteAnnoncement = useDeleteAnnoncement()
|
||||||
|
const { canCreate, canUpdate, canDelete } = usePermissions()
|
||||||
|
|
||||||
const handleDelete = (id: string) => {
|
const handleDelete = (id: string) => {
|
||||||
deleteAnnoncement.mutate(id, {
|
deleteAnnoncement.mutate(id, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
getAnnoncements.refetch()
|
getAnnoncements.refetch()
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error.message[0])
|
toast(error.response?.data?.error.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -38,6 +40,7 @@ const AnnouncementtList: FC = () => {
|
|||||||
<div>
|
<div>
|
||||||
{t('announcement.announcements')}
|
{t('announcement.announcements')}
|
||||||
</div>
|
</div>
|
||||||
|
{canCreate('announcements') && (
|
||||||
<div>
|
<div>
|
||||||
<Link to={Pages.announcement.create}>
|
<Link to={Pages.announcement.create}>
|
||||||
<Button
|
<Button
|
||||||
@@ -53,6 +56,7 @@ const AnnouncementtList: FC = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -88,17 +92,21 @@ const AnnouncementtList: FC = () => {
|
|||||||
<Td text={moment(item.publishAt).format('jYYYY-jMM-jDD')} />
|
<Td text={moment(item.publishAt).format('jYYYY-jMM-jDD')} />
|
||||||
<Td text={''}>
|
<Td text={''}>
|
||||||
<div className='flex gap-2'>
|
<div className='flex gap-2'>
|
||||||
|
{canUpdate('announcements') && (
|
||||||
<Link to={Pages.announcement.detail + item.id}>
|
<Link to={Pages.announcement.detail + item.id}>
|
||||||
<Edit
|
<Edit
|
||||||
className='size-5'
|
className='size-5'
|
||||||
color='#888'
|
color='#888'
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
|
)}
|
||||||
|
{canDelete('announcements') && (
|
||||||
<Trash
|
<Trash
|
||||||
className='size-5'
|
className='size-5'
|
||||||
color='#888'
|
color='#888'
|
||||||
onClick={() => handleDelete(item.id)}
|
onClick={() => handleDelete(item.id)}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Td>
|
</Td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import * as Yup from 'yup'
|
|||||||
import { ServiceItemType } from '../service/types/ServiceTypes'
|
import { ServiceItemType } from '../service/types/ServiceTypes'
|
||||||
import { useGetAnnoncementDetail, useGetCustomersService, useUpdateAnnoncement } from './hooks/useAnnoncementData'
|
import { useGetAnnoncementDetail, useGetCustomersService, useUpdateAnnoncement } from './hooks/useAnnoncementData'
|
||||||
import { ErrorType } from '../../helpers/types'
|
import { ErrorType } from '../../helpers/types'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../components/Toast';
|
||||||
import moment from 'moment-jalaali'
|
import moment from 'moment-jalaali'
|
||||||
import { useNavigate, useParams } from 'react-router-dom'
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
import { Pages } from '../../config/Pages'
|
import { Pages } from '../../config/Pages'
|
||||||
@@ -59,11 +59,11 @@ const Update: FC = () => {
|
|||||||
updateAnnoncement.mutate(values, {
|
updateAnnoncement.mutate(values, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
formik.resetForm()
|
formik.resetForm()
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
navigate(Pages.announcement.list)
|
navigate(Pages.announcement.list)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { FC, useState } from 'react'
|
||||||
|
import LogoImage from '../../assets/images/logo.svg'
|
||||||
|
import LogoSmallImage from '../../assets/images/logo-small.svg'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { Pages } from '../../config/Pages'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import ForgotPasswordStep1 from './components/ForgotPasswordStep1'
|
||||||
|
import ForgotPasswordStep2 from './components/ForgotPasswordStep2'
|
||||||
|
|
||||||
|
const ForgotPassword: FC = () => {
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
const [step, setStep] = useState(1)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full h-full flex justify-center lg:py-[75px] py-4 lg:items-center lg:px-10 px-4'>
|
||||||
|
<div className='flex w-full max-h-[812px] max-w-[1200px] bg-secondary h-full rounded-3xl overflow-hidden'>
|
||||||
|
<div className='flex-1 min-w-[50%] overflow-y-auto bg-white lg:px-9 px-4 py-7'>
|
||||||
|
<div className='flex-1 h-full flex flex-col'>
|
||||||
|
<img src={LogoSmallImage} className='w-8' />
|
||||||
|
|
||||||
|
<div className='flex flex-1 flex-col h-full justify-center'>
|
||||||
|
{
|
||||||
|
step === 1 ?
|
||||||
|
<ForgotPasswordStep1 onSuccess={() => setStep(2)} />
|
||||||
|
:
|
||||||
|
<ForgotPasswordStep2 onBack={() => setStep(1)} />
|
||||||
|
}
|
||||||
|
|
||||||
|
<div className='mt-6 flex justify-center gap-2 items-center text-sm'>
|
||||||
|
<p className='text-description'>
|
||||||
|
{t('auth.back_to_login')}
|
||||||
|
</p>
|
||||||
|
<Link to={Pages.auth.login}>
|
||||||
|
<div>{t('auth.login')}</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex-1 min-w-[50%] lg:flex hidden justify-center items-center'>
|
||||||
|
<img src={LogoImage} className='h-20' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ForgotPassword
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { FC } from 'react'
|
||||||
|
import Input from '../../../components/Input'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { ForgotPasswordType } from '../types/AuthTypes'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
import { useAuthStore } from '../store/AuthStore'
|
||||||
|
import Error from '../../../components/Error'
|
||||||
|
import Button from '../../../components/Button'
|
||||||
|
import { useForgotPassword } from '../hooks/useAuthData'
|
||||||
|
import { toast } from '../../../components/Toast';
|
||||||
|
import { ErrorType } from '../../../helpers/types'
|
||||||
|
import { isEmail } from '../../../config/func'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onSuccess: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ForgotPasswordStep1: FC<Props> = ({ onSuccess }) => {
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
const { email, setEmail } = useAuthStore()
|
||||||
|
const forgotPassword = useForgotPassword()
|
||||||
|
|
||||||
|
const formik = useFormik<ForgotPasswordType>({
|
||||||
|
initialValues: {
|
||||||
|
email: email,
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object({
|
||||||
|
email: Yup.string()
|
||||||
|
.required(t('errors.required'))
|
||||||
|
.test('is-email', t('errors.required'), (value) => !!value && isEmail(value)),
|
||||||
|
}),
|
||||||
|
onSubmit(values) {
|
||||||
|
setEmail(values.email)
|
||||||
|
forgotPassword.mutate({ email: values.email }, {
|
||||||
|
onSuccess() {
|
||||||
|
toast(t('auth.otp_sent'), 'success')
|
||||||
|
onSuccess()
|
||||||
|
},
|
||||||
|
onError(error: ErrorType) {
|
||||||
|
toast(error?.response?.data?.error?.message[0], 'error')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className='mt-5'>
|
||||||
|
<h2 className='text-2xl font-bold'>
|
||||||
|
{t('auth.forgot_password')}
|
||||||
|
</h2>
|
||||||
|
<p className='text-description text-sm mt-2'>
|
||||||
|
{t('auth.forgot_password_description')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-16'>
|
||||||
|
<Input
|
||||||
|
label={t('auth.email')}
|
||||||
|
placeholder={t('auth.enter_email')}
|
||||||
|
type='text'
|
||||||
|
dir='ltr'
|
||||||
|
name='email'
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value={formik.values.email}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{
|
||||||
|
formik.touched.email && formik.errors.email &&
|
||||||
|
<Error
|
||||||
|
errorText={formik.errors.email}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
label={t('auth.next')}
|
||||||
|
className='mt-8'
|
||||||
|
onClick={() => formik.handleSubmit()}
|
||||||
|
isLoading={forgotPassword.isPending}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ForgotPasswordStep1
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
import { FC } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { ResetPasswordType } from '../types/AuthTypes'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
import { useAuthStore } from '../store/AuthStore'
|
||||||
|
import Button from '../../../components/Button'
|
||||||
|
import Input from '../../../components/Input'
|
||||||
|
import Error from '../../../components/Error'
|
||||||
|
import OTPInput from 'react-otp-input'
|
||||||
|
import { useCountDown } from '../../../hooks/useCountDown'
|
||||||
|
import { Pages } from '../../../config/Pages'
|
||||||
|
import { useForgotPassword, useResetPassword } from '../hooks/useAuthData'
|
||||||
|
import { ErrorType } from '../../../helpers/types'
|
||||||
|
import { toast } from '../../../components/Toast';
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onBack: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ForgotPasswordStep2: FC<Props> = ({ onBack }) => {
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const { email } = useAuthStore()
|
||||||
|
const { value, reset } = useCountDown(2, true)
|
||||||
|
const resetPassword = useResetPassword()
|
||||||
|
const forgotPassword = useForgotPassword()
|
||||||
|
|
||||||
|
const formik = useFormik<ResetPasswordType & { confirmPassword: string }>({
|
||||||
|
initialValues: {
|
||||||
|
email: email,
|
||||||
|
code: '',
|
||||||
|
newPassword: '',
|
||||||
|
confirmPassword: '',
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object({
|
||||||
|
code: Yup.string()
|
||||||
|
.required(t('errors.required')),
|
||||||
|
newPassword: Yup.string()
|
||||||
|
.required(t('errors.required')),
|
||||||
|
confirmPassword: Yup.string()
|
||||||
|
.required(t('errors.required'))
|
||||||
|
.oneOf([Yup.ref('newPassword')], t('auth.password_mismatch')),
|
||||||
|
}),
|
||||||
|
onSubmit(values) {
|
||||||
|
resetPassword.mutate({
|
||||||
|
email,
|
||||||
|
code: values.code,
|
||||||
|
newPassword: values.newPassword,
|
||||||
|
}, {
|
||||||
|
onSuccess() {
|
||||||
|
toast(t('success'), 'success')
|
||||||
|
navigate(Pages.auth.login)
|
||||||
|
},
|
||||||
|
onError(error: ErrorType) {
|
||||||
|
toast(error?.response?.data?.error?.message[0], 'error')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleResend = () => {
|
||||||
|
forgotPassword.mutate({ email }, {
|
||||||
|
onSuccess() {
|
||||||
|
reset()
|
||||||
|
toast(t('auth.otp_sent'), 'success')
|
||||||
|
},
|
||||||
|
onError(error: ErrorType) {
|
||||||
|
toast(error?.response?.data?.error?.message[0], 'error')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className='mt-5'>
|
||||||
|
<h2 className='lg:text-2xl font-bold'>
|
||||||
|
{t('auth.reset_password')}
|
||||||
|
</h2>
|
||||||
|
<p className='text-description flex lg:flex-row flex-col lg:gap-1 gap-2 text-sm lg:mt-2 mt-3'>
|
||||||
|
<div className='flex gap-1 flex-wrap'>
|
||||||
|
<div>{t('auth.verfify_code_text_1')}</div>
|
||||||
|
<div>{email}</div>
|
||||||
|
<div>{t('auth.verfify_code_text_2')}</div>
|
||||||
|
</div>
|
||||||
|
<div onClick={onBack} className='text-black cursor-pointer'>
|
||||||
|
{t('auth.change_email')}
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-16'>
|
||||||
|
<div className='text-sm'>
|
||||||
|
{t('auth.verify_code')}
|
||||||
|
</div>
|
||||||
|
<div className='mt-2 w-full flex justify-center dltr otp'>
|
||||||
|
<OTPInput
|
||||||
|
shouldAutoFocus
|
||||||
|
value={formik.values.code}
|
||||||
|
numInputs={5}
|
||||||
|
inputType='tel'
|
||||||
|
onChange={(otp: string) => formik.setFieldValue('code', otp)}
|
||||||
|
renderInput={(props) => <input {...props} className='w-full h-[50px] flex-1 mx-2 bg-white border rounded-2.5' />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
formik.touched.code && formik.errors.code &&
|
||||||
|
<div className='text-xs mt-2 text-red-500'>{formik.errors.code}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div className='flex mt-4 justify-between items-center'>
|
||||||
|
<div
|
||||||
|
onClick={handleResend}
|
||||||
|
className='text-xs text-black cursor-pointer'
|
||||||
|
>
|
||||||
|
{t('auth.resend_code')}
|
||||||
|
</div>
|
||||||
|
<div className='flex gap-1 text-description text-xs'>
|
||||||
|
<div>{value}</div>
|
||||||
|
<div>{t('auth.counter_otp')}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-6'>
|
||||||
|
<Input
|
||||||
|
label={t('auth.new_password')}
|
||||||
|
type='password'
|
||||||
|
className='text-right'
|
||||||
|
name='newPassword'
|
||||||
|
dir='ltr'
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value={formik.values.newPassword}
|
||||||
|
error_text={formik.touched.newPassword && formik.errors.newPassword ? formik.errors.newPassword : ''}
|
||||||
|
/>
|
||||||
|
{
|
||||||
|
formik.touched.newPassword && formik.errors.newPassword &&
|
||||||
|
<Error errorText={formik.errors.newPassword} />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-4'>
|
||||||
|
<Input
|
||||||
|
label={t('auth.confirm_password')}
|
||||||
|
type='password'
|
||||||
|
className='text-right'
|
||||||
|
name='confirmPassword'
|
||||||
|
dir='ltr'
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value={formik.values.confirmPassword}
|
||||||
|
error_text={formik.touched.confirmPassword && formik.errors.confirmPassword ? formik.errors.confirmPassword : ''}
|
||||||
|
/>
|
||||||
|
{
|
||||||
|
formik.touched.confirmPassword && formik.errors.confirmPassword &&
|
||||||
|
<Error errorText={formik.errors.confirmPassword} />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
label={t('auth.reset_password')}
|
||||||
|
className='mt-8'
|
||||||
|
onClick={() => formik.handleSubmit()}
|
||||||
|
isLoading={resetPassword.isPending}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ForgotPasswordStep2
|
||||||
@@ -9,7 +9,7 @@ import Error from '../../../components/Error'
|
|||||||
import Button from '../../../components/Button'
|
import Button from '../../../components/Button'
|
||||||
import { isEmail } from '../../../config/func'
|
import { isEmail } from '../../../config/func'
|
||||||
import { useCheckHasAccount, useLoginWithOtp } from '../hooks/useAuthData'
|
import { useCheckHasAccount, useLoginWithOtp } from '../hooks/useAuthData'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../components/Toast';
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { ErrorType } from '../../../helpers/types'
|
||||||
|
|
||||||
const LoginStep1: FC = () => {
|
const LoginStep1: FC = () => {
|
||||||
@@ -35,7 +35,7 @@ const LoginStep1: FC = () => {
|
|||||||
setStepLogin(2)
|
setStepLogin(2)
|
||||||
},
|
},
|
||||||
onError(error: ErrorType) {
|
onError(error: ErrorType) {
|
||||||
toast.error(error?.response?.data?.error?.message[0])
|
toast(error?.response?.data?.error?.message[0], 'error')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -43,10 +43,10 @@ const LoginStep1: FC = () => {
|
|||||||
loginWithOtp.mutate({ phone: values.phone_email }, {
|
loginWithOtp.mutate({ phone: values.phone_email }, {
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
setStepLogin(2)
|
setStepLogin(2)
|
||||||
toast.success(t('auth.otp_sent'))
|
toast(t('auth.otp_sent'), 'success')
|
||||||
},
|
},
|
||||||
onError(error: ErrorType) {
|
onError(error: ErrorType) {
|
||||||
toast.error(error?.response?.data?.error?.message[0])
|
toast(error?.response?.data?.error?.message[0], 'error')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -69,7 +69,7 @@ const LoginStep1: FC = () => {
|
|||||||
label={t('auth.mobile_or_email')}
|
label={t('auth.mobile_or_email')}
|
||||||
placeholder={t('auth.enter_mobile_or_email')}
|
placeholder={t('auth.enter_mobile_or_email')}
|
||||||
type='text'
|
type='text'
|
||||||
className='text-right'
|
dir='ltr'
|
||||||
name='phone_email'
|
name='phone_email'
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
value={formik.values.phone_email}
|
value={formik.values.phone_email}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { useCountDown } from '../../../hooks/useCountDown'
|
|||||||
import { Pages } from '../../../config/Pages'
|
import { Pages } from '../../../config/Pages'
|
||||||
import { useOtpVerify } from '../hooks/useAuthData'
|
import { useOtpVerify } from '../hooks/useAuthData'
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { ErrorType } from '../../../helpers/types'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../components/Toast';
|
||||||
import { setToken, setRefreshToken } from '../../../config/func'
|
import { setToken, setRefreshToken } from '../../../config/func'
|
||||||
|
|
||||||
const LoginStep2: FC = () => {
|
const LoginStep2: FC = () => {
|
||||||
@@ -42,7 +42,7 @@ const LoginStep2: FC = () => {
|
|||||||
window.location.href = Pages.dashboard
|
window.location.href = Pages.dashboard
|
||||||
},
|
},
|
||||||
onError(error: ErrorType) {
|
onError(error: ErrorType) {
|
||||||
toast.error(error?.response?.data?.error?.message[0])
|
toast(error?.response?.data?.error?.message[0], 'error')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -78,7 +78,13 @@ const LoginStep2: FC = () => {
|
|||||||
value={formik.values.code}
|
value={formik.values.code}
|
||||||
numInputs={5}
|
numInputs={5}
|
||||||
inputType='tel'
|
inputType='tel'
|
||||||
onChange={(otp: string) => formik.setFieldValue('code', otp)}
|
onChange={(otp: string) => {
|
||||||
|
formik.setFieldValue('code', otp).then(() => {
|
||||||
|
if (otp.length === 5 && !otpVerify.isPending) {
|
||||||
|
formik.submitForm()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}
|
||||||
renderInput={(props) => <input {...props} className='w-full h-[50px] flex-1 mx-2 bg-white border rounded-2.5' />}
|
renderInput={(props) => <input {...props} className='w-full h-[50px] flex-1 mx-2 bg-white border rounded-2.5' />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { Link } from 'react-router-dom'
|
|||||||
import { Pages } from '../../../config/Pages'
|
import { Pages } from '../../../config/Pages'
|
||||||
import ArrowLeftIcon from '../../../assets/images/arrow-left.svg'
|
import ArrowLeftIcon from '../../../assets/images/arrow-left.svg'
|
||||||
import { useLoginWithPassword } from '../hooks/useAuthData'
|
import { useLoginWithPassword } from '../hooks/useAuthData'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../components/Toast';
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { ErrorType } from '../../../helpers/types'
|
||||||
import { setRefreshToken } from '../../../config/func'
|
import { setRefreshToken } from '../../../config/func'
|
||||||
import { setToken } from '../../../config/func'
|
import { setToken } from '../../../config/func'
|
||||||
@@ -39,7 +39,7 @@ const LoginStep3: FC = () => {
|
|||||||
window.location.href = Pages.dashboard
|
window.location.href = Pages.dashboard
|
||||||
},
|
},
|
||||||
onError(error: ErrorType) {
|
onError(error: ErrorType) {
|
||||||
toast.error(error?.response?.data?.error?.message[0])
|
toast(error?.response?.data?.error?.message[0], 'error')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -63,6 +63,7 @@ const LoginStep3: FC = () => {
|
|||||||
type='password'
|
type='password'
|
||||||
className='text-right'
|
className='text-right'
|
||||||
name='password'
|
name='password'
|
||||||
|
dir='ltr'
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
value={formik.values.password}
|
value={formik.values.password}
|
||||||
error_text={formik.touched.password && formik.errors.password ? formik.errors.password : ''}
|
error_text={formik.touched.password && formik.errors.password ? formik.errors.password : ''}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { useAuthStore } from '../store/AuthStore'
|
|||||||
import Error from '../../../components/Error'
|
import Error from '../../../components/Error'
|
||||||
import Button from '../../../components/Button'
|
import Button from '../../../components/Button'
|
||||||
import { useCheckHasAccountRegister, useLoginWithOtp } from '../hooks/useAuthData'
|
import { useCheckHasAccountRegister, useLoginWithOtp } from '../hooks/useAuthData'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../components/Toast';
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { ErrorType } from '../../../helpers/types'
|
||||||
|
|
||||||
const RegisterStep1: FC = () => {
|
const RegisterStep1: FC = () => {
|
||||||
@@ -33,7 +33,7 @@ const RegisterStep1: FC = () => {
|
|||||||
setStepLogin(2)
|
setStepLogin(2)
|
||||||
},
|
},
|
||||||
onError(error: ErrorType) {
|
onError(error: ErrorType) {
|
||||||
toast.error(error?.response?.data?.error?.message[0])
|
toast(error?.response?.data?.error?.message[0], 'error')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import DatePickerComponent from '../../../components/DatePicker'
|
|||||||
import Button from '../../../components/Button'
|
import Button from '../../../components/Button'
|
||||||
import { useRegister } from '../hooks/useAuthData'
|
import { useRegister } from '../hooks/useAuthData'
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { ErrorType } from '../../../helpers/types'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../components/Toast';
|
||||||
import { Pages } from '../../../config/Pages'
|
import { Pages } from '../../../config/Pages'
|
||||||
|
|
||||||
const RegisterStep2: FC = () => {
|
const RegisterStep2: FC = () => {
|
||||||
@@ -52,7 +52,7 @@ const RegisterStep2: FC = () => {
|
|||||||
window.location.href = Pages.dashboard
|
window.location.href = Pages.dashboard
|
||||||
},
|
},
|
||||||
onError(error: ErrorType) {
|
onError(error: ErrorType) {
|
||||||
toast.error(error?.response?.data?.error?.message[0])
|
toast(error?.response?.data?.error?.message[0], 'error')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import * as api from '../service/AuthService'
|
import * as api from '../service/AuthService'
|
||||||
import { CheckHasAccountPhoneType, CheckHasAccountType, LoginWithOtpType, LoginWithPasswordType, OtpVerifyType, RegisterType } from '../types/AuthTypes';
|
import { CheckHasAccountPhoneType, CheckHasAccountType, ForgotPasswordType, LoginWithOtpType, LoginWithPasswordType, OtpVerifyType, RegisterType, ResetPasswordType } from '../types/AuthTypes';
|
||||||
|
|
||||||
export const useLoginWithPassword = () => {
|
export const useLoginWithPassword = () => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
@@ -37,3 +37,15 @@ export const useRegister = () => {
|
|||||||
mutationFn: (variables: RegisterType) => api.register(variables),
|
mutationFn: (variables: RegisterType) => api.register(variables),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useForgotPassword = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (variables: ForgotPasswordType) => api.forgotPassword(variables),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useResetPassword = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (variables: ResetPasswordType) => api.resetPassword(variables),
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -2,11 +2,13 @@ import axios from "../../../config/axios";
|
|||||||
import {
|
import {
|
||||||
CheckHasAccountPhoneType,
|
CheckHasAccountPhoneType,
|
||||||
CheckHasAccountType,
|
CheckHasAccountType,
|
||||||
|
ForgotPasswordType,
|
||||||
LoginWithOtpType,
|
LoginWithOtpType,
|
||||||
LoginWithPasswordType,
|
LoginWithPasswordType,
|
||||||
OtpVerifyType,
|
OtpVerifyType,
|
||||||
RefreshTokenType,
|
RefreshTokenType,
|
||||||
RegisterType,
|
RegisterType,
|
||||||
|
ResetPasswordType,
|
||||||
} from "../types/AuthTypes";
|
} from "../types/AuthTypes";
|
||||||
|
|
||||||
export const loginWithPassword = async (params: LoginWithPasswordType) => {
|
export const loginWithPassword = async (params: LoginWithPasswordType) => {
|
||||||
@@ -43,3 +45,13 @@ export const refreshToken = async (params: RefreshTokenType) => {
|
|||||||
const { data } = await axios.post(`/auth/refresh`, params);
|
const { data } = await axios.post(`/auth/refresh`, params);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const forgotPassword = async (params: ForgotPasswordType) => {
|
||||||
|
const { data } = await axios.post(`/auth/forgot-password`, params);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resetPassword = async (params: ResetPasswordType) => {
|
||||||
|
const { data } = await axios.post(`/auth/reset-password`, params);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
@@ -52,3 +52,13 @@ export type RegisterType = {
|
|||||||
export type RefreshTokenType = {
|
export type RefreshTokenType = {
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ForgotPasswordType = {
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ResetPasswordType = {
|
||||||
|
email: string;
|
||||||
|
code: string;
|
||||||
|
newPassword: string;
|
||||||
|
};
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ import * as Yup from 'yup'
|
|||||||
import Textarea from '../../components/Textarea'
|
import Textarea from '../../components/Textarea'
|
||||||
import UploadBoxDraggble from '../../components/UploadBoxDraggble'
|
import UploadBoxDraggble from '../../components/UploadBoxDraggble'
|
||||||
import { useSingleUpload } from '../service/hooks/useServiceData'
|
import { useSingleUpload } from '../service/hooks/useServiceData'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../components/Toast';
|
||||||
import UpdateCategory from './components/UpdateCategory'
|
import UpdateCategory from './components/UpdateCategory'
|
||||||
|
import { usePermissions } from '../../hooks/usePermissions'
|
||||||
const BlogCategory: FC = () => {
|
const BlogCategory: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
@@ -25,6 +26,7 @@ const BlogCategory: FC = () => {
|
|||||||
const singleUpload = useSingleUpload()
|
const singleUpload = useSingleUpload()
|
||||||
const createBlogCategory = useCreateBlogCategory()
|
const createBlogCategory = useCreateBlogCategory()
|
||||||
const deleteBlogCategory = useDeleteBlogCategory()
|
const deleteBlogCategory = useDeleteBlogCategory()
|
||||||
|
const { canCreate, canUpdate, canDelete } = usePermissions()
|
||||||
const formik = useFormik<CreateBlogCategoryType>({
|
const formik = useFormik<CreateBlogCategoryType>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
title: '',
|
title: '',
|
||||||
@@ -38,7 +40,7 @@ const BlogCategory: FC = () => {
|
|||||||
}),
|
}),
|
||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
toast.error('فایل را انتخاب کنید')
|
toast('فایل را انتخاب کنید', 'error')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
@@ -51,7 +53,7 @@ const BlogCategory: FC = () => {
|
|||||||
|
|
||||||
createBlogCategory.mutateAsync(values, {
|
createBlogCategory.mutateAsync(values, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success('دسته بندی با موفقیت ثبت شد')
|
toast('دسته بندی با موفقیت ثبت شد', 'success')
|
||||||
setFile(undefined)
|
setFile(undefined)
|
||||||
formik.resetForm()
|
formik.resetForm()
|
||||||
getBlogCategories.refetch()
|
getBlogCategories.refetch()
|
||||||
@@ -102,15 +104,19 @@ const BlogCategory: FC = () => {
|
|||||||
<Td text={moment(item.createdAt).format('jYYYY/jMM/jDD')} />
|
<Td text={moment(item.createdAt).format('jYYYY/jMM/jDD')} />
|
||||||
<Td text={t('')}>
|
<Td text={t('')}>
|
||||||
<div className='flex gap-2 items-center'>
|
<div className='flex gap-2 items-center'>
|
||||||
|
{canUpdate('blog_categories') && (
|
||||||
<UpdateCategory id={item.id} />
|
<UpdateCategory id={item.id} />
|
||||||
|
)}
|
||||||
|
{canDelete('blog_categories') && (
|
||||||
<Trash size={20} color='red' onClick={() => {
|
<Trash size={20} color='red' onClick={() => {
|
||||||
deleteBlogCategory.mutateAsync(item.id, {
|
deleteBlogCategory.mutateAsync(item.id, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success('دسته بندی با موفقیت حذف شد')
|
toast('دسته بندی با موفقیت حذف شد', 'success')
|
||||||
getBlogCategories.refetch()
|
getBlogCategories.refetch()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}} />
|
}} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Td>
|
</Td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -123,6 +129,7 @@ const BlogCategory: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{canCreate('blog_categories') && (
|
||||||
<div className='relative bg-white min-h-[calc(100vh-185px)] w-sidebar xl:block hidden py-10 px-5 h-fit rounded-3xl'>
|
<div className='relative bg-white min-h-[calc(100vh-185px)] w-sidebar xl:block hidden py-10 px-5 h-fit rounded-3xl'>
|
||||||
<p className='text-md'>{t('blog.add_category')}</p>
|
<p className='text-md'>{t('blog.add_category')}</p>
|
||||||
<div className='text-sm flex items-center justify-between mt-4'>
|
<div className='text-sm flex items-center justify-between mt-4'>
|
||||||
@@ -176,6 +183,7 @@ const BlogCategory: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { BlogItemType } from './types/BlogTypes'
|
|||||||
import moment from 'moment-jalaali'
|
import moment from 'moment-jalaali'
|
||||||
import TrashWithConfrim from '../../components/TrashWithConfrim'
|
import TrashWithConfrim from '../../components/TrashWithConfrim'
|
||||||
import Pagination from '../../components/Pagination'
|
import Pagination from '../../components/Pagination'
|
||||||
|
import { usePermissions } from '../../hooks/usePermissions'
|
||||||
const BlogList: FC = () => {
|
const BlogList: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
@@ -18,6 +19,7 @@ const BlogList: FC = () => {
|
|||||||
const [search, setSearch] = useState<string>('')
|
const [search, setSearch] = useState<string>('')
|
||||||
const getBlogs = useGetBlogs(search, page);
|
const getBlogs = useGetBlogs(search, page);
|
||||||
const deleteBlog = useDeleteBlog()
|
const deleteBlog = useDeleteBlog()
|
||||||
|
const { canCreate, canUpdate, canDelete } = usePermissions()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-4 min-h-[500px]'>
|
<div className='mt-4 min-h-[500px]'>
|
||||||
@@ -26,6 +28,7 @@ const BlogList: FC = () => {
|
|||||||
{t('blog.blog')}
|
{t('blog.blog')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{canCreate('blogs') && (
|
||||||
<Link to={Pages.blog.create}>
|
<Link to={Pages.blog.create}>
|
||||||
<Button
|
<Button
|
||||||
className='w-[172px]'
|
className='w-[172px]'
|
||||||
@@ -38,6 +41,7 @@ const BlogList: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -139,9 +143,12 @@ const BlogList: FC = () => {
|
|||||||
</Td> */}
|
</Td> */}
|
||||||
<Td text={''}>
|
<Td text={''}>
|
||||||
<div className='flex gap-2'>
|
<div className='flex gap-2'>
|
||||||
|
{canUpdate('blogs') && (
|
||||||
<Link to={Pages.blog.detail + item.id}>
|
<Link to={Pages.blog.detail + item.id}>
|
||||||
<Eye size={20} color='#8C90A3' />
|
<Eye size={20} color='#8C90A3' />
|
||||||
</Link>
|
</Link>
|
||||||
|
)}
|
||||||
|
{canDelete('blogs') && (
|
||||||
<TrashWithConfrim
|
<TrashWithConfrim
|
||||||
onDelete={() => {
|
onDelete={() => {
|
||||||
deleteBlog.mutate(item.id, {
|
deleteBlog.mutate(item.id, {
|
||||||
@@ -152,6 +159,7 @@ const BlogList: FC = () => {
|
|||||||
}}
|
}}
|
||||||
isLoading={deleteBlog.isPending}
|
isLoading={deleteBlog.isPending}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Td>
|
</Td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import { BlogCommentType } from './types/BlogTypes'
|
|||||||
import moment from 'moment-jalaali'
|
import moment from 'moment-jalaali'
|
||||||
import { CloseCircle, TickCircle } from 'iconsax-react'
|
import { CloseCircle, TickCircle } from 'iconsax-react'
|
||||||
import { ErrorType } from '../../helpers/types'
|
import { ErrorType } from '../../helpers/types'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../components/Toast';
|
||||||
|
import { usePermissions } from '../../hooks/usePermissions'
|
||||||
const Comments: FC = () => {
|
const Comments: FC = () => {
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
|
const { canUpdate } = usePermissions()
|
||||||
const getBlogComments = useGetBlogComments()
|
const getBlogComments = useGetBlogComments()
|
||||||
const changeStatusComment = useChangeStatusComment()
|
const changeStatusComment = useChangeStatusComment()
|
||||||
const handleChangeStatus = (id: string, status: string) => {
|
const handleChangeStatus = (id: string, status: string) => {
|
||||||
@@ -17,7 +19,7 @@ const Comments: FC = () => {
|
|||||||
getBlogComments.refetch()
|
getBlogComments.refetch()
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error.message[0])
|
toast(error.response?.data?.error.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -49,7 +51,7 @@ const Comments: FC = () => {
|
|||||||
<Td text={moment(item.createdAt).format('jYYYY/jMM/jDD')} />
|
<Td text={moment(item.createdAt).format('jYYYY/jMM/jDD')} />
|
||||||
<Td text=''>
|
<Td text=''>
|
||||||
{
|
{
|
||||||
item.status === 'PENDING' &&
|
item.status === 'PENDING' && canUpdate('blog_comments') &&
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<TickCircle onClick={() => handleChangeStatus(item.id, 'APPROVED')} color='green' size={20} />
|
<TickCircle onClick={() => handleChangeStatus(item.id, 'APPROVED')} color='green' size={20} />
|
||||||
<CloseCircle onClick={() => handleChangeStatus(item.id, 'REJECTED')} color='red' size={20} />
|
<CloseCircle onClick={() => handleChangeStatus(item.id, 'REJECTED')} color='red' size={20} />
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import CheckBoxComponent from '../../components/CheckBoxComponent'
|
|||||||
import UploadBoxDraggble from '../../components/UploadBoxDraggble'
|
import UploadBoxDraggble from '../../components/UploadBoxDraggble'
|
||||||
import QuillEditor from '../../components/QuillEditor'
|
import QuillEditor from '../../components/QuillEditor'
|
||||||
import { useSingleUpload } from '../service/hooks/useServiceData'
|
import { useSingleUpload } from '../service/hooks/useServiceData'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../components/Toast';
|
||||||
import { ErrorType } from '../../helpers/types'
|
import { ErrorType } from '../../helpers/types'
|
||||||
import { Pages } from '../../config/Pages'
|
import { Pages } from '../../config/Pages'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
@@ -57,7 +57,7 @@ const CreateBlog: FC = () => {
|
|||||||
}),
|
}),
|
||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
toast.error(t('errors.required'))
|
toast(t('errors.required'), 'error')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,11 +88,11 @@ const CreateBlog: FC = () => {
|
|||||||
createBlog.mutate(values, {
|
createBlog.mutate(values, {
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
setIsSaved(true)
|
setIsSaved(true)
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
navigate(Pages.blog.list)
|
navigate(Pages.blog.list)
|
||||||
},
|
},
|
||||||
onError(error: ErrorType) {
|
onError(error: ErrorType) {
|
||||||
toast.error(error.response?.data?.error.message[0])
|
toast(error.response?.data?.error.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import CheckBoxComponent from '../../components/CheckBoxComponent'
|
|||||||
import UploadBoxDraggble from '../../components/UploadBoxDraggble'
|
import UploadBoxDraggble from '../../components/UploadBoxDraggble'
|
||||||
import QuillEditor from '../../components/QuillEditor'
|
import QuillEditor from '../../components/QuillEditor'
|
||||||
import { useSingleUpload } from '../service/hooks/useServiceData'
|
import { useSingleUpload } from '../service/hooks/useServiceData'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../components/Toast';
|
||||||
import { ErrorType } from '../../helpers/types'
|
import { ErrorType } from '../../helpers/types'
|
||||||
import { Pages } from '../../config/Pages'
|
import { Pages } from '../../config/Pages'
|
||||||
import { useNavigate, useParams } from 'react-router-dom'
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
@@ -87,11 +87,11 @@ const UpdateBlog: FC = () => {
|
|||||||
updateBlog.mutate({ id: id || '', params: values }, {
|
updateBlog.mutate({ id: id || '', params: values }, {
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
setIsSaved(true)
|
setIsSaved(true)
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
navigate(Pages.blog.list)
|
navigate(Pages.blog.list)
|
||||||
},
|
},
|
||||||
onError(error: ErrorType) {
|
onError(error: ErrorType) {
|
||||||
toast.error(error.response?.data?.error.message[0])
|
toast(error.response?.data?.error.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import UploadBox from '../../../components/UploadBox'
|
|||||||
import { useSingleUpload } from '../../service/hooks/useServiceData'
|
import { useSingleUpload } from '../../service/hooks/useServiceData'
|
||||||
import SwitchComponent from '../../../components/Switch'
|
import SwitchComponent from '../../../components/Switch'
|
||||||
import Button from '../../../components/Button'
|
import Button from '../../../components/Button'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../components/Toast';
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { ErrorType } from '../../../helpers/types'
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string
|
id: string
|
||||||
@@ -42,12 +42,12 @@ const UpdateCategory: FC<Props> = ({ id }) => {
|
|||||||
}
|
}
|
||||||
updateBlogCategory.mutateAsync({ id: id, params: values }, {
|
updateBlogCategory.mutateAsync({ id: id, params: values }, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success('دسته بندی با موفقیت ویرایش شد')
|
toast('دسته بندی با موفقیت ویرایش شد', 'success')
|
||||||
setIsOpen(false)
|
setIsOpen(false)
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error.message[0])
|
toast(error.response?.data?.error.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { useCreateBank } from './hooks/useCardBankData'
|
|||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { Pages } from '../../config/Pages'
|
import { Pages } from '../../config/Pages'
|
||||||
import { ErrorType } from '../../helpers/types'
|
import { ErrorType } from '../../helpers/types'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../components/Toast';
|
||||||
|
|
||||||
const CreateBankCard: FC = () => {
|
const CreateBankCard: FC = () => {
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ const CreateBankCard: FC = () => {
|
|||||||
navigate(Pages.cardBank.list)
|
navigate(Pages.cardBank.list)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { useGetCardBankDetail, useUpdateBank } from './hooks/useCardBankData'
|
|||||||
import { useNavigate, useParams } from 'react-router-dom'
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
import { Pages } from '../../config/Pages'
|
import { Pages } from '../../config/Pages'
|
||||||
import { ErrorType } from '../../helpers/types'
|
import { ErrorType } from '../../helpers/types'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../components/Toast';
|
||||||
|
|
||||||
const EditBankCard: FC = () => {
|
const EditBankCard: FC = () => {
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ const EditBankCard: FC = () => {
|
|||||||
navigate(Pages.cardBank.list)
|
navigate(Pages.cardBank.list)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ import { Link } from 'react-router-dom'
|
|||||||
import { Pages } from '../../config/Pages'
|
import { Pages } from '../../config/Pages'
|
||||||
import Button from '../../components/Button'
|
import Button from '../../components/Button'
|
||||||
import { Add, Eye } from 'iconsax-react'
|
import { Add, Eye } from 'iconsax-react'
|
||||||
|
import { usePermissions } from '../../hooks/usePermissions'
|
||||||
|
|
||||||
const CardBankList: FC = () => {
|
const CardBankList: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
|
const { canCreate, canUpdate } = usePermissions()
|
||||||
const getCardBanks = useGetCardBanks()
|
const getCardBanks = useGetCardBanks()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -23,6 +25,7 @@ const CardBankList: FC = () => {
|
|||||||
{t('cardBank.manage_banks')}
|
{t('cardBank.manage_banks')}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
{canCreate('bank_accounts') &&
|
||||||
<Link to={Pages.cardBank.create}>
|
<Link to={Pages.cardBank.create}>
|
||||||
<Button
|
<Button
|
||||||
className='px-5'
|
className='px-5'
|
||||||
@@ -36,6 +39,7 @@ const CardBankList: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
@@ -63,9 +67,11 @@ const CardBankList: FC = () => {
|
|||||||
<Td text={item.cardNumber} />
|
<Td text={item.cardNumber} />
|
||||||
<Td text={item.IBan} />
|
<Td text={item.IBan} />
|
||||||
<Td text={''}>
|
<Td text={''}>
|
||||||
|
{canUpdate('bank_accounts') &&
|
||||||
<Link to={Pages.cardBank.detail + item.id}>
|
<Link to={Pages.cardBank.detail + item.id}>
|
||||||
<Eye size={20} color='#8C90A3' />
|
<Eye size={20} color='#8C90A3' />
|
||||||
</Link>
|
</Link>
|
||||||
|
}
|
||||||
</Td>
|
</Td>
|
||||||
</tr>
|
</tr>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ import { CriticismsItemTypes } from './types/CriticismsTypes'
|
|||||||
import moment from 'moment-jalaali'
|
import moment from 'moment-jalaali'
|
||||||
import ModalConfrim from '../../components/ModalConfrim'
|
import ModalConfrim from '../../components/ModalConfrim'
|
||||||
import Pagination from '../../components/Pagination'
|
import Pagination from '../../components/Pagination'
|
||||||
|
import { usePermissions } from '../../hooks/usePermissions'
|
||||||
|
|
||||||
const CriticismsList: FC = () => {
|
const CriticismsList: FC = () => {
|
||||||
|
|
||||||
|
const { canDelete } = usePermissions()
|
||||||
const [itemId, setItemId] = useState<string>('')
|
const [itemId, setItemId] = useState<string>('')
|
||||||
const [page, setPage] = useState<number>(1)
|
const [page, setPage] = useState<number>(1)
|
||||||
const [openConfrim, setOpenConfrim] = useState(false)
|
const [openConfrim, setOpenConfrim] = useState(false)
|
||||||
@@ -69,7 +71,9 @@ const CriticismsList: FC = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
</Td>
|
</Td>
|
||||||
<Td text=''>
|
<Td text=''>
|
||||||
|
{canDelete('criticisms') &&
|
||||||
<Trash onClick={() => handleDelete(item.id)} size={20} color='#8C90A3' className='cursor-pointer' />
|
<Trash onClick={() => handleDelete(item.id)} size={20} color='#8C90A3' className='cursor-pointer' />
|
||||||
|
}
|
||||||
</Td>
|
</Td>
|
||||||
</tr>
|
</tr>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { useFormik } from 'formik';
|
|||||||
import { CreateCustomerType, ProvinesItemType } from './types/CustomerTypes';
|
import { CreateCustomerType, ProvinesItemType } from './types/CustomerTypes';
|
||||||
import { useCreateCustomer, useGetCities, useGetProvines } from './hooks/useCustomerData';
|
import { useCreateCustomer, useGetCities, useGetProvines } from './hooks/useCustomerData';
|
||||||
import PageLoading from '../../components/PageLoading';
|
import PageLoading from '../../components/PageLoading';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from '../../components/Toast';
|
||||||
import { ErrorType } from '../../helpers/types';
|
import { ErrorType } from '../../helpers/types';
|
||||||
import { useSingleUpload } from '../service/hooks/useServiceData';
|
import { useSingleUpload } from '../service/hooks/useServiceData';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
@@ -32,11 +32,11 @@ const AddCustomer: FC = () => {
|
|||||||
const handleCreateCustomer = (values: CreateCustomerType) => {
|
const handleCreateCustomer = (values: CreateCustomerType) => {
|
||||||
createCustomer.mutate(values, {
|
createCustomer.mutate(values, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
navigate(Pages.customer.list)
|
navigate(Pages.customer.list)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error.message[0])
|
toast(error.response?.data?.error.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -88,7 +88,7 @@ const AddCustomer: FC = () => {
|
|||||||
values.profilePic = data?.data?.url
|
values.profilePic = data?.data?.url
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+80
-86
@@ -1,122 +1,114 @@
|
|||||||
import { FC, useState } from 'react'
|
import { Add, Eye } from "iconsax-react";
|
||||||
import { useTranslation } from 'react-i18next'
|
import { FC, useState } from "react";
|
||||||
import Button from '../../components/Button'
|
import { useTranslation } from "react-i18next";
|
||||||
import { Add, Eye } from 'iconsax-react'
|
import { Link } from "react-router-dom";
|
||||||
import Select from '../../components/Select'
|
import Button from "../../components/Button";
|
||||||
import Input from '../../components/Input'
|
import Input from "../../components/Input";
|
||||||
import Td from '../../components/Td'
|
import PageLoading from "../../components/PageLoading";
|
||||||
import { Link } from 'react-router-dom'
|
import Pagination from "../../components/Pagination";
|
||||||
import { Pages } from '../../config/Pages'
|
import Td from "../../components/Td";
|
||||||
import { useGetCustomers } from './hooks/useCustomerData'
|
import { Pages } from "../../config/Pages";
|
||||||
import PageLoading from '../../components/PageLoading'
|
import { usePermissions } from "../../hooks/usePermissions";
|
||||||
import { CustomerItemType } from './types/CustomerTypes'
|
import { useGetCustomers } from "./hooks/useCustomerData";
|
||||||
import Pagination from '../../components/Pagination'
|
import { CustomerItemType } from "./types/CustomerTypes";
|
||||||
|
|
||||||
const CustomerList: FC = () => {
|
const CustomerList: FC = () => {
|
||||||
|
const { t } = useTranslation("global");
|
||||||
|
const { canCreate, canUpdate } = usePermissions();
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [limit, setLimit] = useState(10);
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const getCustomers = useGetCustomers(page, limit, false, search);
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
const handleSearch = (value: string) => {
|
||||||
const [page, setPage] = useState(1)
|
if (value !== search) {
|
||||||
const getCustomers = useGetCustomers(page)
|
setPage(1);
|
||||||
|
}
|
||||||
|
setSearch(value);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-4'>
|
<div className="mt-4">
|
||||||
<div className='flex w-full justify-between items-center'>
|
<div className="flex w-full justify-between items-center">
|
||||||
<div>
|
<div>{t("customer.customer_list")}</div>
|
||||||
{t('customer.customer_list')}
|
{canCreate("customers") && (
|
||||||
</div>
|
|
||||||
<Link to={Pages.customer.create}>
|
<Link to={Pages.customer.create}>
|
||||||
<Button
|
<Button className="px-5">
|
||||||
className='px-5'
|
<div className="flex gap-2">
|
||||||
>
|
|
||||||
<div className='flex gap-2'>
|
|
||||||
<Add
|
<Add
|
||||||
className='size-5'
|
className="size-5"
|
||||||
color='#fff'
|
color="#fff"
|
||||||
/>
|
/>
|
||||||
<div>{t('customer.add_cutomer')}</div>
|
<div>{t("customer.add_cutomer")}</div>
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex justify-between items-center mt-12'>
|
<div className="flex justify-end items-center mt-12">
|
||||||
<div className='flex gap-4'>
|
|
||||||
<Select
|
|
||||||
className='w-36'
|
|
||||||
items={[
|
|
||||||
{ label: 'All', value: 'all' },
|
|
||||||
{ label: 'Active', value: 'active' },
|
|
||||||
{ label: 'Inactive', value: 'inactive' },
|
|
||||||
]}
|
|
||||||
placeholder={t('service.category')}
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
className='w-36'
|
|
||||||
items={[
|
|
||||||
{ label: 'All', value: 'all' },
|
|
||||||
{ label: 'Active', value: 'active' },
|
|
||||||
{ label: 'Inactive', value: 'inactive' },
|
|
||||||
]}
|
|
||||||
placeholder={t('service.status')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<Input
|
<Input
|
||||||
variant='search'
|
variant="search"
|
||||||
placeholder={t('service.search')}
|
placeholder={t("search")}
|
||||||
className='bg-white border border-border'
|
className="bg-white border border-border"
|
||||||
|
onChangeSearchFinal={handleSearch}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{getCustomers.isPending ? (
|
||||||
getCustomers.isPending ?
|
|
||||||
<PageLoading />
|
<PageLoading />
|
||||||
:
|
) : (
|
||||||
<div className='relative overflow-x-auto rounded-3xl mt-9 w-full'>
|
<div className="relative overflow-x-auto rounded-3xl mt-9 w-full">
|
||||||
<table className='w-full text-sm '>
|
<table className="w-full text-sm ">
|
||||||
<thead className='thead'>
|
<thead className="thead">
|
||||||
<tr>
|
<tr>
|
||||||
<Td text={t('customer.name')} />
|
<Td text={t("customer.name")} />
|
||||||
<Td text={t('customer.company')} />
|
<Td text={t("customer.company")} />
|
||||||
<Td text={t('customer.email')} />
|
<Td text={t("customer.email")} />
|
||||||
<Td text={t('customer.active_services')} />
|
<Td text={t("customer.active_services")} />
|
||||||
<Td text={t('customer.factor')} />
|
<Td text={t("customer.factor")} />
|
||||||
<Td text={t('customer.tickets')} />
|
<Td text={t("customer.tickets")} />
|
||||||
<Td text={t('customer.detail')} />
|
<Td text={t("customer.detail")} />
|
||||||
<Td text={''} />
|
<Td text={""} />
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{
|
{getCustomers.data?.data?.customers?.map((item: CustomerItemType) => {
|
||||||
getCustomers.data?.data?.customers?.map((item: CustomerItemType) => {
|
|
||||||
return (
|
return (
|
||||||
<tr key={item.id} className='tr'>
|
<tr
|
||||||
<Td text={item.firstName + ' ' + item.lastName} />
|
key={item.id}
|
||||||
|
className="tr"
|
||||||
|
>
|
||||||
|
<Td text={item.firstName + " " + item.lastName} />
|
||||||
<Td text={item?.legalUser?.registrationName} />
|
<Td text={item?.legalUser?.registrationName} />
|
||||||
<Td text={item.email ? item.email : ''} />
|
<Td text={item.email ? item.email : ""} />
|
||||||
<Td text={item.subscriptionsCount} />
|
<Td text={item.subscriptionsCount} />
|
||||||
<Td text={''}>
|
<Td text={""}>
|
||||||
<Link to={Pages.receipts.index + `?user=${item.id}`}>
|
<Link to={Pages.receipts.index + `?user=${item.id}`}>
|
||||||
<div className='text-[#0047FF]'>{item.invoicesCount} صورت حساب</div>
|
<div className="text-[#0047FF]">{item.invoicesCount} صورت حساب</div>
|
||||||
</Link>
|
</Link>
|
||||||
</Td>
|
</Td>
|
||||||
<Td text={''}>
|
<Td text={""}>
|
||||||
<Link to={Pages.ticket.list + `?user=${item.id}`}>
|
<Link to={Pages.ticket.list + `?user=${item.id}`}>
|
||||||
<div className='text-[#0047FF]'>{item.ticketsCount} تیکت</div>
|
<div className="text-[#0047FF]">{item.ticketsCount} تیکت</div>
|
||||||
</Link>
|
</Link>
|
||||||
</Td>
|
</Td>
|
||||||
<Td text={''}>
|
<Td text={""}>
|
||||||
|
{canUpdate("customers") && (
|
||||||
<Link to={Pages.customer.update + item.id}>
|
<Link to={Pages.customer.update + item.id}>
|
||||||
<Eye size={20} color='#8C90A3' />
|
<Eye
|
||||||
|
size={20}
|
||||||
|
color="#8C90A3"
|
||||||
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
|
)}
|
||||||
</Td>
|
</Td>
|
||||||
<Td text={''} />
|
<Td text={""} />
|
||||||
</tr>
|
</tr>
|
||||||
)
|
);
|
||||||
})
|
})}
|
||||||
}
|
|
||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@@ -124,11 +116,13 @@ const CustomerList: FC = () => {
|
|||||||
totalPages={getCustomers.data?.data?.pager?.totalPages}
|
totalPages={getCustomers.data?.data?.pager?.totalPages}
|
||||||
currentPage={page}
|
currentPage={page}
|
||||||
onPageChange={setPage}
|
onPageChange={setPage}
|
||||||
|
limit={limit}
|
||||||
|
onLimitChange={setLimit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default CustomerList
|
export default CustomerList;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { useFormik } from 'formik';
|
|||||||
import { CreateCustomerType, ProvinesItemType } from './types/CustomerTypes';
|
import { CreateCustomerType, ProvinesItemType } from './types/CustomerTypes';
|
||||||
import { useGetCities, useGetCustomerDetail, useGetProvines, useUpdateCustomer } from './hooks/useCustomerData';
|
import { useGetCities, useGetCustomerDetail, useGetProvines, useUpdateCustomer } from './hooks/useCustomerData';
|
||||||
import PageLoading from '../../components/PageLoading';
|
import PageLoading from '../../components/PageLoading';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from '../../components/Toast';
|
||||||
import { ErrorType } from '../../helpers/types';
|
import { ErrorType } from '../../helpers/types';
|
||||||
import { useSingleUpload } from '../service/hooks/useServiceData';
|
import { useSingleUpload } from '../service/hooks/useServiceData';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
@@ -34,11 +34,11 @@ const UpdateCustomer: FC = () => {
|
|||||||
const handleUpdateCustomer = (values: CreateCustomerType) => {
|
const handleUpdateCustomer = (values: CreateCustomerType) => {
|
||||||
updateCustomer.mutate(values, {
|
updateCustomer.mutate(values, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
navigate(Pages.customer.list)
|
navigate(Pages.customer.list)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error.message[0])
|
toast(error.response?.data?.error.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -62,40 +62,33 @@ const UpdateCustomer: FC = () => {
|
|||||||
profilePic: ''
|
profilePic: ''
|
||||||
},
|
},
|
||||||
validationSchema: Yup.object({
|
validationSchema: Yup.object({
|
||||||
phone: Yup.string().required(t('errors.required')),
|
email: Yup.string().test(
|
||||||
firstName: Yup.string().required(t('errors.required')),
|
'email',
|
||||||
lastName: Yup.string().required(t('errors.required')),
|
t('errors.email'),
|
||||||
email: Yup.string().email(t('errors.email')).required(t('errors.required')),
|
(value) => !value || Yup.string().email().isValidSync(value)
|
||||||
birthDate: Yup.string().required(t('errors.required')),
|
),
|
||||||
nationalCode: Yup.string().required(t('errors.required')),
|
|
||||||
economicCode: Yup.string().required(t('errors.required')),
|
|
||||||
registrationName: Yup.string().required(t('errors.required')),
|
|
||||||
registrationCode: Yup.string().required(t('errors.required')),
|
|
||||||
nationalIdentity: Yup.string().required(t('errors.required')),
|
|
||||||
postalCode: Yup.string().required(t('errors.required')),
|
|
||||||
address: Yup.string().required(t('errors.required')),
|
|
||||||
cityId: Yup.string().required(t('errors.required')),
|
|
||||||
}),
|
}),
|
||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
values.cityId = +values.cityId
|
const payload: Partial<CreateCustomerType> = Object.fromEntries(
|
||||||
if (values.password === '') values.password = undefined
|
Object.entries(values).filter(([, value]) => value !== '' && value !== undefined && value !== null)
|
||||||
|
) as Partial<CreateCustomerType>
|
||||||
|
|
||||||
|
if (payload.cityId !== undefined) payload.cityId = +payload.cityId
|
||||||
|
|
||||||
if (file) {
|
if (file) {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', file)
|
formData.append('file', file)
|
||||||
await singleUpload.mutateAsync(formData, {
|
await singleUpload.mutateAsync(formData, {
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
values.profilePic = data?.data?.url
|
payload.profilePic = data?.data?.url
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
handleUpdateCustomer(values)
|
|
||||||
} else {
|
|
||||||
values.profilePic = undefined
|
|
||||||
handleUpdateCustomer(values)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleUpdateCustomer(payload as CreateCustomerType)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import * as api from "../service/CustomerService";
|
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import * as api from "../service/CustomerService";
|
||||||
import { CreateCustomerType } from "../types/CustomerTypes";
|
import { CreateCustomerType } from "../types/CustomerTypes";
|
||||||
|
|
||||||
export const useGetCustomers = (page: number, withoutPaginate?: boolean) => {
|
export const useGetCustomers = (page: number, limit: number, withoutPaginate?: boolean, search?: string) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["customers", page],
|
queryKey: ["customers", page, limit, withoutPaginate, search],
|
||||||
queryFn: () => api.getCustomers(page, withoutPaginate),
|
queryFn: () => api.getCustomers(page, limit, withoutPaginate, search),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -27,8 +27,7 @@ export const useGetCities = (provincesId: string) => {
|
|||||||
export const useCreateCustomer = () => {
|
export const useCreateCustomer = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (variables: CreateCustomerType) =>
|
mutationFn: (variables: CreateCustomerType) => api.createCustomer(variables),
|
||||||
api.createCustomer(variables),
|
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: ["customers"],
|
queryKey: ["customers"],
|
||||||
@@ -48,8 +47,7 @@ export const useGetCustomerDetail = (id?: string) => {
|
|||||||
export const useUpdateCustomer = (id: string) => {
|
export const useUpdateCustomer = (id: string) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (variables: CreateCustomerType) =>
|
mutationFn: (variables: CreateCustomerType) => api.updateCustomer(id, variables),
|
||||||
api.updateCustomer(id, variables),
|
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: ["customers"],
|
queryKey: ["customers"],
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import axios from "../../../config/axios";
|
import axios from "../../../config/axios";
|
||||||
import { CreateCustomerType } from "../types/CustomerTypes";
|
import { CreateCustomerType } from "../types/CustomerTypes";
|
||||||
|
|
||||||
export const getCustomers = async (page: number, withoutPaginate?: boolean) => {
|
export const getCustomers = async (page: number, limit: number, withoutPaginate?: boolean, search?: string) => {
|
||||||
const { data } = await axios.get(
|
const { data } = await axios.get(`/users/customers?page=${page}&limit=${limit}&paginate=${withoutPaginate ? "0" : "1"}&q=${encodeURIComponent(search ?? "")}`);
|
||||||
`/users/customers?page=${page}&paginate=${withoutPaginate ? "0" : "1"}`
|
|
||||||
);
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -28,10 +26,7 @@ export const getDetailCustomer = async (id: string) => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateCustomer = async (
|
export const updateCustomer = async (id: string, params: CreateCustomerType) => {
|
||||||
id: string,
|
|
||||||
params: CreateCustomerType
|
|
||||||
) => {
|
|
||||||
const { data } = await axios.patch(`/users/customers/${id}`, params);
|
const { data } = await axios.patch(`/users/customers/${id}`, params);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import * as Yup from 'yup'
|
|||||||
import { useGetAllServices } from '../service/hooks/useServiceData'
|
import { useGetAllServices } from '../service/hooks/useServiceData'
|
||||||
import { ServiceItemType } from '../service/types/ServiceTypes'
|
import { ServiceItemType } from '../service/types/ServiceTypes'
|
||||||
import CheckBoxComponent from '../../components/CheckBoxComponent'
|
import CheckBoxComponent from '../../components/CheckBoxComponent'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../components/Toast';
|
||||||
import { useCreateDiscount } from './hooks/useDiscountData'
|
import { useCreateDiscount } from './hooks/useDiscountData'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { Pages } from '../../config/Pages'
|
import { Pages } from '../../config/Pages'
|
||||||
@@ -64,11 +64,11 @@ const CreateDiscount: FC = () => {
|
|||||||
}
|
}
|
||||||
createDiscount.mutate(params, {
|
createDiscount.mutate(params, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
navigate(Pages.discount.list)
|
navigate(Pages.discount.list)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,12 +12,14 @@ import moment from 'moment-jalaali'
|
|||||||
import ToggleStatusDiscount from './components/ToggleStatusDiscount'
|
import ToggleStatusDiscount from './components/ToggleStatusDiscount'
|
||||||
import PageLoading from '../../components/PageLoading'
|
import PageLoading from '../../components/PageLoading'
|
||||||
import { NumberFormat } from '../../config/func'
|
import { NumberFormat } from '../../config/func'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../components/Toast';
|
||||||
import { ErrorType } from '../../helpers/types'
|
import { ErrorType } from '../../helpers/types'
|
||||||
|
import { usePermissions } from '../../hooks/usePermissions'
|
||||||
|
|
||||||
const DiscountList: FC = () => {
|
const DiscountList: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
|
const { canCreate, canUpdate, canDelete } = usePermissions()
|
||||||
const [search, setSearch] = useState<string>('')
|
const [search, setSearch] = useState<string>('')
|
||||||
const getDiscounts = useGetDiscounts(search)
|
const getDiscounts = useGetDiscounts(search)
|
||||||
const deleteDiscount = useDeleteDiscount()
|
const deleteDiscount = useDeleteDiscount()
|
||||||
@@ -26,10 +28,10 @@ const DiscountList: FC = () => {
|
|||||||
deleteDiscount.mutate(id, {
|
deleteDiscount.mutate(id, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
getDiscounts.refetch()
|
getDiscounts.refetch()
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error.message[0])
|
toast(error.response?.data?.error.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -40,6 +42,7 @@ const DiscountList: FC = () => {
|
|||||||
{t('discount.list')}
|
{t('discount.list')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{canCreate('discounts') &&
|
||||||
<Link to={Pages.discount.create}>
|
<Link to={Pages.discount.create}>
|
||||||
<Button
|
<Button
|
||||||
className='w-[172px]'
|
className='w-[172px]'
|
||||||
@@ -52,6 +55,7 @@ const DiscountList: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -114,17 +118,21 @@ const DiscountList: FC = () => {
|
|||||||
</Td>
|
</Td>
|
||||||
<Td text={t('')}>
|
<Td text={t('')}>
|
||||||
<div className='flex gap-2 items-center'>
|
<div className='flex gap-2 items-center'>
|
||||||
|
{canUpdate('discounts') &&
|
||||||
<Link
|
<Link
|
||||||
to={Pages.discount.detail + item.id}
|
to={Pages.discount.detail + item.id}
|
||||||
>
|
>
|
||||||
<Edit size={20} color='#888888' />
|
<Edit size={20} color='#888888' />
|
||||||
</Link>
|
</Link>
|
||||||
|
}
|
||||||
|
|
||||||
|
{canDelete('discounts') &&
|
||||||
<Trash
|
<Trash
|
||||||
size={20}
|
size={20}
|
||||||
color='#888888'
|
color='#888888'
|
||||||
onClick={() => handleDelete(item.id)}
|
onClick={() => handleDelete(item.id)}
|
||||||
/>
|
/>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</Td>
|
</Td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import * as Yup from 'yup'
|
|||||||
import { useGetAllServices } from '../service/hooks/useServiceData'
|
import { useGetAllServices } from '../service/hooks/useServiceData'
|
||||||
import { ServiceItemType } from '../service/types/ServiceTypes'
|
import { ServiceItemType } from '../service/types/ServiceTypes'
|
||||||
import CheckBoxComponent from '../../components/CheckBoxComponent'
|
import CheckBoxComponent from '../../components/CheckBoxComponent'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../components/Toast';
|
||||||
import { useGetDiscountDetail, useUpdateDiscount } from './hooks/useDiscountData'
|
import { useGetDiscountDetail, useUpdateDiscount } from './hooks/useDiscountData'
|
||||||
import { useNavigate, useParams } from 'react-router-dom'
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
import { Pages } from '../../config/Pages'
|
import { Pages } from '../../config/Pages'
|
||||||
@@ -66,11 +66,11 @@ const UpdateDiscount: FC = () => {
|
|||||||
}
|
}
|
||||||
updateDiscount.mutate({ id: id || '', params }, {
|
updateDiscount.mutate({ id: id || '', params }, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
navigate(Pages.discount.list)
|
navigate(Pages.discount.list)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import Td from '../../../components/Td'
|
|||||||
import Input from '../../../components/Input'
|
import Input from '../../../components/Input'
|
||||||
import PageLoading from '../../../components/PageLoading'
|
import PageLoading from '../../../components/PageLoading'
|
||||||
import TrashWithConfrim from '../../../components/TrashWithConfrim'
|
import TrashWithConfrim from '../../../components/TrashWithConfrim'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../components/Toast';
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { ErrorType } from '../../../helpers/types'
|
||||||
import { GroupIconType } from '../types/Types'
|
import { GroupIconType } from '../types/Types'
|
||||||
|
|
||||||
@@ -29,10 +29,10 @@ const GroupIconList: FC = () => {
|
|||||||
deleteGroupIcon.mutate(id, {
|
deleteGroupIcon.mutate(id, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
refetch()
|
refetch()
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import Td from '../../../components/Td'
|
|||||||
import Input from '../../../components/Input'
|
import Input from '../../../components/Input'
|
||||||
import PageLoading from '../../../components/PageLoading'
|
import PageLoading from '../../../components/PageLoading'
|
||||||
import TrashWithConfrim from '../../../components/TrashWithConfrim'
|
import TrashWithConfrim from '../../../components/TrashWithConfrim'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../components/Toast';
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { ErrorType } from '../../../helpers/types'
|
||||||
import { IconType } from '../types/Types'
|
import { IconType } from '../types/Types'
|
||||||
import moment from 'moment-jalaali'
|
import moment from 'moment-jalaali'
|
||||||
@@ -30,10 +30,10 @@ const IconsList: FC = () => {
|
|||||||
deleteIcon.mutate(id, {
|
deleteIcon.mutate(id, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
refetch()
|
refetch()
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import DefaulModal from '../../../../components/DefaulModal'
|
|||||||
import Input from '../../../../components/Input'
|
import Input from '../../../../components/Input'
|
||||||
import { useFormik } from 'formik'
|
import { useFormik } from 'formik'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../../components/Toast';
|
||||||
import { TickCircle } from 'iconsax-react'
|
import { TickCircle } from 'iconsax-react'
|
||||||
import { useCreateGroupIcon } from '../../hooks/useIconData'
|
import { useCreateGroupIcon } from '../../hooks/useIconData'
|
||||||
import { CreateGroupIconType } from '../../types/Types'
|
import { CreateGroupIconType } from '../../types/Types'
|
||||||
@@ -34,11 +34,11 @@ const CreateGroupIcon: FC<CreateGroupIconProps> = ({ open, close, onSuccess }) =
|
|||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
formik.resetForm()
|
formik.resetForm()
|
||||||
close()
|
close()
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
onSuccess?.()
|
onSuccess?.()
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import UploadBox from '../../../../components/UploadBox'
|
|||||||
import Select from '../../../../components/Select'
|
import Select from '../../../../components/Select'
|
||||||
import { useFormik } from 'formik'
|
import { useFormik } from 'formik'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../../components/Toast';
|
||||||
import { TickCircle } from 'iconsax-react'
|
import { TickCircle } from 'iconsax-react'
|
||||||
import { useCreateIcon, useGetGroupIcons } from '../../hooks/useIconData'
|
import { useCreateIcon, useGetGroupIcons } from '../../hooks/useIconData'
|
||||||
import { useMultiUpload } from '../../../service/hooks/useServiceData'
|
import { useMultiUpload } from '../../../service/hooks/useServiceData'
|
||||||
@@ -44,7 +44,7 @@ const CreateIcon: FC<CreateIconProps> = ({ open, close, onSuccess }) => {
|
|||||||
}),
|
}),
|
||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
toast.error(t('errors.required'))
|
toast(t('errors.required'), 'error')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,11 +67,11 @@ const CreateIcon: FC<CreateIconProps> = ({ open, close, onSuccess }) => {
|
|||||||
formik.resetForm()
|
formik.resetForm()
|
||||||
setFiles([])
|
setFiles([])
|
||||||
close()
|
close()
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
onSuccess?.()
|
onSuccess?.()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error as ErrorType
|
const err = error as ErrorType
|
||||||
toast.error(err.response?.data?.error?.message?.[0])
|
toast(err.response?.data?.error?.message?.[0], 'error')
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false)
|
setIsSubmitting(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import Td from '../../../components/Td'
|
|||||||
import { ShopType, ShopsResponse } from '../types/Types'
|
import { ShopType, ShopsResponse } from '../types/Types'
|
||||||
import moment from 'moment-jalaali'
|
import moment from 'moment-jalaali'
|
||||||
import { Edit } from 'iconsax-react'
|
import { Edit } from 'iconsax-react'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../components/Toast';
|
||||||
import Button from '../../../components/Button'
|
import Button from '../../../components/Button'
|
||||||
import ShopAdminsModal from './components/ShopAdminsModal'
|
import ShopAdminsModal from './components/ShopAdminsModal'
|
||||||
import Pagination from '../../../components/Pagination'
|
import Pagination from '../../../components/Pagination'
|
||||||
@@ -56,11 +56,11 @@ const ShopsList: FC = () => {
|
|||||||
const handleDeleteShop = (shopId: string) => {
|
const handleDeleteShop = (shopId: string) => {
|
||||||
deleteShop.mutate(shopId, {
|
deleteShop.mutate(shopId, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success('فروشگاه با موفقیت حذف شد')
|
toast('فروشگاه با موفقیت حذف شد', 'success')
|
||||||
queryClient.invalidateQueries({ queryKey: ['shops'] })
|
queryClient.invalidateQueries({ queryKey: ['shops'] })
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast.error('خطا در حذف فروشگاه')
|
toast('خطا در حذف فروشگاه', 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import SwitchComponent from '../../../components/Switch'
|
|||||||
import Button from '../../../components/Button'
|
import Button from '../../../components/Button'
|
||||||
import DatePickerComponent from '../../../components/DatePicker'
|
import DatePickerComponent from '../../../components/DatePicker'
|
||||||
import PageLoading from '../../../components/PageLoading'
|
import PageLoading from '../../../components/PageLoading'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../components/Toast';
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { ErrorType } from '../../../helpers/types'
|
||||||
import { TickCircle } from 'iconsax-react'
|
import { TickCircle } from 'iconsax-react'
|
||||||
import { Pages } from '../../../config/Pages'
|
import { Pages } from '../../../config/Pages'
|
||||||
@@ -57,13 +57,13 @@ const UpdateShop: FC = () => {
|
|||||||
{ id, params },
|
{ id, params },
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
queryClient.invalidateQueries({ queryKey: ['shops'] })
|
queryClient.invalidateQueries({ queryKey: ['shops'] })
|
||||||
queryClient.invalidateQueries({ queryKey: ['shop', id] })
|
queryClient.invalidateQueries({ queryKey: ['shop', id] })
|
||||||
navigate(Pages.dkala.shops.list)
|
navigate(Pages.dkala.shops.list)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message?.[0] || 'خطا در بروزرسانی فروشگاه')
|
toast(error.response?.data?.error?.message?.[0] || 'خطا در بروزرسانی فروشگاه', 'error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import Button from '../../../../components/Button'
|
|||||||
import { useAddAdminShop, useGetSystemRoles } from '../../hooks/useIconData'
|
import { useAddAdminShop, useGetSystemRoles } from '../../hooks/useIconData'
|
||||||
import { SystemRoleType } from '../../types/Types'
|
import { SystemRoleType } from '../../types/Types'
|
||||||
import { ErrorType } from '../../../../helpers/types'
|
import { ErrorType } from '../../../../helpers/types'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../../components/Toast';
|
||||||
import { TickCircle } from 'iconsax-react'
|
import { TickCircle } from 'iconsax-react'
|
||||||
|
|
||||||
interface AddShopAdminFormProps {
|
interface AddShopAdminFormProps {
|
||||||
@@ -46,13 +46,13 @@ const AddShopAdminForm: FC<AddShopAdminFormProps> = ({
|
|||||||
onSubmit: (values) => {
|
onSubmit: (values) => {
|
||||||
addAdmin(values, {
|
addAdmin(values, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
formik.resetForm()
|
formik.resetForm()
|
||||||
onSuccess()
|
onSuccess()
|
||||||
onClose()
|
onClose()
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import Td from '../../../../components/Td'
|
|||||||
import { ShopAdminType } from '../../types/Types'
|
import { ShopAdminType } from '../../types/Types'
|
||||||
import moment from 'moment-jalaali'
|
import moment from 'moment-jalaali'
|
||||||
import TrashWithConfrim from '../../../../components/TrashWithConfrim'
|
import TrashWithConfrim from '../../../../components/TrashWithConfrim'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../../components/Toast';
|
||||||
import { ErrorType } from '../../../../helpers/types'
|
import { ErrorType } from '../../../../helpers/types'
|
||||||
import Button from '../../../../components/Button'
|
import Button from '../../../../components/Button'
|
||||||
import AddShopAdminForm from './AddShopAdminForm'
|
import AddShopAdminForm from './AddShopAdminForm'
|
||||||
@@ -31,10 +31,10 @@ const ShopAdminsModal: FC<ShopAdminsModalProps> = ({ open, close, shopId }) => {
|
|||||||
deleteAdmin({ shopId, adminId }, {
|
deleteAdmin({ shopId, adminId }, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
refetch()
|
refetch()
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import Td from '../../../components/Td'
|
|||||||
import Input from '../../../components/Input'
|
import Input from '../../../components/Input'
|
||||||
import PageLoading from '../../../components/PageLoading'
|
import PageLoading from '../../../components/PageLoading'
|
||||||
import TrashWithConfrim from '../../../components/TrashWithConfrim'
|
import TrashWithConfrim from '../../../components/TrashWithConfrim'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../components/Toast';
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { ErrorType } from '../../../helpers/types'
|
||||||
import { GroupIconType } from '../types/Types'
|
import { GroupIconType } from '../types/Types'
|
||||||
|
|
||||||
@@ -29,10 +29,10 @@ const GroupIconList: FC = () => {
|
|||||||
deleteGroupIcon.mutate(id, {
|
deleteGroupIcon.mutate(id, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
refetch()
|
refetch()
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import Td from '../../../components/Td'
|
|||||||
import Input from '../../../components/Input'
|
import Input from '../../../components/Input'
|
||||||
import PageLoading from '../../../components/PageLoading'
|
import PageLoading from '../../../components/PageLoading'
|
||||||
import TrashWithConfrim from '../../../components/TrashWithConfrim'
|
import TrashWithConfrim from '../../../components/TrashWithConfrim'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../components/Toast';
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { ErrorType } from '../../../helpers/types'
|
||||||
import { IconType } from '../types/Types'
|
import { IconType } from '../types/Types'
|
||||||
import moment from 'moment-jalaali'
|
import moment from 'moment-jalaali'
|
||||||
@@ -30,10 +30,10 @@ const IconsList: FC = () => {
|
|||||||
deleteIcon.mutate(id, {
|
deleteIcon.mutate(id, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
refetch()
|
refetch()
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import DefaulModal from '../../../../components/DefaulModal'
|
|||||||
import Input from '../../../../components/Input'
|
import Input from '../../../../components/Input'
|
||||||
import { useFormik } from 'formik'
|
import { useFormik } from 'formik'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../../components/Toast';
|
||||||
import { TickCircle } from 'iconsax-react'
|
import { TickCircle } from 'iconsax-react'
|
||||||
import { useCreateGroupIcon } from '../../hooks/useIconData'
|
import { useCreateGroupIcon } from '../../hooks/useIconData'
|
||||||
import { CreateGroupIconType } from '../../types/Types'
|
import { CreateGroupIconType } from '../../types/Types'
|
||||||
@@ -34,11 +34,11 @@ const CreateGroupIcon: FC<CreateGroupIconProps> = ({ open, close, onSuccess }) =
|
|||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
formik.resetForm()
|
formik.resetForm()
|
||||||
close()
|
close()
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
onSuccess?.()
|
onSuccess?.()
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import UploadBox from '../../../../components/UploadBox'
|
|||||||
import Select from '../../../../components/Select'
|
import Select from '../../../../components/Select'
|
||||||
import { useFormik } from 'formik'
|
import { useFormik } from 'formik'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../../components/Toast';
|
||||||
import { TickCircle } from 'iconsax-react'
|
import { TickCircle } from 'iconsax-react'
|
||||||
import { useCreateIcon, useGetGroupIcons } from '../../hooks/useIconData'
|
import { useCreateIcon, useGetGroupIcons } from '../../hooks/useIconData'
|
||||||
import { useMultiUpload } from '../../../service/hooks/useServiceData'
|
import { useMultiUpload } from '../../../service/hooks/useServiceData'
|
||||||
@@ -44,7 +44,7 @@ const CreateIcon: FC<CreateIconProps> = ({ open, close, onSuccess }) => {
|
|||||||
}),
|
}),
|
||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
toast.error(t('errors.required'))
|
toast(t('errors.required'), 'error')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,11 +67,11 @@ const CreateIcon: FC<CreateIconProps> = ({ open, close, onSuccess }) => {
|
|||||||
formik.resetForm()
|
formik.resetForm()
|
||||||
setFiles([])
|
setFiles([])
|
||||||
close()
|
close()
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
onSuccess?.()
|
onSuccess?.()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error as ErrorType
|
const err = error as ErrorType
|
||||||
toast.error(err.response?.data?.error?.message?.[0])
|
toast(err.response?.data?.error?.message?.[0], 'error')
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false)
|
setIsSubmitting(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import Td from '../../../components/Td'
|
|||||||
import { RestaurantType, RestaurantsResponse } from '../types/Types'
|
import { RestaurantType, RestaurantsResponse } from '../types/Types'
|
||||||
import moment from 'moment-jalaali'
|
import moment from 'moment-jalaali'
|
||||||
import { Edit } from 'iconsax-react'
|
import { Edit } from 'iconsax-react'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../components/Toast';
|
||||||
import Button from '../../../components/Button'
|
import Button from '../../../components/Button'
|
||||||
import RestaurantAdminsModal from './components/RestaurantAdminsModal'
|
import RestaurantAdminsModal from './components/RestaurantAdminsModal'
|
||||||
import Pagination from '../../../components/Pagination'
|
import Pagination from '../../../components/Pagination'
|
||||||
@@ -56,11 +56,11 @@ const RestaurantsList: FC = () => {
|
|||||||
const handleDeleteRestaurant = (restaurantId: string) => {
|
const handleDeleteRestaurant = (restaurantId: string) => {
|
||||||
deleteRestaurant.mutate(restaurantId, {
|
deleteRestaurant.mutate(restaurantId, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success('رستوران با موفقیت حذف شد')
|
toast('رستوران با موفقیت حذف شد', 'success')
|
||||||
queryClient.invalidateQueries({ queryKey: ['restaurants'] })
|
queryClient.invalidateQueries({ queryKey: ['restaurants'] })
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast.error('خطا در حذف رستوران')
|
toast('خطا در حذف رستوران', 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import SwitchComponent from '../../../components/Switch'
|
|||||||
import Button from '../../../components/Button'
|
import Button from '../../../components/Button'
|
||||||
import DatePickerComponent from '../../../components/DatePicker'
|
import DatePickerComponent from '../../../components/DatePicker'
|
||||||
import PageLoading from '../../../components/PageLoading'
|
import PageLoading from '../../../components/PageLoading'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../components/Toast';
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { ErrorType } from '../../../helpers/types'
|
||||||
import { TickCircle } from 'iconsax-react'
|
import { TickCircle } from 'iconsax-react'
|
||||||
import { Pages } from '../../../config/Pages'
|
import { Pages } from '../../../config/Pages'
|
||||||
@@ -57,13 +57,13 @@ const UpdateRestaurant: FC = () => {
|
|||||||
{ id, params },
|
{ id, params },
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
queryClient.invalidateQueries({ queryKey: ['restaurants'] })
|
queryClient.invalidateQueries({ queryKey: ['restaurants'] })
|
||||||
queryClient.invalidateQueries({ queryKey: ['restaurant', id] })
|
queryClient.invalidateQueries({ queryKey: ['restaurant', id] })
|
||||||
navigate(Pages.dmenu.restaurants.list)
|
navigate(Pages.dmenu.restaurants.list)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message?.[0] || 'خطا در بروزرسانی رستوران')
|
toast(error.response?.data?.error?.message?.[0] || 'خطا در بروزرسانی رستوران', 'error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import Button from '../../../../components/Button'
|
|||||||
import { useAddAdminRestaurant, useGetSystemRoles } from '../../hooks/useIconData'
|
import { useAddAdminRestaurant, useGetSystemRoles } from '../../hooks/useIconData'
|
||||||
import { SystemRoleType } from '../../types/Types'
|
import { SystemRoleType } from '../../types/Types'
|
||||||
import { ErrorType } from '../../../../helpers/types'
|
import { ErrorType } from '../../../../helpers/types'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../../components/Toast';
|
||||||
import { TickCircle } from 'iconsax-react'
|
import { TickCircle } from 'iconsax-react'
|
||||||
|
|
||||||
interface AddRestaurantAdminFormProps {
|
interface AddRestaurantAdminFormProps {
|
||||||
@@ -46,13 +46,13 @@ const AddRestaurantAdminForm: FC<AddRestaurantAdminFormProps> = ({
|
|||||||
onSubmit: (values) => {
|
onSubmit: (values) => {
|
||||||
addAdmin(values, {
|
addAdmin(values, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
formik.resetForm()
|
formik.resetForm()
|
||||||
onSuccess()
|
onSuccess()
|
||||||
onClose()
|
onClose()
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import Td from '../../../../components/Td'
|
|||||||
import { RestaurantAdminType } from '../../types/Types'
|
import { RestaurantAdminType } from '../../types/Types'
|
||||||
import moment from 'moment-jalaali'
|
import moment from 'moment-jalaali'
|
||||||
import TrashWithConfrim from '../../../../components/TrashWithConfrim'
|
import TrashWithConfrim from '../../../../components/TrashWithConfrim'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../../components/Toast';
|
||||||
import { ErrorType } from '../../../../helpers/types'
|
import { ErrorType } from '../../../../helpers/types'
|
||||||
import Button from '../../../../components/Button'
|
import Button from '../../../../components/Button'
|
||||||
import AddRestaurantAdminForm from './AddRestaurantAdminForm'
|
import AddRestaurantAdminForm from './AddRestaurantAdminForm'
|
||||||
@@ -31,10 +31,10 @@ const RestaurantAdminsModal: FC<RestaurantAdminsModalProps> = ({ open, close, re
|
|||||||
deleteAdmin({ restaurantId, adminId }, {
|
deleteAdmin({ restaurantId, adminId }, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
refetch()
|
refetch()
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import Td from '../../../components/Td'
|
|||||||
import Input from '../../../components/Input'
|
import Input from '../../../components/Input'
|
||||||
import PageLoading from '../../../components/PageLoading'
|
import PageLoading from '../../../components/PageLoading'
|
||||||
import TrashWithConfrim from '../../../components/TrashWithConfrim'
|
import TrashWithConfrim from '../../../components/TrashWithConfrim'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../components/Toast';
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { ErrorType } from '../../../helpers/types'
|
||||||
import { StickerType } from '../types/Types'
|
import { StickerType } from '../types/Types'
|
||||||
import moment from 'moment-jalaali'
|
import moment from 'moment-jalaali'
|
||||||
@@ -30,10 +30,10 @@ const StickersList: FC = () => {
|
|||||||
deleteSticker.mutate(id, {
|
deleteSticker.mutate(id, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
refetch()
|
refetch()
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import UploadBox from '../../../../components/UploadBox'
|
|||||||
import Select from '../../../../components/Select'
|
import Select from '../../../../components/Select'
|
||||||
import { useFormik } from 'formik'
|
import { useFormik } from 'formik'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../../components/Toast';
|
||||||
import { TickCircle } from 'iconsax-react'
|
import { TickCircle } from 'iconsax-react'
|
||||||
import { useCreateSticker, useGetGroupStickers } from '../../hooks/useStickerData'
|
import { useCreateSticker, useGetGroupStickers } from '../../hooks/useStickerData'
|
||||||
import { useSingleUpload } from '../../../service/hooks/useServiceData'
|
import { useSingleUpload } from '../../../service/hooks/useServiceData'
|
||||||
@@ -38,11 +38,11 @@ const CreateSticker: FC<CreateStickerProps> = ({ open, close, onSuccess }) => {
|
|||||||
formik.resetForm()
|
formik.resetForm()
|
||||||
setFile(undefined)
|
setFile(undefined)
|
||||||
close()
|
close()
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
onSuccess?.()
|
onSuccess?.()
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,7 @@ const CreateSticker: FC<CreateStickerProps> = ({ open, close, onSuccess }) => {
|
|||||||
}),
|
}),
|
||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
toast.error(t('errors.required'))
|
toast(t('errors.required'), 'error')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
@@ -69,7 +69,7 @@ const CreateSticker: FC<CreateStickerProps> = ({ open, close, onSuccess }) => {
|
|||||||
handleCreateSticker(values)
|
handleCreateSticker(values)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import Textarea from '../../components/Textarea'
|
|||||||
import Switch from '../../components/Switch'
|
import Switch from '../../components/Switch'
|
||||||
import { TickCircle } from 'iconsax-react'
|
import { TickCircle } from 'iconsax-react'
|
||||||
import { useCreateGuide } from './hooks/useGuideData'
|
import { useCreateGuide } from './hooks/useGuideData'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../components/Toast';
|
||||||
import { ErrorType } from '../../helpers/types'
|
import { ErrorType } from '../../helpers/types'
|
||||||
import { Pages } from '../../config/Pages'
|
import { Pages } from '../../config/Pages'
|
||||||
|
|
||||||
@@ -50,11 +50,11 @@ const Create: FC = () => {
|
|||||||
serviceId: id || '',
|
serviceId: id || '',
|
||||||
}, {
|
}, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
navigate(Pages.services.guide + id)
|
navigate(Pages.services.guide + id)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message?.[0] || t('error'))
|
toast(error.response?.data?.error?.message?.[0] || t('error'))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import Td from '../../components/Td'
|
|||||||
import StatusWithText from '../../components/StatusWithText'
|
import StatusWithText from '../../components/StatusWithText'
|
||||||
import TrashWithConfrim from '../../components/TrashWithConfrim'
|
import TrashWithConfrim from '../../components/TrashWithConfrim'
|
||||||
import { useDeleteGuide } from './hooks/useGuideData'
|
import { useDeleteGuide } from './hooks/useGuideData'
|
||||||
|
import { usePermissions } from '../../hooks/usePermissions'
|
||||||
|
|
||||||
type GuideItem = {
|
type GuideItem = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -24,6 +25,7 @@ type GuideItem = {
|
|||||||
|
|
||||||
const List: FC = () => {
|
const List: FC = () => {
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
|
const { canCreate, canUpdate, canDelete } = usePermissions()
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
const getGuides = useGetGuides(id || '')
|
const getGuides = useGetGuides(id || '')
|
||||||
const deleteGuide = useDeleteGuide(id || '')
|
const deleteGuide = useDeleteGuide(id || '')
|
||||||
@@ -36,6 +38,7 @@ const List: FC = () => {
|
|||||||
<div>
|
<div>
|
||||||
{t('guide.list_guide')}
|
{t('guide.list_guide')}
|
||||||
</div>
|
</div>
|
||||||
|
{canCreate('services') && (
|
||||||
<div>
|
<div>
|
||||||
<Link to={Pages.services.guide + id + '/create'}>
|
<Link to={Pages.services.guide + id + '/create'}>
|
||||||
<Button
|
<Button
|
||||||
@@ -51,6 +54,7 @@ const List: FC = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -85,13 +89,17 @@ const List: FC = () => {
|
|||||||
</Td>
|
</Td>
|
||||||
<Td text=''>
|
<Td text=''>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
|
{canUpdate('services') && (
|
||||||
<Link to={Pages.services.guide + id + '/' + item.id}>
|
<Link to={Pages.services.guide + id + '/' + item.id}>
|
||||||
<Eye size={20} color='#8C90A3' />
|
<Eye size={20} color='#8C90A3' />
|
||||||
</Link>
|
</Link>
|
||||||
|
)}
|
||||||
|
{canDelete('services') && (
|
||||||
<TrashWithConfrim
|
<TrashWithConfrim
|
||||||
onDelete={() => deleteGuide.mutate(item.id)}
|
onDelete={() => deleteGuide.mutate(item.id)}
|
||||||
isLoading={deleteGuide.isPending}
|
isLoading={deleteGuide.isPending}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Td>
|
</Td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import Textarea from '../../components/Textarea'
|
|||||||
import Switch from '../../components/Switch'
|
import Switch from '../../components/Switch'
|
||||||
import { TickCircle } from 'iconsax-react'
|
import { TickCircle } from 'iconsax-react'
|
||||||
import { useGetGuideDetail, useUpdateGuide } from './hooks/useGuideData'
|
import { useGetGuideDetail, useUpdateGuide } from './hooks/useGuideData'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../components/Toast';
|
||||||
import { ErrorType } from '../../helpers/types'
|
import { ErrorType } from '../../helpers/types'
|
||||||
import { Pages } from '../../config/Pages'
|
import { Pages } from '../../config/Pages'
|
||||||
|
|
||||||
@@ -53,11 +53,11 @@ const Update: FC = () => {
|
|||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
navigate(Pages.services.guide + id)
|
navigate(Pages.services.guide + id)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message?.[0] || t('error'))
|
toast(error.response?.data?.error?.message?.[0] || t('error'))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +1,32 @@
|
|||||||
import { FC, ReactNode } from 'react'
|
import { FC, ReactNode } from "react";
|
||||||
import { Link } from 'react-router-dom'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
title: string
|
title: string;
|
||||||
icon: ReactNode,
|
icon: ReactNode;
|
||||||
color: string,
|
color: string;
|
||||||
count: number,
|
count: number;
|
||||||
description: string,
|
description: string;
|
||||||
to?: string
|
to?: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
const ItemDashboard: FC<Props> = (props: Props) => {
|
const ItemDashboard: FC<Props> = (props: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={props.to ? props.to : ''} className='p-6 min-w-[40%] xl:min-w-[15%] flex flex-col items-center xl:items-start flex-1 h-[178px] bg-white rounded-3xl'>
|
<div className="p-6 min-w-[40%] xl:min-w-[15%] flex flex-col items-center xl:items-start flex-1 h-[178px] bg-white rounded-3xl">
|
||||||
<div className='size-10 rounded-full bg-[#EEF0F7] flex justify-center items-center'>
|
<div className="size-10 rounded-full bg-[#EEF0F7] flex justify-center items-center">{props.icon}</div>
|
||||||
{props.icon}
|
<div className="mt-5">{props.title}</div>
|
||||||
</div>
|
|
||||||
<div className='mt-5'>
|
|
||||||
{props.title}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-5 text-xs text-description flex gap-1.5 items-center'>
|
<div className="mt-5 text-xs text-description flex gap-1.5 items-center">
|
||||||
<div style={{ background: props.color }} className='size-1.5 rounded-full'></div>
|
<div
|
||||||
<div className='flex gap-0.5'>
|
style={{ background: props.color }}
|
||||||
|
className="size-1.5 rounded-full"
|
||||||
|
></div>
|
||||||
|
<div className="flex gap-0.5">
|
||||||
<div>{props.count}</div>
|
<div>{props.count}</div>
|
||||||
<div className='whitespace-nowrap'>
|
<div className="whitespace-nowrap">{props.description}</div>
|
||||||
{props.description}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
);
|
||||||
)
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export default ItemDashboard
|
export default ItemDashboard;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Pagination from '../../components/Pagination'
|
|||||||
import { useGetCategory } from './hooks/useLearningData'
|
import { useGetCategory } from './hooks/useLearningData'
|
||||||
import { CategoryLearningType } from './types/LearningTypes'
|
import { CategoryLearningType } from './types/LearningTypes'
|
||||||
import CreateCategory from './components/CreateCategory'
|
import CreateCategory from './components/CreateCategory'
|
||||||
|
import { usePermissions } from '../../hooks/usePermissions'
|
||||||
|
|
||||||
|
|
||||||
const LearningCategory: FC = () => {
|
const LearningCategory: FC = () => {
|
||||||
@@ -13,6 +14,7 @@ const LearningCategory: FC = () => {
|
|||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
const [page, setPage] = useState<number>(1)
|
const [page, setPage] = useState<number>(1)
|
||||||
const getCategory = useGetCategory()
|
const getCategory = useGetCategory()
|
||||||
|
const { canCreate } = usePermissions()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-4'>
|
<div className='mt-4'>
|
||||||
@@ -65,7 +67,9 @@ const LearningCategory: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{canCreate('learnings') && (
|
||||||
<CreateCategory />
|
<CreateCategory />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import Textarea from '../../components/Textarea'
|
|||||||
import PageLoading from '../../components/PageLoading'
|
import PageLoading from '../../components/PageLoading'
|
||||||
import CreateLearningSidebar from './components/CreateLearningSidebar'
|
import CreateLearningSidebar from './components/CreateLearningSidebar'
|
||||||
import { ErrorType } from '../../helpers/types'
|
import { ErrorType } from '../../helpers/types'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../components/Toast';
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { Pages } from '../../config/Pages'
|
import { Pages } from '../../config/Pages'
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ const CreateLearning: FC = () => {
|
|||||||
values.videoUrl = data?.data?.url
|
values.videoUrl = data?.data?.url
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -63,21 +63,21 @@ const CreateLearning: FC = () => {
|
|||||||
values.coverUrl = data?.data?.url
|
values.coverUrl = data?.data?.url
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
createLearning.mutate(values, {
|
createLearning.mutate(values, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
navigate(Pages.learning.list)
|
navigate(Pages.learning.list)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
toast.error(t('learning.error_video_cover_required'))
|
toast(t('learning.error_video_cover_required'), 'error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ import PageLoading from '../../components/PageLoading'
|
|||||||
import Td from '../../components/Td'
|
import Td from '../../components/Td'
|
||||||
import { LearningItemType } from './types/LearningTypes'
|
import { LearningItemType } from './types/LearningTypes'
|
||||||
import Pagination from '../../components/Pagination'
|
import Pagination from '../../components/Pagination'
|
||||||
|
import { usePermissions } from '../../hooks/usePermissions'
|
||||||
const LearningList: FC = () => {
|
const LearningList: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
const [page, setPage] = useState<number>(1)
|
const [page, setPage] = useState<number>(1)
|
||||||
const getLearning = useGetLearning(page)
|
const getLearning = useGetLearning(page)
|
||||||
|
const { canCreate } = usePermissions()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-4'>
|
<div className='mt-4'>
|
||||||
@@ -21,6 +23,7 @@ const LearningList: FC = () => {
|
|||||||
<div>
|
<div>
|
||||||
{t('learning.list_learning')}
|
{t('learning.list_learning')}
|
||||||
</div>
|
</div>
|
||||||
|
{canCreate('learnings') && (
|
||||||
<div>
|
<div>
|
||||||
<Link to={Pages.learning.create}>
|
<Link to={Pages.learning.create}>
|
||||||
<Button
|
<Button
|
||||||
@@ -36,6 +39,7 @@ const LearningList: FC = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Button from '../../../components/Button'
|
|||||||
import { TickCircle } from 'iconsax-react'
|
import { TickCircle } from 'iconsax-react'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { useFormik } from 'formik'
|
import { useFormik } from 'formik'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../components/Toast';
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { ErrorType } from '../../../helpers/types'
|
||||||
import { CreateCategoryLearningType } from '../types/LearningTypes'
|
import { CreateCategoryLearningType } from '../types/LearningTypes'
|
||||||
import { useCreateCategory, useGetCategory } from '../hooks/useLearningData'
|
import { useCreateCategory, useGetCategory } from '../hooks/useLearningData'
|
||||||
@@ -29,10 +29,10 @@ const CreateCategory: FC = () => {
|
|||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
formik.resetForm()
|
formik.resetForm()
|
||||||
getCategory.refetch()
|
getCategory.refetch()
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error?.response?.data?.error?.message[0])
|
toast(error?.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { timeAgo } from '../../config/func';
|
|||||||
import InfiniteScroll from 'react-infinite-scroll-component';
|
import InfiniteScroll from 'react-infinite-scroll-component';
|
||||||
import MoonLoader from "react-spinners/MoonLoader"
|
import MoonLoader from "react-spinners/MoonLoader"
|
||||||
import Button from '../../components/Button';
|
import Button from '../../components/Button';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from '../../components/Toast';
|
||||||
// import { useGetDashboardSummary } from '../home/hooks/useHomeData';
|
// import { useGetDashboardSummary } from '../home/hooks/useHomeData';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Pages } from '../../config/Pages';
|
import { Pages } from '../../config/Pages';
|
||||||
@@ -33,7 +33,7 @@ const Notifications: FC = () => {
|
|||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
getDashboard.refetch()
|
getDashboard.refetch()
|
||||||
refetch()
|
refetch()
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ import Pagination from '../../components/Pagination'
|
|||||||
import moment from 'moment-jalaali'
|
import moment from 'moment-jalaali'
|
||||||
import Accept from './components/Accept'
|
import Accept from './components/Accept'
|
||||||
import Reject from './components/Reject'
|
import Reject from './components/Reject'
|
||||||
|
import { usePermissions } from '../../hooks/usePermissions'
|
||||||
|
|
||||||
const PaymentList: FC = () => {
|
const PaymentList: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
|
const { canUpdate } = usePermissions()
|
||||||
const [page, setPage] = useState<number>(1)
|
const [page, setPage] = useState<number>(1)
|
||||||
const [pageGateWay, setPageGateWay] = useState<number>(1)
|
const [pageGateWay, setPageGateWay] = useState<number>(1)
|
||||||
const [activeTab, setActiveTab] = useState<PaymentStatusType>('GATEWAY')
|
const [activeTab, setActiveTab] = useState<PaymentStatusType>('GATEWAY')
|
||||||
@@ -140,7 +142,7 @@ const PaymentList: FC = () => {
|
|||||||
<Td text={t(`payment.${item.status}`)} />
|
<Td text={t(`payment.${item.status}`)} />
|
||||||
<Td text={''}>
|
<Td text={''}>
|
||||||
{
|
{
|
||||||
item.status === 'PENDING' &&
|
item.status === 'PENDING' && canUpdate('payments') &&
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className='flex gap-2'>
|
<div className='flex gap-2'>
|
||||||
<Accept
|
<Accept
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import Button from '../../../components/Button'
|
import Button from '../../../components/Button'
|
||||||
import { useApproveTransfer } from '../hooks/usePaymentData'
|
import { useApproveTransfer } from '../hooks/usePaymentData'
|
||||||
import ModalConfrim from '../../../components/ModalConfrim'
|
import ModalConfrim from '../../../components/ModalConfrim'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../components/Toast';
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { ErrorType } from '../../../helpers/types'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -19,12 +19,12 @@ const Accept: FC<Props> = (props: Props) => {
|
|||||||
const handleApproveTransfer = () => {
|
const handleApproveTransfer = () => {
|
||||||
approveTransfer.mutate(props.id, {
|
approveTransfer.mutate(props.id, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
setOpenConfrimModal(false)
|
setOpenConfrimModal(false)
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
},
|
},
|
||||||
onError(error: ErrorType) {
|
onError(error: ErrorType) {
|
||||||
toast.error(error?.response?.data?.error?.message[0])
|
toast(error?.response?.data?.error?.message[0], 'error')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Button from '../../../components/Button'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useRejectTransfer } from '../hooks/usePaymentData'
|
import { useRejectTransfer } from '../hooks/usePaymentData'
|
||||||
import ModalConfrim from '../../../components/ModalConfrim'
|
import ModalConfrim from '../../../components/ModalConfrim'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../components/Toast';
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { ErrorType } from '../../../helpers/types'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -20,16 +20,16 @@ const Reject: FC<Props> = (props: Props) => {
|
|||||||
if (text && text.length > 0) {
|
if (text && text.length > 0) {
|
||||||
rejectTransfer.mutate({ id: props.id, comment: text }, {
|
rejectTransfer.mutate({ id: props.id, comment: text }, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
setOpenConfrimModal(false)
|
setOpenConfrimModal(false)
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
},
|
},
|
||||||
onError(error: ErrorType) {
|
onError(error: ErrorType) {
|
||||||
toast.error(error?.response?.data?.error?.message[0])
|
toast(error?.response?.data?.error?.message[0], 'error')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
toast.error(t('payment.reject_comment_error'))
|
toast(t('payment.reject_comment_error'), 'error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-127
@@ -1,70 +1,31 @@
|
|||||||
import { FC, useCallback, useEffect, useState } from 'react'
|
import { FC, useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import AvatarImage from '../../assets/images/avatar_image.png'
|
import AvatarImage from '../../assets/images/avatar_image.png'
|
||||||
import Button from '../../components/Button'
|
import Button from '../../components/Button'
|
||||||
import { DocumentUpload } from 'iconsax-react'
|
import { DocumentUpload } from 'iconsax-react'
|
||||||
import Input from '../../components/Input'
|
|
||||||
import DatePickerComponent from '../../components/DatePicker'
|
|
||||||
import { useGetProfile, useUpdateProfile } from './hooks/useProfileData'
|
import { useGetProfile, useUpdateProfile } from './hooks/useProfileData'
|
||||||
import Username from './components/Username'
|
import Username from './components/Username'
|
||||||
import { useDropzone } from 'react-dropzone'
|
import { useDropzone } from 'react-dropzone'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../components/Toast'
|
||||||
import { ErrorType } from '../../helpers/types'
|
import { ErrorType } from '../../helpers/types'
|
||||||
import { UpdateProfileType } from './types/ProfileTypes'
|
import { UpdateProfileType } from './types/ProfileTypes'
|
||||||
import PageLoading from '../../components/PageLoading'
|
import PageLoading from '../../components/PageLoading'
|
||||||
import { useFormik } from 'formik'
|
|
||||||
import * as Yup from 'yup'
|
|
||||||
import Email from './components/Email'
|
import Email from './components/Email'
|
||||||
import Phone from './components/Phone'
|
import Phone from './components/Phone'
|
||||||
import { useGetProvines } from '../customer/hooks/useCustomerData'
|
import FirstName from './components/FirstName'
|
||||||
|
import LastName from './components/LastName'
|
||||||
|
import BirthDate from './components/BirthDate'
|
||||||
|
import NationalCode from './components/NationalCode'
|
||||||
import { useSingleUpload } from '../service/hooks/useServiceData'
|
import { useSingleUpload } from '../service/hooks/useServiceData'
|
||||||
|
|
||||||
const Profile: FC = () => {
|
const Profile: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
const [file, setFile] = useState<File>()
|
const [file, setFile] = useState<File>()
|
||||||
const getProvines = useGetProvines()
|
|
||||||
const getProfile = useGetProfile()
|
const getProfile = useGetProfile()
|
||||||
const singleUpload = useSingleUpload()
|
const singleUpload = useSingleUpload()
|
||||||
const updateProfile = useUpdateProfile()
|
const updateProfile = useUpdateProfile()
|
||||||
|
|
||||||
const formik = useFormik<UpdateProfileType>({
|
|
||||||
initialValues: {
|
|
||||||
cityId: '',
|
|
||||||
userAddress: '',
|
|
||||||
postalCode: '',
|
|
||||||
},
|
|
||||||
validationSchema: Yup.object({
|
|
||||||
cityId: Yup.string().required(t('errors.required')),
|
|
||||||
userAddress: Yup.string().required(t('errors.required')),
|
|
||||||
postalCode: Yup.string().required(t('errors.required')),
|
|
||||||
}),
|
|
||||||
onSubmit: (values) => {
|
|
||||||
updateProfile.mutate(values, {
|
|
||||||
onSuccess: () => {
|
|
||||||
toast.success(t('success'))
|
|
||||||
},
|
|
||||||
onError: (error: ErrorType) => {
|
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
|
|
||||||
if (getProfile.data) {
|
|
||||||
formik.setValues({
|
|
||||||
cityId: getProfile.data.data.user.city?.id || '',
|
|
||||||
userAddress: getProfile.data.data.user.userAddress || '',
|
|
||||||
postalCode: getProfile.data.data.user.postalCode || '',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [getProfile.data])
|
|
||||||
|
|
||||||
|
|
||||||
const onDrop = useCallback((acceptedFiles: File[]) => {
|
const onDrop = useCallback((acceptedFiles: File[]) => {
|
||||||
|
|
||||||
if (acceptedFiles.length > 0) {
|
if (acceptedFiles.length > 0) {
|
||||||
@@ -80,19 +41,19 @@ const Profile: FC = () => {
|
|||||||
}
|
}
|
||||||
updateProfile.mutate(params, {
|
updateProfile.mutate(params, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('profile.image_uploaded_successfully'))
|
toast(t('profile.image_uploaded_successfully'), 'success')
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message?.[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message?.[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
toast.error(t('errors.is_not_image'))
|
toast(t('errors.is_not_image'), 'error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +68,7 @@ const Profile: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
getProfile.isPending || getProvines.isPending ?
|
getProfile.isPending ?
|
||||||
<PageLoading />
|
<PageLoading />
|
||||||
:
|
:
|
||||||
<div className='bg-white rounded-3xl xl:p-6 p-4 mt-8 text-sm'>
|
<div className='bg-white rounded-3xl xl:p-6 p-4 mt-8 text-sm'>
|
||||||
@@ -175,86 +136,14 @@ const Profile: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex-1'>
|
<div className='flex-1'>
|
||||||
<DatePickerComponent
|
<FirstName firstName={getProfile.data?.data?.user?.firstName} />
|
||||||
label={t('profile.date_of_birth')}
|
<LastName lastName={getProfile.data?.data?.user?.lastName} />
|
||||||
onChange={() => { }}
|
|
||||||
placeholder=''
|
|
||||||
defaulValue={getProfile.data?.data?.user?.birthDate}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className='xl:mt-7 mt-4'>
|
<div className='xl:mt-7 mt-4'>
|
||||||
<Input
|
<BirthDate birthDate={getProfile.data?.data?.user?.birthDate} />
|
||||||
label={t('profile.national_code')}
|
</div>
|
||||||
value={getProfile.data?.data?.user?.nationalCode}
|
<NationalCode nationalCode={getProfile.data?.data?.user?.nationalCode} />
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* <div className='mt-8 flex xl:flex-row flex-col xl:gap-0 gap-7'>
|
|
||||||
<div className='flex-1'>
|
|
||||||
<div>
|
|
||||||
{t('profile.address')}
|
|
||||||
</div>
|
|
||||||
<div className='text-description text-xs mt-2'>
|
|
||||||
{t('profile.address_live')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='flex-1'>
|
|
||||||
<div className='rowTwoInput'>
|
|
||||||
<Select
|
|
||||||
label={t('profile.province')}
|
|
||||||
items={getProvines.data?.data?.provinces.map((item: ProvinesItemType) => ({ label: item.name, value: item.id }))}
|
|
||||||
onChange={(e) => setProvinesId(e.target.value)}
|
|
||||||
className='bg-white border'
|
|
||||||
placeholder={t('select')}
|
|
||||||
value={provinesId}
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
label={t('profile.city')}
|
|
||||||
items={getCities.data?.data?.cities ? getCities.data?.data?.cities.map((item: ProvinesItemType) => ({ label: item.name, value: item.id })) : []}
|
|
||||||
placeholder={t('select')}
|
|
||||||
{...formik.getFieldProps('cityId')}
|
|
||||||
error_text={formik.touched.cityId && formik.errors.cityId ? formik.errors.cityId : ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='xl:mt-7 mt-4'>
|
|
||||||
<Input
|
|
||||||
label={t('profile.postal_code')}
|
|
||||||
{...formik.getFieldProps('postalCode')}
|
|
||||||
error_text={formik.touched.postalCode && formik.errors.postalCode ? formik.errors.postalCode : ''}
|
|
||||||
value={formik.values.postalCode}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='xl:mt-7 mt-4'>
|
|
||||||
<Textarea
|
|
||||||
label={t('profile.address')}
|
|
||||||
defaultValue={formik.values.userAddress}
|
|
||||||
{...formik.getFieldProps('userAddress')}
|
|
||||||
error_text={formik.touched.userAddress && formik.errors.userAddress ? formik.errors.userAddress : ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex justify-end mt-5'>
|
|
||||||
<Button
|
|
||||||
className='xl:w-fit px-10'
|
|
||||||
onClick={() => formik.handleSubmit()}
|
|
||||||
isLoading={updateProfile.isPending}
|
|
||||||
>
|
|
||||||
<div className='flex gap-2 items-center'>
|
|
||||||
<TickCircle size={20} color='white' />
|
|
||||||
<div>
|
|
||||||
{t('save')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div> */}
|
|
||||||
|
|
||||||
<div className='h-20 xl:hidden'></div>
|
<div className='h-20 xl:hidden'></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { FC, useEffect, useState } from 'react'
|
||||||
|
import Input from '../../../components/Input'
|
||||||
|
import Button from '../../../components/Button'
|
||||||
|
import DatePickerComponent from '../../../components/DatePicker'
|
||||||
|
import { Edit } from 'iconsax-react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useUpdateProfile } from '../hooks/useProfileData'
|
||||||
|
import { UpdateProfileType } from '../types/ProfileTypes'
|
||||||
|
import { toast } from '../../../components/Toast'
|
||||||
|
import { ErrorType } from '../../../helpers/types'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
birthDate: string | null | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const BirthDate: FC<Props> = (props: Props) => {
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
const [value, setValue] = useState<string>(props.birthDate || '')
|
||||||
|
const [isReadOnly, setIsReadOnly] = useState<boolean>(true)
|
||||||
|
const updateProfile = useUpdateProfile()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(props.birthDate || '')
|
||||||
|
}, [props.birthDate])
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
const params: UpdateProfileType = { birthDate: value }
|
||||||
|
updateProfile.mutate(params, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast(t('success'), 'success')
|
||||||
|
setIsReadOnly(true)
|
||||||
|
},
|
||||||
|
onError: (error: ErrorType) => {
|
||||||
|
toast(error.response?.data?.error?.message?.[0], 'error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDisabled = !value || value === (props.birthDate || '')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex items-end xl:gap-6 gap-3'>
|
||||||
|
{
|
||||||
|
isReadOnly ? (
|
||||||
|
<Input
|
||||||
|
label={t('profile.date_of_birth')}
|
||||||
|
value={value}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<DatePickerComponent
|
||||||
|
label={t('profile.date_of_birth')}
|
||||||
|
onChange={setValue}
|
||||||
|
placeholder=''
|
||||||
|
defaulValue={value}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
isReadOnly ? (
|
||||||
|
<div onClick={() => setIsReadOnly(false)} className='flex cursor-pointer relative -top-3 gap-1 text-[#0047FF] text-xs items-center'>
|
||||||
|
<Edit className='xl:size-[18px] size-4' color='#0047FF' />
|
||||||
|
<div>{t('edit')}</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
label={t('save')}
|
||||||
|
className='px-4 w-fit'
|
||||||
|
disabled={isDisabled}
|
||||||
|
onClick={handleSave}
|
||||||
|
isLoading={updateProfile.isPending}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BirthDate
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
import { FC, Fragment, useState } from 'react'
|
import { FC, Fragment, useEffect, useState } from 'react'
|
||||||
import Input from '../../../components/Input'
|
import Input from '../../../components/Input'
|
||||||
import { Edit, TickCircle } from 'iconsax-react'
|
import { Edit } from 'iconsax-react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Button from '../../../components/Button'
|
import Button from '../../../components/Button'
|
||||||
import { isEmail } from '../../../config/func'
|
import { isEmail } from '../../../config/func'
|
||||||
import DefaulModal from '../../../components/DefaulModal'
|
|
||||||
import OTPInput from 'react-otp-input'
|
|
||||||
import { useUpdateEmail } from '../hooks/useProfileData'
|
import { useUpdateEmail } from '../hooks/useProfileData'
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { ErrorType } from '../../../helpers/types'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../components/Toast'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
email: string | null,
|
email: string | null,
|
||||||
@@ -20,17 +18,20 @@ const Email: FC<Props> = (props: Props) => {
|
|||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
const [email, setEmail] = useState<string>(props.email ? props.email : '')
|
const [email, setEmail] = useState<string>(props.email ? props.email : '')
|
||||||
const [isReadOnly, setIsReadOnly] = useState<boolean>(true)
|
const [isReadOnly, setIsReadOnly] = useState<boolean>(true)
|
||||||
const [showModal, setShowModal] = useState<boolean>(false)
|
|
||||||
const [code, setCode] = useState<string>('')
|
|
||||||
const updateEmail = useUpdateEmail()
|
const updateEmail = useUpdateEmail()
|
||||||
|
|
||||||
const handleShowModal = () => {
|
useEffect(() => {
|
||||||
updateEmail.mutate({ email: email }, {
|
setEmail(props.email ? props.email : '')
|
||||||
|
}, [props.email])
|
||||||
|
|
||||||
|
const handleSendVerification = () => {
|
||||||
|
updateEmail.mutate({ email }, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('profile.link_email'))
|
toast(t('profile.link_email'), 'success')
|
||||||
|
setIsReadOnly(true)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error.message[0])
|
toast(error.response?.data?.error?.message?.[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -45,81 +46,42 @@ const Email: FC<Props> = (props: Props) => {
|
|||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
/>
|
/>
|
||||||
{
|
{
|
||||||
!props.isVerified &&
|
!props.isVerified && isReadOnly &&
|
||||||
<div className='text-[10px] absolute left-24 text-red-400 top-[34px]'>
|
<div className='text-[10px] absolute left-24 text-red-400 top-[34px]'>
|
||||||
{t('profile.email_not_verified')}
|
{t('profile.email_not_verified')}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
isReadOnly ?
|
isReadOnly ?
|
||||||
<div onClick={() => setIsReadOnly(false)} className='flex cursor-pointer relative -top-3 gap-1 text-[#0047FF] text-xs items-center'>
|
<div className='flex items-center gap-3 relative -top-3'>
|
||||||
|
{
|
||||||
|
!props.isVerified && !!email &&
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
onClick={handleSendVerification}
|
||||||
|
disabled={updateEmail.isPending}
|
||||||
|
className='text-[#0047FF] text-xs whitespace-nowrap disabled:opacity-50'
|
||||||
|
>
|
||||||
|
{t('profile.resend_email')}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
<div onClick={() => setIsReadOnly(false)} className='flex cursor-pointer gap-1 text-[#0047FF] text-xs items-center'>
|
||||||
<Edit className='xl:size-[18px] size-4' color='#0047FF' />
|
<Edit className='xl:size-[18px] size-4' color='#0047FF' />
|
||||||
<div>
|
<div>
|
||||||
{t('edit')}
|
{t('edit')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
:
|
:
|
||||||
<Button
|
<Button
|
||||||
label={t('save')}
|
label={t('save')}
|
||||||
className='px-4 w-fit'
|
className='px-4 w-fit'
|
||||||
disabled={!isEmail(email)}
|
disabled={!isEmail(email)}
|
||||||
onClick={handleShowModal}
|
onClick={handleSendVerification}
|
||||||
isLoading={updateEmail.isPending}
|
isLoading={updateEmail.isPending}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DefaulModal
|
|
||||||
open={showModal}
|
|
||||||
close={() => setShowModal(false)}
|
|
||||||
isHeader
|
|
||||||
title_header={t('profile.confrim_email')}
|
|
||||||
>
|
|
||||||
<div className='mt-7'>
|
|
||||||
<div className='text-xs text-center'>
|
|
||||||
کد تایید به ایمیل {email} ارسال شده است.برای تایید کد مربوطه را وارد کنید.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-10'>کد تایید</div>
|
|
||||||
<div className='mt-2 w-full flex justify-center dltr otp'>
|
|
||||||
<OTPInput
|
|
||||||
value={code}
|
|
||||||
onChange={setCode}
|
|
||||||
shouldAutoFocus
|
|
||||||
renderInput={(props) =>
|
|
||||||
<input
|
|
||||||
{...props}
|
|
||||||
type='tel'
|
|
||||||
autoComplete="one-time-code"
|
|
||||||
inputMode="numeric"
|
|
||||||
className='w-full h-[50px] flex-1 mx-2 bg-white bg-opacity-30 border rounded-2.5' />
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-14 flex justify-end border-t border-border pt-8'>
|
|
||||||
<div className='flex gap-5'>
|
|
||||||
<Button
|
|
||||||
className='bg-white bg-opacity-40 text-description w-[150px] text-xs'
|
|
||||||
label='لغو'
|
|
||||||
onClick={() => setShowModal(false)}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
className='w-[150px] text-xs'
|
|
||||||
>
|
|
||||||
<div className='flex gap-2 items-center'>
|
|
||||||
<TickCircle
|
|
||||||
size={20}
|
|
||||||
color='white'
|
|
||||||
/>
|
|
||||||
<div>{t('wallet.submit_slip')}</div>
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</DefaulModal>
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { FC, useEffect, useState } from 'react'
|
||||||
|
import Input from '../../../components/Input'
|
||||||
|
import Button from '../../../components/Button'
|
||||||
|
import { Edit } from 'iconsax-react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useUpdateProfile } from '../hooks/useProfileData'
|
||||||
|
import { UpdateProfileType } from '../types/ProfileTypes'
|
||||||
|
import { toast } from '../../../components/Toast'
|
||||||
|
import { ErrorType } from '../../../helpers/types'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
firstName: string | null | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const FirstName: FC<Props> = (props: Props) => {
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
const [value, setValue] = useState<string>(props.firstName || '')
|
||||||
|
const [isReadOnly, setIsReadOnly] = useState<boolean>(true)
|
||||||
|
const updateProfile = useUpdateProfile()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(props.firstName || '')
|
||||||
|
}, [props.firstName])
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
const params: UpdateProfileType = { firstName: value.trim() }
|
||||||
|
updateProfile.mutate(params, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast(t('success'), 'success')
|
||||||
|
setIsReadOnly(true)
|
||||||
|
},
|
||||||
|
onError: (error: ErrorType) => {
|
||||||
|
toast(error.response?.data?.error?.message?.[0], 'error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDisabled = value.trim().length < 2 || value.trim() === (props.firstName || '')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex items-end xl:gap-6 gap-3 xl:mt-7 mt-4'>
|
||||||
|
<Input
|
||||||
|
label={t('profile.first_name')}
|
||||||
|
value={value}
|
||||||
|
readOnly={isReadOnly}
|
||||||
|
onChange={(e) => setValue(e.target.value)}
|
||||||
|
/>
|
||||||
|
{
|
||||||
|
isReadOnly ? (
|
||||||
|
<div onClick={() => setIsReadOnly(false)} className='flex cursor-pointer relative -top-3 gap-1 text-[#0047FF] text-xs items-center'>
|
||||||
|
<Edit className='xl:size-[18px] size-4' color='#0047FF' />
|
||||||
|
<div>{t('edit')}</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
label={t('save')}
|
||||||
|
className='px-4 w-fit'
|
||||||
|
disabled={isDisabled}
|
||||||
|
onClick={handleSave}
|
||||||
|
isLoading={updateProfile.isPending}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FirstName
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { FC, useEffect, useState } from 'react'
|
||||||
|
import Input from '../../../components/Input'
|
||||||
|
import Button from '../../../components/Button'
|
||||||
|
import { Edit } from 'iconsax-react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useUpdateProfile } from '../hooks/useProfileData'
|
||||||
|
import { UpdateProfileType } from '../types/ProfileTypes'
|
||||||
|
import { toast } from '../../../components/Toast'
|
||||||
|
import { ErrorType } from '../../../helpers/types'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
lastName: string | null | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const LastName: FC<Props> = (props: Props) => {
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
const [value, setValue] = useState<string>(props.lastName || '')
|
||||||
|
const [isReadOnly, setIsReadOnly] = useState<boolean>(true)
|
||||||
|
const updateProfile = useUpdateProfile()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(props.lastName || '')
|
||||||
|
}, [props.lastName])
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
const params: UpdateProfileType = { lastName: value.trim() }
|
||||||
|
updateProfile.mutate(params, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast(t('success'), 'success')
|
||||||
|
setIsReadOnly(true)
|
||||||
|
},
|
||||||
|
onError: (error: ErrorType) => {
|
||||||
|
toast(error.response?.data?.error?.message?.[0], 'error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDisabled = value.trim().length < 2 || value.trim() === (props.lastName || '')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex items-end xl:gap-6 gap-3 xl:mt-7 mt-4'>
|
||||||
|
<Input
|
||||||
|
label={t('profile.last_name')}
|
||||||
|
value={value}
|
||||||
|
readOnly={isReadOnly}
|
||||||
|
onChange={(e) => setValue(e.target.value)}
|
||||||
|
/>
|
||||||
|
{
|
||||||
|
isReadOnly ? (
|
||||||
|
<div onClick={() => setIsReadOnly(false)} className='flex cursor-pointer relative -top-3 gap-1 text-[#0047FF] text-xs items-center'>
|
||||||
|
<Edit className='xl:size-[18px] size-4' color='#0047FF' />
|
||||||
|
<div>{t('edit')}</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
label={t('save')}
|
||||||
|
className='px-4 w-fit'
|
||||||
|
disabled={isDisabled}
|
||||||
|
onClick={handleSave}
|
||||||
|
isLoading={updateProfile.isPending}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LastName
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { FC, useEffect, useState } from 'react'
|
||||||
|
import Input from '../../../components/Input'
|
||||||
|
import Button from '../../../components/Button'
|
||||||
|
import { Edit } from 'iconsax-react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useUpdateProfile } from '../hooks/useProfileData'
|
||||||
|
import { UpdateProfileType } from '../types/ProfileTypes'
|
||||||
|
import { toast } from '../../../components/Toast'
|
||||||
|
import { ErrorType } from '../../../helpers/types'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
nationalCode: string | null | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const NationalCode: FC<Props> = (props: Props) => {
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
const [value, setValue] = useState<string>(props.nationalCode || '')
|
||||||
|
const [isReadOnly, setIsReadOnly] = useState<boolean>(true)
|
||||||
|
const updateProfile = useUpdateProfile()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(props.nationalCode || '')
|
||||||
|
}, [props.nationalCode])
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
const params: UpdateProfileType = { nationalCode: value }
|
||||||
|
updateProfile.mutate(params, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast(t('success'), 'success')
|
||||||
|
setIsReadOnly(true)
|
||||||
|
},
|
||||||
|
onError: (error: ErrorType) => {
|
||||||
|
toast(error.response?.data?.error?.message?.[0], 'error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDisabled = value.length !== 10 || value === (props.nationalCode || '')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex items-end xl:gap-6 gap-3 xl:mt-7 mt-4'>
|
||||||
|
<Input
|
||||||
|
label={t('profile.national_code')}
|
||||||
|
value={value}
|
||||||
|
readOnly={isReadOnly}
|
||||||
|
onChange={(e) => {
|
||||||
|
const next = e.target.value.replace(/\D/g, '').slice(0, 10)
|
||||||
|
setValue(next)
|
||||||
|
}}
|
||||||
|
inputMode='numeric'
|
||||||
|
maxLength={10}
|
||||||
|
/>
|
||||||
|
{
|
||||||
|
isReadOnly ? (
|
||||||
|
<div onClick={() => setIsReadOnly(false)} className='flex cursor-pointer relative -top-3 gap-1 text-[#0047FF] text-xs items-center'>
|
||||||
|
<Edit className='xl:size-[18px] size-4' color='#0047FF' />
|
||||||
|
<div>{t('edit')}</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
label={t('save')}
|
||||||
|
className='px-4 w-fit'
|
||||||
|
disabled={isDisabled}
|
||||||
|
onClick={handleSave}
|
||||||
|
isLoading={updateProfile.isPending}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NationalCode
|
||||||
@@ -7,7 +7,7 @@ import DefaulModal from '../../../components/DefaulModal'
|
|||||||
import OTPInput from 'react-otp-input'
|
import OTPInput from 'react-otp-input'
|
||||||
import { useUpdatePhone, useVerifyPhone } from '../hooks/useProfileData'
|
import { useUpdatePhone, useVerifyPhone } from '../hooks/useProfileData'
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { ErrorType } from '../../../helpers/types'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../components/Toast';
|
||||||
import { VerifyPhoneType } from '../types/ProfileTypes'
|
import { VerifyPhoneType } from '../types/ProfileTypes'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -30,7 +30,7 @@ const Phone: FC<Props> = (props: Props) => {
|
|||||||
setShowModal(true)
|
setShowModal(true)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error.message[0])
|
toast(error.response?.data?.error.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -45,10 +45,10 @@ const Phone: FC<Props> = (props: Props) => {
|
|||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setShowModal(false)
|
setShowModal(false)
|
||||||
setIsReadOnly(true)
|
setIsReadOnly(true)
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error.message[0])
|
toast(error.response?.data?.error.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import Button from '../../../components/Button'
|
import Button from '../../../components/Button'
|
||||||
import { useCheckUserName, useUpdateProfile } from '../hooks/useProfileData'
|
import { useCheckUserName, useUpdateProfile } from '../hooks/useProfileData'
|
||||||
import { CheckUserNameType, UpdateProfileType } from '../types/ProfileTypes'
|
import { CheckUserNameType, UpdateProfileType } from '../types/ProfileTypes'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../components/Toast';
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { ErrorType } from '../../../helpers/types'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -54,13 +54,13 @@ const Username: FC<Props> = (props: Props) => {
|
|||||||
|
|
||||||
updateProfile.mutate(params, {
|
updateProfile.mutate(params, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('profile.username_save_success'))
|
toast(t('profile.username_save_success'), 'success')
|
||||||
setCanEdit(false)
|
setCanEdit(false)
|
||||||
setCanSave(false)
|
setCanSave(false)
|
||||||
setStatus('')
|
setStatus('')
|
||||||
},
|
},
|
||||||
onError(error: ErrorType) {
|
onError(error: ErrorType) {
|
||||||
toast.error(error.response?.data?.error?.message?.[0])
|
toast(error.response?.data?.error?.message?.[0], 'error')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import * as api from '../service/ProfileService'
|
import * as api from '../service/ProfileService'
|
||||||
import { CheckUserNameType, UpdateEmailType, UpdatePhoneType, UpdateProfileType, VerifyPhoneType } from '../types/ProfileTypes';
|
import { CheckUserNameType, UpdateEmailType, UpdatePhoneType, UpdateProfileType, VerifyPhoneType } from '../types/ProfileTypes';
|
||||||
|
|
||||||
@@ -16,14 +16,22 @@ export const useCheckUserName = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useUpdateProfile = () => {
|
export const useUpdateProfile = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (variables: UpdateProfileType) => api.updateProfile(variables),
|
mutationFn: (variables: UpdateProfileType) => api.updateProfile(variables),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["profile"] });
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useUpdateEmail = () => {
|
export const useUpdateEmail = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (variables: UpdateEmailType) => api.updateEmail(variables),
|
mutationFn: (variables: UpdateEmailType) => api.updateEmail(variables),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["profile"] });
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,7 +42,11 @@ export const useUpdatePhone = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useVerifyPhone = () => {
|
export const useVerifyPhone = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (variables: VerifyPhoneType) => api.verifyPhone(variables),
|
mutationFn: (variables: VerifyPhoneType) => api.verifyPhone(variables),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["profile"] });
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -7,6 +7,7 @@ export type UpdateProfileType = {
|
|||||||
firstName?: string;
|
firstName?: string;
|
||||||
lastName?: string;
|
lastName?: string;
|
||||||
birthDate?: string;
|
birthDate?: string;
|
||||||
|
nationalCode?: string;
|
||||||
userName?: string;
|
userName?: string;
|
||||||
profilePic?: string;
|
profilePic?: string;
|
||||||
cityId?: string;
|
cityId?: string;
|
||||||
|
|||||||
+248
-283
@@ -1,62 +1,64 @@
|
|||||||
import { ChangeEvent, FC, Fragment, useState } from 'react'
|
import { useFormik } from "formik";
|
||||||
import { useTranslation } from 'react-i18next'
|
import { Add, TickCircle, TickSquare, Trash } from "iconsax-react";
|
||||||
import Button from '../../components/Button'
|
import { ChangeEvent, FC, Fragment, useState } from "react";
|
||||||
import { Add, TickCircle, TickSquare, Trash } from 'iconsax-react'
|
import { useTranslation } from "react-i18next";
|
||||||
import Select from '../../components/Select'
|
import { useNavigate } from "react-router-dom";
|
||||||
import Input from '../../components/Input'
|
import * as Yup from "yup";
|
||||||
import { CreateReceiptType, ReceiptCreateItemsType } from './types/ReceiptTypes'
|
import Button from "../../components/Button";
|
||||||
import { useFormik } from 'formik'
|
import Input from "../../components/Input";
|
||||||
import * as Yup from 'yup'
|
import PageLoading from "../../components/PageLoading";
|
||||||
import { toast } from 'react-toastify'
|
import Select from "../../components/Select";
|
||||||
import { useGetCustomers } from '../customer/hooks/useCustomerData'
|
import SwitchComponent from "../../components/Switch";
|
||||||
import { CustomerItemType } from '../customer/types/CustomerTypes'
|
import { toast } from "../../components/Toast";
|
||||||
import PageLoading from '../../components/PageLoading'
|
import { Pages } from "../../config/Pages";
|
||||||
import { useCreateInvoice } from './hooks/useReceiptData'
|
import { ErrorType } from "../../helpers/types";
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useGetCustomers } from "../customer/hooks/useCustomerData";
|
||||||
import { Pages } from '../../config/Pages'
|
import { CustomerItemType } from "../customer/types/CustomerTypes";
|
||||||
import { ErrorType } from '../../helpers/types'
|
import { RecurringPeriodEnum } from "./enum/ReceipEnum";
|
||||||
import { RecurringPeriodEnum } from './enum/ReceipEnum'
|
import { useCreateInvoice } from "./hooks/useReceiptData";
|
||||||
import SwitchComponent from '../../components/Switch'
|
import { CreateReceiptType, ReceiptCreateItemsType } from "./types/ReceiptTypes";
|
||||||
const CreateReceipt: FC = () => {
|
const CreateReceipt: FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
const navigate = useNavigate()
|
const { t } = useTranslation("global");
|
||||||
const { t } = useTranslation('global')
|
const [items, setItems] = useState<ReceiptCreateItemsType[]>([]);
|
||||||
const [items, setItems] = useState<ReceiptCreateItemsType[]>([])
|
const [customer, setCustomer] = useState<CustomerItemType>();
|
||||||
const [customer, setCustomer] = useState<CustomerItemType>()
|
const [isRecurring, setIsRecurring] = useState<boolean>(false);
|
||||||
const [isRecurring, setIsRecurring] = useState<boolean>(false)
|
const [type, setType] = useState<RecurringPeriodEnum>(RecurringPeriodEnum.WEEKLY);
|
||||||
const [type, setType] = useState<RecurringPeriodEnum>(RecurringPeriodEnum.WEEKLY)
|
const [maxRecurringCycles, setMaxRecurringCycles] = useState<number>(0);
|
||||||
const [maxRecurringCycles, setMaxRecurringCycles] = useState<number>(0)
|
const getCustomers = useGetCustomers(1, 50, true);
|
||||||
const getCustomers = useGetCustomers(1, true)
|
const createInvoice = useCreateInvoice();
|
||||||
const createInvoice = useCreateInvoice()
|
|
||||||
|
|
||||||
const formik = useFormik<ReceiptCreateItemsType>({
|
const formik = useFormik<ReceiptCreateItemsType>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
name: '',
|
name: "",
|
||||||
count: '',
|
count: "",
|
||||||
unitPrice: '',
|
unitPrice: "",
|
||||||
discount: ''
|
discount: "",
|
||||||
},
|
},
|
||||||
validationSchema: Yup.object({
|
validationSchema: Yup.object({
|
||||||
name: Yup.string().required(t('errors.required')),
|
name: Yup.string().required(t("errors.required")),
|
||||||
count: Yup.string().required(t('errors.required')),
|
count: Yup.string().required(t("errors.required")),
|
||||||
unitPrice: Yup.string().required(t('errors.required')),
|
unitPrice: Yup.string().required(t("errors.required")),
|
||||||
}),
|
}),
|
||||||
onSubmit: (values) => {
|
onSubmit: (values) => {
|
||||||
setItems((prev) => [...prev, {
|
setItems((prev) => [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
count: +values.count,
|
count: +values.count,
|
||||||
discount: +values.discount,
|
discount: +values.discount,
|
||||||
name: values.name,
|
name: values.name,
|
||||||
unitPrice: +values.unitPrice
|
unitPrice: +values.unitPrice,
|
||||||
}])
|
},
|
||||||
formik.resetForm()
|
]);
|
||||||
}
|
formik.resetForm();
|
||||||
})
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const handleChangeCustomer = (e: ChangeEvent<HTMLSelectElement>) => {
|
const handleChangeCustomer = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||||
const value = e.target.value
|
const value = e.target.value;
|
||||||
const customer = getCustomers.data?.data?.customers?.find((item: CustomerItemType) => item.id === value)
|
const customer = getCustomers.data?.data?.customers?.find((item: CustomerItemType) => item.id === value);
|
||||||
setCustomer(customer)
|
setCustomer(customer);
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
if (customer) {
|
if (customer) {
|
||||||
@@ -65,406 +67,369 @@ const CreateReceipt: FC = () => {
|
|||||||
items: items,
|
items: items,
|
||||||
isRecurring: isRecurring,
|
isRecurring: isRecurring,
|
||||||
recurringPeriod: isRecurring ? +type : undefined,
|
recurringPeriod: isRecurring ? +type : undefined,
|
||||||
maxRecurringCycles: isRecurring ? maxRecurringCycles : undefined
|
maxRecurringCycles: isRecurring ? maxRecurringCycles : undefined,
|
||||||
}
|
};
|
||||||
|
|
||||||
createInvoice.mutate(params, {
|
createInvoice.mutate(params, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('success'))
|
toast(t("success"), "success");
|
||||||
navigate(Pages.receipts.index)
|
navigate(Pages.receipts.index);
|
||||||
},
|
},
|
||||||
onError(error: ErrorType) {
|
onError(error: ErrorType) {
|
||||||
toast.error(error.response?.data?.error.message[0])
|
toast(error.response?.data?.error.message[0], "error");
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleRemove = (index: number) => {
|
const handleRemove = (index: number) => {
|
||||||
setItems((prev) => prev.filter((_, i) => i !== index))
|
setItems((prev) => prev.filter((_, i) => i !== index));
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-4'>
|
<div className="mt-4">
|
||||||
{
|
{getCustomers.isPending ? (
|
||||||
getCustomers.isPending ?
|
|
||||||
<PageLoading />
|
<PageLoading />
|
||||||
:
|
) : (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className='flex justify-between items-center'>
|
<div className="flex justify-between items-center">
|
||||||
<div>
|
<div>{t("receip.add_receip")}</div>
|
||||||
{t('receip.add_receip')}
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
className='px-5'
|
className="px-5"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
isLoading={createInvoice.isPending}
|
isLoading={createInvoice.isPending}
|
||||||
>
|
>
|
||||||
<div className='flex gap-2 items-center'>
|
<div className="flex gap-2 items-center">
|
||||||
<TickCircle size={20} color='white' />
|
<TickCircle
|
||||||
<div>
|
size={20}
|
||||||
{t('receip.submit_receip')}
|
color="white"
|
||||||
</div>
|
/>
|
||||||
|
<div>{t("receip.submit_receip")}</div>
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex gap-6 xl:mt-8 mt-4'>
|
<div className="flex gap-6 xl:mt-8 mt-4">
|
||||||
<div className='flex-1 bg-white py-8 xl:px-10 px-4 rounded-3xl'>
|
<div className="flex-1 bg-white py-8 xl:px-10 px-4 rounded-3xl">
|
||||||
<div>
|
<div>{t("receip.customer_information")}</div>
|
||||||
{t('receip.customer_information')}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-8'>
|
<div className="mt-8">
|
||||||
<div className='w-full xl:w-1/2'>
|
<div className="w-full xl:w-1/2">
|
||||||
<Select
|
<Select
|
||||||
label={t('receip.customer')}
|
label={t("receip.customer")}
|
||||||
placeholder={t('select')}
|
placeholder={t("select")}
|
||||||
items={getCustomers.data?.data?.customers?.map((item: CustomerItemType) => {
|
items={getCustomers.data?.data?.customers?.map((item: CustomerItemType) => {
|
||||||
return {
|
return {
|
||||||
label: item.firstName + ' ' + item.lastName,
|
label: item.firstName + " " + item.lastName,
|
||||||
value: item.id
|
value: item.id,
|
||||||
}
|
};
|
||||||
})}
|
})}
|
||||||
onChange={handleChangeCustomer}
|
onChange={handleChangeCustomer}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{customer && (
|
||||||
customer &&
|
<div className="mt-10 bg-[#F6F7FB] p-8 rounded-3xl text-sm">
|
||||||
<div className='mt-10 bg-[#F6F7FB] p-8 rounded-3xl text-sm'>
|
<div className="flex justify-between items-center border-b border-border border-dashed pb-7">
|
||||||
<div className='flex justify-between items-center border-b border-border border-dashed pb-7'>
|
<div className="flex items-center gap-2">
|
||||||
<div className='flex items-center gap-2'>
|
<div className="text-description">{t("receip.type_person")}</div>
|
||||||
<div className='text-description'>{t('receip.type_person')}</div>
|
<div>{customer.legalUser ? "حقوقی" : "حقیقی"}</div>
|
||||||
<div>{customer.legalUser ? 'حقوقی' : 'حقیقی'}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2'>
|
<div className="flex items-center gap-2">
|
||||||
<div className='text-description'>{t('receip.representativeـname')}</div>
|
<div className="text-description">{t("receip.representativeـname")}</div>
|
||||||
<div>{customer.firstName + ' ' + customer.lastName}</div>
|
<div>{customer.firstName + " " + customer.lastName}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2'>
|
<div className="flex items-center gap-2">
|
||||||
<div className='text-description'>{t('receip.company_name')}</div>
|
<div className="text-description">{t("receip.company_name")}</div>
|
||||||
<div>{customer?.legalUser?.registrationName}</div>
|
<div>{customer?.legalUser?.registrationName}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2'>
|
<div className="flex items-center gap-2">
|
||||||
<div className='text-description'>{t('receip.registration_number')}</div>
|
<div className="text-description">{t("receip.registration_number")}</div>
|
||||||
<div>{customer?.legalUser?.registrationCode}</div>
|
<div>{customer?.legalUser?.registrationCode}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-between items-center mt-7'>
|
<div className="flex justify-between items-center mt-7">
|
||||||
<div className='flex items-center gap-2 flex-1'>
|
<div className="flex items-center gap-2 flex-1">
|
||||||
<div className='text-description'>{t('receip.tel_company')}</div>
|
<div className="text-description">{t("receip.tel_company")}</div>
|
||||||
<div>{customer?.realUser?.phone || customer?.legalUser?.phone}</div>
|
<div>{customer?.realUser?.phone || customer?.legalUser?.phone}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2 flex-1'>
|
<div className="flex items-center gap-2 flex-1">
|
||||||
<div className='text-description'>{t('receip.national_code')}</div>
|
<div className="text-description">{t("receip.national_code")}</div>
|
||||||
<div>{customer.nationalCode}</div>
|
<div>{customer.nationalCode}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2 flex-1'>
|
<div className="flex items-center gap-2 flex-1">
|
||||||
<div className='text-description'>{t('receip.postal_code')}</div>
|
<div className="text-description">{t("receip.postal_code")}</div>
|
||||||
<div>{customer?.realUser?.address?.postalCode || customer?.legalUser?.address?.postalCode}</div>
|
<div>{customer?.realUser?.address?.postalCode || customer?.legalUser?.address?.postalCode}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-between items-center mt-7'>
|
<div className="flex justify-between items-center mt-7">
|
||||||
<div className='flex items-center gap-2 flex-1'>
|
<div className="flex items-center gap-2 flex-1">
|
||||||
<div className='text-description'>{t('receip.state')}</div>
|
<div className="text-description">{t("receip.state")}</div>
|
||||||
<div>{customer?.realUser?.address?.city?.province?.name || customer?.legalUser?.address?.city?.province?.name}</div>
|
<div>{customer?.realUser?.address?.city?.province?.name || customer?.legalUser?.address?.city?.province?.name}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2 flex-1'>
|
<div className="flex items-center gap-2 flex-1">
|
||||||
<div className='text-description'>{t('receip.city')}</div>
|
<div className="text-description">{t("receip.city")}</div>
|
||||||
<div>{customer?.realUser?.address?.city?.name || customer?.legalUser?.address?.city?.name}</div>
|
<div>{customer?.realUser?.address?.city?.name || customer?.legalUser?.address?.city?.name}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2 flex-1'>
|
<div className="flex items-center gap-2 flex-1">
|
||||||
<div className='text-description'>{t('receip.address')}</div>
|
<div className="text-description">{t("receip.address")}</div>
|
||||||
<div>{customer?.realUser?.address?.fullAddress || customer?.legalUser?.address?.fullAddress}</div>
|
<div>{customer?.realUser?.address?.fullAddress || customer?.legalUser?.address?.fullAddress}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
|
|
||||||
<div className='mt-10'>
|
<div className="mt-10">
|
||||||
<SwitchComponent
|
<SwitchComponent
|
||||||
label={t('receip.recurring')}
|
label={t("receip.recurring")}
|
||||||
active={isRecurring}
|
active={isRecurring}
|
||||||
onChange={(value) => setIsRecurring(value)}
|
onChange={(value) => setIsRecurring(value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{isRecurring && (
|
||||||
isRecurring &&
|
<div className="mt-10">
|
||||||
<div className='mt-10'>
|
<div>{t("receip.select_type")}</div>
|
||||||
<div>
|
|
||||||
{t('receip.select_type')}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-4 rowTwoInput'>
|
<div className="mt-4 rowTwoInput">
|
||||||
<Select
|
<Select
|
||||||
label={t('select')}
|
label={t("select")}
|
||||||
items={[
|
items={[
|
||||||
{ label: t('receip.WEEKLY'), value: RecurringPeriodEnum.WEEKLY.toString() },
|
{ label: t("receip.WEEKLY"), value: RecurringPeriodEnum.WEEKLY.toString() },
|
||||||
{ label: t('receip.MONTHLY'), value: RecurringPeriodEnum.MONTHLY.toString() },
|
{ label: t("receip.MONTHLY"), value: RecurringPeriodEnum.MONTHLY.toString() },
|
||||||
{ label: t('receip.QUARTERLY'), value: RecurringPeriodEnum.QUARTERLY.toString() },
|
{ label: t("receip.QUARTERLY"), value: RecurringPeriodEnum.QUARTERLY.toString() },
|
||||||
{ label: t('receip.SEMIANNUALLY'), value: RecurringPeriodEnum.SEMIANNUALLY.toString() },
|
{ label: t("receip.SEMIANNUALLY"), value: RecurringPeriodEnum.SEMIANNUALLY.toString() },
|
||||||
{ label: t('receip.ANNUALLY'), value: RecurringPeriodEnum.ANNUALLY.toString() }
|
{ label: t("receip.ANNUALLY"), value: RecurringPeriodEnum.ANNUALLY.toString() },
|
||||||
]}
|
]}
|
||||||
onChange={(e) => setType(e.target.value as unknown as RecurringPeriodEnum)}
|
onChange={(e) => setType(e.target.value as unknown as RecurringPeriodEnum)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
label={t('receip.maxRecurringCycles')}
|
label={t("receip.maxRecurringCycles")}
|
||||||
name='maxRecurringCycles'
|
name="maxRecurringCycles"
|
||||||
value={maxRecurringCycles}
|
value={maxRecurringCycles}
|
||||||
onChange={(e) => setMaxRecurringCycles(+e.target.value)}
|
onChange={(e) => setMaxRecurringCycles(+e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
|
|
||||||
<div className='mt-10'>
|
<div className="mt-10">
|
||||||
<div>{t('receip.receipt_information')}</div>
|
<div>{t("receip.receipt_information")}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-8 border-b border-dashed border-border pb-7 flex items-end justify-between text-xs text-description'>
|
<div className="mt-8 border-b border-dashed border-border pb-7 flex items-end justify-between text-xs text-description">
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
<div className="flex flex-col gap-2 items-center">
|
||||||
<div>
|
<div>{t("receip.number")}</div>
|
||||||
{t('receip.number')}
|
<div className="size-10 bg-[#EBEDF5] rounded-xl flex justify-center items-center">{items.length + 1}</div>
|
||||||
</div>
|
|
||||||
<div className='size-10 bg-[#EBEDF5] rounded-xl flex justify-center items-center'>
|
|
||||||
{items.length + 1}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
<div className="flex flex-col gap-2 items-center">
|
||||||
<div>
|
<div>{t("receip.product_name")}</div>
|
||||||
{t('receip.product_name')}
|
|
||||||
</div>
|
|
||||||
<Input
|
<Input
|
||||||
className='text-center'
|
className="text-center"
|
||||||
name='name'
|
name="name"
|
||||||
value={formik.values.name}
|
value={formik.values.name}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
<div className="flex flex-col gap-2 items-center">
|
||||||
<div>
|
<div>{t("receip.count")}</div>
|
||||||
{t('receip.count')}
|
|
||||||
</div>
|
|
||||||
<Input
|
<Input
|
||||||
type='number'
|
type="number"
|
||||||
className='w-16 text-center'
|
className="w-16 text-center"
|
||||||
name='count'
|
name="count"
|
||||||
value={formik.values.count}
|
value={formik.values.count}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
<div className="flex flex-col gap-2 items-center">
|
||||||
<div>
|
<div>{t("receip.unit_amount")}</div>
|
||||||
{t('receip.unit_amount')}
|
|
||||||
</div>
|
|
||||||
<Input
|
<Input
|
||||||
className='text-center'
|
className="text-center"
|
||||||
name='unitPrice'
|
name="unitPrice"
|
||||||
value={formik.values.unitPrice}
|
value={formik.values.unitPrice}
|
||||||
onChange={(e) => formik.setFieldValue('unitPrice', e.target.value)}
|
onChange={(e) => formik.setFieldValue("unitPrice", e.target.value)}
|
||||||
seprator
|
seprator
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
<div className="flex flex-col gap-2 items-center">
|
||||||
<div>
|
<div>{t("receip.discount")}</div>
|
||||||
{t('receip.discount')}
|
|
||||||
</div>
|
|
||||||
<Input
|
<Input
|
||||||
type='number'
|
type="number"
|
||||||
className='w-16 text-center'
|
className="w-16 text-center"
|
||||||
name='discount'
|
name="discount"
|
||||||
value={formik.values.discount}
|
value={formik.values.discount}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
<div className="flex flex-col gap-2 items-center">
|
||||||
<div>
|
<div>{t("receip.total_amount")}</div>
|
||||||
{t('receip.total_amount')}
|
|
||||||
</div>
|
|
||||||
<Input
|
<Input
|
||||||
className='text-center bg-[#EBEDF5]'
|
className="text-center bg-[#EBEDF5]"
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div onClick={() => {
|
<div
|
||||||
|
onClick={() => {
|
||||||
if (formik.errors.name || formik.errors.count || formik.errors.unitPrice || formik.errors.discount) {
|
if (formik.errors.name || formik.errors.count || formik.errors.unitPrice || formik.errors.discount) {
|
||||||
toast.error(t('receip.error_empty'))
|
toast(t("receip.error_empty"), "error");
|
||||||
} else {
|
} else {
|
||||||
formik.handleSubmit()
|
formik.handleSubmit();
|
||||||
}
|
}
|
||||||
}} className='size-10 border border-border rounded-xl flex justify-center items-center'>
|
}}
|
||||||
<Add size={20} color='black' />
|
className="size-10 border border-border rounded-xl flex justify-center items-center"
|
||||||
|
>
|
||||||
|
<Add
|
||||||
|
size={20}
|
||||||
|
color="black"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{items.map((item, index: number) => {
|
||||||
</div>
|
|
||||||
|
|
||||||
{
|
|
||||||
items.map((item, index: number) => {
|
|
||||||
return (
|
return (
|
||||||
<div key={item.name} className='mt-6 flex items-end justify-between text-xs text-description'>
|
<div
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
key={item.name}
|
||||||
{
|
className="mt-6 flex items-end justify-between text-xs text-description"
|
||||||
index === 0 &&
|
>
|
||||||
<div>
|
<div className="flex flex-col gap-2 items-center">
|
||||||
{t('receip.number')}
|
{index === 0 && <div>{t("receip.number")}</div>}
|
||||||
|
<div className="size-10 bg-[#EBEDF5] rounded-xl flex justify-center items-center">{index + 1}</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
<div className="flex flex-col gap-2 items-center">
|
||||||
<div className='size-10 bg-[#EBEDF5] rounded-xl flex justify-center items-center'>
|
{index === 0 && <div>{t("receip.product_name")}</div>}
|
||||||
{index + 1}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
|
||||||
{
|
|
||||||
index === 0 &&
|
|
||||||
<div>
|
|
||||||
{t('receip.product_name')}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<Input
|
<Input
|
||||||
className='text-center'
|
className="text-center"
|
||||||
name='name'
|
name="name"
|
||||||
readOnly
|
readOnly
|
||||||
value={item.name}
|
value={item.name}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
<div className="flex flex-col gap-2 items-center">
|
||||||
{
|
{index === 0 && <div>{t("receip.count")}</div>}
|
||||||
index === 0 &&
|
|
||||||
<div>
|
|
||||||
{t('receip.count')}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<Input
|
<Input
|
||||||
type='number'
|
type="number"
|
||||||
className='w-16 text-center'
|
className="w-16 text-center"
|
||||||
name='count'
|
name="count"
|
||||||
readOnly
|
readOnly
|
||||||
value={item.count}
|
value={item.count}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
<div className="flex flex-col gap-2 items-center">
|
||||||
{
|
{index === 0 && <div>{t("receip.unit_amount")}</div>}
|
||||||
index === 0 &&
|
|
||||||
<div>
|
|
||||||
{t('receip.unit_amount')}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<Input
|
<Input
|
||||||
className='text-center'
|
className="text-center"
|
||||||
name='unitPrice'
|
name="unitPrice"
|
||||||
readOnly
|
readOnly
|
||||||
value={item.unitPrice}
|
value={item.unitPrice}
|
||||||
seprator
|
seprator
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
<div className="flex flex-col gap-2 items-center">
|
||||||
{
|
{index === 0 && <div>{t("receip.discount")}</div>}
|
||||||
index === 0 &&
|
|
||||||
<div>
|
|
||||||
{t('receip.discount')}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<Input
|
<Input
|
||||||
type='number'
|
type="number"
|
||||||
className='w-16 text-center'
|
className="w-16 text-center"
|
||||||
name='discount'
|
name="discount"
|
||||||
readOnly
|
readOnly
|
||||||
value={item.discount}
|
value={item.discount}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
<div className="flex flex-col gap-2 items-center">
|
||||||
{
|
{index === 0 && <div>{t("receip.total_amount")}</div>}
|
||||||
index === 0 &&
|
|
||||||
<div>
|
|
||||||
{t('receip.total_amount')}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<Input
|
<Input
|
||||||
className='text-center bg-[#EBEDF5]'
|
className="text-center bg-[#EBEDF5]"
|
||||||
readOnly
|
readOnly
|
||||||
value={((Number(item.unitPrice) || 0) * (Number(item.count) || 0) - (Number(item.unitPrice) || 0) * (Number(item.count) || 0) * (Number(item.discount) / 100 || 0)).toFixed(1)}
|
value={((Number(item.unitPrice) || 0) * (Number(item.count) || 0) - (Number(item.unitPrice) || 0) * (Number(item.count) || 0) * (Number(item.discount) / 100 || 0)).toFixed(1)}
|
||||||
seprator
|
seprator
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div onClick={() => handleRemove(index)} className='size-10 border border-border rounded-xl flex justify-center items-center'>
|
<div
|
||||||
<Trash size={20} color='black' />
|
onClick={() => handleRemove(index)}
|
||||||
|
className="size-10 border border-border rounded-xl flex justify-center items-center"
|
||||||
|
>
|
||||||
|
<Trash
|
||||||
|
size={20}
|
||||||
|
color="black"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
})
|
})}
|
||||||
}
|
|
||||||
|
|
||||||
|
<div className="mt-8 flex flex-col gap-4 items-end">
|
||||||
|
<div className="xl:w-1/2 h-12 w-full rounded-xl flex px-4 bg-[#EBEDF5] text-sm text-description justify-between items-center">
|
||||||
<div className='mt-8 flex flex-col gap-4 items-end'>
|
<div>{t("receip.tax")}</div>
|
||||||
<div className='xl:w-1/2 h-12 w-full rounded-xl flex px-4 bg-[#EBEDF5] text-sm text-description justify-between items-center'>
|
<div className="text-black">
|
||||||
<div>
|
|
||||||
{t('receip.tax')}
|
|
||||||
</div>
|
|
||||||
<div className='text-black'>
|
|
||||||
{((items.reduce((acc, item) => acc + (Number(item.unitPrice) || 0) * (Number(item.count) || 0) - (Number(item.unitPrice) || 0) * (Number(item.count) || 0) * (Number(item.discount) / 100 || 0), 0)) * 0.10).toFixed(1)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='xl:w-1/2 h-12 w-full rounded-xl flex px-4 bg-[#EBEDF5] text-sm text-description justify-between items-center'>
|
|
||||||
<div>
|
|
||||||
{t('receip.total')}
|
|
||||||
</div>
|
|
||||||
<div className='text-black'>
|
|
||||||
{(
|
{(
|
||||||
items.reduce((acc, item) => acc + (Number(item.unitPrice) || 0) * (Number(item.count) || 0) - (Number(item.unitPrice) || 0) * (Number(item.count) || 0) * (Number(item.discount) / 100 || 0), 0) * 1.10
|
items.reduce(
|
||||||
|
(acc, item) => acc + (Number(item.unitPrice) || 0) * (Number(item.count) || 0) - (Number(item.unitPrice) || 0) * (Number(item.count) || 0) * (Number(item.discount) / 100 || 0),
|
||||||
|
0,
|
||||||
|
) * 0.1
|
||||||
|
).toFixed(1)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="xl:w-1/2 h-12 w-full rounded-xl flex px-4 bg-[#EBEDF5] text-sm text-description justify-between items-center">
|
||||||
|
<div>{t("receip.total")}</div>
|
||||||
|
<div className="text-black">
|
||||||
|
{(
|
||||||
|
items.reduce(
|
||||||
|
(acc, item) => acc + (Number(item.unitPrice) || 0) * (Number(item.count) || 0) - (Number(item.unitPrice) || 0) * (Number(item.count) || 0) * (Number(item.discount) / 100 || 0),
|
||||||
|
0,
|
||||||
|
) * 1.1
|
||||||
).toFixed(1)}
|
).toFixed(1)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='bg-white w-sidebar 3xl:block hidden py-10 px-5 h-fit rounded-3xl'>
|
<div className="bg-white w-sidebar 3xl:block hidden py-10 px-5 h-fit rounded-3xl">
|
||||||
<div className='text-sm'>
|
<div className="text-sm">{t("ticket.title_hint")}</div>
|
||||||
{t('ticket.title_hint')}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-6'>
|
<div className="mt-6">
|
||||||
<div className='flex items-start gap-2 border-b pb-5'>
|
<div className="flex items-start gap-2 border-b pb-5">
|
||||||
<div>
|
<div>
|
||||||
<TickSquare size={20} color='black' variant='Bold' />
|
<TickSquare
|
||||||
|
size={20}
|
||||||
|
color="black"
|
||||||
|
variant="Bold"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='text-description text-xs leading-5'>
|
<div className="text-description text-xs leading-5">سوالات - مشکلاتی که به یک موضوع مربوط میشوند را در یک درخواست پشتیبانی پیگیر باشید و چند درخواست برای یک موضوع باز نکنید.</div>
|
||||||
سوالات - مشکلاتی که به یک موضوع مربوط میشوند را در یک درخواست پشتیبانی پیگیر باشید و چند درخواست برای یک موضوع باز نکنید.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex items-start gap-2 mt-6 border-b pb-5">
|
||||||
<div className='flex items-start gap-2 mt-6 border-b pb-5'>
|
|
||||||
<div>
|
<div>
|
||||||
<TickSquare size={20} color='black' variant='Bold' />
|
<TickSquare
|
||||||
|
size={20}
|
||||||
|
color="black"
|
||||||
|
variant="Bold"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='text-description text-xs leading-5'>
|
<div className="text-description text-xs leading-5">لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.</div>
|
||||||
لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex items-start gap-2 mt-6">
|
||||||
<div className='flex items-start gap-2 mt-6'>
|
|
||||||
<div>
|
<div>
|
||||||
<TickSquare size={20} color='black' variant='Bold' />
|
<TickSquare
|
||||||
|
size={20}
|
||||||
|
color="black"
|
||||||
|
variant="Bold"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='text-description text-xs leading-5'>
|
<div className="text-description text-xs leading-5">
|
||||||
پاسخگویی 24 ساعته تلفنی را تنها از میهن وب هاست می توانید انتظار داشته باشید .بخش پشتیبانی در هر ساعتی حتی در روز های تعطیل آماده پیگیری سریع مشکلات کاربران است.
|
پاسخگویی 24 ساعته تلفنی را تنها از میهن وب هاست می توانید انتظار داشته باشید .بخش پشتیبانی در هر ساعتی حتی در روز های تعطیل آماده پیگیری سریع مشکلات کاربران است.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -472,9 +437,9 @@ const CreateReceipt: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default CreateReceipt
|
export default CreateReceipt;
|
||||||
|
|||||||
+263
-296
@@ -1,22 +1,22 @@
|
|||||||
import { ChangeEvent, FC, Fragment, useState, useEffect } from 'react'
|
import { useFormik } from "formik";
|
||||||
import { useTranslation } from 'react-i18next'
|
import { Add, TickCircle, TickSquare, Trash } from "iconsax-react";
|
||||||
import Button from '../../components/Button'
|
import { ChangeEvent, FC, Fragment, useEffect, useState } from "react";
|
||||||
import { Add, TickCircle, TickSquare, Trash } from 'iconsax-react'
|
import { useTranslation } from "react-i18next";
|
||||||
import Select from '../../components/Select'
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import Input from '../../components/Input'
|
import * as Yup from "yup";
|
||||||
import { CreateReceiptType, ReceiptCreateItemsType } from './types/ReceiptTypes'
|
import Button from "../../components/Button";
|
||||||
import { useFormik } from 'formik'
|
import Input from "../../components/Input";
|
||||||
import * as Yup from 'yup'
|
import PageLoading from "../../components/PageLoading";
|
||||||
import { toast } from 'react-toastify'
|
import Select from "../../components/Select";
|
||||||
import { useGetCustomers } from '../customer/hooks/useCustomerData'
|
import SwitchComponent from "../../components/Switch";
|
||||||
import { CustomerItemType } from '../customer/types/CustomerTypes'
|
import { toast } from "../../components/Toast";
|
||||||
import PageLoading from '../../components/PageLoading'
|
import { Pages } from "../../config/Pages";
|
||||||
import { useGetInvoiceById, useUpdateInvoice } from './hooks/useReceiptData'
|
import { ErrorType } from "../../helpers/types";
|
||||||
import { useNavigate, useParams } from 'react-router-dom'
|
import { useGetCustomers } from "../customer/hooks/useCustomerData";
|
||||||
import { Pages } from '../../config/Pages'
|
import { CustomerItemType } from "../customer/types/CustomerTypes";
|
||||||
import { ErrorType } from '../../helpers/types'
|
import { RecurringPeriodEnum } from "./enum/ReceipEnum";
|
||||||
import { RecurringPeriodEnum } from './enum/ReceipEnum'
|
import { useGetInvoiceById, useUpdateInvoice } from "./hooks/useReceiptData";
|
||||||
import SwitchComponent from '../../components/Switch'
|
import { CreateReceiptType, ReceiptCreateItemsType } from "./types/ReceiptTypes";
|
||||||
|
|
||||||
// Add interface for invoice item from API
|
// Add interface for invoice item from API
|
||||||
interface InvoiceItemType {
|
interface InvoiceItemType {
|
||||||
@@ -32,47 +32,49 @@ interface InvoiceItemType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CreateReceipt: FC = () => {
|
const CreateReceipt: FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
const navigate = useNavigate()
|
const { id } = useParams();
|
||||||
const { id } = useParams()
|
const { t } = useTranslation("global");
|
||||||
const { t } = useTranslation('global')
|
const { data: invoice, isPending: isLoading } = useGetInvoiceById(id as string);
|
||||||
const { data: invoice, isPending: isLoading } = useGetInvoiceById(id as string)
|
const updateInvoice = useUpdateInvoice();
|
||||||
const updateInvoice = useUpdateInvoice()
|
const [items, setItems] = useState<ReceiptCreateItemsType[]>([]);
|
||||||
const [items, setItems] = useState<ReceiptCreateItemsType[]>([])
|
const [customer, setCustomer] = useState<CustomerItemType>();
|
||||||
const [customer, setCustomer] = useState<CustomerItemType>()
|
const [isRecurring, setIsRecurring] = useState<boolean>(false);
|
||||||
const [isRecurring, setIsRecurring] = useState<boolean>(false)
|
const [type, setType] = useState<RecurringPeriodEnum>(RecurringPeriodEnum.WEEKLY);
|
||||||
const [type, setType] = useState<RecurringPeriodEnum>(RecurringPeriodEnum.WEEKLY)
|
const [maxRecurringCycles, setMaxRecurringCycles] = useState<number>(0);
|
||||||
const [maxRecurringCycles, setMaxRecurringCycles] = useState<number>(0)
|
const getCustomers = useGetCustomers(1, 50, true);
|
||||||
const getCustomers = useGetCustomers(1, true)
|
|
||||||
|
|
||||||
const formik = useFormik<ReceiptCreateItemsType>({
|
const formik = useFormik<ReceiptCreateItemsType>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
name: '',
|
name: "",
|
||||||
count: '',
|
count: "",
|
||||||
unitPrice: '',
|
unitPrice: "",
|
||||||
discount: ''
|
discount: "",
|
||||||
},
|
},
|
||||||
validationSchema: Yup.object({
|
validationSchema: Yup.object({
|
||||||
name: Yup.string().required(t('errors.required')),
|
name: Yup.string().required(t("errors.required")),
|
||||||
count: Yup.string().required(t('errors.required')),
|
count: Yup.string().required(t("errors.required")),
|
||||||
unitPrice: Yup.string().required(t('errors.required')),
|
unitPrice: Yup.string().required(t("errors.required")),
|
||||||
}),
|
}),
|
||||||
onSubmit: (values) => {
|
onSubmit: (values) => {
|
||||||
setItems((prev) => [...prev, {
|
setItems((prev) => [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
count: +values.count,
|
count: +values.count,
|
||||||
discount: +values.discount,
|
discount: +values.discount,
|
||||||
name: values.name,
|
name: values.name,
|
||||||
unitPrice: +values.unitPrice
|
unitPrice: +values.unitPrice,
|
||||||
}])
|
},
|
||||||
formik.resetForm()
|
]);
|
||||||
}
|
formik.resetForm();
|
||||||
})
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const handleChangeCustomer = (e: ChangeEvent<HTMLSelectElement>) => {
|
const handleChangeCustomer = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||||
const value = e.target.value
|
const value = e.target.value;
|
||||||
const customer = getCustomers.data?.data?.customers?.find((item: CustomerItemType) => item.id === value)
|
const customer = getCustomers.data?.data?.customers?.find((item: CustomerItemType) => item.id === value);
|
||||||
setCustomer(customer)
|
setCustomer(customer);
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
if (customer) {
|
if (customer) {
|
||||||
@@ -81,28 +83,30 @@ const CreateReceipt: FC = () => {
|
|||||||
items: items,
|
items: items,
|
||||||
isRecurring: isRecurring,
|
isRecurring: isRecurring,
|
||||||
recurringPeriod: isRecurring ? +type : undefined,
|
recurringPeriod: isRecurring ? +type : undefined,
|
||||||
maxRecurringCycles: isRecurring ? maxRecurringCycles : undefined
|
maxRecurringCycles: isRecurring ? maxRecurringCycles : undefined,
|
||||||
}
|
};
|
||||||
|
|
||||||
updateInvoice.mutate({ id: id as string, params }, {
|
updateInvoice.mutate(
|
||||||
|
{ id: id as string, params },
|
||||||
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('success'))
|
toast(t("success"), "success");
|
||||||
navigate(Pages.receipts.index)
|
navigate(Pages.receipts.index);
|
||||||
},
|
},
|
||||||
onError(error: ErrorType) {
|
onError(error: ErrorType) {
|
||||||
toast.error(error.response?.data?.error.message[0])
|
toast(error.response?.data?.error.message[0], "error");
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleRemove = (index: number) => {
|
const handleRemove = (index: number) => {
|
||||||
setItems((prev) => prev.filter((_, i) => i !== index))
|
setItems((prev) => prev.filter((_, i) => i !== index));
|
||||||
}
|
};
|
||||||
|
|
||||||
// Prefill form when editing an invoice
|
// Prefill form when editing an invoice
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
// Check if we need to access invoice.data or directly invoice
|
// Check if we need to access invoice.data or directly invoice
|
||||||
const invoiceData = invoice?.data?.invoice || invoice;
|
const invoiceData = invoice?.data?.invoice || invoice;
|
||||||
|
|
||||||
@@ -111,9 +115,7 @@ const CreateReceipt: FC = () => {
|
|||||||
|
|
||||||
// 1. If there's a user, find matching customer
|
// 1. If there's a user, find matching customer
|
||||||
if (invoiceData.user && getCustomers.data?.data?.customers) {
|
if (invoiceData.user && getCustomers.data?.data?.customers) {
|
||||||
const foundCustomer = getCustomers.data.data.customers.find(
|
const foundCustomer = getCustomers.data.data.customers.find((item: CustomerItemType) => item.id === invoiceData.user.id);
|
||||||
(item: CustomerItemType) => item.id === invoiceData.user.id
|
|
||||||
);
|
|
||||||
setCustomer(foundCustomer);
|
setCustomer(foundCustomer);
|
||||||
console.log("Found customer:", foundCustomer);
|
console.log("Found customer:", foundCustomer);
|
||||||
setCustomer(foundCustomer);
|
setCustomer(foundCustomer);
|
||||||
@@ -123,20 +125,21 @@ const CreateReceipt: FC = () => {
|
|||||||
if (invoiceData.items && invoiceData.items.length > 0) {
|
if (invoiceData.items && invoiceData.items.length > 0) {
|
||||||
console.log("Setting items:", invoiceData.items);
|
console.log("Setting items:", invoiceData.items);
|
||||||
// Map API items to the format expected by our component
|
// Map API items to the format expected by our component
|
||||||
setItems(invoiceData.items.map((item: InvoiceItemType) => ({
|
setItems(
|
||||||
|
invoiceData.items.map((item: InvoiceItemType) => ({
|
||||||
name: item.name,
|
name: item.name,
|
||||||
count: item.count,
|
count: item.count,
|
||||||
unitPrice: item.unitPrice,
|
unitPrice: item.unitPrice,
|
||||||
discount: item.discount || 0
|
discount: item.discount || 0,
|
||||||
})));
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
// 3. Set formik values from first item
|
// 3. Set formik values from first item
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Set recurring settings
|
// 4. Set recurring settings
|
||||||
setIsRecurring(Boolean(invoiceData.isRecurring));
|
setIsRecurring(Boolean(invoiceData.isRecurring));
|
||||||
if (typeof invoiceData.recurringPeriod === 'number') {
|
if (typeof invoiceData.recurringPeriod === "number") {
|
||||||
setType(invoiceData.recurringPeriod.toString());
|
setType(invoiceData.recurringPeriod.toString());
|
||||||
}
|
}
|
||||||
setMaxRecurringCycles(invoiceData.maxRecurringCycles || 0);
|
setMaxRecurringCycles(invoiceData.maxRecurringCycles || 0);
|
||||||
@@ -149,8 +152,8 @@ const CreateReceipt: FC = () => {
|
|||||||
// Trigger the onChange manually with a synthetic event
|
// Trigger the onChange manually with a synthetic event
|
||||||
const event = {
|
const event = {
|
||||||
target: {
|
target: {
|
||||||
value: invoice.user.id
|
value: invoice.user.id,
|
||||||
}
|
},
|
||||||
} as ChangeEvent<HTMLSelectElement>;
|
} as ChangeEvent<HTMLSelectElement>;
|
||||||
|
|
||||||
handleChangeCustomer(event);
|
handleChangeCustomer(event);
|
||||||
@@ -166,387 +169,351 @@ const CreateReceipt: FC = () => {
|
|||||||
}, [customer, invoice]);
|
}, [customer, invoice]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-4'>
|
<div className="mt-4">
|
||||||
{
|
{getCustomers.isPending || isLoading ? (
|
||||||
getCustomers.isPending || isLoading ?
|
|
||||||
<PageLoading />
|
<PageLoading />
|
||||||
:
|
) : (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className='flex justify-between items-center'>
|
<div className="flex justify-between items-center">
|
||||||
<div>
|
<div>{t("receip.add_receip")}</div>
|
||||||
{t('receip.add_receip')}
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
className='px-5'
|
className="px-5"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
isLoading={updateInvoice.isPending}
|
isLoading={updateInvoice.isPending}
|
||||||
>
|
>
|
||||||
<div className='flex gap-2 items-center'>
|
<div className="flex gap-2 items-center">
|
||||||
<TickCircle size={20} color='white' />
|
<TickCircle
|
||||||
<div>
|
size={20}
|
||||||
{t('receip.submit_receip')}
|
color="white"
|
||||||
</div>
|
/>
|
||||||
|
<div>{t("receip.submit_receip")}</div>
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex gap-6 xl:mt-8 mt-4'>
|
<div className="flex gap-6 xl:mt-8 mt-4">
|
||||||
<div className='flex-1 bg-white py-8 xl:px-10 px-4 rounded-3xl'>
|
<div className="flex-1 bg-white py-8 xl:px-10 px-4 rounded-3xl">
|
||||||
<div>
|
<div>{t("receip.customer_information")}</div>
|
||||||
{t('receip.customer_information')}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-8'>
|
<div className="mt-8">
|
||||||
<div className='w-full xl:w-1/2'>
|
<div className="w-full xl:w-1/2">
|
||||||
<Select
|
<Select
|
||||||
label={t('receip.customer')}
|
label={t("receip.customer")}
|
||||||
placeholder={t('select')}
|
placeholder={t("select")}
|
||||||
value={(invoice?.data?.invoice || invoice)?.user?.id}
|
value={(invoice?.data?.invoice || invoice)?.user?.id}
|
||||||
items={getCustomers.data?.data?.customers?.map((item: CustomerItemType) => {
|
items={getCustomers.data?.data?.customers?.map((item: CustomerItemType) => {
|
||||||
return {
|
return {
|
||||||
label: item.firstName + ' ' + item.lastName,
|
label: item.firstName + " " + item.lastName,
|
||||||
value: item.id
|
value: item.id,
|
||||||
}
|
};
|
||||||
})}
|
})}
|
||||||
onChange={handleChangeCustomer}
|
onChange={handleChangeCustomer}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{customer && (
|
||||||
customer &&
|
<div className="mt-10 bg-[#F6F7FB] p-8 rounded-3xl text-sm">
|
||||||
<div className='mt-10 bg-[#F6F7FB] p-8 rounded-3xl text-sm'>
|
<div className="flex justify-between items-center border-b border-border border-dashed pb-7">
|
||||||
<div className='flex justify-between items-center border-b border-border border-dashed pb-7'>
|
<div className="flex items-center gap-2">
|
||||||
<div className='flex items-center gap-2'>
|
<div className="text-description">{t("receip.type_person")}</div>
|
||||||
<div className='text-description'>{t('receip.type_person')}</div>
|
<div>{customer.legalUser ? "حقوقی" : "حقیقی"}</div>
|
||||||
<div>{customer.legalUser ? 'حقوقی' : 'حقیقی'}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2'>
|
<div className="flex items-center gap-2">
|
||||||
<div className='text-description'>{t('receip.representativeـname')}</div>
|
<div className="text-description">{t("receip.representativeـname")}</div>
|
||||||
<div>{customer.firstName + ' ' + customer.lastName}</div>
|
<div>{customer.firstName + " " + customer.lastName}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2'>
|
<div className="flex items-center gap-2">
|
||||||
<div className='text-description'>{t('receip.company_name')}</div>
|
<div className="text-description">{t("receip.company_name")}</div>
|
||||||
<div>{customer?.legalUser?.registrationName}</div>
|
<div>{customer?.legalUser?.registrationName}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2'>
|
<div className="flex items-center gap-2">
|
||||||
<div className='text-description'>{t('receip.registration_number')}</div>
|
<div className="text-description">{t("receip.registration_number")}</div>
|
||||||
<div>{customer?.legalUser?.registrationCode}</div>
|
<div>{customer?.legalUser?.registrationCode}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-between items-center mt-7'>
|
<div className="flex justify-between items-center mt-7">
|
||||||
<div className='flex items-center gap-2 flex-1'>
|
<div className="flex items-center gap-2 flex-1">
|
||||||
<div className='text-description'>{t('receip.tel_company')}</div>
|
<div className="text-description">{t("receip.tel_company")}</div>
|
||||||
<div>{customer?.realUser?.phone || customer?.legalUser?.phone}</div>
|
<div>{customer?.realUser?.phone || customer?.legalUser?.phone}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2 flex-1'>
|
<div className="flex items-center gap-2 flex-1">
|
||||||
<div className='text-description'>{t('receip.national_code')}</div>
|
<div className="text-description">{t("receip.national_code")}</div>
|
||||||
<div>{customer.nationalCode}</div>
|
<div>{customer.nationalCode}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2 flex-1'>
|
<div className="flex items-center gap-2 flex-1">
|
||||||
<div className='text-description'>{t('receip.postal_code')}</div>
|
<div className="text-description">{t("receip.postal_code")}</div>
|
||||||
<div>{customer?.realUser?.address?.postalCode || customer?.legalUser?.address?.postalCode}</div>
|
<div>{customer?.realUser?.address?.postalCode || customer?.legalUser?.address?.postalCode}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-between items-center mt-7'>
|
<div className="flex justify-between items-center mt-7">
|
||||||
<div className='flex items-center gap-2 flex-1'>
|
<div className="flex items-center gap-2 flex-1">
|
||||||
<div className='text-description'>{t('receip.state')}</div>
|
<div className="text-description">{t("receip.state")}</div>
|
||||||
<div>{customer?.realUser?.address?.city?.province?.name || customer?.legalUser?.address?.city?.province?.name}</div>
|
<div>{customer?.realUser?.address?.city?.province?.name || customer?.legalUser?.address?.city?.province?.name}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2 flex-1'>
|
<div className="flex items-center gap-2 flex-1">
|
||||||
<div className='text-description'>{t('receip.city')}</div>
|
<div className="text-description">{t("receip.city")}</div>
|
||||||
<div>{customer?.realUser?.address?.city?.name || customer?.legalUser?.address?.city?.name}</div>
|
<div>{customer?.realUser?.address?.city?.name || customer?.legalUser?.address?.city?.name}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2 flex-1'>
|
<div className="flex items-center gap-2 flex-1">
|
||||||
<div className='text-description'>{t('receip.address')}</div>
|
<div className="text-description">{t("receip.address")}</div>
|
||||||
<div>{customer?.realUser?.address?.fullAddress || customer?.legalUser?.address?.fullAddress}</div>
|
<div>{customer?.realUser?.address?.fullAddress || customer?.legalUser?.address?.fullAddress}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
|
|
||||||
<div className='mt-10'>
|
<div className="mt-10">
|
||||||
<SwitchComponent
|
<SwitchComponent
|
||||||
label={t('receip.recurring')}
|
label={t("receip.recurring")}
|
||||||
active={isRecurring}
|
active={isRecurring}
|
||||||
onChange={(value) => setIsRecurring(value)}
|
onChange={(value) => setIsRecurring(value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{isRecurring && (
|
||||||
isRecurring &&
|
<div className="mt-10">
|
||||||
<div className='mt-10'>
|
<div>{t("receip.select_type")}</div>
|
||||||
<div>
|
|
||||||
{t('receip.select_type')}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-4 rowTwoInput'>
|
<div className="mt-4 rowTwoInput">
|
||||||
<Select
|
<Select
|
||||||
label={t('select')}
|
label={t("select")}
|
||||||
items={[
|
items={[
|
||||||
{ label: t('receip.WEEKLY'), value: RecurringPeriodEnum.WEEKLY.toString() },
|
{ label: t("receip.WEEKLY"), value: RecurringPeriodEnum.WEEKLY.toString() },
|
||||||
{ label: t('receip.MONTHLY'), value: RecurringPeriodEnum.MONTHLY.toString() },
|
{ label: t("receip.MONTHLY"), value: RecurringPeriodEnum.MONTHLY.toString() },
|
||||||
{ label: t('receip.QUARTERLY'), value: RecurringPeriodEnum.QUARTERLY.toString() },
|
{ label: t("receip.QUARTERLY"), value: RecurringPeriodEnum.QUARTERLY.toString() },
|
||||||
{ label: t('receip.SEMIANNUALLY'), value: RecurringPeriodEnum.SEMIANNUALLY.toString() },
|
{ label: t("receip.SEMIANNUALLY"), value: RecurringPeriodEnum.SEMIANNUALLY.toString() },
|
||||||
{ label: t('receip.ANNUALLY'), value: RecurringPeriodEnum.ANNUALLY.toString() }
|
{ label: t("receip.ANNUALLY"), value: RecurringPeriodEnum.ANNUALLY.toString() },
|
||||||
]}
|
]}
|
||||||
value={type}
|
value={type}
|
||||||
onChange={(e) => setType(e.target.value as unknown as RecurringPeriodEnum)}
|
onChange={(e) => setType(e.target.value as unknown as RecurringPeriodEnum)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
label={t('receip.maxRecurringCycles')}
|
label={t("receip.maxRecurringCycles")}
|
||||||
name='maxRecurringCycles'
|
name="maxRecurringCycles"
|
||||||
value={maxRecurringCycles}
|
value={maxRecurringCycles}
|
||||||
onChange={(e) => setMaxRecurringCycles(+e.target.value)}
|
onChange={(e) => setMaxRecurringCycles(+e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
|
|
||||||
<div className='mt-10'>
|
<div className="mt-10">
|
||||||
<div>{t('receip.receipt_information')}</div>
|
<div>{t("receip.receipt_information")}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-8 border-b border-dashed border-border pb-7 flex items-end justify-between text-xs text-description'>
|
<div className="mt-8 border-b border-dashed border-border pb-7 flex items-end justify-between text-xs text-description">
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
<div className="flex flex-col gap-2 items-center">
|
||||||
<div>
|
<div>{t("receip.number")}</div>
|
||||||
{t('receip.number')}
|
<div className="size-10 bg-[#EBEDF5] rounded-xl flex justify-center items-center">{items.length + 1}</div>
|
||||||
</div>
|
|
||||||
<div className='size-10 bg-[#EBEDF5] rounded-xl flex justify-center items-center'>
|
|
||||||
{items.length + 1}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
<div className="flex flex-col gap-2 items-center">
|
||||||
<div>
|
<div>{t("receip.product_name")}</div>
|
||||||
{t('receip.product_name')}
|
|
||||||
</div>
|
|
||||||
<Input
|
<Input
|
||||||
className='text-center'
|
className="text-center"
|
||||||
name='name'
|
name="name"
|
||||||
value={formik.values.name}
|
value={formik.values.name}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
<div className="flex flex-col gap-2 items-center">
|
||||||
<div>
|
<div>{t("receip.count")}</div>
|
||||||
{t('receip.count')}
|
|
||||||
</div>
|
|
||||||
<Input
|
<Input
|
||||||
type='number'
|
type="number"
|
||||||
className='w-16 text-center'
|
className="w-16 text-center"
|
||||||
name='count'
|
name="count"
|
||||||
value={formik.values.count}
|
value={formik.values.count}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
<div className="flex flex-col gap-2 items-center">
|
||||||
<div>
|
<div>{t("receip.unit_amount")}</div>
|
||||||
{t('receip.unit_amount')}
|
|
||||||
</div>
|
|
||||||
<Input
|
<Input
|
||||||
className='text-center'
|
className="text-center"
|
||||||
name='unitPrice'
|
name="unitPrice"
|
||||||
value={formik.values.unitPrice}
|
value={formik.values.unitPrice}
|
||||||
onChange={(e) => formik.setFieldValue('unitPrice', e.target.value)}
|
onChange={(e) => formik.setFieldValue("unitPrice", e.target.value)}
|
||||||
seprator
|
seprator
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
<div className="flex flex-col gap-2 items-center">
|
||||||
<div>
|
<div>{t("receip.discount")}</div>
|
||||||
{t('receip.discount')}
|
|
||||||
</div>
|
|
||||||
<Input
|
<Input
|
||||||
type='number'
|
type="number"
|
||||||
className='w-16 text-center'
|
className="w-16 text-center"
|
||||||
name='discount'
|
name="discount"
|
||||||
value={formik.values.discount}
|
value={formik.values.discount}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
<div className="flex flex-col gap-2 items-center">
|
||||||
<div>
|
<div>{t("receip.total_amount")}</div>
|
||||||
{t('receip.total_amount')}
|
|
||||||
</div>
|
|
||||||
<Input
|
<Input
|
||||||
className='text-center bg-[#EBEDF5]'
|
className="text-center bg-[#EBEDF5]"
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div onClick={() => {
|
<div
|
||||||
|
onClick={() => {
|
||||||
if (formik.errors.name || formik.errors.count || formik.errors.unitPrice || formik.errors.discount) {
|
if (formik.errors.name || formik.errors.count || formik.errors.unitPrice || formik.errors.discount) {
|
||||||
toast.error(t('receip.error_empty'))
|
toast(t("receip.error_empty"), "error");
|
||||||
} else {
|
} else {
|
||||||
formik.handleSubmit()
|
formik.handleSubmit();
|
||||||
}
|
}
|
||||||
}} className='size-10 border border-border rounded-xl flex justify-center items-center'>
|
}}
|
||||||
<Add size={20} color='black' />
|
className="size-10 border border-border rounded-xl flex justify-center items-center"
|
||||||
|
>
|
||||||
|
<Add
|
||||||
|
size={20}
|
||||||
|
color="black"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{items.map((item, index: number) => {
|
||||||
</div>
|
|
||||||
|
|
||||||
{
|
|
||||||
items.map((item, index: number) => {
|
|
||||||
return (
|
return (
|
||||||
<div key={item.name} className='mt-6 flex items-end justify-between text-xs text-description'>
|
<div
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
key={item.name}
|
||||||
{
|
className="mt-6 flex items-end justify-between text-xs text-description"
|
||||||
index === 0 &&
|
>
|
||||||
<div>
|
<div className="flex flex-col gap-2 items-center">
|
||||||
{t('receip.number')}
|
{index === 0 && <div>{t("receip.number")}</div>}
|
||||||
|
<div className="size-10 bg-[#EBEDF5] rounded-xl flex justify-center items-center">{index + 1}</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
<div className="flex flex-col gap-2 items-center">
|
||||||
<div className='size-10 bg-[#EBEDF5] rounded-xl flex justify-center items-center'>
|
{index === 0 && <div>{t("receip.product_name")}</div>}
|
||||||
{index + 1}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
|
||||||
{
|
|
||||||
index === 0 &&
|
|
||||||
<div>
|
|
||||||
{t('receip.product_name')}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<Input
|
<Input
|
||||||
className='text-center'
|
className="text-center"
|
||||||
name='name'
|
name="name"
|
||||||
readOnly
|
readOnly
|
||||||
value={item.name}
|
value={item.name}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
<div className="flex flex-col gap-2 items-center">
|
||||||
{
|
{index === 0 && <div>{t("receip.count")}</div>}
|
||||||
index === 0 &&
|
|
||||||
<div>
|
|
||||||
{t('receip.count')}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<Input
|
<Input
|
||||||
type='number'
|
type="number"
|
||||||
className='w-16 text-center'
|
className="w-16 text-center"
|
||||||
name='count'
|
name="count"
|
||||||
readOnly
|
readOnly
|
||||||
value={item.count}
|
value={item.count}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
<div className="flex flex-col gap-2 items-center">
|
||||||
{
|
{index === 0 && <div>{t("receip.unit_amount")}</div>}
|
||||||
index === 0 &&
|
|
||||||
<div>
|
|
||||||
{t('receip.unit_amount')}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<Input
|
<Input
|
||||||
className='text-center'
|
className="text-center"
|
||||||
name='unitPrice'
|
name="unitPrice"
|
||||||
readOnly
|
readOnly
|
||||||
value={item.unitPrice}
|
value={item.unitPrice}
|
||||||
seprator
|
seprator
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
<div className="flex flex-col gap-2 items-center">
|
||||||
{
|
{index === 0 && <div>{t("receip.discount")}</div>}
|
||||||
index === 0 &&
|
|
||||||
<div>
|
|
||||||
{t('receip.discount')}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<Input
|
<Input
|
||||||
type='number'
|
type="number"
|
||||||
className='w-16 text-center'
|
className="w-16 text-center"
|
||||||
name='discount'
|
name="discount"
|
||||||
readOnly
|
readOnly
|
||||||
value={item.discount}
|
value={item.discount}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-col gap-2 items-center'>
|
<div className="flex flex-col gap-2 items-center">
|
||||||
{
|
{index === 0 && <div>{t("receip.total_amount")}</div>}
|
||||||
index === 0 &&
|
|
||||||
<div>
|
|
||||||
{t('receip.total_amount')}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<Input
|
<Input
|
||||||
className='text-center bg-[#EBEDF5]'
|
className="text-center bg-[#EBEDF5]"
|
||||||
readOnly
|
readOnly
|
||||||
value={((Number(item.unitPrice) || 0) * (Number(item.count) || 0) - (Number(item.unitPrice) || 0) * (Number(item.count) || 0) * (Number(item.discount) / 100 || 0)).toFixed(1)}
|
value={((Number(item.unitPrice) || 0) * (Number(item.count) || 0) - (Number(item.unitPrice) || 0) * (Number(item.count) || 0) * (Number(item.discount) / 100 || 0)).toFixed(1)}
|
||||||
seprator
|
seprator
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div onClick={() => handleRemove(index)} className='size-10 border border-border rounded-xl flex justify-center items-center'>
|
<div
|
||||||
<Trash size={20} color='black' />
|
onClick={() => handleRemove(index)}
|
||||||
|
className="size-10 border border-border rounded-xl flex justify-center items-center"
|
||||||
|
>
|
||||||
|
<Trash
|
||||||
|
size={20}
|
||||||
|
color="black"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
})
|
})}
|
||||||
}
|
|
||||||
|
|
||||||
|
<div className="mt-8 flex flex-col gap-4 items-end">
|
||||||
|
<div className="xl:w-1/2 h-12 w-full rounded-xl flex px-4 bg-[#EBEDF5] text-sm text-description justify-between items-center">
|
||||||
<div className='mt-8 flex flex-col gap-4 items-end'>
|
<div>{t("receip.tax")}</div>
|
||||||
<div className='xl:w-1/2 h-12 w-full rounded-xl flex px-4 bg-[#EBEDF5] text-sm text-description justify-between items-center'>
|
<div className="text-black">
|
||||||
<div>
|
|
||||||
{t('receip.tax')}
|
|
||||||
</div>
|
|
||||||
<div className='text-black'>
|
|
||||||
{(items.reduce((acc, item) => acc + (Number(item.unitPrice) || 0) * (Number(item.count) || 0) - (Number(item.unitPrice) || 0) * (Number(item.count) || 0) * (Number(item.discount) / 100 || 0), 0) * 0.10).toFixed(1)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='xl:w-1/2 h-12 w-full rounded-xl flex px-4 bg-[#EBEDF5] text-sm text-description justify-between items-center'>
|
|
||||||
<div>
|
|
||||||
{t('receip.total')}
|
|
||||||
</div>
|
|
||||||
<div className='text-black'>
|
|
||||||
{(
|
{(
|
||||||
items.reduce((acc, item) => acc + (Number(item.unitPrice) || 0) * (Number(item.count) || 0) - (Number(item.unitPrice) || 0) * (Number(item.count) || 0) * (Number(item.discount) / 100 || 0), 0) * 1.10
|
items.reduce(
|
||||||
|
(acc, item) => acc + (Number(item.unitPrice) || 0) * (Number(item.count) || 0) - (Number(item.unitPrice) || 0) * (Number(item.count) || 0) * (Number(item.discount) / 100 || 0),
|
||||||
|
0,
|
||||||
|
) * 0.1
|
||||||
|
).toFixed(1)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="xl:w-1/2 h-12 w-full rounded-xl flex px-4 bg-[#EBEDF5] text-sm text-description justify-between items-center">
|
||||||
|
<div>{t("receip.total")}</div>
|
||||||
|
<div className="text-black">
|
||||||
|
{(
|
||||||
|
items.reduce(
|
||||||
|
(acc, item) => acc + (Number(item.unitPrice) || 0) * (Number(item.count) || 0) - (Number(item.unitPrice) || 0) * (Number(item.count) || 0) * (Number(item.discount) / 100 || 0),
|
||||||
|
0,
|
||||||
|
) * 1.1
|
||||||
).toFixed(1)}
|
).toFixed(1)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='bg-white w-sidebar 3xl:block hidden py-10 px-5 h-fit rounded-3xl'>
|
<div className="bg-white w-sidebar 3xl:block hidden py-10 px-5 h-fit rounded-3xl">
|
||||||
<div className='text-sm'>
|
<div className="text-sm">{t("ticket.title_hint")}</div>
|
||||||
{t('ticket.title_hint')}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-6'>
|
<div className="mt-6">
|
||||||
<div className='flex items-start gap-2 border-b pb-5'>
|
<div className="flex items-start gap-2 border-b pb-5">
|
||||||
<div>
|
<div>
|
||||||
<TickSquare size={20} color='black' variant='Bold' />
|
<TickSquare
|
||||||
|
size={20}
|
||||||
|
color="black"
|
||||||
|
variant="Bold"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='text-description text-xs leading-5'>
|
<div className="text-description text-xs leading-5">سوالات - مشکلاتی که به یک موضوع مربوط میشوند را در یک درخواست پشتیبانی پیگیر باشید و چند درخواست برای یک موضوع باز نکنید.</div>
|
||||||
سوالات - مشکلاتی که به یک موضوع مربوط میشوند را در یک درخواست پشتیبانی پیگیر باشید و چند درخواست برای یک موضوع باز نکنید.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex items-start gap-2 mt-6 border-b pb-5">
|
||||||
<div className='flex items-start gap-2 mt-6 border-b pb-5'>
|
|
||||||
<div>
|
<div>
|
||||||
<TickSquare size={20} color='black' variant='Bold' />
|
<TickSquare
|
||||||
|
size={20}
|
||||||
|
color="black"
|
||||||
|
variant="Bold"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='text-description text-xs leading-5'>
|
<div className="text-description text-xs leading-5">لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.</div>
|
||||||
لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex items-start gap-2 mt-6">
|
||||||
<div className='flex items-start gap-2 mt-6'>
|
|
||||||
<div>
|
<div>
|
||||||
<TickSquare size={20} color='black' variant='Bold' />
|
<TickSquare
|
||||||
|
size={20}
|
||||||
|
color="black"
|
||||||
|
variant="Bold"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='text-description text-xs leading-5'>
|
<div className="text-description text-xs leading-5">
|
||||||
پاسخگویی 24 ساعته تلفنی را تنها از میهن وب هاست می توانید انتظار داشته باشید .بخش پشتیبانی در هر ساعتی حتی در روز های تعطیل آماده پیگیری سریع مشکلات کاربران است.
|
پاسخگویی 24 ساعته تلفنی را تنها از میهن وب هاست می توانید انتظار داشته باشید .بخش پشتیبانی در هر ساعتی حتی در روز های تعطیل آماده پیگیری سریع مشکلات کاربران است.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -554,9 +521,9 @@ const CreateReceipt: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default CreateReceipt
|
export default CreateReceipt;
|
||||||
|
|||||||
@@ -16,11 +16,13 @@ import Pagination from '../../components/Pagination'
|
|||||||
import { Pages } from '../../config/Pages'
|
import { Pages } from '../../config/Pages'
|
||||||
import TrashWithConfrim from '../../components/TrashWithConfrim'
|
import TrashWithConfrim from '../../components/TrashWithConfrim'
|
||||||
import { ErrorType } from '../../helpers/types'
|
import { ErrorType } from '../../helpers/types'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../components/Toast';
|
||||||
|
import { usePermissions } from '../../hooks/usePermissions'
|
||||||
|
|
||||||
const ReceiptsList: FC = () => {
|
const ReceiptsList: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
|
const { canUpdate, canDelete } = usePermissions()
|
||||||
const [page, setPage] = useState<number>(1)
|
const [page, setPage] = useState<number>(1)
|
||||||
const [searchParams] = useSearchParams()
|
const [searchParams] = useSearchParams()
|
||||||
const [activeTab, setActiveTab] = useState<InvoiceStatus>('PENDING')
|
const [activeTab, setActiveTab] = useState<InvoiceStatus>('PENDING')
|
||||||
@@ -34,11 +36,11 @@ const ReceiptsList: FC = () => {
|
|||||||
const handleDeleteInvoice = (id: string) => {
|
const handleDeleteInvoice = (id: string) => {
|
||||||
deleteInvoice.mutate(id, {
|
deleteInvoice.mutate(id, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
getInvoices.refetch()
|
getInvoices.refetch()
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -177,13 +179,17 @@ const ReceiptsList: FC = () => {
|
|||||||
</Td>
|
</Td>
|
||||||
<Td text={''}>
|
<Td text={''}>
|
||||||
<div className='flex gap-2'>
|
<div className='flex gap-2'>
|
||||||
|
{canUpdate('invoices') &&
|
||||||
<Link to={Pages.receipts.detail + item.id}>
|
<Link to={Pages.receipts.detail + item.id}>
|
||||||
<Edit size={20} color='#888' />
|
<Edit size={20} color='#888' />
|
||||||
</Link>
|
</Link>
|
||||||
|
}
|
||||||
|
{canDelete('invoices') &&
|
||||||
<TrashWithConfrim
|
<TrashWithConfrim
|
||||||
onDelete={() => handleDeleteInvoice(item.id)}
|
onDelete={() => handleDeleteInvoice(item.id)}
|
||||||
isLoading={deleteInvoice.isPending}
|
isLoading={deleteInvoice.isPending}
|
||||||
/>
|
/>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</Td>
|
</Td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import { useCreateResellers } from './hooks/useResellerData'
|
import { useCreateResellers } from './hooks/useResellerData'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { ErrorType } from '../../helpers/types'
|
import { ErrorType } from '../../helpers/types'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../components/Toast';
|
||||||
import Input from '../../components/Input'
|
import Input from '../../components/Input'
|
||||||
|
|
||||||
const CreateReseller: FC = () => {
|
const CreateReseller: FC = () => {
|
||||||
@@ -32,7 +32,7 @@ const CreateReseller: FC = () => {
|
|||||||
navigate(-1)
|
navigate(-1)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ import { useGetResellers } from './hooks/useResellerData'
|
|||||||
import PageLoading from '../../components/PageLoading'
|
import PageLoading from '../../components/PageLoading'
|
||||||
import Td from '../../components/Td'
|
import Td from '../../components/Td'
|
||||||
import { ResellerItemType } from './types/Types'
|
import { ResellerItemType } from './types/Types'
|
||||||
|
import { usePermissions } from '../../hooks/usePermissions'
|
||||||
|
|
||||||
const ResellerList: FC = () => {
|
const ResellerList: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
|
const { canCreate } = usePermissions()
|
||||||
const getReseller = useGetResellers()
|
const getReseller = useGetResellers()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -21,6 +23,7 @@ const ResellerList: FC = () => {
|
|||||||
{t('reseller.list')}
|
{t('reseller.list')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{canCreate('reseller') && (
|
||||||
<Link to={Pages.reseller.create}>
|
<Link to={Pages.reseller.create}>
|
||||||
<Button
|
<Button
|
||||||
className='w-[172px]'
|
className='w-[172px]'
|
||||||
@@ -33,6 +36,7 @@ const ResellerList: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Button from '../../../components/Button'
|
|||||||
import DefaulModal from '../../../components/DefaulModal'
|
import DefaulModal from '../../../components/DefaulModal'
|
||||||
import { usePayment } from '../hooks/useResellerData'
|
import { usePayment } from '../hooks/useResellerData'
|
||||||
import Input from '../../../components/Input'
|
import Input from '../../../components/Input'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../../components/Toast';
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { ErrorType } from '../../../helpers/types'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -26,11 +26,11 @@ const Payment: FC<Props> = ({ id, refetch }) => {
|
|||||||
refetch()
|
refetch()
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error.message[0])
|
toast(error.response?.data?.error.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
toast.error('شماره تراکنش رو پر کنید')
|
toast('شماره تراکنش رو پر کنید', 'error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import * as Yup from 'yup'
|
|||||||
import { useCreateService, useMultiUpload, useSingleUpload } from './hooks/useServiceData';
|
import { useCreateService, useMultiUpload, useSingleUpload } from './hooks/useServiceData';
|
||||||
import moment from 'moment-jalaali';
|
import moment from 'moment-jalaali';
|
||||||
import { ErrorType } from '../../helpers/types';
|
import { ErrorType } from '../../helpers/types';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from '../../components/Toast';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Pages } from '../../config/Pages';
|
import { Pages } from '../../config/Pages';
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ const AddService: FC = () => {
|
|||||||
values.icon = data?.data?.url
|
values.icon = data?.data?.url
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ const AddService: FC = () => {
|
|||||||
values.images = await data.data.map((item: { url: string }) => item?.url)
|
values.images = await data.data.map((item: { url: string }) => item?.url)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ const AddService: FC = () => {
|
|||||||
values.audios = await data.data.map((item: { url: string }) => item?.url)
|
values.audios = await data.data.map((item: { url: string }) => item?.url)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -144,7 +144,7 @@ const AddService: FC = () => {
|
|||||||
values.videos = await data.data.map((item: { url: string }) => item?.url)
|
values.videos = await data.data.map((item: { url: string }) => item?.url)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -159,15 +159,15 @@ const AddService: FC = () => {
|
|||||||
createService.mutate(params, {
|
createService.mutate(params, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
formik.resetForm()
|
formik.resetForm()
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
navigate(Pages.services.list)
|
navigate(Pages.services.list)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
toast.error(t('errors.upload_image'))
|
toast(t('errors.upload_image'), 'error')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user