fix some bug
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "new-york",
|
||||||
|
"rsc": false,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "tailwind.config.js",
|
||||||
|
"css": "src/index.css",
|
||||||
|
"baseColor": "neutral",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"utils": "@/lib/utils",
|
||||||
|
"ui": "@/components/ui",
|
||||||
|
"lib": "@/lib",
|
||||||
|
"hooks": "@/hooks"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Generated
+251
-13
@@ -9,9 +9,12 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^2.2.7",
|
"@headlessui/react": "^2.2.7",
|
||||||
|
"@radix-ui/react-checkbox": "^1.3.3",
|
||||||
|
"@radix-ui/react-icons": "^1.3.2",
|
||||||
"@tailwindcss/vite": "^4.1.12",
|
"@tailwindcss/vite": "^4.1.12",
|
||||||
"@tanstack/react-query": "^5.85.5",
|
"@tanstack/react-query": "^5.85.5",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
"formik": "^2.4.6",
|
"formik": "^2.4.6",
|
||||||
"i18next": "^25.4.2",
|
"i18next": "^25.4.2",
|
||||||
"iconsax-react": "^0.0.8",
|
"iconsax-react": "^0.0.8",
|
||||||
@@ -20,6 +23,7 @@
|
|||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
"react-dropzone": "^14.3.8",
|
"react-dropzone": "^14.3.8",
|
||||||
"react-i18next": "^15.7.2",
|
"react-i18next": "^15.7.2",
|
||||||
|
"react-loading-skeleton": "^3.5.0",
|
||||||
"react-multi-date-picker": "^4.5.2",
|
"react-multi-date-picker": "^4.5.2",
|
||||||
"react-otp-input": "^3.1.1",
|
"react-otp-input": "^3.1.1",
|
||||||
"react-router-dom": "^7.8.2",
|
"react-router-dom": "^7.8.2",
|
||||||
@@ -1156,6 +1160,231 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/primitive": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-checkbox": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/primitive": "1.1.3",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2",
|
||||||
|
"@radix-ui/react-context": "1.1.2",
|
||||||
|
"@radix-ui/react-presence": "1.1.5",
|
||||||
|
"@radix-ui/react-primitive": "2.1.3",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||||
|
"@radix-ui/react-use-previous": "1.1.1",
|
||||||
|
"@radix-ui/react-use-size": "1.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-compose-refs": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-context": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-icons": {
|
||||||
|
"version": "1.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz",
|
||||||
|
"integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-presence": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-primitive": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-slot": "1.2.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-use-controllable-state": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-use-effect-event": "0.0.2",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-use-effect-event": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-use-layout-effect": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-use-previous": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-use-size": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@react-aria/focus": {
|
"node_modules/@react-aria/focus": {
|
||||||
"version": "3.21.1",
|
"version": "3.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.21.1.tgz",
|
"resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.21.1.tgz",
|
||||||
@@ -1927,7 +2156,7 @@
|
|||||||
"version": "19.1.8",
|
"version": "19.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.8.tgz",
|
||||||
"integrity": "sha512-xG7xaBMJCpcK0RpN8jDbAACQo54ycO6h4dSSmgv8+fu6ZIAdANkx/WsawASUjVXYfy+J9AbUpRMNNEsXCDfDBQ==",
|
"integrity": "sha512-xG7xaBMJCpcK0RpN8jDbAACQo54ycO6h4dSSmgv8+fu6ZIAdANkx/WsawASUjVXYfy+J9AbUpRMNNEsXCDfDBQ==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^19.0.0"
|
"@types/react": "^19.0.0"
|
||||||
@@ -2342,9 +2571,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.11.0",
|
"version": "1.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.1.tgz",
|
||||||
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
|
"integrity": "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.6",
|
||||||
@@ -4361,6 +4590,15 @@
|
|||||||
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
|
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/react-loading-skeleton": {
|
||||||
|
"version": "3.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.5.0.tgz",
|
||||||
|
"integrity": "sha512-gxxSyLbrEAdXTKgfbpBEFZCO/P153DnqSCQau2+o6lNy1jgMRr2MmRmOzMmyrwSaSYLRB8g7b0waYPmUjz7IhQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-multi-date-picker": {
|
"node_modules/react-multi-date-picker": {
|
||||||
"version": "4.5.2",
|
"version": "4.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-multi-date-picker/-/react-multi-date-picker-4.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-multi-date-picker/-/react-multi-date-picker-4.5.2.tgz",
|
||||||
@@ -4713,13 +4951,13 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.14",
|
"version": "0.2.15",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||||
"integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
|
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fdir": "^6.4.4",
|
"fdir": "^6.5.0",
|
||||||
"picomatch": "^4.0.2"
|
"picomatch": "^4.0.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
@@ -4909,9 +5147,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.1.3",
|
"version": "7.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz",
|
||||||
"integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==",
|
"integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
@@ -4919,7 +5157,7 @@
|
|||||||
"picomatch": "^4.0.3",
|
"picomatch": "^4.0.3",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"rollup": "^4.43.0",
|
"rollup": "^4.43.0",
|
||||||
"tinyglobby": "^0.2.14"
|
"tinyglobby": "^0.2.15"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"vite": "bin/vite.js"
|
"vite": "bin/vite.js"
|
||||||
|
|||||||
@@ -11,9 +11,12 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^2.2.7",
|
"@headlessui/react": "^2.2.7",
|
||||||
|
"@radix-ui/react-checkbox": "^1.3.3",
|
||||||
|
"@radix-ui/react-icons": "^1.3.2",
|
||||||
"@tailwindcss/vite": "^4.1.12",
|
"@tailwindcss/vite": "^4.1.12",
|
||||||
"@tanstack/react-query": "^5.85.5",
|
"@tanstack/react-query": "^5.85.5",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
"formik": "^2.4.6",
|
"formik": "^2.4.6",
|
||||||
"i18next": "^25.4.2",
|
"i18next": "^25.4.2",
|
||||||
"iconsax-react": "^0.0.8",
|
"iconsax-react": "^0.0.8",
|
||||||
@@ -22,6 +25,7 @@
|
|||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
"react-dropzone": "^14.3.8",
|
"react-dropzone": "^14.3.8",
|
||||||
"react-i18next": "^15.7.2",
|
"react-i18next": "^15.7.2",
|
||||||
|
"react-loading-skeleton": "^3.5.0",
|
||||||
"react-multi-date-picker": "^4.5.2",
|
"react-multi-date-picker": "^4.5.2",
|
||||||
"react-otp-input": "^3.1.1",
|
"react-otp-input": "^3.1.1",
|
||||||
"react-router-dom": "^7.8.2",
|
"react-router-dom": "^7.8.2",
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { type FC, Fragment } from 'react'
|
||||||
|
import Td from './Td'
|
||||||
|
import Skeleton from 'react-loading-skeleton'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
tdCount: number,
|
||||||
|
trCount?: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
const DefaultTableSkeleton: FC<Props> = ({ tdCount, trCount = 5 }) => {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
{
|
||||||
|
Array.from({ length: trCount }).map((_, rowIndex) => (
|
||||||
|
<tr className="w-full h-[64px] md:h-[74px] bg-white border-t border-[#EAEDF5]" key={rowIndex}>
|
||||||
|
{
|
||||||
|
Array.from({ length: tdCount }).map((_, colIndex) => (
|
||||||
|
<Td text={''} key={colIndex}>
|
||||||
|
<Skeleton />
|
||||||
|
</Td>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DefaultTableSkeleton
|
||||||
@@ -11,33 +11,41 @@ const Pagination: React.FC<PaginationProps> = ({
|
|||||||
totalPages,
|
totalPages,
|
||||||
onPageChange,
|
onPageChange,
|
||||||
}) => {
|
}) => {
|
||||||
|
// اگر فقط یک صفحه داریم، pagination نمایش داده نمیشود
|
||||||
|
if (totalPages <= 1) return null;
|
||||||
|
|
||||||
const getPageNumbers = () => {
|
const getPageNumbers = () => {
|
||||||
const pageNumbers: (number | string)[] = [];
|
const pageNumbers: (number | string)[] = [];
|
||||||
const maxVisiblePages = 5; // تعداد حداکثری صفحات قابل مشاهده
|
const maxVisiblePages = window.innerWidth < 768 ? 3 : 5; // تعداد کمتر برای موبایل
|
||||||
|
|
||||||
if (totalPages <= maxVisiblePages) {
|
if (totalPages <= maxVisiblePages) {
|
||||||
for (let i = 1; i <= totalPages; i++) {
|
for (let i = 1; i <= totalPages; i++) {
|
||||||
pageNumbers.push(i);
|
pageNumbers.push(i);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// نمایش صفحه اول
|
||||||
|
pageNumbers.push(1);
|
||||||
|
|
||||||
if (currentPage > 3) {
|
if (currentPage > 3) {
|
||||||
pageNumbers.push(1);
|
pageNumbers.push("...");
|
||||||
if (currentPage > 4) {
|
|
||||||
pageNumbers.push("...");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const start = Math.max(2, currentPage - 1);
|
// صفحات میانی
|
||||||
const end = Math.min(totalPages - 1, currentPage + 1);
|
const start = Math.max(2, Math.min(currentPage - 1, totalPages - 3));
|
||||||
|
const end = Math.min(totalPages - 1, Math.max(currentPage + 1, 4));
|
||||||
|
|
||||||
for (let i = start; i <= end; i++) {
|
for (let i = start; i <= end; i++) {
|
||||||
pageNumbers.push(i);
|
if (!pageNumbers.includes(i)) {
|
||||||
|
pageNumbers.push(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentPage < totalPages - 2) {
|
if (currentPage < totalPages - 2) {
|
||||||
if (currentPage < totalPages - 3) {
|
pageNumbers.push("...");
|
||||||
pageNumbers.push("...");
|
}
|
||||||
}
|
|
||||||
|
// نمایش صفحه آخر
|
||||||
|
if (!pageNumbers.includes(totalPages)) {
|
||||||
pageNumbers.push(totalPages);
|
pageNumbers.push(totalPages);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,50 +56,52 @@ const Pagination: React.FC<PaginationProps> = ({
|
|||||||
const pageNumbers = getPageNumbers();
|
const pageNumbers = getPageNumbers();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-2 text-xs justify-center items-center space-x-2 mt-4">
|
<div className="flex justify-center items-center gap-1 md:gap-2 py-4 md:py-6 mt-3 md:mt-4 border-t border-gray-100">
|
||||||
{/* دکمه قبلی */}
|
{/* دکمه صفحه قبل */}
|
||||||
{/* <button
|
<button
|
||||||
className={`px-3 py-1 rounded-md ${currentPage === 1
|
className={`px-2 md:px-3 py-2 text-xs md:text-sm rounded-lg transition-colors ${currentPage === 1
|
||||||
? "text-gray-400 cursor-not-allowed"
|
? "text-gray-400 cursor-not-allowed"
|
||||||
: "text-blue-600 hover:bg-gray-100"
|
: "text-gray-600 hover:bg-gray-100 hover:text-gray-800"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => currentPage > 1 && onPageChange(currentPage - 1)}
|
onClick={() => currentPage > 1 && onPageChange(currentPage - 1)}
|
||||||
disabled={currentPage === 1}
|
disabled={currentPage === 1}
|
||||||
>
|
>
|
||||||
قبلی
|
قبلی
|
||||||
</button> */}
|
</button>
|
||||||
|
|
||||||
{/* شماره صفحات */}
|
{/* شماره صفحات */}
|
||||||
{pageNumbers.map((page, index) =>
|
<div className="flex gap-1">
|
||||||
typeof page === "number" ? (
|
{pageNumbers.map((page, index) =>
|
||||||
<button
|
typeof page === "number" ? (
|
||||||
key={index}
|
<button
|
||||||
className={`size-8 rounded-md ${currentPage === page
|
key={index}
|
||||||
? "bg-primary text-white"
|
className={`w-8 h-8 md:w-10 md:h-10 text-xs md:text-sm rounded-lg transition-colors ${currentPage === page
|
||||||
: "text-primary bg-[#EAECF5] hover:bg-gray-100"
|
? "bg-primary text-white shadow-sm"
|
||||||
}`}
|
: "text-gray-600 hover:bg-gray-100 hover:text-gray-800"
|
||||||
onClick={() => onPageChange(page)}
|
}`}
|
||||||
>
|
onClick={() => onPageChange(page)}
|
||||||
{page}
|
>
|
||||||
</button>
|
{page}
|
||||||
) : (
|
</button>
|
||||||
<span key={index} className="px-3 py-1 text-gray-500">
|
) : (
|
||||||
{page}
|
<span key={index} className="flex items-center px-1 md:px-2 text-gray-400 text-xs md:text-sm">
|
||||||
</span>
|
{page}
|
||||||
)
|
</span>
|
||||||
)}
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* دکمه بعدی */}
|
{/* دکمه صفحه بعد */}
|
||||||
{/* <button
|
<button
|
||||||
className={`px-3 py-1 rounded-md ${currentPage === totalPages
|
className={`px-2 md:px-3 py-2 text-xs md:text-sm rounded-lg transition-colors ${currentPage === totalPages
|
||||||
? "text-gray-400 cursor-not-allowed"
|
? "text-gray-400 cursor-not-allowed"
|
||||||
: "text-blue-600 hover:bg-gray-100"
|
: "text-gray-600 hover:bg-gray-100 hover:text-gray-800"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => currentPage < totalPages && onPageChange(currentPage + 1)}
|
onClick={() => currentPage < totalPages && onPageChange(currentPage + 1)}
|
||||||
disabled={currentPage === totalPages}
|
disabled={currentPage === totalPages}
|
||||||
>
|
>
|
||||||
بعدی
|
بعدی
|
||||||
</button> */}
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
import React, { useState, useRef, useEffect } from 'react'
|
||||||
|
import { createPortal } from 'react-dom'
|
||||||
|
import { More } from 'iconsax-react'
|
||||||
|
|
||||||
|
export interface RowActionItem {
|
||||||
|
label: string;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
onClick: () => void;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RowActionsDropdownProps {
|
||||||
|
actions: RowActionItem[];
|
||||||
|
className?: string;
|
||||||
|
trigger?: React.ReactNode;
|
||||||
|
topOffset?: number;
|
||||||
|
leftOffset?: number;
|
||||||
|
topOffsetMobile?: number;
|
||||||
|
leftOffsetMobile?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RowActionsDropdown: React.FC<RowActionsDropdownProps> = ({
|
||||||
|
actions,
|
||||||
|
className = '',
|
||||||
|
trigger,
|
||||||
|
topOffset = 0,
|
||||||
|
leftOffset = 0,
|
||||||
|
topOffsetMobile = 0,
|
||||||
|
leftOffsetMobile = 0
|
||||||
|
}) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0 })
|
||||||
|
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||||
|
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||||
|
setIsOpen(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousedown', handleClickOutside)
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const calculatePosition = (rect: DOMRect) => {
|
||||||
|
const isMobile = window.innerWidth < 768
|
||||||
|
const dropdownWidth = isMobile ? 140 : 150
|
||||||
|
const dropdownHeight = actions.length * (isMobile ? 36 : 40)
|
||||||
|
|
||||||
|
// انتخاب offset بر اساس سایز صفحه
|
||||||
|
const currentTopOffset = isMobile ? topOffsetMobile : topOffset
|
||||||
|
const currentLeftOffset = isMobile ? leftOffsetMobile : leftOffset
|
||||||
|
|
||||||
|
let left = rect.left + window.scrollX - dropdownWidth + rect.width + currentLeftOffset
|
||||||
|
let top = rect.bottom + window.scrollY + currentTopOffset
|
||||||
|
|
||||||
|
if (left + dropdownWidth > window.innerWidth) {
|
||||||
|
left = rect.left + window.scrollX - dropdownWidth + currentLeftOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left < 0) {
|
||||||
|
left = isMobile ? 20 : 40
|
||||||
|
}
|
||||||
|
|
||||||
|
if (top + dropdownHeight > window.innerHeight + window.scrollY) {
|
||||||
|
top = rect.top + window.scrollY - dropdownHeight + currentTopOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
// اطمینان از اینکه dropdown از صفحه خارج نشود
|
||||||
|
if (left < 0) left = 20
|
||||||
|
if (left + dropdownWidth > window.innerWidth) left = window.innerWidth - dropdownWidth - 20
|
||||||
|
if (top < 0) top = 20
|
||||||
|
if (top + dropdownHeight > window.innerHeight) top = window.innerHeight - dropdownHeight - 20
|
||||||
|
|
||||||
|
return { top, left }
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleToggle = (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
if (!isOpen && buttonRef.current) {
|
||||||
|
const rect = buttonRef.current.getBoundingClientRect()
|
||||||
|
const calculatedPosition = calculatePosition(rect)
|
||||||
|
setDropdownPosition(calculatedPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsOpen(!isOpen)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleActionClick = (action: RowActionItem) => {
|
||||||
|
action.onClick()
|
||||||
|
setIsOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderDropdown = () => {
|
||||||
|
if (!isOpen) return null
|
||||||
|
|
||||||
|
return createPortal(
|
||||||
|
<div
|
||||||
|
ref={dropdownRef}
|
||||||
|
className="fixed py-3 md:py-3 bg-white dark:bg-[#252526] rounded-xl md:rounded-2xl shadow-lg z-[5] min-w-[140px] md:min-w-[150px] border border-[#d0d0d0]"
|
||||||
|
style={{
|
||||||
|
top: `${dropdownPosition.top}px`,
|
||||||
|
left: `${dropdownPosition.left}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{actions.map((action, index) => (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
onClick={() => handleActionClick(action)}
|
||||||
|
className={`w-full mt-1 text-right px-2 md:px-3 py-1 md:py-1.5 text-xs md:text-[13px] flex items-center gap-2 hover:bg-gray-50 dark:hover:bg-[#2d2d30] ${index === 0 ? 'rounded-t-lg' : ''} ${action.className || ''}`}
|
||||||
|
>
|
||||||
|
{action.icon && <span className="ml-2">{action.icon}</span>}
|
||||||
|
<span className={`${action.label === 'حذف' ? 'text-red-500' : ''} dark:text-gray-200`}>{action.label}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
<button
|
||||||
|
ref={buttonRef}
|
||||||
|
onClick={handleToggle}
|
||||||
|
className="mt-2 hover:bg-gray-100 dark:hover:bg-[#2d2d30] rounded-full transition-colors"
|
||||||
|
aria-haspopup="menu"
|
||||||
|
aria-expanded={isOpen}
|
||||||
|
>
|
||||||
|
{trigger ? trigger : (
|
||||||
|
<More size={16} color={'black'} className="rotate-90" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{renderDropdown()}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RowActionsDropdown
|
||||||
@@ -0,0 +1,363 @@
|
|||||||
|
import React, { Fragment, useState, memo } from 'react';
|
||||||
|
import DefaultTableSkeleton from '../components/DefaultTableSkeleton';
|
||||||
|
import Td from '../components/Td';
|
||||||
|
import type { TableProps, RowDataType, ColumnType } from '@/components/types/TableTypes';
|
||||||
|
import { Checkbox } from '../components/ui/checkbox';
|
||||||
|
import RowActionsDropdown from '../components/RowActionsDropdown';
|
||||||
|
import Pagination from '../components/Pagination';
|
||||||
|
|
||||||
|
const Table = <T extends RowDataType>({
|
||||||
|
columns,
|
||||||
|
data,
|
||||||
|
isLoading = false,
|
||||||
|
onRowClick,
|
||||||
|
className = '',
|
||||||
|
rowClassName = '',
|
||||||
|
noDataMessage = 'هیچ دادهای یافت نشد',
|
||||||
|
headerClassName = 'bg-gray-50',
|
||||||
|
emptyRowsCount = 5,
|
||||||
|
showHeader = true,
|
||||||
|
actions = null,
|
||||||
|
actionsPosition = 'top',
|
||||||
|
selectable = false,
|
||||||
|
selectedActions = null,
|
||||||
|
onSelectionChange,
|
||||||
|
rowActions,
|
||||||
|
pagination,
|
||||||
|
}: TableProps<T>): React.ReactElement => {
|
||||||
|
|
||||||
|
const [selectedRows, setSelectedRows] = useState<T[]>([]);
|
||||||
|
|
||||||
|
const getRowClassName = (item: T, index: number): string => {
|
||||||
|
if (typeof rowClassName === 'function') {
|
||||||
|
return rowClassName(item, index);
|
||||||
|
}
|
||||||
|
return rowClassName;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCellClassName = (column: ColumnType<T>): string => {
|
||||||
|
const alignClass = column.align ? `text-${column.align}` : '';
|
||||||
|
return `px-3 md:px-6 py-3 md:py-4 whitespace-nowrap text-xs ${alignClass} ${column.className || ''}`.trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRowClick = (item: T) => {
|
||||||
|
if (onRowClick) {
|
||||||
|
onRowClick(item.id, item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectAll = () => {
|
||||||
|
const dataArray = data || [];
|
||||||
|
const newSelectedRows = selectedRows.length === dataArray.length ? [] : [...dataArray];
|
||||||
|
setSelectedRows(newSelectedRows);
|
||||||
|
if (onSelectionChange) {
|
||||||
|
onSelectionChange(newSelectedRows);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSingleRowSelect = (item: T, checked: boolean) => {
|
||||||
|
const newSelectedRows = checked
|
||||||
|
? [...selectedRows, item]
|
||||||
|
: selectedRows.filter((row) => row.id !== item.id);
|
||||||
|
setSelectedRows(newSelectedRows);
|
||||||
|
if (onSelectionChange) {
|
||||||
|
onSelectionChange(newSelectedRows);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isRowSelected = (item: T): boolean => {
|
||||||
|
return selectedRows.some((row) => row.id === item.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Gmail-style layout برای وقتی header نمایش داده نمیشود
|
||||||
|
const renderGmailStyleRows = () => {
|
||||||
|
const hasActions = (actionsPosition === 'top' || actionsPosition === 'above-header') && (actions || (selectable && selectedRows.length > 0 && selectedActions));
|
||||||
|
const roundedClass = hasActions ? 'rounded-b-2xl' : 'rounded-2xl';
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className={`bg-white ${roundedClass} overflow-hidden`}>
|
||||||
|
{Array.from({ length: emptyRowsCount }).map((_, index) => (
|
||||||
|
<div key={index} className={`h-[48px] bg-gray-100 animate-pulse ${index !== 0 ? 'border-t border-[#EAEDF5]' : ''}`}></div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className={`bg-white ${roundedClass} py-6 md:py-8 text-center text-gray-500`}>
|
||||||
|
{noDataMessage}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`bg-white ${roundedClass} overflow-hidden`}>
|
||||||
|
{data.map((item, rowIndex) => (
|
||||||
|
<div
|
||||||
|
key={item.id}
|
||||||
|
className={`w-full min-h-[48px] hover:bg-[#F4F5F9] ${onRowClick ? 'cursor-pointer' : ''} ${getRowClassName(item, rowIndex)} px-4 py-3 flex items-center gap-3 ${rowIndex !== 0 ? 'border-t border-[#EAEDF5]' : ''}`}
|
||||||
|
onClick={() => handleRowClick(item)}
|
||||||
|
>
|
||||||
|
{selectable && (
|
||||||
|
<div
|
||||||
|
className="flex-shrink-0 relative z-[5]"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={isRowSelected(item)}
|
||||||
|
onCheckedChange={(checked) => handleSingleRowSelect(item, !!checked)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
{/* محتوای اصلی - ستون اول */}
|
||||||
|
<div className="flex items-center mb-0.5 text-sm font-medium">
|
||||||
|
{columns[0]?.render ? columns[0].render(item, rowIndex) : (item[columns[0]?.key] as React.ReactNode)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* اطلاعات اضافی - سه ستون آخر در یک خط */}
|
||||||
|
{columns.length > 1 && (
|
||||||
|
<div className="flex items-center gap-3 text-xs text-gray-500">
|
||||||
|
{columns.slice(1).map((col) => (
|
||||||
|
<div key={col.key} className="truncate">
|
||||||
|
{col.render ? col.render(item, rowIndex) : (item[col.key] as React.ReactNode)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{rowActions && (
|
||||||
|
<div
|
||||||
|
className="flex-shrink-0 relative z-[5]"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<RowActionsDropdown actions={rowActions(item)} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderTableBody = () => {
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<DefaultTableSkeleton tdCount={columns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0)} trCount={emptyRowsCount} />
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={columns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0)} className="px-3 md:px-6 py-6 md:py-8 text-center text-gray-500">
|
||||||
|
{noDataMessage}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (data || []).map((item, rowIndex) => (
|
||||||
|
<tr
|
||||||
|
key={item.id}
|
||||||
|
className={`w-full h-[64px] md:h-[74px] bg-white border-t border-[#EAEDF5] ${onRowClick ? 'cursor-pointer' : ''} ${getRowClassName(item, rowIndex)}`}
|
||||||
|
onClick={() => handleRowClick(item)}
|
||||||
|
>
|
||||||
|
{selectable && (
|
||||||
|
<td
|
||||||
|
className="px-3 md:px-6 py-3 md:py-4 w-8 md:w-10 relative z-[5]"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={isRowSelected(item)}
|
||||||
|
onCheckedChange={(checked) => handleSingleRowSelect(item, !!checked)}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
|
{columns.map((col) => (
|
||||||
|
<td
|
||||||
|
key={`${item.id}-${col.key}`}
|
||||||
|
className={getCellClassName(col)}
|
||||||
|
style={{ width: col.width }}
|
||||||
|
>
|
||||||
|
{col.render ? col.render(item, rowIndex) : item[col.key] as React.ReactNode}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
{rowActions && (
|
||||||
|
<td
|
||||||
|
className="px-3 md:px-6 py-3 md:py-4 w-8 md:w-10 relative z-[5]"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<RowActionsDropdown actions={rowActions(item)} />
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
|
</tr>
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderHeader = () => {
|
||||||
|
if (!showHeader || actionsPosition === 'header-replace') return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<thead className={`h-[60px] md:h-[69px] bg-white text-sm text-[#8C90A3] ${headerClassName}`}>
|
||||||
|
<tr>
|
||||||
|
{selectable && (
|
||||||
|
<th
|
||||||
|
className="px-3 md:px-6 py-3 md:py-4 w-8 md:w-10 relative z-[5] first:rounded-tr-2xl md:first:rounded-tr-3xl"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={(data?.length || 0) > 0 && selectedRows.length === (data?.length || 0)}
|
||||||
|
onCheckedChange={handleSelectAll}
|
||||||
|
/>
|
||||||
|
</th>
|
||||||
|
)}
|
||||||
|
{columns.map((col) => (
|
||||||
|
<Td key={col.key} text={col.title} />
|
||||||
|
))}
|
||||||
|
{rowActions && (
|
||||||
|
<th className="px-3 md:px-6 py-3 md:py-4 w-8 md:w-10 rounded-tl-2xl md:rounded-tl-3xl"></th>
|
||||||
|
)}
|
||||||
|
</tr>
|
||||||
|
</thead >
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderActions = () => {
|
||||||
|
if (!actions && (!selectable || selectedRows.length === 0 || !selectedActions)) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-[64px] rounded-t-2xl md:rounded-t-3xl md:h-[74px] bg-white flex items-center px-3 md:px-6">
|
||||||
|
<div className='flex items-center gap-2 md:gap-4'>
|
||||||
|
{actions}
|
||||||
|
{selectable && selectedRows.length > 0 && selectedActions && selectedActions}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// اگر header نشان داده نمیشود، از Gmail-style layout فقط در موبایل استفاده کن
|
||||||
|
if (!showHeader) {
|
||||||
|
return (
|
||||||
|
<div className={`relative mt-6 md:mt-9 w-full ${className}`}>
|
||||||
|
{/* نسخه موبایل - Gmail style */}
|
||||||
|
<div className={`md:hidden`}>
|
||||||
|
{(actionsPosition === 'top' || actionsPosition === 'above-header') && (
|
||||||
|
<div className="h-[64px] bg-white flex items-center px-4 rounded-t-2xl border-b border-[#EAEDF5]">
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
{actions}
|
||||||
|
{selectable && selectedRows.length > 0 && selectedActions && selectedActions}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{renderGmailStyleRows()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* نسخه دسکتاپ - Table معمولی */}
|
||||||
|
<div className={`hidden md:block`}>
|
||||||
|
{(actionsPosition === 'top' || actionsPosition === 'above-header') && (
|
||||||
|
<div className="h-[74px] bg-white flex items-center px-6 rounded-t-3xl">
|
||||||
|
<div className='flex items-center gap-4'>
|
||||||
|
{actions}
|
||||||
|
{selectable && selectedRows.length > 0 && selectedActions && selectedActions}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={`overflow-x-auto overflow-hidden ${(actionsPosition === 'top' || actionsPosition === 'above-header') && (actions || (selectable && selectedRows.length > 0 && selectedActions)) ? 'rounded-b-3xl' : 'rounded-3xl'} bg-white`}>
|
||||||
|
<table className="w-full text-sm border-collapse min-w-[600px]">
|
||||||
|
<tbody>
|
||||||
|
{renderTableBody()}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{pagination && (
|
||||||
|
<Pagination
|
||||||
|
currentPage={pagination.currentPage}
|
||||||
|
totalPages={pagination.totalPages}
|
||||||
|
onPageChange={pagination.onPageChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`relative mt-6 md:mt-9 w-full ${className}`}>
|
||||||
|
{(actionsPosition === 'top' || actionsPosition === 'above-header') && renderActions()}
|
||||||
|
|
||||||
|
<div className={`overflow-x-auto overflow-hidden ${showHeader && actionsPosition !== 'header-replace' ? 'rounded-2xl md:rounded-3xl' : 'rounded-b-2xl md:rounded-b-3xl'} bg-white`}>
|
||||||
|
<table className="w-full text-sm border-collapse min-w-[600px]">
|
||||||
|
{renderHeader()}
|
||||||
|
{actionsPosition === 'header-replace' && (
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colSpan={columns.length} className="p-0">
|
||||||
|
{renderActions()}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
)}
|
||||||
|
<tbody>
|
||||||
|
{renderTableBody()}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{pagination && (
|
||||||
|
<Pagination
|
||||||
|
currentPage={pagination.currentPage}
|
||||||
|
totalPages={pagination.totalPages}
|
||||||
|
onPageChange={pagination.onPageChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(Table) as <T extends RowDataType>(props: TableProps<T>) => React.ReactElement;
|
||||||
|
|
||||||
|
/*
|
||||||
|
مثال استفاده با pagination:
|
||||||
|
|
||||||
|
import Table from '@/components/Table';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
const MyComponent = () => {
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const totalPages = 10;
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ title: 'نام', key: 'name' },
|
||||||
|
{ title: 'ایمیل', key: 'email' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
{ id: 1, name: 'علی', email: 'ali@example.com' },
|
||||||
|
{ id: 2, name: 'فاطمه', email: 'fateme@example.com' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const handlePageChange = (page: number) => {
|
||||||
|
setCurrentPage(page);
|
||||||
|
// اینجا میتوانید API call جدید برای دریافت دادههای صفحه جدید بزنید
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
data={data}
|
||||||
|
pagination={{
|
||||||
|
currentPage,
|
||||||
|
totalPages,
|
||||||
|
onPageChange: handlePageChange
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
*/
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
import { type FC, type ReactNode } from 'react'
|
import type { FC, ReactNode } from 'react'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
text: string,
|
text: string | number | ReactNode,
|
||||||
children?: ReactNode,
|
children?: ReactNode,
|
||||||
dir?: string,
|
dir?: string,
|
||||||
|
className?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const Td: FC<Props> = (props: Props) => {
|
const Td: FC<Props> = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<td className='td' style={{ direction: props.dir === "ltr" ? "ltr" : "rtl" }}>
|
<td className={`px-3 md:px-6 py-3 md:py-4 whitespace-nowrap text-xs ${props.className}`} style={{ direction: props.dir === "ltr" ? "ltr" : "rtl" }}>
|
||||||
{
|
{
|
||||||
props.text ?
|
props.text ?
|
||||||
props.text
|
props.text
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
export interface RowDataType {
|
||||||
|
id: string | number;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ColumnType<T extends RowDataType> {
|
||||||
|
title: string;
|
||||||
|
key: string;
|
||||||
|
align?: "left" | "center" | "right";
|
||||||
|
className?: string;
|
||||||
|
width?: string | number;
|
||||||
|
render?: (item: T, index?: number) => React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaginationType {
|
||||||
|
currentPage: number;
|
||||||
|
totalPages: number;
|
||||||
|
onPageChange: (page: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ActionType {
|
||||||
|
label: string;
|
||||||
|
onClick: () => void;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TableProps<T extends RowDataType> {
|
||||||
|
columns: ColumnType<T>[];
|
||||||
|
data?: T[];
|
||||||
|
isLoading?: boolean;
|
||||||
|
onRowClick?: (id: string | number, item: T) => void;
|
||||||
|
className?: string;
|
||||||
|
rowClassName?: string | ((item: T, index: number) => string);
|
||||||
|
noDataMessage?: string;
|
||||||
|
headerClassName?: string;
|
||||||
|
emptyRowsCount?: number;
|
||||||
|
showHeader?: boolean;
|
||||||
|
actions?: React.ReactNode;
|
||||||
|
actionsPosition?: "top" | "above-header" | "bottom" | "header-replace";
|
||||||
|
selectable?: boolean;
|
||||||
|
selectedActions?: React.ReactNode;
|
||||||
|
onSelectionChange?: (selectedRows: T[]) => void;
|
||||||
|
rowActions?: (item: T) => ActionType[];
|
||||||
|
pagination?: PaginationType;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { CheckIcon } from "@radix-ui/react-icons"
|
||||||
|
|
||||||
|
const Checkbox = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CheckboxPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"grid place-content-center peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<CheckboxPrimitive.Indicator
|
||||||
|
className={cn("grid place-content-center text-current")}
|
||||||
|
>
|
||||||
|
<CheckIcon className="h-4 w-4" />
|
||||||
|
</CheckboxPrimitive.Indicator>
|
||||||
|
</CheckboxPrimitive.Root>
|
||||||
|
))
|
||||||
|
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Checkbox }
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { clsx, type ClassValue } from "clsx";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs));
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { type FC } from 'react'
|
import { type FC } from 'react'
|
||||||
import Input from '../../../components/Input'
|
import Input from '../../../components/Input'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { LoginType } from '../types/AuthTypes'
|
import { type LoginType } from '../types/AuthTypes'
|
||||||
import { useFormik } from 'formik'
|
import { useFormik } from 'formik'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { useAuthStore } from '../store/AuthStore'
|
import { useAuthStore } from '../store/AuthStore'
|
||||||
@@ -10,7 +10,7 @@ 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 'react-toastify'
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { type ErrorType } from '../../../helpers/types'
|
||||||
|
|
||||||
const LoginStep1: FC = () => {
|
const LoginStep1: FC = () => {
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { type FC } from 'react'
|
import { type FC } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { OtpVerifyType } from '../types/AuthTypes'
|
import { type OtpVerifyType } from '../types/AuthTypes'
|
||||||
import { useFormik } from 'formik'
|
import { useFormik } from 'formik'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { useAuthStore } from '../store/AuthStore'
|
import { useAuthStore } from '../store/AuthStore'
|
||||||
@@ -10,7 +10,7 @@ import { useCountDown } from '../../../hooks/useCountDown'
|
|||||||
// import ArrowLeftIcon from '../../../assets/images/arrow-left.svg'
|
// import ArrowLeftIcon from '../../../assets/images/arrow-left.svg'
|
||||||
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 { type ErrorType } from '../../../helpers/types'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { setToken, setRefreshToken } from '../../../config/func'
|
import { setToken, setRefreshToken } from '../../../config/func'
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { type FC } from 'react'
|
import { type FC } from 'react'
|
||||||
import Input from '../../../components/Input'
|
import Input from '../../../components/Input'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { LoginWithPasswordType } from '../types/AuthTypes'
|
import { type LoginWithPasswordType } from '../types/AuthTypes'
|
||||||
import { useFormik } from 'formik'
|
import { useFormik } from 'formik'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { useAuthStore } from '../store/AuthStore'
|
import { useAuthStore } from '../store/AuthStore'
|
||||||
@@ -12,7 +12,7 @@ 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 'react-toastify'
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { type ErrorType } from '../../../helpers/types'
|
||||||
import { setRefreshToken } from '../../../config/func'
|
import { setRefreshToken } from '../../../config/func'
|
||||||
import { setToken } from '../../../config/func'
|
import { setToken } from '../../../config/func'
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { type FC } from 'react'
|
import { type FC } from 'react'
|
||||||
import Input from '../../../components/Input'
|
import Input from '../../../components/Input'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { CheckHasAccountPhoneType } from '../types/AuthTypes'
|
import { type CheckHasAccountPhoneType } from '../types/AuthTypes'
|
||||||
import { useFormik } from 'formik'
|
import { useFormik } from 'formik'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { useAuthStore } from '../store/AuthStore'
|
import { useAuthStore } from '../store/AuthStore'
|
||||||
@@ -9,7 +9,7 @@ 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 'react-toastify'
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { type ErrorType } from '../../../helpers/types'
|
||||||
|
|
||||||
const RegisterStep1: FC = () => {
|
const RegisterStep1: FC = () => {
|
||||||
|
|
||||||
|
|||||||
+7
-1
@@ -21,7 +21,13 @@
|
|||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"erasableSyntaxOnly": true,
|
"erasableSyntaxOnly": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noUncheckedSideEffectImports": true
|
"noUncheckedSideEffectImports": true,
|
||||||
|
|
||||||
|
/* Path Mapping */
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-1
@@ -3,5 +3,11 @@
|
|||||||
"references": [
|
"references": [
|
||||||
{ "path": "./tsconfig.app.json" },
|
{ "path": "./tsconfig.app.json" },
|
||||||
{ "path": "./tsconfig.node.json" }
|
{ "path": "./tsconfig.node.json" }
|
||||||
]
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react(), tailwindcss()],
|
plugins: [react(), tailwindcss()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": path.resolve(__dirname, "./src"),
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user