food admin
This commit is contained in:
Generated
+65
@@ -18,6 +18,7 @@
|
|||||||
"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",
|
||||||
|
"moment-jalaali": "^0.10.4",
|
||||||
"rc-rate": "^2.13.1",
|
"rc-rate": "^2.13.1",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
@@ -36,6 +37,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.33.0",
|
"@eslint/js": "^9.33.0",
|
||||||
|
"@types/moment-jalaali": "^0.7.9",
|
||||||
"@types/react": "^19.1.10",
|
"@types/react": "^19.1.10",
|
||||||
"@types/react-dom": "^19.1.7",
|
"@types/react-dom": "^19.1.7",
|
||||||
"@vitejs/plugin-react": "^5.0.0",
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
@@ -96,6 +98,7 @@
|
|||||||
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
|
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
@@ -2143,11 +2146,22 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/moment-jalaali": {
|
||||||
|
"version": "0.7.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/moment-jalaali/-/moment-jalaali-0.7.9.tgz",
|
||||||
|
"integrity": "sha512-gsDOoAzRnCfQTbfdlUrCvX6R0wIto6CvwfvV2C3j4qJLK+DEiTK8Rl/xlOCBO9C6qeUfX8oyZ2UfjnXJTOvHSA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"moment": ">=2.14.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.1.11",
|
"version": "19.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.11.tgz",
|
||||||
"integrity": "sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==",
|
"integrity": "sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
@@ -2158,6 +2172,7 @@
|
|||||||
"integrity": "sha512-xG7xaBMJCpcK0RpN8jDbAACQo54ycO6h4dSSmgv8+fu6ZIAdANkx/WsawASUjVXYfy+J9AbUpRMNNEsXCDfDBQ==",
|
"integrity": "sha512-xG7xaBMJCpcK0RpN8jDbAACQo54ycO6h4dSSmgv8+fu6ZIAdANkx/WsawASUjVXYfy+J9AbUpRMNNEsXCDfDBQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^19.0.0"
|
"@types/react": "^19.0.0"
|
||||||
}
|
}
|
||||||
@@ -2208,6 +2223,7 @@
|
|||||||
"integrity": "sha512-gTtSdWX9xiMPA/7MV9STjJOOYtWwIJIYxkQxnSV1U3xcE+mnJSH3f6zI0RYP+ew66WSlZ5ed+h0VCxsvdC1jJg==",
|
"integrity": "sha512-gTtSdWX9xiMPA/7MV9STjJOOYtWwIJIYxkQxnSV1U3xcE+mnJSH3f6zI0RYP+ew66WSlZ5ed+h0VCxsvdC1jJg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.41.0",
|
"@typescript-eslint/scope-manager": "8.41.0",
|
||||||
"@typescript-eslint/types": "8.41.0",
|
"@typescript-eslint/types": "8.41.0",
|
||||||
@@ -2460,6 +2476,7 @@
|
|||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -2632,6 +2649,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001735",
|
"caniuse-lite": "^1.0.30001735",
|
||||||
"electron-to-chromium": "^1.5.204",
|
"electron-to-chromium": "^1.5.204",
|
||||||
@@ -3007,6 +3025,7 @@
|
|||||||
"integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==",
|
"integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -3604,6 +3623,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.27.6"
|
"@babel/runtime": "^7.27.6"
|
||||||
},
|
},
|
||||||
@@ -3705,6 +3725,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/jalaali-js": {
|
||||||
|
"version": "1.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/jalaali-js/-/jalaali-js-1.2.8.tgz",
|
||||||
|
"integrity": "sha512-Jl/EwY84JwjW2wsWqeU4pNd22VNQ7EkjI36bDuLw31wH98WQW4fPjD0+mG7cdCK+Y8D6s9R3zLiQ3LaKu6bD8A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/jiti": {
|
"node_modules/jiti": {
|
||||||
"version": "2.5.1",
|
"version": "2.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
|
||||||
@@ -4201,6 +4227,38 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/moment": {
|
||||||
|
"version": "2.30.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
||||||
|
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/moment-jalaali": {
|
||||||
|
"version": "0.10.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment-jalaali/-/moment-jalaali-0.10.4.tgz",
|
||||||
|
"integrity": "sha512-/eD0HeyvATznb5iE0G1BHjKRZAFEpJ9ZNUkcHwXhNgt1WJJVVzHD7+uDmqzZWVFLdbGme2gvIXKb3ezDYOXcZA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"jalaali-js": "^1.2.7",
|
||||||
|
"moment": "^2.29.4",
|
||||||
|
"moment-timezone": "^0.5.46"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/moment-timezone": {
|
||||||
|
"version": "0.5.48",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz",
|
||||||
|
"integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"moment": "^2.29.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
@@ -4380,6 +4438,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.11",
|
"nanoid": "^3.3.11",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
@@ -4503,6 +4562,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
|
||||||
"integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
|
"integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -4518,6 +4578,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
|
||||||
"integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
|
"integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.26.0"
|
"scheduler": "^0.26.0"
|
||||||
},
|
},
|
||||||
@@ -4988,6 +5049,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"
|
||||||
},
|
},
|
||||||
@@ -5064,6 +5126,7 @@
|
|||||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||||
"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"
|
||||||
@@ -5151,6 +5214,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz",
|
||||||
"integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==",
|
"integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
@@ -5242,6 +5306,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"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"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",
|
||||||
|
"moment-jalaali": "^0.10.4",
|
||||||
"rc-rate": "^2.13.1",
|
"rc-rate": "^2.13.1",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
@@ -38,6 +39,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.33.0",
|
"@eslint/js": "^9.33.0",
|
||||||
|
"@types/moment-jalaali": "^0.7.9",
|
||||||
"@types/react": "^19.1.10",
|
"@types/react": "^19.1.10",
|
||||||
"@types/react-dom": "^19.1.7",
|
"@types/react-dom": "^19.1.7",
|
||||||
"@vitejs/plugin-react": "^5.0.0",
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
|
|||||||
@@ -0,0 +1,224 @@
|
|||||||
|
import { type FC, useEffect, useState, type ChangeEvent, useRef } from 'react';
|
||||||
|
import { Filter } from 'iconsax-react';
|
||||||
|
import DatePicker from './DatePicker';
|
||||||
|
import Input from './Input';
|
||||||
|
import Select, { type ItemsSelectType } from './Select';
|
||||||
|
import DefaulModal from './DefaulModal';
|
||||||
|
import moment from 'moment-jalaali'
|
||||||
|
import Button from './Button';
|
||||||
|
|
||||||
|
export type DateFieldType = {
|
||||||
|
type: 'date';
|
||||||
|
name: string;
|
||||||
|
placeholder: string;
|
||||||
|
defaultValue?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SelectFieldType = {
|
||||||
|
type: 'select';
|
||||||
|
name: string;
|
||||||
|
placeholder: string;
|
||||||
|
options: ItemsSelectType[];
|
||||||
|
defaultValue?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InputFieldType = {
|
||||||
|
type: 'input';
|
||||||
|
name: string;
|
||||||
|
placeholder: string;
|
||||||
|
defaultValue?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FieldType = DateFieldType | SelectFieldType | InputFieldType;
|
||||||
|
|
||||||
|
export type FilterValues = Record<string, string | null>;
|
||||||
|
|
||||||
|
interface FiltersProps {
|
||||||
|
fields: FieldType[];
|
||||||
|
onChange: (filters: FilterValues) => void;
|
||||||
|
initialValues?: FilterValues;
|
||||||
|
className?: string;
|
||||||
|
fieldClassName?: string;
|
||||||
|
searchField?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Filters: FC<FiltersProps> = ({
|
||||||
|
fields,
|
||||||
|
onChange,
|
||||||
|
initialValues = {},
|
||||||
|
className = "flex flex-col md:flex-row justify-between items-start md:items-center gap-4",
|
||||||
|
fieldClassName = "flex flex-col sm:flex-row gap-3 md:gap-4 w-full md:w-auto",
|
||||||
|
searchField = "search"
|
||||||
|
}) => {
|
||||||
|
const [filters, setFilters] = useState<FilterValues>({});
|
||||||
|
const [isFiltersOpen, setIsFiltersOpen] = useState(false);
|
||||||
|
const [inputValues, setInputValues] = useState<Record<string, string>>({});
|
||||||
|
const onChangeRef = useRef(onChange);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onChangeRef.current = onChange;
|
||||||
|
}, [onChange]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (Object.keys(initialValues).length > 0) {
|
||||||
|
setFilters(initialValues);
|
||||||
|
const inputFields: Record<string, string> = {};
|
||||||
|
fields.forEach(field => {
|
||||||
|
if (field.type === 'input' && initialValues[field.name]) {
|
||||||
|
inputFields[field.name] = initialValues[field.name] as string;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setInputValues(inputFields);
|
||||||
|
} else {
|
||||||
|
const defaultValues: FilterValues = {};
|
||||||
|
const defaultInputs: Record<string, string> = {};
|
||||||
|
fields.forEach(field => {
|
||||||
|
if ('defaultValue' in field && field.defaultValue !== undefined) {
|
||||||
|
defaultValues[field.name] = field.defaultValue;
|
||||||
|
if (field.type === 'input') {
|
||||||
|
defaultInputs[field.name] = field.defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Object.keys(defaultValues).length > 0) {
|
||||||
|
setFilters(defaultValues);
|
||||||
|
setInputValues(defaultInputs);
|
||||||
|
onChange(defaultValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [fields, initialValues, onChange]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timeouts: Record<string, number> = {};
|
||||||
|
|
||||||
|
Object.keys(inputValues).forEach(fieldName => {
|
||||||
|
timeouts[fieldName] = setTimeout(() => {
|
||||||
|
setFilters(currentFilters => {
|
||||||
|
const currentFilter = currentFilters[fieldName];
|
||||||
|
const newValue = inputValues[fieldName];
|
||||||
|
|
||||||
|
if (currentFilter !== newValue) {
|
||||||
|
const newFilters = { ...currentFilters, [fieldName]: newValue || null };
|
||||||
|
onChangeRef.current(newFilters);
|
||||||
|
return newFilters;
|
||||||
|
}
|
||||||
|
return currentFilters;
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
Object.values(timeouts).forEach(timeout => clearTimeout(timeout));
|
||||||
|
};
|
||||||
|
}, [inputValues]);
|
||||||
|
|
||||||
|
const handleChange = (name: string, value: string | null) => {
|
||||||
|
const newFilters = { ...filters, [name]: value };
|
||||||
|
setFilters(newFilters);
|
||||||
|
onChange(newFilters);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (name: string, event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = event.target.value;
|
||||||
|
setInputValues(prev => ({ ...prev, [name]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderField = (field: FieldType) => {
|
||||||
|
const currentValue = field.type === 'input' ? inputValues[field.name] : filters[field.name];
|
||||||
|
|
||||||
|
switch (field.type) {
|
||||||
|
case 'date':
|
||||||
|
return (
|
||||||
|
<DatePicker
|
||||||
|
key={field.name}
|
||||||
|
placeholder={field.placeholder}
|
||||||
|
onChange={(value) => handleChange(field.name, moment(value, 'jYYYY/jMM/jDD').format('YYYY-MM-DD'))}
|
||||||
|
defaulValue={currentValue || field.defaultValue || ''}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'select':
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
key={field.name}
|
||||||
|
placeholder={field.placeholder}
|
||||||
|
onChange={(e) => handleChange(field.name, e.target.value)}
|
||||||
|
defaultValue={currentValue || field.defaultValue || ''}
|
||||||
|
items={field.options}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'input':
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
key={field.name}
|
||||||
|
placeholder={field.placeholder}
|
||||||
|
variant="search"
|
||||||
|
className="w-full md:min-w-[230px] md:max-w-[230px]"
|
||||||
|
value={currentValue || field.defaultValue || ''}
|
||||||
|
onChange={(e) => handleInputChange(field.name, e)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchFieldObj = fields.find(field => field.name === searchField);
|
||||||
|
const otherFields = fields.filter(field => field.name !== searchField);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
<div className="flex md:hidden items-center justify-between w-full mb-2">
|
||||||
|
<button
|
||||||
|
onClick={() => setIsFiltersOpen(!isFiltersOpen)}
|
||||||
|
className='flex items-center gap-2 px-3 py-2 rounded-lg transition-colors bg-card text-card-foreground border border-border hover:bg-secondary hover:bg-opacity-80'
|
||||||
|
>
|
||||||
|
<Filter
|
||||||
|
size={18}
|
||||||
|
color='#000000'
|
||||||
|
/>
|
||||||
|
<span className="text-sm">فیلترها</span>
|
||||||
|
</button>
|
||||||
|
{searchFieldObj && (
|
||||||
|
<div className="flex-1 mr-4">
|
||||||
|
{renderField(searchFieldObj)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="hidden md:flex md:flex-row justify-between items-center gap-4 w-full">
|
||||||
|
<div className={fieldClassName}>
|
||||||
|
{fields.map(renderField)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DefaulModal
|
||||||
|
open={isFiltersOpen}
|
||||||
|
close={() => setIsFiltersOpen(false)}
|
||||||
|
isHeader={true}
|
||||||
|
title_header="فیلتر کردن نتایج"
|
||||||
|
>
|
||||||
|
<div className="pb-5 gap-4 flex flex-col-reverse mt-3">
|
||||||
|
{otherFields.map(field => (
|
||||||
|
<div key={field.name} className="w-full">
|
||||||
|
<label className="block text-sm font-medium text-foreground mb-2">
|
||||||
|
{field.placeholder}
|
||||||
|
</label>
|
||||||
|
<div className="w-full">
|
||||||
|
{renderField(field)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className='mt-6'>
|
||||||
|
<Button
|
||||||
|
label='بستن'
|
||||||
|
onClick={() => setIsFiltersOpen(false)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DefaulModal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Filters;
|
||||||
@@ -34,9 +34,9 @@ const Input: FC<Props> = (props: Props) => {
|
|||||||
const [search, setSearch] = useState<string>('')
|
const [search, setSearch] = useState<string>('')
|
||||||
|
|
||||||
const inputClass = clx(
|
const inputClass = clx(
|
||||||
'w-full h-10 text-black block px-4 text-xs rounded-xl border border-border',
|
'w-full bg-white h-10 text-black block px-4 text-xs rounded-xl border border-border',
|
||||||
props.readOnly && 'bg-gray-100 border-0 text-description',
|
props.readOnly && 'bg-gray-100 border-0 text-description',
|
||||||
props.variant === 'search' && 'bg-[#EEF0F7] border-0 ps-10',
|
props.variant === 'search' && 'border-0 ps-10',
|
||||||
props.className
|
props.className
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ const RowActionsDropdown: React.FC<RowActionsDropdownProps> = ({
|
|||||||
return createPortal(
|
return createPortal(
|
||||||
<div
|
<div
|
||||||
ref={dropdownRef}
|
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]"
|
className="fixed py-2 bg-white rounded-lg shadow-xl z-[5] min-w-[140px] md:min-w-[150px] border border-gray-200"
|
||||||
style={{
|
style={{
|
||||||
top: `${dropdownPosition.top}px`,
|
top: `${dropdownPosition.top}px`,
|
||||||
left: `${dropdownPosition.left}px`,
|
left: `${dropdownPosition.left}px`,
|
||||||
@@ -112,10 +112,10 @@ const RowActionsDropdown: React.FC<RowActionsDropdownProps> = ({
|
|||||||
<button
|
<button
|
||||||
key={index}
|
key={index}
|
||||||
onClick={() => handleActionClick(action)}
|
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 || ''}`}
|
className={`w-full text-right px-3 py-2.5 text-sm flex items-center gap-2 hover:bg-gray-50 transition-colors ${index === 0 ? 'rounded-t-lg' : ''} ${index === actions.length - 1 ? 'rounded-b-lg' : ''} ${action.className || ''}`}
|
||||||
>
|
>
|
||||||
{action.icon && <span className="ml-2">{action.icon}</span>}
|
{action.icon && <span className="ml-2 flex-shrink-0">{action.icon}</span>}
|
||||||
<span className={`${action.label === 'حذف' ? 'text-red-500' : ''} dark:text-gray-200`}>{action.label}</span>
|
<span className={`flex-1 ${action.label === 'حذف' ? 'text-red-500' : 'text-gray-700'}`}>{action.label}</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>,
|
</div>,
|
||||||
@@ -128,12 +128,12 @@ const RowActionsDropdown: React.FC<RowActionsDropdownProps> = ({
|
|||||||
<button
|
<button
|
||||||
ref={buttonRef}
|
ref={buttonRef}
|
||||||
onClick={handleToggle}
|
onClick={handleToggle}
|
||||||
className="mt-2 hover:bg-gray-100 dark:hover:bg-[#2d2d30] rounded-full transition-colors"
|
className={`p-1.5 hover:bg-gray-100 rounded-lg transition-colors ${isOpen ? 'bg-gray-100' : ''}`}
|
||||||
aria-haspopup="menu"
|
aria-haspopup="menu"
|
||||||
aria-expanded={isOpen}
|
aria-expanded={isOpen}
|
||||||
>
|
>
|
||||||
{trigger ? trigger : (
|
{trigger ? trigger : (
|
||||||
<More size={16} color={'black'} className="rotate-90" />
|
<More size={18} color="#6B7280" className="rotate-90" />
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
import { type FC, useState } from 'react'
|
||||||
|
import Table from '@/components/Table'
|
||||||
|
import Button from '@/components/Button'
|
||||||
|
import Input from '@/components/Input'
|
||||||
|
import Select from '@/components/Select'
|
||||||
|
import SwitchComponent from '@/components/Switch'
|
||||||
|
import UploadBox from '@/components/UploadBox'
|
||||||
|
import { type RowActionItem } from '@/components/RowActionsDropdown'
|
||||||
|
import { Add } from 'iconsax-react'
|
||||||
|
import Filters from '@/components/Filters'
|
||||||
|
|
||||||
|
interface CategoryData {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
icon: string
|
||||||
|
iconColor: string
|
||||||
|
order: number
|
||||||
|
foodCount: number
|
||||||
|
status: boolean
|
||||||
|
[key: string]: string | number | boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const CategoryFood: FC = () => {
|
||||||
|
const [selectedRow, setSelectedRow] = useState<number | null>(null)
|
||||||
|
const [currentPage, setCurrentPage] = useState(1)
|
||||||
|
|
||||||
|
// فرم دستهبندی جدید
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
isActive: true,
|
||||||
|
categoryStatus: '',
|
||||||
|
title: '',
|
||||||
|
order: '',
|
||||||
|
icon: [] as File[]
|
||||||
|
})
|
||||||
|
|
||||||
|
// دادههای تستی
|
||||||
|
const [categories] = useState<CategoryData[]>([
|
||||||
|
{ id: 1, name: 'ایرانی', icon: '🍕', iconColor: '#FF6B6B', order: 1, foodCount: 14, status: true },
|
||||||
|
{ id: 2, name: 'پیتزا آمریکایی', icon: '🍕', iconColor: '#FF6B6B', order: 2, foodCount: 14, status: true },
|
||||||
|
{ id: 3, name: 'پیتزا آمریکایی', icon: '🍕', iconColor: '#FF6B6B', order: 3, foodCount: 14, status: true },
|
||||||
|
{ id: 4, name: 'پیتزا آمریکایی', icon: '🍕', iconColor: '#FF6B6B', order: 4, foodCount: 14, status: true },
|
||||||
|
{ id: 5, name: 'پیتزا آمریکایی', icon: '🍕', iconColor: '#FF6B6B', order: 5, foodCount: 14, status: true },
|
||||||
|
{ id: 6, name: 'پیتزا آمریکایی', icon: '🍕', iconColor: '#FF6B6B', order: 6, foodCount: 14, status: true },
|
||||||
|
])
|
||||||
|
|
||||||
|
const orderOptions = Array.from({ length: 20 }, (_, i) => ({
|
||||||
|
value: String(i + 1),
|
||||||
|
label: String(i + 1)
|
||||||
|
}))
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
console.log('ذخیره:', formData)
|
||||||
|
// پاک کردن فرم بعد از ذخیره
|
||||||
|
setFormData({
|
||||||
|
isActive: true,
|
||||||
|
categoryStatus: '',
|
||||||
|
title: '',
|
||||||
|
order: '',
|
||||||
|
icon: []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const rowActions = (item: CategoryData): RowActionItem[] => [
|
||||||
|
{
|
||||||
|
label: 'ویرایش',
|
||||||
|
onClick: () => {
|
||||||
|
console.log('ویرایش', item)
|
||||||
|
// بارگذاری دادهها در فرم
|
||||||
|
setFormData({
|
||||||
|
isActive: item.status,
|
||||||
|
categoryStatus: '',
|
||||||
|
title: item.name,
|
||||||
|
order: String(item.order),
|
||||||
|
icon: []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'حذف',
|
||||||
|
onClick: () => console.log('حذف', item)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: 'آیکون',
|
||||||
|
key: 'icon',
|
||||||
|
render: (item: CategoryData) => (
|
||||||
|
<div
|
||||||
|
className="w-8 h-8 rounded-lg flex items-center justify-center text-lg"
|
||||||
|
style={{ backgroundColor: item.iconColor }}
|
||||||
|
>
|
||||||
|
{item.icon}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'عنوان',
|
||||||
|
key: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'ترتیب نمایش',
|
||||||
|
key: 'order',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'تعداد غذا',
|
||||||
|
key: 'foodCount',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'وضعیت',
|
||||||
|
key: 'status',
|
||||||
|
render: (item: CategoryData) => (
|
||||||
|
<SwitchComponent
|
||||||
|
active={item.status}
|
||||||
|
onChange={(value) => console.log('تغییر وضعیت:', item.id, value)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex gap-6 p-6">
|
||||||
|
{/* بخش اصلی - جدول */}
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center justify-between mb-6">
|
||||||
|
<h1 className="text-lg font-light">دسته بندی غذا</h1>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Filters
|
||||||
|
fields={[
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'search',
|
||||||
|
placeholder: 'جستجو',
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
onChange={() => { }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
data={categories}
|
||||||
|
selectable
|
||||||
|
rowActions={rowActions}
|
||||||
|
onRowClick={(id) => setSelectedRow(id as number)}
|
||||||
|
rowClassName={(item) =>
|
||||||
|
selectedRow === item.id ? 'bg-blue-50 border-2 border-blue-400 border-dashed' : ''
|
||||||
|
}
|
||||||
|
pagination={{
|
||||||
|
currentPage,
|
||||||
|
totalPages: 2,
|
||||||
|
onPageChange: setCurrentPage
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* سایدبار - فرم اضافه کردن */}
|
||||||
|
<div className="w-[300px] bg-white rounded-3xl p-6 h-fit sticky top-6">
|
||||||
|
<h2 className="font-light mb-6">اضافه کردن دسته بندی</h2>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between mb-6">
|
||||||
|
<div className="text-sm">فعال</div>
|
||||||
|
<SwitchComponent
|
||||||
|
active={formData.isActive}
|
||||||
|
onChange={(value) => setFormData({ ...formData, isActive: value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<Input
|
||||||
|
label="وضعیت دسته بندی"
|
||||||
|
value={formData.categoryStatus}
|
||||||
|
onChange={(e) => setFormData({ ...formData, categoryStatus: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<Input
|
||||||
|
label="عنوان دسته بندی"
|
||||||
|
value={formData.title}
|
||||||
|
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<Select
|
||||||
|
label="ترتیب نمایش"
|
||||||
|
items={orderOptions}
|
||||||
|
value={formData.order}
|
||||||
|
onChange={(e) => setFormData({ ...formData, order: e.target.value })}
|
||||||
|
placeholder="انتخاب کنید"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-6">
|
||||||
|
<UploadBox
|
||||||
|
label="آیکون دسته بندی"
|
||||||
|
onChange={(files) => setFormData({ ...formData, icon: files })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button onClick={handleSave} className="w-full">
|
||||||
|
<Add size={20} className="ml-2" />
|
||||||
|
ذخیره
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CategoryFood
|
||||||
@@ -8,6 +8,7 @@ import { useSharedStore } from '../shared/store/sharedStore'
|
|||||||
import FoodsList from '@/pages/food/List'
|
import FoodsList from '@/pages/food/List'
|
||||||
import { Pages } from '@/config/Pages'
|
import { Pages } from '@/config/Pages'
|
||||||
import CreateFood from '@/pages/food/Create'
|
import CreateFood from '@/pages/food/Create'
|
||||||
|
import CategoryFood from '@/pages/food/Category'
|
||||||
|
|
||||||
const MainRouter: FC = () => {
|
const MainRouter: FC = () => {
|
||||||
|
|
||||||
@@ -28,6 +29,7 @@ const MainRouter: FC = () => {
|
|||||||
<Routes>
|
<Routes>
|
||||||
<Route path={Pages.foods.list} element={<FoodsList />} />
|
<Route path={Pages.foods.list} element={<FoodsList />} />
|
||||||
<Route path={Pages.foods.add} element={<CreateFood />} />
|
<Route path={Pages.foods.add} element={<CreateFood />} />
|
||||||
|
<Route path={Pages.foods.category} element={<CategoryFood />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user