diff --git a/ecosystem.config.js.example b/ecosystem.config.js.example new file mode 100644 index 0000000..75150b2 --- /dev/null +++ b/ecosystem.config.js.example @@ -0,0 +1,18 @@ +module.exports = { + apps: [{ + name: "rms-v3", + script: 'node_modules/next/dist/bin/next', // cluster mode run with node only, not npm + args: 'start -p 3000', + exec_mode: "cluster", // default fork + instances: "max", + kill_timeout: 4000, + wait_ready: true, + autorestart: true, + watch: false, + max_memory_restart: "1G", + log_date_format: "YYYY-MM-DD HH:mm Z", + env_prod: { + APP_ENV: 'prod' // APP_ENV=prod + } + }], +}; \ No newline at end of file diff --git a/jsconfig.json b/jsconfig.json index b8d6842..d9112b4 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,7 +1,12 @@ { "compilerOptions": { "paths": { - "@/*": ["./src/*"] + "@/*": [ + "./src/*" + ], + "^/*": [ + "./public/*" + ] } } } diff --git a/package.json b/package.json index 7ca72ba..3e7a01c 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,17 @@ "@mui/icons-material": "^5.15.6", "@mui/material": "^5.15.6", "@mui/material-nextjs": "^5.15.6", + "@mui/x-date-pickers": "^6.19.5", + "date-fns": "^3.3.1", + "date-fns-jalali": "^2.13.0-0", + "dayjs": "^1.11.10", + "formik": "^2.4.5", + "jalali-moment": "^3.3.11", + "material-react-table": "^2.11.2", "next": "14.1.0", "react": "^18", "react-dom": "^18", + "sass": "^1.75.0", "stylis": "^4.3.1", "stylis-plugin-rtl": "^2.1.1" }, diff --git a/public/fontiran.scss b/public/fontiran.scss new file mode 100644 index 0000000..5ee0036 --- /dev/null +++ b/public/fontiran.scss @@ -0,0 +1,176 @@ +/** +* +* Name: IRAN Sans-Serif Font +* Version: 5.0 +* Author: Moslem Ebrahimi (moslemebrahimi.com) +* Created on: Dec 25, 2012 +* Updated on: Sep 01, 2017 +* Website: http://fontiran.com +* Copyright: Commercial/Proprietary Software +-------------------------------------------------------------------------------------- +فونت های ایران سن سریف یک نرم افزار مالکیتی محسوب می شود. جهت آگاهی از قوانین استفاده از این فونت ها لطفا به وب سایت (فونت ایران دات کام) مراجعه نمایید +-------------------------------------------------------------------------------------- +IRAN Sans-serif fonts are considered a proprietary software. To gain information about the laws regarding the use of these fonts, please visit www.fontiran.com +-------------------------------------------------------------------------------------- +This set of fonts are used in this project under the license: (.....) +-------------------------------------------------------------------------------------- +* +**/ +@font-face { + font-family: IRANSansFaNum; + font-style: normal; + font-weight: 900; + src: url('./fonts/eot/IRANSansWeb(FaNum)_Black.eot'); + src: url('./fonts/eot/IRANSansWeb(FaNum)_Black.eot?#iefix') format('embedded-opentype'), /* IE6-8 */ + url('./fonts/woff2/IRANSansWeb(FaNum)_Black.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/ + url('./fonts/woff/IRANSansWeb(FaNum)_Black.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ + url('./fonts/ttf/IRANSansWeb(FaNum)_Black.ttf') format('truetype'); +} + +@font-face { + font-family: IRANSansFaNum; + font-style: normal; + font-weight: bold; + src: url('./fonts/eot/IRANSansWeb(FaNum)_Bold.eot'); + src: url('./fonts/eot/IRANSansWeb(FaNum)_Bold.eot?#iefix') format('embedded-opentype'), /* IE6-8 */ + url('./fonts/woff2/IRANSansWeb(FaNum)_Bold.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/ + url('./fonts/woff/IRANSansWeb(FaNum)_Bold.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ + url('./fonts/ttf/IRANSansWeb(FaNum)_Bold.ttf') format('truetype'); +} + +@font-face { + font-family: IRANSansFaNum; + font-style: normal; + font-weight: 500; + src: url('./fonts/eot/IRANSansWeb(FaNum)_Medium.eot'); + src: url('./fonts/eot/IRANSansWeb(FaNum)_Medium.eot?#iefix') format('embedded-opentype'), /* IE6-8 */ + url('./fonts/woff2/IRANSansWeb(FaNum)_Medium.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/ + url('./fonts/woff/IRANSansWeb(FaNum)_Medium.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ + url('./fonts/ttf/IRANSansWeb(FaNum)_Medium.ttf') format('truetype'); +} + +@font-face { + font-family: IRANSansFaNum; + font-style: normal; + font-weight: 300; + src: url('./fonts/eot/IRANSansWeb(FaNum)_Light.eot'); + src: url('./fonts/eot/IRANSansWeb(FaNum)_Light.eot?#iefix') format('embedded-opentype'), /* IE6-8 */ + url('./fonts/woff2/IRANSansWeb(FaNum)_Light.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/ + url('./fonts/woff/IRANSansWeb(FaNum)_Light.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ + url('./fonts/ttf/IRANSansWeb(FaNum)_Light.ttf') format('truetype'); +} + +@font-face { + font-family: IRANSansFaNum; + font-style: normal; + font-weight: 200; + src: url('./fonts/eot/IRANSansWeb(FaNum)_UltraLight.eot'); + src: url('./fonts/eot/IRANSansWeb(FaNum)_UltraLight.eot?#iefix') format('embedded-opentype'), /* IE6-8 */ + url('./fonts/woff2/IRANSansWeb(FaNum)_UltraLight.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/ + url('./fonts/woff/IRANSansWeb(FaNum)_UltraLight.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ + url('./fonts/ttf/IRANSansWeb(FaNum)_UltraLight.ttf') format('truetype'); +} + +@font-face { + font-family: IRANSansFaNum; + font-style: normal; + font-weight: normal; + src: url('./fonts/eot/IRANSansWeb(FaNum).eot'); + src: url('./fonts/eot/IRANSansWeb(FaNum).eot?#iefix') format('embedded-opentype'), /* IE6-8 */ + url('./fonts/woff2/IRANSansWeb(FaNum).woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/ + url('./fonts/woff/IRANSansWeb(FaNum).woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ + url('./fonts/ttf/IRANSansWeb(FaNum).ttf') format('truetype'); +} + +@font-face { + font-family: IRANSans; + font-style: normal; + font-weight: 900; + src: url('./fonts/eot/IRANSansWeb_Black.eot'); + src: url('./fonts/eot/IRANSansWeb_Black.eot?#iefix') format('embedded-opentype'), /* IE6-8 */ + url('./fonts/woff2/IRANSansWeb_Black.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/ + url('./fonts/woff/IRANSansWeb_Black.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ + url('./fonts/ttf/IRANSansWeb_Black.ttf') format('truetype'); +} + +@font-face { + font-family: IRANSans; + font-style: normal; + font-weight: bold; + src: url('./fonts/eot/IRANSansWeb_Bold.eot'); + src: url('./fonts/eot/IRANSansWeb_Bold.eot?#iefix') format('embedded-opentype'), /* IE6-8 */ + url('./fonts/woff2/IRANSansWeb_Bold.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/ + url('./fonts/woff/IRANSansWeb_Bold.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ + url('./fonts/ttf/IRANSansWeb_Bold.ttf') format('truetype'); +} + +@font-face { + font-family: IRANSans; + font-style: normal; + font-weight: 500; + src: url('./fonts/eot/IRANSansWeb_Medium.eot'); + src: url('./fonts/eot/IRANSansWeb_Medium.eot?#iefix') format('embedded-opentype'), /* IE6-8 */ + url('./fonts/woff2/IRANSansWeb_Medium.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/ + url('./fonts/woff/IRANSansWeb_Medium.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ + url('./fonts/ttf/IRANSansWeb_Medium.ttf') format('truetype'); +} + +@font-face { + font-family: IRANSans; + font-style: normal; + font-weight: 300; + src: url('./fonts/eot/IRANSansWeb_Light.eot'); + src: url('./fonts/eot/IRANSansWeb_Light.eot?#iefix') format('embedded-opentype'), /* IE6-8 */ + url('./fonts/woff2/IRANSansWeb_Light.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/ + url('./fonts/woff/IRANSansWeb_Light.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ + url('./fonts/ttf/IRANSansWeb_Light.ttf') format('truetype'); +} + +@font-face { + font-family: IRANSans; + font-style: normal; + font-weight: 200; + src: url('./fonts/eot/IRANSansWeb_UltraLight.eot'); + src: url('./fonts/eot/IRANSansWeb_UltraLight.eot?#iefix') format('embedded-opentype'), /* IE6-8 */ + url('./fonts/woff2/IRANSansWeb_UltraLight.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/ + url('./fonts/woff/IRANSansWeb_UltraLight.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ + url('./fonts/ttf/IRANSansWeb_UltraLight.ttf') format('truetype'); +} + +@font-face { + font-family: IRANSans; + font-style: normal; + font-weight: normal; + src: url('./fonts/eot/IRANSansWeb.eot'); + src: url('./fonts/eot/IRANSansWeb.eot?#iefix') format('embedded-opentype'), /* IE6-8 */ + url('./fonts/woff2/IRANSansWeb.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/ + url('./fonts/woff/IRANSansWeb.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ + url('./fonts/ttf/IRANSansWeb.ttf') format('truetype'); +} + +@font-face { + font-family: Parastoo; + font-style: normal; + font-weight: normal; + src: url('./fonts/eot/Parastoo.eot'); + src: url('./fonts/eot/Parastoo.eot?#iefix') format('embedded-opentype'), /* IE6-8 */ + url('./fonts/woff/Parastoo.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ + url('./fonts/ttf/Parastoo.ttf') format('truetype'); +} + +@font-face { + font-family: Parastoo; + font-style: normal; + font-weight: bold; + src: url('./fonts/eot/Parastoo-Bold.eot'); + src: url('./fonts/eot/Parastoo-Bold.eot?#iefix') format('embedded-opentype'), /* IE6-8 */ + url('./fonts/woff/Parastoo-Bold.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/ + url('./fonts/ttf/Parastoo-Bold.ttf') format('truetype'); +} + +@font-face { + font-family: Bnazanin; + font-style: normal; + font-weight: normal; + src: url('./fonts/ttf/BNazanin.ttf') format('truetype'); +} diff --git a/public/fonts/eot/IRANSansWeb(FaNum).eot b/public/fonts/eot/IRANSansWeb(FaNum).eot new file mode 100644 index 0000000..5a6fdd0 Binary files /dev/null and b/public/fonts/eot/IRANSansWeb(FaNum).eot differ diff --git a/public/fonts/eot/IRANSansWeb(FaNum)_Black.eot b/public/fonts/eot/IRANSansWeb(FaNum)_Black.eot new file mode 100644 index 0000000..a012bc6 Binary files /dev/null and b/public/fonts/eot/IRANSansWeb(FaNum)_Black.eot differ diff --git a/public/fonts/eot/IRANSansWeb(FaNum)_Bold.eot b/public/fonts/eot/IRANSansWeb(FaNum)_Bold.eot new file mode 100644 index 0000000..82525e5 Binary files /dev/null and b/public/fonts/eot/IRANSansWeb(FaNum)_Bold.eot differ diff --git a/public/fonts/eot/IRANSansWeb(FaNum)_Light.eot b/public/fonts/eot/IRANSansWeb(FaNum)_Light.eot new file mode 100644 index 0000000..f899008 Binary files /dev/null and b/public/fonts/eot/IRANSansWeb(FaNum)_Light.eot differ diff --git a/public/fonts/eot/IRANSansWeb(FaNum)_Medium.eot b/public/fonts/eot/IRANSansWeb(FaNum)_Medium.eot new file mode 100644 index 0000000..117c2c8 Binary files /dev/null and b/public/fonts/eot/IRANSansWeb(FaNum)_Medium.eot differ diff --git a/public/fonts/eot/IRANSansWeb(FaNum)_UltraLight.eot b/public/fonts/eot/IRANSansWeb(FaNum)_UltraLight.eot new file mode 100644 index 0000000..9cd9b66 Binary files /dev/null and b/public/fonts/eot/IRANSansWeb(FaNum)_UltraLight.eot differ diff --git a/public/fonts/eot/IRANSansWeb.eot b/public/fonts/eot/IRANSansWeb.eot new file mode 100644 index 0000000..553eec8 Binary files /dev/null and b/public/fonts/eot/IRANSansWeb.eot differ diff --git a/public/fonts/eot/IRANSansWeb_Black.eot b/public/fonts/eot/IRANSansWeb_Black.eot new file mode 100644 index 0000000..ee7c6a0 Binary files /dev/null and b/public/fonts/eot/IRANSansWeb_Black.eot differ diff --git a/public/fonts/eot/IRANSansWeb_Bold.eot b/public/fonts/eot/IRANSansWeb_Bold.eot new file mode 100644 index 0000000..746a6e6 Binary files /dev/null and b/public/fonts/eot/IRANSansWeb_Bold.eot differ diff --git a/public/fonts/eot/IRANSansWeb_Light.eot b/public/fonts/eot/IRANSansWeb_Light.eot new file mode 100644 index 0000000..f79a681 Binary files /dev/null and b/public/fonts/eot/IRANSansWeb_Light.eot differ diff --git a/public/fonts/eot/IRANSansWeb_Medium.eot b/public/fonts/eot/IRANSansWeb_Medium.eot new file mode 100644 index 0000000..8b31d6b Binary files /dev/null and b/public/fonts/eot/IRANSansWeb_Medium.eot differ diff --git a/public/fonts/eot/IRANSansWeb_UltraLight.eot b/public/fonts/eot/IRANSansWeb_UltraLight.eot new file mode 100644 index 0000000..fd4b205 Binary files /dev/null and b/public/fonts/eot/IRANSansWeb_UltraLight.eot differ diff --git a/public/fonts/eot/Parastoo-Bold.eot b/public/fonts/eot/Parastoo-Bold.eot new file mode 100644 index 0000000..4dc70ba Binary files /dev/null and b/public/fonts/eot/Parastoo-Bold.eot differ diff --git a/public/fonts/eot/Parastoo.eot b/public/fonts/eot/Parastoo.eot new file mode 100644 index 0000000..fd15eda Binary files /dev/null and b/public/fonts/eot/Parastoo.eot differ diff --git a/public/fonts/ttf/BNazanin.ttf b/public/fonts/ttf/BNazanin.ttf new file mode 100644 index 0000000..4e59251 Binary files /dev/null and b/public/fonts/ttf/BNazanin.ttf differ diff --git a/public/fonts/ttf/IRANSansWeb(FaNum).ttf b/public/fonts/ttf/IRANSansWeb(FaNum).ttf new file mode 100644 index 0000000..cd74265 Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb(FaNum).ttf differ diff --git a/public/fonts/ttf/IRANSansWeb(FaNum)_Black.ttf b/public/fonts/ttf/IRANSansWeb(FaNum)_Black.ttf new file mode 100644 index 0000000..0f84dbf Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb(FaNum)_Black.ttf differ diff --git a/public/fonts/ttf/IRANSansWeb(FaNum)_Bold.ttf b/public/fonts/ttf/IRANSansWeb(FaNum)_Bold.ttf new file mode 100644 index 0000000..6fa2412 Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb(FaNum)_Bold.ttf differ diff --git a/public/fonts/ttf/IRANSansWeb(FaNum)_Light.ttf b/public/fonts/ttf/IRANSansWeb(FaNum)_Light.ttf new file mode 100644 index 0000000..d9000c9 Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb(FaNum)_Light.ttf differ diff --git a/public/fonts/ttf/IRANSansWeb(FaNum)_Medium.ttf b/public/fonts/ttf/IRANSansWeb(FaNum)_Medium.ttf new file mode 100644 index 0000000..e20001c Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb(FaNum)_Medium.ttf differ diff --git a/public/fonts/ttf/IRANSansWeb(FaNum)_UltraLight.ttf b/public/fonts/ttf/IRANSansWeb(FaNum)_UltraLight.ttf new file mode 100644 index 0000000..0072606 Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb(FaNum)_UltraLight.ttf differ diff --git a/public/fonts/ttf/IRANSansWeb.ttf b/public/fonts/ttf/IRANSansWeb.ttf new file mode 100644 index 0000000..7416c53 Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb.ttf differ diff --git a/public/fonts/ttf/IRANSansWeb_Black.ttf b/public/fonts/ttf/IRANSansWeb_Black.ttf new file mode 100644 index 0000000..df4928f Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb_Black.ttf differ diff --git a/public/fonts/ttf/IRANSansWeb_Bold.ttf b/public/fonts/ttf/IRANSansWeb_Bold.ttf new file mode 100644 index 0000000..158fbaa Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb_Bold.ttf differ diff --git a/public/fonts/ttf/IRANSansWeb_Light.ttf b/public/fonts/ttf/IRANSansWeb_Light.ttf new file mode 100644 index 0000000..3060c60 Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb_Light.ttf differ diff --git a/public/fonts/ttf/IRANSansWeb_Medium.ttf b/public/fonts/ttf/IRANSansWeb_Medium.ttf new file mode 100644 index 0000000..6dd6c5d Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb_Medium.ttf differ diff --git a/public/fonts/ttf/IRANSansWeb_UltraLight.ttf b/public/fonts/ttf/IRANSansWeb_UltraLight.ttf new file mode 100644 index 0000000..7ebe1bf Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb_UltraLight.ttf differ diff --git a/public/fonts/ttf/Parastoo-Bold.ttf b/public/fonts/ttf/Parastoo-Bold.ttf new file mode 100644 index 0000000..5ef27c9 Binary files /dev/null and b/public/fonts/ttf/Parastoo-Bold.ttf differ diff --git a/public/fonts/ttf/Parastoo.ttf b/public/fonts/ttf/Parastoo.ttf new file mode 100644 index 0000000..f52889c Binary files /dev/null and b/public/fonts/ttf/Parastoo.ttf differ diff --git a/public/fonts/woff/IRANSansWeb(FaNum).woff b/public/fonts/woff/IRANSansWeb(FaNum).woff new file mode 100644 index 0000000..e34dfc4 Binary files /dev/null and b/public/fonts/woff/IRANSansWeb(FaNum).woff differ diff --git a/public/fonts/woff/IRANSansWeb(FaNum)_Black.woff b/public/fonts/woff/IRANSansWeb(FaNum)_Black.woff new file mode 100644 index 0000000..bbbca77 Binary files /dev/null and b/public/fonts/woff/IRANSansWeb(FaNum)_Black.woff differ diff --git a/public/fonts/woff/IRANSansWeb(FaNum)_Bold.woff b/public/fonts/woff/IRANSansWeb(FaNum)_Bold.woff new file mode 100644 index 0000000..05d933e Binary files /dev/null and b/public/fonts/woff/IRANSansWeb(FaNum)_Bold.woff differ diff --git a/public/fonts/woff/IRANSansWeb(FaNum)_Light.woff b/public/fonts/woff/IRANSansWeb(FaNum)_Light.woff new file mode 100644 index 0000000..fe80d47 Binary files /dev/null and b/public/fonts/woff/IRANSansWeb(FaNum)_Light.woff differ diff --git a/public/fonts/woff/IRANSansWeb(FaNum)_Medium.woff b/public/fonts/woff/IRANSansWeb(FaNum)_Medium.woff new file mode 100644 index 0000000..f13a870 Binary files /dev/null and b/public/fonts/woff/IRANSansWeb(FaNum)_Medium.woff differ diff --git a/public/fonts/woff/IRANSansWeb(FaNum)_UltraLight.woff b/public/fonts/woff/IRANSansWeb(FaNum)_UltraLight.woff new file mode 100644 index 0000000..9c653a4 Binary files /dev/null and b/public/fonts/woff/IRANSansWeb(FaNum)_UltraLight.woff differ diff --git a/public/fonts/woff/IRANSansWeb.woff b/public/fonts/woff/IRANSansWeb.woff new file mode 100644 index 0000000..abb63f2 Binary files /dev/null and b/public/fonts/woff/IRANSansWeb.woff differ diff --git a/public/fonts/woff/IRANSansWeb_Black.woff b/public/fonts/woff/IRANSansWeb_Black.woff new file mode 100644 index 0000000..12c8a6e Binary files /dev/null and b/public/fonts/woff/IRANSansWeb_Black.woff differ diff --git a/public/fonts/woff/IRANSansWeb_Bold.woff b/public/fonts/woff/IRANSansWeb_Bold.woff new file mode 100644 index 0000000..801c55d Binary files /dev/null and b/public/fonts/woff/IRANSansWeb_Bold.woff differ diff --git a/public/fonts/woff/IRANSansWeb_Light.woff b/public/fonts/woff/IRANSansWeb_Light.woff new file mode 100644 index 0000000..99b8d5d Binary files /dev/null and b/public/fonts/woff/IRANSansWeb_Light.woff differ diff --git a/public/fonts/woff/IRANSansWeb_Medium.woff b/public/fonts/woff/IRANSansWeb_Medium.woff new file mode 100644 index 0000000..322c396 Binary files /dev/null and b/public/fonts/woff/IRANSansWeb_Medium.woff differ diff --git a/public/fonts/woff/IRANSansWeb_UltraLight.woff b/public/fonts/woff/IRANSansWeb_UltraLight.woff new file mode 100644 index 0000000..31c21ca Binary files /dev/null and b/public/fonts/woff/IRANSansWeb_UltraLight.woff differ diff --git a/public/fonts/woff/Parastoo-Bold.woff b/public/fonts/woff/Parastoo-Bold.woff new file mode 100644 index 0000000..c6c05ff Binary files /dev/null and b/public/fonts/woff/Parastoo-Bold.woff differ diff --git a/public/fonts/woff/Parastoo.woff b/public/fonts/woff/Parastoo.woff new file mode 100644 index 0000000..e5efc4d Binary files /dev/null and b/public/fonts/woff/Parastoo.woff differ diff --git a/public/fonts/woff2/IRANSansWeb(FaNum).woff2 b/public/fonts/woff2/IRANSansWeb(FaNum).woff2 new file mode 100644 index 0000000..30a377d Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb(FaNum).woff2 differ diff --git a/public/fonts/woff2/IRANSansWeb(FaNum)_Black.woff2 b/public/fonts/woff2/IRANSansWeb(FaNum)_Black.woff2 new file mode 100644 index 0000000..0b15441 Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb(FaNum)_Black.woff2 differ diff --git a/public/fonts/woff2/IRANSansWeb(FaNum)_Bold.woff2 b/public/fonts/woff2/IRANSansWeb(FaNum)_Bold.woff2 new file mode 100644 index 0000000..f2353b2 Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb(FaNum)_Bold.woff2 differ diff --git a/public/fonts/woff2/IRANSansWeb(FaNum)_Light.woff2 b/public/fonts/woff2/IRANSansWeb(FaNum)_Light.woff2 new file mode 100644 index 0000000..562d663 Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb(FaNum)_Light.woff2 differ diff --git a/public/fonts/woff2/IRANSansWeb(FaNum)_Medium.woff2 b/public/fonts/woff2/IRANSansWeb(FaNum)_Medium.woff2 new file mode 100644 index 0000000..a69f4d0 Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb(FaNum)_Medium.woff2 differ diff --git a/public/fonts/woff2/IRANSansWeb(FaNum)_UltraLight.woff2 b/public/fonts/woff2/IRANSansWeb(FaNum)_UltraLight.woff2 new file mode 100644 index 0000000..1114f71 Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb(FaNum)_UltraLight.woff2 differ diff --git a/public/fonts/woff2/IRANSansWeb.woff2 b/public/fonts/woff2/IRANSansWeb.woff2 new file mode 100644 index 0000000..21bbba5 Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb.woff2 differ diff --git a/public/fonts/woff2/IRANSansWeb_Black.woff2 b/public/fonts/woff2/IRANSansWeb_Black.woff2 new file mode 100644 index 0000000..da34aad Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb_Black.woff2 differ diff --git a/public/fonts/woff2/IRANSansWeb_Bold.woff2 b/public/fonts/woff2/IRANSansWeb_Bold.woff2 new file mode 100644 index 0000000..dd1436c Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb_Bold.woff2 differ diff --git a/public/fonts/woff2/IRANSansWeb_Light.woff2 b/public/fonts/woff2/IRANSansWeb_Light.woff2 new file mode 100644 index 0000000..16dc9f1 Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb_Light.woff2 differ diff --git a/public/fonts/woff2/IRANSansWeb_Medium.woff2 b/public/fonts/woff2/IRANSansWeb_Medium.woff2 new file mode 100644 index 0000000..5fb39d4 Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb_Medium.woff2 differ diff --git a/public/fonts/woff2/IRANSansWeb_UltraLight.woff2 b/public/fonts/woff2/IRANSansWeb_UltraLight.woff2 new file mode 100644 index 0000000..eeeec11 Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb_UltraLight.woff2 differ diff --git a/public/global.scss b/public/global.scss new file mode 100644 index 0000000..7802651 --- /dev/null +++ b/public/global.scss @@ -0,0 +1 @@ +@import "fontiran"; \ No newline at end of file diff --git a/src/app/(withAuth)/dashboard/template.js b/src/app/(withAuth)/dashboard/template.js index 1d86153..c44d626 100644 --- a/src/app/(withAuth)/dashboard/template.js +++ b/src/app/(withAuth)/dashboard/template.js @@ -1,11 +1,16 @@ +import {Box, Stack} from "@mui/material"; +import HeaderWithLogo from "@/components/layouts/dashboard/headerWithLogo"; +import HeaderWithSidebar from "@/components/layouts/dashboard/headerWithSidebar"; + const Template = ({children}) => { - return ( - <> - header - sidebar - {children} - - ) + return ( + + + + {children} + + + ) } export default Template \ No newline at end of file diff --git a/src/app/layout.js b/src/app/layout.js index ab64299..c23a7ec 100644 --- a/src/app/layout.js +++ b/src/app/layout.js @@ -1,11 +1,17 @@ +import {TableSettingProvider} from "@/lib/contexts/tableSetting"; + export const metadata = { title: 'سامانه جامع راهداری', } export default function RootLayout({children}) { - return ( - - {children} - - ) + return ( + + + + {children} + + + + ) } diff --git a/src/app/page.js b/src/app/page.js index 7be9aa2..ed41c91 100644 --- a/src/app/page.js +++ b/src/app/page.js @@ -1,5 +1,148 @@ -const Page = () => { - return (<>) +'use client' +import DataTable from "@/core/components/DataTable"; +import { useMemo } from "react"; + +function Page() { + const columns = useMemo( + () => [ + { + accessorKey: 'fullname', + header: 'نام کامل', + id: "fullname", + columns: [ + { + accessorKey: 'name.firstName', + header: 'نام', + id: "firstName", + enableColumnFilter: true, + datatype: "text", + filterFn: "contains", + enableClickToCopy: true, + columnFilterModeOptions: [ + "equals", + "notEquals", + "contains" + ], + }, + { + accessorKey: 'name.lastName', + header: 'نام خانوادگی', + id: "lastName", + enableColumnFilter: true, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: [ + "equals", + "notEquals", + "contains" + ], + }, + ], + enableColumnFilter: true, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: [ + "equals", + "notEquals", + "contains" + ], + }, + { + accessorKey: 'address', + header: 'آدرس', + id: "address", + enableColumnFilter: true, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: [ + "equals", + "notEquals", + "contains" + ], + }, + { + accessorKey: 'city', + header: 'شهر', + id: "city", + enableColumnFilter: true, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: [ + "equals", + "notEquals", + "contains", + "lessThan", + "greaterThan", + "between", + ], + + }, + { + accessorKey: 'state', + header: 'وضعیت', + id: "state", + text: 'نام', + enableColumnFilter: true, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: [ + "equals", + "notEquals", + "contains" + ], + }, + { + accessorKey: 'state', + header: 'محدوده', + id: "betweenNum", + text: 'محدوده', + enableColumnFilter: true, + datatype: "text", + filterFn: "betweenNum", + columnFilterModeOptions: [], + }, + { + accessorKey: 'state', + header: 'تاریخ', + id: "betweenDate", + text: 'تاریخ', + enableColumnFilter: true, + datatype: "text", + filterFn: "betweenDate", + columnFilterModeOptions: [], + }, + { + accessorKey: 'state', + header: 'شلکت', + id: "select2", + text: 'تاریخ', + type: 'select2', + enableColumnFilter: true, + datatype: "text", + filterFn: "equals", + columnFilterModeOptions: [], + }, + { + accessorKey: 'state', + header: 'سلکت', + id: "select", + text: 'تنتنت', + type: 'select', + enableColumnFilter: true, + datatype: "text", + filterFn: "equals", + columnFilterModeOptions: [], + }, + ], + [], + ); + + return ( + <> + + + + ); } -export default Page \ No newline at end of file +export default Page; diff --git a/src/app/template.js b/src/app/template.js index 0443d64..cc8a8f2 100644 --- a/src/app/template.js +++ b/src/app/template.js @@ -2,6 +2,7 @@ import cacheProviderRtl from "@/core/utils/cacheRtl"; import {CssBaseline, ThemeProvider} from "@mui/material"; import theme from "@/core/utils/theme"; import {AppRouterCacheProvider} from "@mui/material-nextjs/v14-appRouter"; +import "^/global.scss" const Template = ({children}) => { return ( diff --git a/src/components/BetweenDateFilter.jsx b/src/components/BetweenDateFilter.jsx new file mode 100644 index 0000000..e8e8c18 --- /dev/null +++ b/src/components/BetweenDateFilter.jsx @@ -0,0 +1,31 @@ +import {Grid} from '@mui/material'; +import MuiDatePicker from "@/components/MuiDatePicker"; +import {useEffect, useState} from "react"; + +const BetWeenDateFilter = ({formik, id , defaultValue}) => { + useEffect(() => { + formik.setFieldValue(`${id}[0]`, defaultValue?.[0] || ''); + formik.setFieldValue(`${id}[1]`, defaultValue?.[1] || ''); + }, []); + + const [dateRange, setDateRange] = useState(defaultValue); + + const handleDateChange = (index, value) => { + const newRange = [...dateRange]; + newRange[index] = value; + setDateRange(newRange); + }; + + return ( + + + handleDateChange(0, value)}/> + + + handleDateChange(1, value)}/> + + + ); +}; + +export default BetWeenDateFilter; diff --git a/src/components/BetweenNumberFilter.jsx b/src/components/BetweenNumberFilter.jsx new file mode 100644 index 0000000..0b3512a --- /dev/null +++ b/src/components/BetweenNumberFilter.jsx @@ -0,0 +1,58 @@ +import {Grid, TextField} from '@mui/material'; +import {useEffect} from "react"; + +const BetweenNumberFilter = ({formik, id , defaultValue}) => { + + useEffect(() => { + formik.setFieldValue(`${id}[0]`, defaultValue?.[0] || ''); + formik.setFieldValue(`${id}[1]`, defaultValue?.[1] || ''); + }, []); + const handleBlur = (index) => () => { + const minValue = formik.values[id][0]; + const maxValue = formik.values[id][1]; + + if (index === 0 && minValue !== '' && maxValue !== '' && parseInt(minValue) > parseInt(maxValue)) { + formik.setFieldValue(`${id}[0]`, maxValue); + } + + if (index === 1 && minValue !== '' && maxValue !== '' && parseInt(maxValue) < parseInt(minValue)) { + formik.setFieldValue(`${id}[1]`, minValue); + } + }; + return ( + + + + + + + + + ); +}; + +export default BetweenNumberFilter; diff --git a/src/components/CustomToolbar.jsx b/src/components/CustomToolbar.jsx new file mode 100644 index 0000000..344b5da --- /dev/null +++ b/src/components/CustomToolbar.jsx @@ -0,0 +1,20 @@ +import FilterColumn from "@/components/FilterColumn"; + +const CustomToolbar = ({columns}) => { + return ( + + ) +} +export default CustomToolbar + + + + + + + + + + + + diff --git a/src/components/DataTableMain.jsx b/src/components/DataTableMain.jsx new file mode 100644 index 0000000..a6e8c91 --- /dev/null +++ b/src/components/DataTableMain.jsx @@ -0,0 +1,141 @@ +import DataTableTest from "@/components/DataTableTest"; +import FilterColumn from "@/components/FilterColumn"; +import {useMemo} from "react"; +import {Button} from "@mui/material"; + +function DataTableMain() { + const columns = useMemo( + () => [ + { + accessorKey: 'name.firstName', + header: 'نام', + size: 150, + id: "firstName", + enableColumnFilter: true, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: [ + "equals", + "notEquals", + "contains" + ], + }, + { + accessorKey: 'name.lastName', + header: 'نام خانوادگی', + size: 150, + id: "lastName", + enableColumnFilter: true, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: [ + "equals", + "notEquals", + "contains" + ], + }, + { + accessorKey: 'address', + header: 'آدرس', + size: 200, + id: "address", + enableColumnFilter: true, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: [ + "equals", + "notEquals", + "contains" + ], + }, + { + accessorKey: 'city', + header: 'شهر', + size: 150, + id: "city", + enableColumnFilter: true, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: [ + "equals", + "notEquals", + "contains", + "lessThan", + "greaterThan", + "between", + ], + + }, + { + accessorKey: 'state', + header: 'وضعیت', + size: 150, + id: "state", + text:'نام', + enableColumnFilter: true, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: [ + "equals", + "notEquals", + "contains" + ], + }, + { + accessorKey: 'state', + header: 'محدوده', + size: 150, + id: "betweenNum", + text:'محدوده', + enableColumnFilter: true, + datatype: "text", + filterFn: "betweenNum", + columnFilterModeOptions: [], + }, + { + accessorKey: 'state', + header: 'تاریخ', + size: 150, + id: "betweenDate", + text:'تاریخ', + enableColumnFilter: true, + datatype: "text", + filterFn: "betweenDate", + columnFilterModeOptions: [], + }, + { + accessorKey: 'state', + header: 'شلکت', + size: 150, + id: "select2", + text:'تاریخ', + type: 'select2', + enableColumnFilter: true, + datatype: "text", + filterFn: "equals", + columnFilterModeOptions: [], + }, + { + accessorKey: 'state', + header: 'سلکت', + size: 150, + id: "select", + text:'تنتنت', + type: 'select', + enableColumnFilter: true, + datatype: "text", + filterFn: "equals", + columnFilterModeOptions: [], + }, + ], + [], + ); + return ( + <> + + + + ); +} + +export default DataTableMain; diff --git a/src/components/DataTableTest.jsx b/src/components/DataTableTest.jsx new file mode 100644 index 0000000..bf610fb --- /dev/null +++ b/src/components/DataTableTest.jsx @@ -0,0 +1,104 @@ +"use client" +import {useMemo, useState} from 'react'; +import {MaterialReactTable} from 'material-react-table'; +import useTableSetting from "@/lib/hooks/useTableSetting"; +import {IconButton, Tooltip, Typography} from "@mui/material"; +import CustomToolbar from "@/components/CustomToolbar"; + + +const data = [ + { + name: { + firstName: 'John', + lastName: 'Doe', + }, + address: '261 Erdman Ford', + city: 'East Daphne', + state: 'Kentucky', + }, + { + name: { + firstName: 'Jane', + lastName: 'Doe', + }, + address: '769 Dominic Grove', + city: 'Columbus', + state: 'Ohio', + }, + { + name: { + firstName: 'Joe', + lastName: 'Doe', + }, + address: '566 Brakus Inlet', + city: 'South Linda', + state: 'West Virginia', + }, + { + name: { + firstName: 'Kevin', + lastName: 'Vandy', + }, + address: '722 Emie Stream', + city: 'Lincoln', + state: 'Nebraska', + }, + { + name: { + firstName: 'Joshua', + lastName: 'Rolluffs', + }, + address: '32188 Larkin Turnpike', + city: 'Charleston', + state: 'South Carolina', + }, +]; + +function RefreshIcon() { + return null; +} + +const TestDataTable = ({columns}) => { + const [userId, setUserId] = useState(2); + const [pageName, setPageName] = useState('loan'); + const [tableName, setTableName] = useState('loan_table'); + const {settingStore, hideAction, filterAction, sortAction,} = useTableSetting(); + + const onColumnVisibilityChange = (event) => { + const settingValue = event(); + hideAction(userId, pageName, tableName, settingValue, columns); + }; + const onSortingChange = (event) => { + const settingValue = event(settingStore?.[userId]?.[pageName]?.[tableName]?.['sorts'] || []); + sortAction(userId, pageName, tableName, settingValue, columns); + } + + return ( + <> + {settingStore?.[userId]?.[pageName]?.[tableName]?.['summary']} + (<> + + )} + initialState={{ + columnVisibility: settingStore?.[userId]?.[pageName]?.[tableName]?.['hides'], + sorting: settingStore?.[userId]?.[pageName]?.[tableName]?.['sorts'] || [] + }} + state={{ + columnVisibility: settingStore?.[userId]?.[pageName]?.[tableName]?.['hides'], + sorting: settingStore?.[userId]?.[pageName]?.[tableName]?.['sorts'] || [] + }} + onColumnVisibilityChange={onColumnVisibilityChange} + onSortingChange={onSortingChange} + /> + + ); +}; + +export default TestDataTable; + diff --git a/src/components/FilterBox.jsx b/src/components/FilterBox.jsx new file mode 100644 index 0000000..f887918 --- /dev/null +++ b/src/components/FilterBox.jsx @@ -0,0 +1,30 @@ +import { Popper, Paper, List, ListItem, ListItemText, ClickAwayListener } from '@mui/material'; + +function FilterBox({anchorEl , open = false, handleClose = () => {} , columnFilterModeOptions , onSelectFilter , defaultFilter}) { + + const id = open ? 'simple-popper' : undefined; + const handleSelectFilter = (filterFn) => { + onSelectFilter(filterFn); + handleClose(); + }; + + return ( +
+ + + + + {columnFilterModeOptions.map((option, index) => ( + handleSelectFilter(option)} selected={option === defaultFilter} sx={{ cursor: 'pointer' }}> + + + ))} + + + + +
+ ); +} + +export default FilterBox; diff --git a/src/components/FilterColumn.jsx b/src/components/FilterColumn.jsx new file mode 100644 index 0000000..08e42c0 --- /dev/null +++ b/src/components/FilterColumn.jsx @@ -0,0 +1,204 @@ +"use client" +import { + Box, + Button, Divider, Drawer, + FormControl, IconButton, styled, Typography +} from '@mui/material'; +import {useFormik} from 'formik'; +import TextFieldWithFnBox from "@/components/TextFieldWithFnBox"; +import {useEffect, useState} from "react"; +import useTableSetting from "@/lib/hooks/useTableSetting"; +import BetweenNumberFilter from "@/components/BetweenNumberFilter"; +import BetWeenDateFilter from "@/components/BetweenDateFilter"; +import SelectBoxMultiple from "@/components/SelectBoxMultiple"; +import SelectBox from "@/components/SelectBox"; +import CloseIcon from '@mui/icons-material/Close'; +import { useTheme } from '@mui/material/styles'; + +function FilterColumn({ columns }) { + const [open, setOpen] = useState(false); + const [selectedFilters, setSelectedFilters] = useState([]); + const [userId, setUserId] = useState(2); + const [pageName, setPageName] = useState('loan'); + const [tableName, setTableName] = useState('loan_table'); + const {settingStore, hideAction, filterAction, sortAction,} = useTableSetting(); + + const DrawerHeader = styled('div')(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + padding: theme.spacing(0, 1), + justifyContent: 'space-between', + })); + const toggleDrawer = (newOpen) => () => { + setOpen(newOpen); + }; + const handleDrawerClose = () => { + setOpen(false); + }; + useEffect(() => { + const settingValue = settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []; + setSelectedFilters(settingValue.length > 0 ? settingValue : []); + }, [settingStore]); + + const handleSelectFilter = (fieldName, filterFn) => { + const filterIndex = selectedFilters.findIndex(filter => filter.id === fieldName); + + if (filterIndex !== -1) { + const updatedFilters = [...selectedFilters]; + updatedFilters[filterIndex].fn = filterFn; + setSelectedFilters(updatedFilters); + } else { + const newFilter = { id: fieldName, fn: filterFn }; + const updatedFilters = [...selectedFilters, newFilter]; + setSelectedFilters(updatedFilters); + } + }; + + const initialValues = columns.reduce((acc, column) => { + if (column.enableColumnFilter) { + const filter = settingStore?.[userId]?.[pageName]?.[tableName]?.['filters']?.find((filter) => filter.id === column.id); + if (column.filterFn === 'betweenNum' || column.filterFn === 'betweenDate') { + acc[column.id] = filter?.value || ["", ""]; + } + else if ( column.type === 'select2' ) { + acc[column.id] = filter?.value || []; + } + else { + acc[column.id] = filter?.value || ''; + } + } + return acc; + }, {}); + + const formik = useFormik({ + initialValues, + onSubmit: (values) => { + const updatedFilters = selectedFilters.map(filter => { + const value = values[filter.id]; + if (value !== undefined) { + return { ...filter, value }; + } + return filter; + }); + Object.entries(values).forEach(([fieldName, value]) => { + const filterIndex = updatedFilters.findIndex(filter => filter.id === fieldName); + if (filterIndex === -1 && value !== '' && value !== null && value !== undefined) { + const column = columns.find(col => col.id === fieldName); + updatedFilters.push({ id: fieldName, value, fn: column.filterFn, datatype: column.datatype }); + } + }); + + const newArrayFilter = updatedFilters.filter((filter) => { + const value = values[filter.id]; + + return ( + value !== '' && + value !== null && + value !== undefined && + !(Array.isArray(value) && (value.length === 0 || (value.length === 2 && value.every((v) => v === '')))) + ); + }); + + setSelectedFilters(newArrayFilter); + filterAction(userId, pageName, tableName, newArrayFilter, columns); + } + + }); + + return ( + <> + + + + فیلتر + + + + + + + {columns.map((column, index) => ( + column.enableColumnFilter && + (column.filterFn === 'betweenDate' ? + handleSelectFilter(column.id, filterFn)} + defaultValue={(settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.value || ''} + /> + : column.filterFn === 'betweenNum' ? + handleSelectFilter(column.id, filterFn)} + defaultValue={(settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.value || ["", ""]} + /> + : column.type === 'select2' ? + handleSelectFilter(column.id, filterFn)} + defaultValue={(settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.value || []} + /> + : column.type === 'select' ? + handleSelectFilter(column.id, filterFn)} + defaultValue={(settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.value || ''} + /> + : + filter.id === column.id)?.fn || column.filterFn} + columnFilterModeOptions={column.columnFilterModeOptions} + onSelectFilter={(filterFn) => handleSelectFilter(column.id, filterFn)} + defaultValue={formik.values[column.id] || (settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.value || ''} + /> + ) + ))} + + + + + + + + + + + ); +} + +export default FilterColumn; + + diff --git a/src/components/MapTest.jsx b/src/components/MapTest.jsx new file mode 100644 index 0000000..8ccea0a --- /dev/null +++ b/src/components/MapTest.jsx @@ -0,0 +1,41 @@ +"use client" +import {Typography} from "@mui/material"; + +const data = [ + { + id : "hello", + Subitems : [ + {id : "amin",firstName : "amin", lastName:"ali",Subitems : [{id : "shahrokh",shahrokh : "shahrokh"}], hasSubitems : true, showSubitems : false}, + {id : "amir",firstName : "amir", lastName:"akbar", hasSubitems : false, showSubitems : false}, + ], + hasSubitems : true, + showSubitems : false + }, + { + id : "hi", + Subitems : [ + {id : "ali",firstName : "ali", lastName:"ali", hasSubitems : false, showSubitems : false}, + {id : "akbar",firstName : "akbar", lastName:"akbar", hasSubitems : false, showSubitems : false}, + ], + hasSubitems : true, + showSubitems : false + } +] +const MapTest = () => { + const Amin = data.map((item) => { + return item.hasSubitems ? {...item, Subitems: item.Subitems.map((subitem) => true ? { + ...subitem, showSubitems: !item.showSubitems + } : subitem)} : item + }) + + + console.log(Amin) + + + return ( + <> + hello + + ) +} +export default MapTest \ No newline at end of file diff --git a/src/components/MuiDatePicker.jsx b/src/components/MuiDatePicker.jsx new file mode 100644 index 0000000..f2b0876 --- /dev/null +++ b/src/components/MuiDatePicker.jsx @@ -0,0 +1,67 @@ +import { LocalizationProvider, MobileDateTimePicker } from "@mui/x-date-pickers"; +import { AdapterDateFnsJalali } from "@mui/x-date-pickers/AdapterDateFnsJalali"; +import moment from "jalali-moment"; +import { faIR } from "@mui/x-date-pickers/locales"; +import { Box, IconButton, InputAdornment, TextField } from "@mui/material"; +import ClearIcon from "@mui/icons-material/Clear"; +import { useState } from "react"; + +export default function MuiDatePicker({label, name, formik, defaultValue, minDate, maxDate, onDateChange }) { + const [pickerValue, setPickerValue] = useState(defaultValue ? new Date(defaultValue) : null); + + return ( + + + { + const date = new Date(newValue); + const formattedDate = moment(date) + .locale("en") + .format("YYYY-MM-DD HH:mm"); + formik.setFieldValue(name, formattedDate); + onDateChange(formattedDate); + setPickerValue(newValue); + }} + slotProps={{ + textField: { + placeholder: "تاریخ خود را وارد کنید", + helperText: label, + InputProps: { + endAdornment: ( + + { + event.stopPropagation(); + formik.setFieldValue(name, ""); + setPickerValue(null); + }} + sx={{ + color: "#bfbfbf", + '&:hover': { + backgroundColor: 'rgba(189, 189, 189, 0.1)', + }, + }} + > + + + + ), + }, + }, + }} + value={pickerValue} + minDate={minDate ? new Date(minDate) : null} + maxDate={maxDate ? new Date(maxDate) : null} + /> + + + ); +} diff --git a/src/components/SelectBox.jsx b/src/components/SelectBox.jsx new file mode 100644 index 0000000..88cce6e --- /dev/null +++ b/src/components/SelectBox.jsx @@ -0,0 +1,53 @@ +import InputLabel from '@mui/material/InputLabel'; +import MenuItem from '@mui/material/MenuItem'; +import FormControl from '@mui/material/FormControl'; +import Select from '@mui/material/Select'; +import {useEffect, useState} from "react"; +const names = [ + 'Oliver Hansen', + 'Van Henry', + 'April Tucker', + 'Ralph Hubbard', + 'Omar Alexander', + 'Carlos Abbott', + 'Miriam Wagner', + 'Bradley Wilkerson', + 'Virginia Andrews', + 'Kelly Snyder', +]; +const SelectBox = ({ formik, id, defaultValue }) => { + + useEffect(() => { + formik.setFieldValue(id, defaultValue || ''); + }, []); + + const [selectedValue, setSelectedValue] = useState(defaultValue || ''); + + const handleChange = (event) => { + const { value } = event.target; + setSelectedValue(value); + formik.setFieldValue(id, value); + }; + + return ( + <> + + انتخاب + + + + ); +}; + +export default SelectBox; diff --git a/src/components/SelectBoxMultiple.jsx b/src/components/SelectBoxMultiple.jsx new file mode 100644 index 0000000..4355c72 --- /dev/null +++ b/src/components/SelectBoxMultiple.jsx @@ -0,0 +1,116 @@ +import {useTheme} from '@mui/material/styles'; +import Box from '@mui/material/Box'; +import OutlinedInput from '@mui/material/OutlinedInput'; +import InputLabel from '@mui/material/InputLabel'; +import MenuItem from '@mui/material/MenuItem'; +import FormControl from '@mui/material/FormControl'; +import Select from '@mui/material/Select'; +import Chip from '@mui/material/Chip'; +import {useEffect, useState} from "react"; + +const ITEM_HEIGHT = 48; +const ITEM_PADDING_TOP = 8; +const MenuProps = { + PaperProps: { + style: { + maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, + width: 250, + }, + }, +}; + +const names = [ + 'Oliver Hansen', + 'Van Henry', + 'April Tucker', + 'Ralph Hubbard', + 'Omar Alexander', + 'Carlos Abbott', + 'Miriam Wagner', + 'Bradley Wilkerson', + 'Virginia Andrews', + 'Kelly Snyder', +]; + +function getStyles(name, personName, theme) { + return { + fontWeight: + personName.indexOf(name) === -1 + ? theme.typography.fontWeightRegular + : theme.typography.fontWeightMedium, + }; +} + +const SelectBoxMultiple = ({formik, id, defaultValue}) => { + const theme = useTheme(); + const [personName, setPersonName] = useState(defaultValue || []); + + useEffect(() => { + defaultValue.forEach((value, index) => { + formik.setFieldValue(`${id}[${index}]`, value || ''); + }); + + }, []); + + const handleChange = (event) => { + const { + target: {value}, + } = event; + + const selectedValues = typeof value === 'string' ? value.split(',').filter(item => item.trim() !== '') : value; + + formik.setFieldValue(id, []); + + if (selectedValues.length === 0) { + setPersonName([]); + formik.setFieldValue(id, []); + } else { + setPersonName(selectedValues); + selectedValues.forEach((selectedValue, index) => { + formik.setFieldValue(`${id}[${index}]`, selectedValue); + }); + } + }; + + return ( + <> + + انتخاب + + + + ); +}; + +export default SelectBoxMultiple; diff --git a/src/components/TextFieldWithFnBox.jsx b/src/components/TextFieldWithFnBox.jsx new file mode 100644 index 0000000..6ba443b --- /dev/null +++ b/src/components/TextFieldWithFnBox.jsx @@ -0,0 +1,51 @@ +import {IconButton, Paper, TextField} from '@mui/material'; +import ArtTrackIcon from '@mui/icons-material/ArtTrack'; +import {useEffect, useState} from "react"; +import FilterBox from "@/components/FilterBox"; + +const TextFieldWithFnBox = ({formik, id , header, defaultValue, defaultFilter , columnFilterModeOptions ,onSelectFilter}) => { + + const [anchorEl, setAnchorEl] = useState(null); + const [open, setOpen] = useState(false); + + useEffect(() => { + formik.setFieldValue(id, defaultValue || ''); + }, []); + + const handleClick = (event) => { + setOpen((prevOpen) => !prevOpen); + setAnchorEl(anchorEl ? null : event.currentTarget); + }; + + const handleClose = () => { + setOpen(false); + setAnchorEl(null); + }; + + return ( + <> + + + + ) + }} + sx={{ + position: 'relative' + }} + /> + + + ); +}; + +export default TextFieldWithFnBox; diff --git a/src/components/layouts/dashboard/headerWithLogo/index.jsx b/src/components/layouts/dashboard/headerWithLogo/index.jsx new file mode 100644 index 0000000..f16f58c --- /dev/null +++ b/src/components/layouts/dashboard/headerWithLogo/index.jsx @@ -0,0 +1,9 @@ +import {Box, Stack} from "@mui/material"; + +const HeaderWithLogo = () => { + return ( + header1 + header2 + ) +} +export default HeaderWithLogo \ No newline at end of file diff --git a/src/components/layouts/dashboard/headerWithSidebar/index.jsx b/src/components/layouts/dashboard/headerWithSidebar/index.jsx new file mode 100644 index 0000000..eeb88a3 --- /dev/null +++ b/src/components/layouts/dashboard/headerWithSidebar/index.jsx @@ -0,0 +1,94 @@ +'use client' +import {useState} from "react"; +import {Box, Drawer, IconButton, Paper, styled, Toolbar, Typography} from "@mui/material"; +import MuiAppBar from '@mui/material/AppBar'; +import MenuIcon from '@mui/icons-material/Menu'; +import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import SidebarMenu from "@/core/components/SidebarMenu"; + +const drawerWidth = 260; + +const Main = styled(Paper, {shouldForwardProp: (prop) => prop !== 'open'})(({theme, open}) => ({ + flexGrow: 1, transition: theme.transitions.create('margin', { + easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen, + }), marginLeft: `-${drawerWidth}px`, ...(open && { + transition: theme.transitions.create('margin', { + easing: theme.transitions.easing.easeOut, duration: theme.transitions.duration.enteringScreen, + }), marginLeft: 0, + }), +}),); + +const AppBar = styled(MuiAppBar, { + shouldForwardProp: (prop) => prop !== 'open', +})(({theme, open}) => ({ + transition: theme.transitions.create(['margin', 'width'], { + easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen, + }), ...(open && { + width: `calc(100% - ${drawerWidth}px)`, + marginLeft: `${drawerWidth}px`, + transition: theme.transitions.create(['margin', 'width'], { + easing: theme.transitions.easing.easeOut, duration: theme.transitions.duration.enteringScreen, + }), + }), +})); + +const DrawerHeader = styled('div')(({theme}) => ({ + display: 'flex', alignItems: 'center', padding: theme.spacing(0, 1), // necessary for content to be below app bar + ...theme.mixins.toolbar, justifyContent: 'flex-end', +})); + +const HeaderWithSidebar = ({children}) => { + const [open, setOpen] = useState(false); + + const handleDrawerOpen = () => { + setOpen(true); + }; + + const handleDrawerClose = () => { + setOpen(false); + }; + return ( + + + + + + + menu + + + + + + + + + + + +
+ + {children} +
+
) +} +export default HeaderWithSidebar \ No newline at end of file diff --git a/src/core/components/DataTable/Main.js b/src/core/components/DataTable/Main.js new file mode 100644 index 0000000..4a38956 --- /dev/null +++ b/src/core/components/DataTable/Main.js @@ -0,0 +1,19 @@ +import { useMaterialReactTable } from "material-react-table"; +import DataTable_Paper from "./table/Paper"; + +const isTableInstanceProp = (props) => props.table !== undefined; + +const DataTable_Main = (props) => { + let table; + + if (isTableInstanceProp(props)) { + table = props.table; + } else { + // eslint-disable-next-line react-hooks/rules-of-hooks + table = useMaterialReactTable(props); + } + + return ; +}; + +export default DataTable_Main; diff --git a/src/core/components/DataTable/body/TableBody.js b/src/core/components/DataTable/body/TableBody.js new file mode 100644 index 0000000..e1620a3 --- /dev/null +++ b/src/core/components/DataTable/body/TableBody.js @@ -0,0 +1,207 @@ +import { parseFromValuesOrFunc } from "@/core/utils/utils"; +import { TableBody, Typography } from "@mui/material"; +import { useMRT_RowVirtualizer, useMRT_Rows } from "material-react-table"; +import { memo, useMemo } from "react"; +import DataTable_TableBodyRow, { Memo_DataTable_TableBodyRow } from "./TableBodyRow"; + +const DataTable_TableBody = ({ + columnVirtualizer, + table, + ...rest +}) => { + const { + getBottomRows, + getIsSomeRowsPinned, + getRowModel, + getState, + getTopRows, + options: { + enableStickyFooter, + enableStickyHeader, + layoutMode, + localization, + memoMode, + muiTableBodyProps, + renderDetailPanel, + renderEmptyRowsFallback, + rowPinningDisplayMode, + }, + refs: { tableFooterRef, tableHeadRef, tablePaperRef }, + } = table; + const { columnFilters, globalFilter, isFullScreen, rowPinning } = getState(); + + const tableBodyProps = { + ...parseFromValuesOrFunc(muiTableBodyProps, { table }), + ...rest, + }; + + const tableHeadHeight = + ((enableStickyHeader || isFullScreen) && + tableHeadRef.current?.clientHeight) || + 0; + const tableFooterHeight = + (enableStickyFooter && tableFooterRef.current?.clientHeight) || 0; + + const pinnedRowIds = useMemo(() => { + if (!rowPinning.bottom?.length && !rowPinning.top?.length) return []; + return getRowModel() + .rows.filter((row) => row.getIsPinned()) + .map((r) => r.id); + }, [rowPinning, getRowModel().rows]); + + const rows = useMRT_Rows(table); + + const rowVirtualizer = useMRT_RowVirtualizer(table, rows); + + const { virtualRows } = rowVirtualizer ?? {}; + + const commonRowProps = { + columnVirtualizer, + numRows: rows.length, + table, + }; + + return ( + <> + {!rowPinningDisplayMode?.includes('sticky') && + getIsSomeRowsPinned('top') && ( + ({ + display: layoutMode?.startsWith('grid') ? 'grid' : undefined, + position: 'sticky', + top: tableHeadHeight - 1, + zIndex: 1, + ...(parseFromValuesOrFunc(tableBodyProps?.sx, theme)), + })} + > + {getTopRows().map((row, staticRowIndex) => { + const props = { + ...commonRowProps, + row, + staticRowIndex, + }; + return memoMode === 'rows' ? ( + + ) : ( + + ); + })} + + )} + ({ + display: layoutMode?.startsWith('grid') ? 'grid' : undefined, + height: rowVirtualizer + ? `${rowVirtualizer.getTotalSize()}px` + : undefined, + minHeight: !rows.length ? '100px' : undefined, + position: 'relative', + ...(parseFromValuesOrFunc(tableBodyProps?.sx, theme)), + })} + > + {tableBodyProps?.children ?? + (!rows.length ? ( + + + {renderEmptyRowsFallback?.({ table }) ?? ( + + {globalFilter || columnFilters.length + ? localization.noResultsFound + : localization.noRecordsToDisplay} + + )} + + + ) : ( + <> + {(virtualRows ?? rows).map((rowOrVirtualRow, staticRowIndex) => { + let row = rowOrVirtualRow; + if (rowVirtualizer) { + if (renderDetailPanel) { + if (rowOrVirtualRow.index % 2 === 1) { + return null; + } else { + staticRowIndex = rowOrVirtualRow.index / 2; + } + } else { + staticRowIndex = rowOrVirtualRow.index; + } + row = rows[staticRowIndex]; + } + const props = { + ...commonRowProps, + pinnedRowIds, + row, + rowVirtualizer, + staticRowIndex, + virtualRow: rowVirtualizer + ? (rowOrVirtualRow) + : undefined, + }; + const key = `${row.id}-${row.index}`; + return memoMode === 'rows' ? ( + + ) : ( + + ); + })} + + ))} + + {!rowPinningDisplayMode?.includes('sticky') && + getIsSomeRowsPinned('bottom') && ( + ({ + bottom: tableFooterHeight - 1, + display: layoutMode?.startsWith('grid') ? 'grid' : undefined, + position: 'sticky', + zIndex: 1, + ...(parseFromValuesOrFunc(tableBodyProps?.sx, theme)), + })} + > + {getBottomRows().map((row, staticRowIndex) => { + const props = { + ...commonRowProps, + row, + staticRowIndex, + }; + return memoMode === 'rows' ? ( + + ) : ( + + ); + })} + + )} + + ); + +} +export default DataTable_TableBody + +export const Memo_DataTable_TableBody = memo( + DataTable_TableBody, + (prev, next) => prev.table.options.data === next.table.options.data, +); \ No newline at end of file diff --git a/src/core/components/DataTable/body/TableBodyCell.js b/src/core/components/DataTable/body/TableBodyCell.js new file mode 100644 index 0000000..9ef022d --- /dev/null +++ b/src/core/components/DataTable/body/TableBodyCell.js @@ -0,0 +1,306 @@ +import { isCellEditable, openEditingCell } from "material-react-table"; + +const { parseFromValuesOrFunc, getCommonMRTCellStyles } = require("@/core/utils/utils"); +const { useTheme } = require("@emotion/react"); +const { TableCell, Skeleton } = require("@mui/material"); +const { useState, useEffect, useMemo, memo } = require("react"); +const { default: DataTable_CopyButton } = require("../buttons/CopyButton"); +const { default: DataTable_TableBodyCellValue } = require("./TableBodyCellValue"); + +const DataTable_TableBodyCell = ({ + cell, + numRows, + rowRef, + staticColumnIndex, + staticRowIndex, + table, + ...rest +}) => { + const theme = useTheme(); + const { + getState, + options: { + columnResizeDirection, + columnResizeMode, + createDisplayMode, + editDisplayMode, + enableCellActions, + enableClickToCopy, + enableColumnOrdering, + enableColumnPinning, + enableGrouping, + layoutMode, + mrtTheme: { draggingBorderColor }, + muiSkeletonProps, + muiTableBodyCellProps, + }, + setHoveredColumn, + } = table; + const { + actionCell, + columnSizingInfo, + creatingRow, + density, + draggingColumn, + draggingRow, + editingCell, + editingRow, + hoveredColumn, + hoveredRow, + isLoading, + showSkeletons, + } = getState(); + const { column, row } = cell; + const { columnDef } = column; + const { columnDefType } = columnDef; + + const args = { cell, column, row, table }; + const tableCellProps = { + ...parseFromValuesOrFunc(muiTableBodyCellProps, args), + ...parseFromValuesOrFunc(columnDef.muiTableBodyCellProps, args), + ...rest, + }; + + const skeletonProps = parseFromValuesOrFunc(muiSkeletonProps, { + cell, + column, + row, + table, + }); + + const [skeletonWidth, setSkeletonWidth] = useState(100); + useEffect(() => { + if ((!isLoading && !showSkeletons) || skeletonWidth !== 100) return; + const size = column.getSize(); + setSkeletonWidth( + columnDefType === 'display' + ? size / 2 + : Math.round(Math.random() * (size - size / 3) + size / 3), + ); + }, [isLoading, showSkeletons]); + + const draggingBorders = useMemo(() => { + const isDraggingColumn = draggingColumn?.id === column.id; + const isHoveredColumn = hoveredColumn?.id === column.id; + const isDraggingRow = draggingRow?.id === row.id; + const isHoveredRow = hoveredRow?.id === row.id; + const isFirstColumn = column.getIsFirstColumn(); + const isLastColumn = column.getIsLastColumn(); + const isLastRow = numRows && staticRowIndex === numRows - 1; + const isResizingColumn = columnSizingInfo.isResizingColumn === column.id; + const showResizeBorder = + isResizingColumn && columnResizeMode === 'onChange'; + + const borderStyle = showResizeBorder + ? `2px solid ${draggingBorderColor} !important` + : isDraggingColumn || isDraggingRow + ? `1px dashed ${theme.palette.grey[500]} !important` + : isHoveredColumn || isHoveredRow || isResizingColumn + ? `2px dashed ${draggingBorderColor} !important` + : undefined; + + if (showResizeBorder) { + return columnResizeDirection === 'ltr' + ? { borderRight: borderStyle } + : { borderLeft: borderStyle }; + } + + return borderStyle + ? { + borderBottom: + isDraggingRow || isHoveredRow || (isLastRow && !isResizingColumn) + ? borderStyle + : undefined, + borderLeft: + isDraggingColumn || + isHoveredColumn || + ((isDraggingRow || isHoveredRow) && isFirstColumn) + ? borderStyle + : undefined, + borderRight: + isDraggingColumn || + isHoveredColumn || + ((isDraggingRow || isHoveredRow) && isLastColumn) + ? borderStyle + : undefined, + borderTop: isDraggingRow || isHoveredRow ? borderStyle : undefined, + } + : undefined; + }, [ + columnSizingInfo.isResizingColumn, + draggingColumn, + draggingRow, + hoveredColumn, + hoveredRow, + staticRowIndex, + ]); + + const isColumnPinned = + enableColumnPinning && + columnDef.columnDefType !== 'group' && + column.getIsPinned(); + + const isEditable = isCellEditable({ cell, table }); + + const isEditing = + isEditable && + !['custom', 'modal'].includes(editDisplayMode) && + (editDisplayMode === 'table' || + editingRow?.id === row.id || + editingCell?.id === cell.id) && + !row.getIsGrouped(); + + const isCreating = + isEditable && createDisplayMode === 'row' && creatingRow?.id === row.id; + + const showClickToCopyButton = + (parseFromValuesOrFunc(enableClickToCopy, cell) === true || + parseFromValuesOrFunc(columnDef.enableClickToCopy, cell) === true) && + !['context-menu', false].includes( + // @ts-ignore + parseFromValuesOrFunc(columnDef.enableClickToCopy, cell), + ); + + const isRightClickable = parseFromValuesOrFunc(enableCellActions, cell); + + const cellValueProps = { + cell, + table, + }; + + const handleDoubleClick = (event) => { + tableCellProps?.onDoubleClick?.(event); + openEditingCell({ cell, table }); + }; + + const handleDragEnter = (e) => { + tableCellProps?.onDragEnter?.(e); + if (enableGrouping && hoveredColumn?.id === 'drop-zone') { + setHoveredColumn(null); + } + if (enableColumnOrdering && draggingColumn) { + setHoveredColumn( + columnDef.enableColumnOrdering !== false ? column : null, + ); + } + }; + + const handleDragOver = (e) => { + if (columnDef.enableColumnOrdering !== false) { + e.preventDefault(); + } + }; + + const handleContextMenu = (e) => { + tableCellProps?.onContextMenu?.(e); + if (isRightClickable) { + e.preventDefault(); + table.setActionCell(cell); + table.refs.actionCellRef.current = e.currentTarget; + } + }; + + return ( + ({ + '&:hover': { + outline: + actionCell?.id === cell.id || + (editDisplayMode === 'cell' && isEditable) || + (editDisplayMode === 'table' && (isCreating || isEditing)) + ? `1px solid ${theme.palette.grey[500]}` + : undefined, + textOverflow: 'clip', + }, + alignItems: layoutMode?.startsWith('grid') ? 'center' : undefined, + cursor: isRightClickable + ? 'context-menu' + : isEditable && editDisplayMode === 'cell' + ? 'pointer' + : 'inherit', + outline: + actionCell?.id === cell.id + ? `1px solid ${theme.palette.grey[500]}` + : undefined, + outlineOffset: '-1px', + overflow: 'hidden', + p: + density === 'compact' + ? columnDefType === 'display' + ? '0 0.5rem' + : '0.5rem' + : density === 'comfortable' + ? columnDefType === 'display' + ? '0.5rem 0.75rem' + : '1rem' + : columnDefType === 'display' + ? '1rem 1.25rem' + : '1.5rem', + + textOverflow: columnDefType !== 'display' ? 'ellipsis' : undefined, + whiteSpace: + row.getIsPinned() || density === 'compact' ? 'nowrap' : 'normal', + ...getCommonMRTCellStyles({ + column, + table, + tableCellProps, + theme, + }), + ...draggingBorders, + })} + > + {tableCellProps.children ?? ( + <> + {cell.getIsPlaceholder() ? ( + columnDef.PlaceholderCell?.({ cell, column, row, table }) ?? null + ) : showSkeletons !== false && (isLoading || showSkeletons) ? ( + + ) : columnDefType === 'display' && + (['mrt-row-expand', 'mrt-row-numbers', 'mrt-row-select'].includes( + column.id, + ) || + !row.getIsGrouped()) ? ( + columnDef.Cell?.({ + cell, + column, + renderedCellValue: cell.renderValue(), + row, + rowRef, + staticColumnIndex, + staticRowIndex, + table, + }) + ) : showClickToCopyButton && columnDef.enableClickToCopy !== false ? ( + + + + ) : ( + + )} + {cell.getIsGrouped() && !columnDef.GroupedCell && ( + <> ({row.subRows?.length}) + )} + + )} + + ); + +} +export default DataTable_TableBodyCell; + +export const Memo_DataTable_TableBodyCell = memo( + DataTable_TableBodyCell, + (prev, next) => next.cell === prev.cell, +); \ No newline at end of file diff --git a/src/core/components/DataTable/body/TableBodyCellValue.js b/src/core/components/DataTable/body/TableBodyCellValue.js new file mode 100644 index 0000000..6d4f05f --- /dev/null +++ b/src/core/components/DataTable/body/TableBodyCellValue.js @@ -0,0 +1,112 @@ +import { Box } from "@mui/material"; + +const allowedTypes = ["string", "number"]; + +const DataTable_TableBodyCellValue = ({ + cell, + rowRef, + staticColumnIndex, + staticRowIndex, + table, +}) => { + const { + getState, + options: { + enableFilterMatchHighlighting, + mrtTheme: { matchHighlightColor }, + }, + } = table; + const { column, row } = cell; + const { columnDef } = column; + const { globalFilter, globalFilterFn } = getState(); + const filterValue = column.getFilterValue(); + + let renderedCellValue = + cell.getIsAggregated() && columnDef.AggregatedCell + ? columnDef.AggregatedCell({ + cell, + column, + row, + table, + }) + : row.getIsGrouped() && !cell.getIsGrouped() + ? null + : cell.getIsGrouped() && columnDef.GroupedCell + ? columnDef.GroupedCell({ + cell, + column, + row, + table, + }) + : undefined; + + const isGroupedValue = renderedCellValue !== undefined; + + if (!isGroupedValue) { + renderedCellValue = cell.renderValue(); + } + + if ( + enableFilterMatchHighlighting && + columnDef.enableFilterMatchHighlighting !== false && + String(renderedCellValue) && + allowedTypes.includes(typeof renderedCellValue) && + ((filterValue && + allowedTypes.includes(typeof filterValue) && + ["autocomplete", "text"].includes(columnDef.filterVariant)) || + (globalFilter && + allowedTypes.includes(typeof globalFilter) && + column.getCanGlobalFilter())) + ) { + const chunks = highlightWords?.({ + matchExactly: + (filterValue ? columnDef._filterFn : globalFilterFn) !== "fuzzy", + query: (filterValue ?? globalFilter ?? "").toString(), + text: renderedCellValue?.toString(), + }); + if (chunks?.length > 1 || chunks?.[0]?.match) { + renderedCellValue = ( + + {chunks?.map(({ key, match, text }) => ( + + )) ?? renderedCellValue} + + ); + } + } + + if (columnDef.Cell && !isGroupedValue) { + renderedCellValue = columnDef.Cell({ + cell, + column, + renderedCellValue, + row, + rowRef, + staticColumnIndex, + staticRowIndex, + table, + }); + } + + return renderedCellValue; +}; +export default DataTable_TableBodyCellValue; diff --git a/src/core/components/DataTable/body/TableBodyRow.js b/src/core/components/DataTable/body/TableBodyRow.js new file mode 100644 index 0000000..e500eb0 --- /dev/null +++ b/src/core/components/DataTable/body/TableBodyRow.js @@ -0,0 +1,254 @@ +import { commonCellBeforeAfterStyles, getCommonPinnedCellStyles, parseFromValuesOrFunc } from "@/core/utils/utils"; +import { useTheme } from "@emotion/react"; +import { TableRow, alpha, darken, lighten } from "@mui/material"; +import { getIsRowSelected } from "material-react-table"; +import { memo, useMemo, useRef } from "react"; +import DataTable_TableBodyCell, { Memo_DataTable_TableBodyCell } from "./TableBodyCell"; +import DataTable_TableDetailPanel from "./TableDetailPanel"; + +const DataTable_TableBodyRow = ({ + columnVirtualizer, + numRows, + pinnedRowIds, + row, + rowVirtualizer, + staticRowIndex, + table, + virtualRow, + ...rest +}) => { + const theme = useTheme(); + + const { + getState, + options: { + enableRowOrdering, + enableRowPinning, + enableStickyFooter, + enableStickyHeader, + layoutMode, + memoMode, + mrtTheme: { + baseBackgroundColor, + pinnedRowBackgroundColor, + selectedRowBackgroundColor, + }, + muiTableBodyRowProps, + renderDetailPanel, + rowPinningDisplayMode, + }, + refs: { tableFooterRef, tableHeadRef }, + setHoveredRow, + } = table; + const { + density, + draggingColumn, + draggingRow, + editingCell, + editingRow, + hoveredRow, + isFullScreen, + rowPinning, + } = getState(); + + const visibleCells = row.getVisibleCells(); + + const { virtualColumns, virtualPaddingLeft, virtualPaddingRight } = + columnVirtualizer ?? {}; + + const isRowSelected = getIsRowSelected({ row, table }); + const isRowPinned = enableRowPinning && row.getIsPinned(); + const isDraggingRow = draggingRow?.id === row.id; + const isHoveredRow = hoveredRow?.id === row.id; + + const tableRowProps = { + ...parseFromValuesOrFunc(muiTableBodyRowProps, { + row, + staticRowIndex, + table, + }), + ...rest, + }; + + const [bottomPinnedIndex, topPinnedIndex] = useMemo(() => { + if ( + !enableRowPinning || + !rowPinningDisplayMode?.includes('sticky') || + !pinnedRowIds || + !row.getIsPinned() + ) + return []; + return [ + [...pinnedRowIds].reverse().indexOf(row.id), + pinnedRowIds.indexOf(row.id), + ]; + }, [pinnedRowIds, rowPinning]); + + const tableHeadHeight = + ((enableStickyHeader || isFullScreen) && + tableHeadRef.current?.clientHeight) || + 0; + const tableFooterHeight = + (enableStickyFooter && tableFooterRef.current?.clientHeight) || 0; + + const sx = parseFromValuesOrFunc(tableRowProps?.sx, theme); + + const defaultRowHeight = + density === 'compact' ? 37 : density === 'comfortable' ? 53 : 69; + + const customRowHeight = + // @ts-ignore + parseInt(tableRowProps?.style?.height ?? sx?.height, 10) || undefined; + + const rowHeight = customRowHeight || defaultRowHeight; + + const handleDragEnter = (_e) => { + if (enableRowOrdering && draggingRow) { + setHoveredRow(row); + } + }; + + const handleDragOver = (e) => { + e.preventDefault(); + }; + + const rowRef = useRef(null); + + const cellHighlightColor = isRowSelected + ? selectedRowBackgroundColor + : isRowPinned + ? pinnedRowBackgroundColor + : undefined; + + const cellHighlightColorHover = + tableRowProps?.hover !== false + ? isRowSelected + ? cellHighlightColor + : theme.palette.mode === 'dark' + ? `${lighten(baseBackgroundColor, 0.3)}` + : `${darken(baseBackgroundColor, 0.3)}` + : undefined; + + return ( + <> + { + if (node) { + rowRef.current = node; + rowVirtualizer?.measureElement(node); + } + }} + selected={isRowSelected} + {...tableRowProps} + style={{ + transform: virtualRow + ? `translateY(${virtualRow.start}px)` + : undefined, + ...tableRowProps?.style, + }} + sx={(theme) => ({ + '&:hover td:after': cellHighlightColorHover + ? { + backgroundColor: alpha(cellHighlightColorHover, 0.3), + ...commonCellBeforeAfterStyles, + } + : undefined, + backgroundColor: `${baseBackgroundColor} !important`, + bottom: + !virtualRow && bottomPinnedIndex !== undefined && isRowPinned + ? `${bottomPinnedIndex * rowHeight + + (enableStickyFooter ? tableFooterHeight - 1 : 0) + }px` + : undefined, + boxSizing: 'border-box', + display: layoutMode?.startsWith('grid') ? 'flex' : undefined, + opacity: isRowPinned ? 0.97 : isDraggingRow || isHoveredRow ? 0.5 : 1, + position: virtualRow + ? 'absolute' + : rowPinningDisplayMode?.includes('sticky') && isRowPinned + ? 'sticky' + : 'relative', + td: { + ...getCommonPinnedCellStyles({ table, theme }), + }, + 'td:after': cellHighlightColor + ? { + backgroundColor: cellHighlightColor, + ...commonCellBeforeAfterStyles, + } + : undefined, + top: virtualRow + ? 0 + : topPinnedIndex !== undefined && isRowPinned + ? `${topPinnedIndex * rowHeight + + (enableStickyHeader || isFullScreen ? tableHeadHeight - 1 : 0) + }px` + : undefined, + transition: virtualRow ? 'none' : 'all 150ms ease-in-out', + width: '100%', + zIndex: + rowPinningDisplayMode?.includes('sticky') && isRowPinned ? 2 : 0, + ...(sx), + })} + > + {virtualPaddingLeft ? ( + + ) : null} + {(virtualColumns ?? visibleCells).map( + (cellOrVirtualCell, staticColumnIndex) => { + let cell = cellOrVirtualCell; + if (columnVirtualizer) { + staticColumnIndex = (cellOrVirtualCell).index; + cell = visibleCells[staticColumnIndex]; + } + const props = { + cell, + numRows, + rowRef, + staticColumnIndex, + staticRowIndex, + table, + }; + return cell ? ( + memoMode === 'cells' && + cell.column.columnDef.columnDefType === 'data' && + !draggingColumn && + !draggingRow && + editingCell?.id !== cell.id && + editingRow?.id !== row.id ? ( + + ) : ( + + ) + ) : null; + }, + )} + {virtualPaddingRight ? ( + + ) : null} + + {renderDetailPanel && !row.getIsGrouped() && ( + + )} + + ); +} +export default DataTable_TableBodyRow + +export const Memo_DataTable_TableBodyRow = memo( + DataTable_TableBodyRow, + (prev, next) => + prev.row === next.row && prev.staticRowIndex === next.staticRowIndex, +); \ No newline at end of file diff --git a/src/core/components/DataTable/body/TableDetailPanel.js b/src/core/components/DataTable/body/TableDetailPanel.js new file mode 100644 index 0000000..0c9f8a9 --- /dev/null +++ b/src/core/components/DataTable/body/TableDetailPanel.js @@ -0,0 +1,93 @@ +import { parseFromValuesOrFunc } from "@/core/utils/utils"; +import { Collapse, TableCell, TableRow } from "@mui/material"; + +const DataTable_TableDetailPanel = ({ + parentRowRef, + row, + rowVirtualizer, + staticRowIndex, + table, + virtualRow, + ...rest +}) => { + const { + getState, + getVisibleLeafColumns, + options: { + layoutMode, + mrtTheme: { baseBackgroundColor }, + muiDetailPanelProps, + muiTableBodyRowProps, + renderDetailPanel, + }, + } = table; + const { isLoading } = getState(); + + + const tableRowProps = parseFromValuesOrFunc(muiTableBodyRowProps, { + isDetailPanel: true, + row, + staticRowIndex, + table, + }); + + const tableCellProps = { + ...parseFromValuesOrFunc(muiDetailPanelProps, { + row, + table, + }), + ...rest, + }; + + const DetailPanel = !isLoading && renderDetailPanel?.({ row, table }); + + return ( + { + if (node) { + rowVirtualizer?.measureElement?.(node); + } + }} + {...tableRowProps} + sx={(theme) => ({ + display: layoutMode?.startsWith('grid') ? 'flex' : undefined, + position: virtualRow ? 'absolute' : undefined, + top: virtualRow + ? `${parentRowRef.current?.getBoundingClientRect()?.height}px` + : undefined, + transform: virtualRow + ? `translateY(${virtualRow?.start}px)` + : undefined, + width: '100%', + ...(parseFromValuesOrFunc(tableRowProps?.sx, theme)), + })} + > + ({ + backgroundColor: virtualRow ? baseBackgroundColor : undefined, + borderBottom: !row.getIsExpanded() ? 'none' : undefined, + display: layoutMode?.startsWith('grid') ? 'flex' : undefined, + py: !!DetailPanel && row.getIsExpanded() ? '1rem' : 0, + transition: !virtualRow ? 'all 150ms ease-in-out' : undefined, + width: `100%`, + ...(parseFromValuesOrFunc(tableCellProps?.sx, theme)), + })} + > + {virtualRow ? ( + row.getIsExpanded() && DetailPanel + ) : ( + + {DetailPanel} + + )} + + + ); + +} +export default DataTable_TableDetailPanel \ No newline at end of file diff --git a/src/core/components/DataTable/buttons/CopyButton.js b/src/core/components/DataTable/buttons/CopyButton.js new file mode 100644 index 0000000..c8878e0 --- /dev/null +++ b/src/core/components/DataTable/buttons/CopyButton.js @@ -0,0 +1,75 @@ +import { getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils"; +import { Button, Tooltip } from "@mui/material"; +import { useState } from "react"; + +const DataTable_CopyButton = ({ + cell, + table, + ...rest +}) => { + const { + options: { localization, muiCopyButtonProps }, + } = table; + const { column, row } = cell; + const { columnDef } = column; + + const [copied, setCopied] = useState(false); + + const handleCopy = (event, text) => { + event.stopPropagation(); + navigator.clipboard.writeText(text); + setCopied(true); + setTimeout(() => setCopied(false), 4000); + }; + + const buttonProps = { + ...parseFromValuesOrFunc(muiCopyButtonProps, { + cell, + column, + row, + table, + }), + ...parseFromValuesOrFunc(columnDef.muiCopyButtonProps, { + cell, + column, + row, + table, + }), + ...rest, + }; + + return ( + + + )} + {enableColumnOrdering && ( + + )} + {enableColumnPinning && ( + + )} + {enableHiding && ( + + )} + + + {allColumns.map((column, index) => ( + + ))} + + ); +} +export default DataTable_ShowHideColumnsMenu \ No newline at end of file diff --git a/src/core/components/DataTable/menus/ShowHideColumnsMenuItems.js b/src/core/components/DataTable/menus/ShowHideColumnsMenuItems.js new file mode 100644 index 0000000..58425d9 --- /dev/null +++ b/src/core/components/DataTable/menus/ShowHideColumnsMenuItems.js @@ -0,0 +1,160 @@ +import { getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils"; +import { Box, FormControlLabel, MenuItem, Switch, Tooltip, Typography } from "@mui/material"; +import { useRef, useState } from "react"; +import DataTable_GrabHandleButton from "../buttons/GrabHandleButton"; + +const DataTable_ShowHideColumnsMenuItems = ({ + allColumns, + column, + hoveredColumn, + isNestedColumns, + setHoveredColumn, + table, + ...rest +}) => { + const { + getState, + options: { + enableColumnOrdering, + enableHiding, + localization, + mrtTheme: { draggingBorderColor }, + }, + setColumnOrder, + } = table; + + const { columnOrder } = getState(); + const { columnDef } = column; + const { columnDefType } = columnDef; + + const switchChecked = column.getIsVisible(); + + const handleToggleColumnHidden = (column) => { + if (columnDefType === 'group') { + column?.columns?.forEach?.((childColumn) => { + childColumn.toggleVisibility(!switchChecked); + }); + } else { + column.toggleVisibility(); + } + }; + + const menuItemRef = useRef(null); + + const [isDragging, setIsDragging] = useState(false); + + const handleDragStart = (e) => { + setIsDragging(true); + try { + e.dataTransfer.setDragImage(menuItemRef.current, 0, 0); + } catch (e) { + console.error(e); + } + }; + + const handleDragEnd = (_e) => { + setIsDragging(false); + setHoveredColumn(null); + if (hoveredColumn) { + setColumnOrder(reorderColumn(column, hoveredColumn, columnOrder)); + } + }; + + const handleDragEnter = (_e) => { + if (!isDragging && columnDef.enableColumnOrdering !== false) { + setHoveredColumn(column); + } + }; + + if (!columnDef.header || columnDef.visibleInShowHideMenu === false) { + return null; + } + + return ( + <> + ({ + alignItems: 'center', + justifyContent: 'flex-start', + my: 0, + opacity: isDragging ? 0.5 : 1, + outline: isDragging + ? `2px dashed ${theme.palette.grey[500]}` + : hoveredColumn?.id === column.id + ? `2px dashed ${draggingBorderColor}` + : 'none', + outlineOffset: '-2px', + pl: `${(column.depth + 0.5) * 2}rem`, + py: '6px', + ...(parseFromValuesOrFunc(rest?.sx, theme)), + })} + > + + {columnDefType !== 'group' && + enableColumnOrdering && + !isNestedColumns && + (columnDef.enableColumnOrdering !== false ? ( + + ) : ( + + ))} + {enableHiding ? ( + + + + } + disabled={!column.getCanHide()} + label={columnDef.header} + onChange={() => handleToggleColumnHidden(column)} + /> + ) : ( + + {columnDef.header} + + )} + + + {column.columns?.map((c, i) => ( + + ))} + + ); + +} +export default DataTable_ShowHideColumnsMenuItems; \ No newline at end of file diff --git a/src/core/components/DataTable/table/Paper.js b/src/core/components/DataTable/table/Paper.js new file mode 100644 index 0000000..db3bbec --- /dev/null +++ b/src/core/components/DataTable/table/Paper.js @@ -0,0 +1,78 @@ +import { parseFromValuesOrFunc } from "@/core/utils/utils"; +import { Paper } from "@mui/material"; +import DataTable_BottomToolbar from "../toolbar/BottomToolbar"; +import DataTable_TopToolbar from "../toolbar/TopToolbar"; +import DataTable_TableContainer from "./TableContainer"; + +const DataTable_Paper = ({ table, ...rest }) => { + const { + getState, + options: { + enableBottomToolbar, + enableTopToolbar, + mrtTheme: { baseBackgroundColor }, + muiTablePaperProps, + renderBottomToolbar, + renderTopToolbar, + }, + refs: { tablePaperRef }, + } = table; + + const { isFullScreen } = getState(); + + const paperProps = { + ...parseFromValuesOrFunc(muiTablePaperProps, { table }), + ...rest, + }; + + return ( + { + tablePaperRef.current = ref; + if (paperProps?.ref) { + //@ts-ignore + paperProps.ref.current = ref; + } + }} + style={{ + ...(isFullScreen + ? { + bottom: 0, + height: '100dvh', + left: 0, + margin: 0, + maxHeight: '100dvh', + maxWidth: '100dvw', + padding: 0, + position: 'fixed', + right: 0, + top: 0, + width: '100dvw', + zIndex: 999, + } + : {}), + ...paperProps?.style, + }} + sx={(theme) => ({ + backgroundColor: baseBackgroundColor, + backgroundImage: 'unset', + overflow: 'hidden', + transition: 'all 100ms ease-in-out', + ...(parseFromValuesOrFunc(paperProps?.sx, theme)), + })} + > + {enableTopToolbar && + (parseFromValuesOrFunc(renderTopToolbar, { table }) ?? ( + + ))} + + {enableBottomToolbar && + (parseFromValuesOrFunc(renderBottomToolbar, { table }) ?? ( + + ))} + + ); +}; +export default DataTable_Paper; diff --git a/src/core/components/DataTable/table/Table.js b/src/core/components/DataTable/table/Table.js new file mode 100644 index 0000000..ca49d0c --- /dev/null +++ b/src/core/components/DataTable/table/Table.js @@ -0,0 +1,77 @@ +import { parseCSSVarId, parseFromValuesOrFunc } from "@/core/utils/utils"; +import { Table } from "@mui/material"; +import { useMRT_ColumnVirtualizer } from "material-react-table"; +import { useMemo } from "react"; +import DataTable_TableBody, { Memo_DataTable_TableBody } from "../body/TableBody"; +import DataTable_TableHead from "../head/TableHead"; + +const DataTable_Table = ({ + table, + ...rest +}) => { + const { + getFlatHeaders, + getState, + options: { + columns, + enableStickyHeader, + enableTableFooter, + enableTableHead, + layoutMode, + memoMode, + muiTableProps, + renderCaption, + }, + } = table; + const { columnSizing, columnSizingInfo, columnVisibility, isFullScreen } = + getState(); + + const tableProps = { + ...parseFromValuesOrFunc(muiTableProps, { table }), + ...rest, + }; + + const Caption = parseFromValuesOrFunc(renderCaption, { table }); + + const columnSizeVars = useMemo(() => { + const headers = getFlatHeaders(); + const colSizes = {}; + for (let i = 0; i < headers.length; i++) { + const header = headers[i]; + const colSize = header.getSize(); + colSizes[`--header-${parseCSSVarId(header.id)}-size`] = colSize; + colSizes[`--col-${parseCSSVarId(header.column.id)}-size`] = colSize; + } + return colSizes; + }, [columns, columnSizing, columnSizingInfo, columnVisibility]); + + const columnVirtualizer = useMRT_ColumnVirtualizer(table); + + const commonTableGroupProps = { + columnVirtualizer, + table, + }; + + return ( + ({ + display: layoutMode?.startsWith('grid') ? 'grid' : undefined, + position: 'relative', + ...(parseFromValuesOrFunc(tableProps?.sx, theme)), + })} + > + {!!Caption && } + {enableTableHead && } + {memoMode === 'table-body' || columnSizingInfo.isResizingColumn ? ( + + ) : ( + + )} + {/* {enableTableFooter && } */} +
{Caption}
+ ); +} +export default DataTable_Table \ No newline at end of file diff --git a/src/core/components/DataTable/table/TableContainer.js b/src/core/components/DataTable/table/TableContainer.js new file mode 100644 index 0000000..aa4b625 --- /dev/null +++ b/src/core/components/DataTable/table/TableContainer.js @@ -0,0 +1,94 @@ +import { parseFromValuesOrFunc } from "@/core/utils/utils"; +import { TableContainer } from "@mui/material"; +import { useEffect, useLayoutEffect, useState } from "react"; +import DataTable_CellActionMenu from "../menus/CellActionMenu"; +import DataTable_Table from "./Table"; +import DataTable_TableLoadingOverlay from "./TableLoadingOverlay"; + +const useIsomorphicLayoutEffect = + typeof window !== 'undefined' ? useLayoutEffect : useEffect; + +const DataTable_TableContainer = ({ + table, + ...rest +}) => { + const { + getState, + options: { + enableCellActions, + enableStickyHeader, + muiTableContainerProps, + }, + refs: { bottomToolbarRef, tableContainerRef, topToolbarRef }, + } = table; + const { + actionCell, + isFullScreen, + isLoading, + showLoadingOverlay, + } = getState(); + + const loading = + showLoadingOverlay !== false && (isLoading || showLoadingOverlay); + + const [totalToolbarHeight, setTotalToolbarHeight] = useState(0); + + const tableContainerProps = { + ...parseFromValuesOrFunc(muiTableContainerProps, { + table, + }), + ...rest, + }; + + useIsomorphicLayoutEffect(() => { + const topToolbarHeight = + typeof document !== 'undefined' + ? topToolbarRef.current?.offsetHeight ?? 0 + : 0; + + const bottomToolbarHeight = + typeof document !== 'undefined' + ? bottomToolbarRef?.current?.offsetHeight ?? 0 + : 0; + + setTotalToolbarHeight(topToolbarHeight + bottomToolbarHeight); + }); + + return ( + { + if (node) { + tableContainerRef.current = node; + if (tableContainerProps?.ref) { + //@ts-ignore + tableContainerProps.ref.current = node; + } + } + }} + style={{ + maxHeight: isFullScreen + ? `calc(100vh - ${totalToolbarHeight}px)` + : undefined, + ...tableContainerProps?.style, + }} + sx={(theme) => ({ + maxHeight: enableStickyHeader + ? `clamp(350px, calc(100vh - ${totalToolbarHeight}px), 9999px)` + : undefined, + maxWidth: '100%', + overflow: 'auto', + position: 'relative', + ...(parseFromValuesOrFunc(tableContainerProps?.sx, theme)), + })} + > + {loading ? : null} + + {enableCellActions && actionCell && } + + ); + +} +export default DataTable_TableContainer \ No newline at end of file diff --git a/src/core/components/DataTable/table/TableLoadingOverlay.js b/src/core/components/DataTable/table/TableLoadingOverlay.js new file mode 100644 index 0000000..55e4ae5 --- /dev/null +++ b/src/core/components/DataTable/table/TableLoadingOverlay.js @@ -0,0 +1,48 @@ +import { Box, CircularProgress } from "@mui/material"; + +const DataTable_TableLoadingOverlay = ({ + table, + ...rest +}) => { + const { + options: { + localization, + mrtTheme: { baseBackgroundColor }, + muiCircularProgressProps, + }, + } = table; + + const circularProgressProps = { + ...parseFromValuesOrFunc(muiCircularProgressProps, { table }), + ...rest, + }; + + return ( + + {circularProgressProps?.Component ?? ( + + )} + + ); + +} +export default DataTable_TableLoadingOverlay \ No newline at end of file diff --git a/src/core/components/DataTable/toolbar/BottomToolbar.js b/src/core/components/DataTable/toolbar/BottomToolbar.js new file mode 100644 index 0000000..15e7f5a --- /dev/null +++ b/src/core/components/DataTable/toolbar/BottomToolbar.js @@ -0,0 +1,88 @@ +import { getCommonToolbarStyles, parseFromValuesOrFunc } from "@/core/utils/utils"; +import { Box, alpha, useMediaQuery } from "@mui/material"; +import DataTable_LinearProgressBar from "./LinearProgressBar"; +import DataTable_TablePagination from "./TablePagination"; + +const DataTable_BottomToolbar = ({ + table, + ...rest +}) => { + const { + getState, + options: { + enablePagination, + muiBottomToolbarProps, + positionPagination, + renderBottomToolbarCustomActions, + }, + refs: { bottomToolbarRef }, + } = table; + const { isFullScreen } = getState(); + + const isMobile = useMediaQuery('(max-width:720px)'); + const toolbarProps = { + ...parseFromValuesOrFunc(muiBottomToolbarProps, { table }), + ...rest, + }; + + const stackAlertBanner = isMobile || !!renderBottomToolbarCustomActions; + return ( + { + if (node) { + bottomToolbarRef.current = node; + if (toolbarProps?.ref) { + // @ts-ignore + toolbarProps.ref.current = node; + } + } + }} + sx={(theme) => ({ + ...getCommonToolbarStyles({ table, theme }), + bottom: isFullScreen ? '0' : undefined, + boxShadow: `0 1px 2px -1px ${alpha( + theme.palette.grey[700], + 0.5, + )} inset`, + left: 0, + position: isFullScreen ? 'fixed' : 'relative', + right: 0, + ...(parseFromValuesOrFunc(toolbarProps?.sx, theme)), + })} + > + + + {renderBottomToolbarCustomActions ? ( + renderBottomToolbarCustomActions({ table }) + ) : ( + + )} + + {enablePagination && + ['both', 'bottom'].includes(positionPagination ?? '') && ( + + )} + + + + ); +} +export default DataTable_BottomToolbar \ No newline at end of file diff --git a/src/core/components/DataTable/toolbar/LinearProgressBar.js b/src/core/components/DataTable/toolbar/LinearProgressBar.js new file mode 100644 index 0000000..cdcbc0a --- /dev/null +++ b/src/core/components/DataTable/toolbar/LinearProgressBar.js @@ -0,0 +1,44 @@ +import { parseFromValuesOrFunc } from "@/core/utils/utils"; +import { Collapse, LinearProgress } from "@mui/material"; + +const DataTable_LinearProgressBar = ({ + isTopToolbar, + table, + ...rest +}) => { + const { + getState, + options: { muiLinearProgressProps }, + } = table; + const { isSaving, showProgressBars } = getState(); + + const linearProgressProps = { + ...parseFromValuesOrFunc(muiLinearProgressProps, { + isTopToolbar, + table, + }), + ...rest, + }; + + return ( + + + + ); +} +export default DataTable_LinearProgressBar \ No newline at end of file diff --git a/src/core/components/DataTable/toolbar/TablePagination.js b/src/core/components/DataTable/toolbar/TablePagination.js new file mode 100644 index 0000000..cd70fda --- /dev/null +++ b/src/core/components/DataTable/toolbar/TablePagination.js @@ -0,0 +1,217 @@ +import { flipIconStyles, getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils"; +import { useTheme } from "@emotion/react"; +import { Box, IconButton, InputLabel, MenuItem, Pagination, PaginationItem, Select, Tooltip, Typography, useMediaQuery } from "@mui/material"; + +const defaultRowsPerPage = [5, 10, 15, 20, 25, 30, 50, 100]; + +const DataTable_TablePagination = ({ + position = 'bottom', + table, + ...rest +}) => { + const theme = useTheme(); + const isMobile = useMediaQuery('(max-width: 720px)'); + + const { + getState, + options: { + enableToolbarInternalActions, + icons: { ChevronLeftIcon, ChevronRightIcon, FirstPageIcon, LastPageIcon }, + localization, + muiPaginationProps, + paginationDisplayMode, + }, + } = table; + + const { + pagination: { pageIndex = 0, pageSize = 10 }, + showGlobalFilter, + } = getState(); + + const paginationProps = { + ...parseFromValuesOrFunc(muiPaginationProps, { + table, + }), + ...rest, + }; + + const totalRowCount = table.getRowCount(); + const numberOfPages = table.getPageCount(); + const showFirstLastPageButtons = numberOfPages > 2; + const firstRowIndex = pageIndex * pageSize; + const lastRowIndex = Math.min(pageIndex * pageSize + pageSize, totalRowCount); + + const { + SelectProps = {}, + disabled = false, + rowsPerPageOptions = defaultRowsPerPage, + showFirstButton = showFirstLastPageButtons, + showLastButton = showFirstLastPageButtons, + showRowsPerPage = true, + ...restPaginationProps + } = paginationProps ?? {}; + + const disableBack = pageIndex <= 0 || disabled; + const disableNext = lastRowIndex >= totalRowCount || disabled; + + if (isMobile && SelectProps?.native !== false) { + SelectProps.native = true; + } + + const tooltipProps = getCommonTooltipProps(); + + return ( + + {showRowsPerPage && ( + + + {localization.rowsPerPage} + + + + )} + {paginationDisplayMode === 'pages' ? ( + table.setPageIndex(newPageIndex - 1)} + page={pageIndex + 1} + renderItem={(item) => ( + + )} + showFirstButton={showFirstButton} + showLastButton={showLastButton} + {...restPaginationProps} + /> + ) : paginationDisplayMode === 'default' ? ( + <> + {`${lastRowIndex === 0 ? 0 : (firstRowIndex + 1).toLocaleString() + }-${lastRowIndex.toLocaleString()} ${localization.of + } ${totalRowCount.toLocaleString()}`} + + {showFirstButton && ( + + + table.firstPage()} + size="small" + > + + + + + )} + + + table.previousPage()} + size="small" + > + + + + + + + table.nextPage()} + size="small" + > + + + + + {showLastButton && ( + + + table.lastPage()} + size="small" + > + + + + + )} + + + ) : null} + + ); +} +export default DataTable_TablePagination \ No newline at end of file diff --git a/src/core/components/DataTable/toolbar/ToolbarInternalButtons.js b/src/core/components/DataTable/toolbar/ToolbarInternalButtons.js new file mode 100644 index 0000000..606add0 --- /dev/null +++ b/src/core/components/DataTable/toolbar/ToolbarInternalButtons.js @@ -0,0 +1,47 @@ +import { parseFromValuesOrFunc } from "@/core/utils/utils"; +import { Box } from "@mui/material"; +import DataTable_ShowHideColumnsButton from "../buttons/ShowHideColumnsButton"; +import DataTable_ToggleFiltersButton from "../buttons/ToggleFiltersButton"; + +const DataTable_ToolbarInternalButtons = ({ table, ...rest }) => { + const { + options: { + columnFilterDisplayMode, + enableColumnFilters, + enableColumnOrdering, + enableColumnPinning, + enableFilters, + enableHiding, + renderToolbarInternalActions, + }, + } = table; + + + return ( + ({ + alignItems: 'center', + display: 'flex', + zIndex: 3, + ...(parseFromValuesOrFunc(rest?.sx, theme)), + })} + > + {renderToolbarInternalActions?.({ + table, + }) ?? ( + <> + {enableFilters && + enableColumnFilters && + columnFilterDisplayMode !== 'popover' && ( + + )} + {(enableHiding || enableColumnOrdering || enableColumnPinning) && ( + + )} + + )} + + ); +} +export default DataTable_ToolbarInternalButtons \ No newline at end of file diff --git a/src/core/components/DataTable/toolbar/TopToolbar.js b/src/core/components/DataTable/toolbar/TopToolbar.js new file mode 100644 index 0000000..4eb0aba --- /dev/null +++ b/src/core/components/DataTable/toolbar/TopToolbar.js @@ -0,0 +1,86 @@ +import { getCommonToolbarStyles, parseFromValuesOrFunc } from "@/core/utils/utils"; +import { Box, useMediaQuery } from "@mui/material"; +import DataTable_LinearProgressBar from "./LinearProgressBar"; +import DataTable_TablePagination from "./TablePagination"; +import DataTable_ToolbarInternalButtons from "./ToolbarInternalButtons"; + +const DataTable_TopToolbar = ({ table }) => { + const { + getState, + options: { + enablePagination, + enableToolbarInternalActions, + muiTopToolbarProps, + positionPagination, + renderTopToolbarCustomActions, + }, + refs: { topToolbarRef }, + } = table; + const { isFullScreen, showGlobalFilter } = getState(); + + const isMobile = useMediaQuery('(max-width:720px)'); + const isTablet = useMediaQuery('(max-width:1024px)'); + + const toolbarProps = parseFromValuesOrFunc(muiTopToolbarProps, { table }); + + const stackAlertBanner = + isMobile || + !!renderTopToolbarCustomActions || + (showGlobalFilter && isTablet); + + return ( + { + topToolbarRef.current = ref; + if (toolbarProps?.ref) { + // @ts-ignore + toolbarProps.ref.current = ref; + } + }} + sx={(theme) => ({ + ...getCommonToolbarStyles({ table, theme }), + position: isFullScreen ? 'sticky' : 'relative', + top: isFullScreen ? '0' : undefined, + ...(parseFromValuesOrFunc(toolbarProps?.sx, theme)), + })} + > + + {renderTopToolbarCustomActions?.({ table }) ?? } + {enableToolbarInternalActions && ( + + + + )} + + {enablePagination && + ['both', 'top'].includes(positionPagination ?? '') && ( + + )} + + + ); +} + +export default DataTable_TopToolbar \ No newline at end of file diff --git a/src/core/components/SidebarListItems.jsx b/src/core/components/SidebarListItems.jsx new file mode 100644 index 0000000..9ffcac2 --- /dev/null +++ b/src/core/components/SidebarListItems.jsx @@ -0,0 +1,46 @@ +import {Collapse, ListItem, ListItemButton, ListItemIcon, ListItemText} from "@mui/material"; +import { ExpandLess, ExpandMore } from '@mui/icons-material'; +import WifiIcon from '@mui/icons-material/Wifi'; +import SidebarSubitems from "@/core/components/SidebarSubitems"; + +const SidebarListItems = ({menuItem, dispatch}) => { + return( + <> + + { + dispatch({type: "COLLAPSE_MENU", id: menuItem.id}) + }}> + + {menuItem.icon} + + + {menuItem.hasSubitems ? (menuItem.showSubitems ? : ) : null } + + + + { + menuItem.hasSubitems ? (menuItem.Subitems.map((subitem,index)=>{ + return( + + ) + })) : null + } + + + + ) +} +export default SidebarListItems \ No newline at end of file diff --git a/src/core/components/SidebarMenu.jsx b/src/core/components/SidebarMenu.jsx new file mode 100644 index 0000000..f7589c3 --- /dev/null +++ b/src/core/components/SidebarMenu.jsx @@ -0,0 +1,201 @@ +"use client" +import {List} from "@mui/material"; +import SidebarListItems from "@/core/components/SidebarListItems"; +import {useEffect, useReducer, useState} from "react"; +import {usePathname} from 'next/navigation' +import SecurityIcon from '@mui/icons-material/Security'; +import SpaceDashboardIcon from "@mui/icons-material/SpaceDashboard"; +import AssessmentIcon from '@mui/icons-material/Assessment'; +import AirlineSeatReclineNormalIcon from "@mui/icons-material/AirlineSeatReclineNormal"; +import RepartitionIcon from '@mui/icons-material/Repartition'; + +const data = [ + { + id: "hello", + type: "menu", + route: "/hello", + icon: , + selected: false, + Subitems: [ + { + id: "amin", + route: "/s", + icon: , + firstName: "amin", + lastName: "ali", + Subitems: [ + { + id: "shahrokh", shahrokh: "shahrokh", type: "page", selected: false, route: "/dashbssoard", icon: + }, + { + id: "shasdasdsahrokh", shahrokh: "shahroasdasdkh", type: "page", selected: false, route: "/dashboardsd", icon: + } + ], + hasSubitems: true, + showSubitems: false, + type: "menu", + selected: false, + }, + { + id: "amir", + route: "/dashboarsad", + icon: , + firstName: "amir", + lastName: "akbar", + hasSubitems: false, + showSubitems: false, + selected: false, + type: "page", + }, + ], + hasSubitems: true, + showSubitems: false + }, + { + id: "hi", + type: "menu", + icon: , + route: "/hi", + selected: false, + Subitems: [ + { + id: "ali", + route: "/dashboardss", + firstName: "ali", + lastName: "ali", + icon: , + hasSubitems: false, + showSubitems: false, + type: "page", + selected: false, + }, + { + id: "akbar", + route: "/akbar", + firstName: "akbar", + icon: , + lastName: "akbar", + hasSubitems: false, + showSubitems: false, + type: "page", + selected: false, + }, + ], + hasSubitems: true, + showSubitems: false + }, + { + id: "hoo", + type: "page", + route: "/dashboard", + icon: , + selected: false, + hasSubitems: false, + showSubitems: false + } +] + +function reducer(state, action) { + switch (action.type) { + case "COLLAPSE_MENU": + return state.map((item) => action.id == item.id ? { + ...item, showSubitems: !item.showSubitems + } : item) + case "COLLAPSE_SUB_ITEMS" : + return state.map((item) => { + return item.hasSubitems ? { + ...item, Subitems: item.Subitems.map((subitem) => action.id === subitem.id ? { + ...subitem, showSubitems: !subitem.showSubitems + } : subitem) + } : item + }) + case "COLLAPSE_SUB_SECOND_ITEMS" : + return state.map((item) => { + return item.hasSubitems ? { + ...item, Subitems: item.Subitems.map((subitem) => { + return subitem.hasSubitems ? { + ...subitem, + Subitems: subitem.Subitems.map((secondSubitem) => action.id === secondSubitem.id ? { + ...secondSubitem, showSubitems: !secondSubitem.showSubitems + } : secondSubitem) + } : subitem + }) + } : item + }) + case "SELECTED": + return state.map((item) => { + return item.type === "page" ? { + ...item, selected: action.route === item.route,showSubitems: item.route === action.route + } : item.Subitems && Array.isArray(item.Subitems) ? { + ...item, Subitems: item.Subitems.map((subitem) => { + return subitem.type === "page" ? { + ...subitem, selected: action.route === subitem.route,showSubitems: subitem.route === action.route + } : subitem.Subitems && Array.isArray(subitem.Subitems) ? { + ...subitem, Subitems: subitem.Subitems.map((secondSubItem) => ({ + ...secondSubItem, selected: secondSubItem.route === action.route + })),showSubitems: subitem.Subitems.some((secondSubItem) => secondSubItem.route === action.route), + } : subitem + }),showSubitems: item.Subitems.some((subitem) => subitem.showSubitems), + } : item + }); + // case "SELECTED": + // return state.map((item) => { + // return item.type === "page" ? { + // ...item, + // selected: action.route === item.route + // } : item.Subitems && Array.isArray(item.Subitems) ? { + // ...item, + // Subitems: item.Subitems.map((subitem) => { + // const updatedSubitem = subitem.type === "page" ? { + // ...subitem, + // selected: action.route === subitem.route + // } : subitem.Subitems && Array.isArray(subitem.Subitems) ? { + // ...subitem, + // Subitems: subitem.Subitems.map((secondSubItem) => ({ + // ...secondSubItem, + // selected: secondSubItem.route === action.route + // })), + // showSubitems: subitem.Subitems.some((secondSubItem) => secondSubItem.route === action.route), + // } : subitem; + // + // console.log("Updated subitem:", updatedSubitem); // Log the updated subitem + // return updatedSubitem; + // }), + // showSubitems: item.Subitems.some((subitem) => subitem.showSubitems), + // } : item + // }); + + + default: + throw new Error(); + } +} + +const SidebarMenu = () => { + const [menuItems, dispatch] = useReducer(reducer, data); + const pathname = usePathname() + const [selectedKey, setSelectedKey] = useState(null); + useEffect(() => { + dispatch({type: "SELECTED", route: pathname}); + setSelectedKey(pathname); + }, [pathname]); + + useEffect(() => { + selectedKey && document.querySelector(`[data-route="${selectedKey}"]`)?.scrollIntoView({ + behavior: "smooth", block: "center" + }); + }, [selectedKey]); + + return ( + + { + menuItems.map((menuItem) => { + return ( + + ) + }) + } + + ) +} +export default SidebarMenu \ No newline at end of file diff --git a/src/core/components/SidebarSubitems.jsx b/src/core/components/SidebarSubitems.jsx new file mode 100644 index 0000000..275befb --- /dev/null +++ b/src/core/components/SidebarSubitems.jsx @@ -0,0 +1,74 @@ +import {Collapse, List, ListItem, ListItemButton, ListItemIcon, ListItemText} from "@mui/material"; +import {ExpandLess, ExpandMore} from "@mui/icons-material"; + +const SidebarSubitems = ({subitem, dispatch}) => { + return ( + <> + + + { + dispatch({type: "COLLAPSE_SUB_ITEMS", id: subitem.id}) + }}> + + {subitem.icon} + + + {subitem.hasSubitems ? (subitem.showSubitems ? : ) : null + } + + + + { + subitem.hasSubitems ? (subitem.Subitems.map((subSubitem, index) => { + return ( + + + + { + dispatch({type: "COLLAPSE_SUB_SECOND_ITEMS", id: subSubitem.id}) + }} + selected={subSubitem.selected} + sx={{ + '&.Mui-selected': { + backgroundColor: 'rgba(0, 0, 0, 0.05)', + }, + }} + > + + {subSubitem.icon} + + + + + + + ) + })) : null + } + + ) +} +export default SidebarSubitems \ No newline at end of file diff --git a/src/core/utils/flattenArrayOfObjects.js b/src/core/utils/flattenArrayOfObjects.js new file mode 100644 index 0000000..4dd4612 --- /dev/null +++ b/src/core/utils/flattenArrayOfObjects.js @@ -0,0 +1,10 @@ +export const flattenArrayOfObjects = (array, key) => { + return array.reduce((acc, obj) => { + if (Array.isArray(obj[key])) { + acc.push(...flattenArrayOfObjects(obj[key])); + } else { + acc.push(obj); + } + return acc; + }, []); +}; \ No newline at end of file diff --git a/src/core/utils/theme.js b/src/core/utils/theme.js index be10390..3abbb52 100644 --- a/src/core/utils/theme.js +++ b/src/core/utils/theme.js @@ -1,8 +1,12 @@ 'use client' -import {createTheme} from "@mui/material"; +import { createTheme } from "@mui/material"; const theme = createTheme({ - direction: 'rtl' + direction: 'rtl', + typography: { + fontFamily: `IRANSansFaNum, sans-serif`, + fontSize: 12 + }, }); export default theme \ No newline at end of file diff --git a/src/core/utils/utils.js b/src/core/utils/utils.js new file mode 100644 index 0000000..98e0252 --- /dev/null +++ b/src/core/utils/utils.js @@ -0,0 +1,162 @@ +import { alpha, darken } from "@mui/material"; + +export const parseCSSVarId = (id) => id.replace(/[^a-zA-Z0-9]/g, '_'); + +export const parseFromValuesOrFunc = ( + fn, + arg +) => (fn instanceof Function ? fn(arg) : fn); + +export const getCommonToolbarStyles = ({ + table, +}) => ({ + alignItems: 'flex-start', + backgroundColor: table.options.mrtTheme.baseBackgroundColor, + display: 'grid', + flexWrap: 'wrap-reverse', + minHeight: '3.5rem', + overflow: 'hidden', + position: 'relative', + transition: 'all 150ms ease-in-out', + zIndex: 1, +}); + +export const getCommonTooltipProps = ( + placement, +) => ({ + disableInteractive: true, + enterDelay: 500, + enterNextDelay: 500, + placement, +}); + +export const flipIconStyles = (theme) => + theme.direction === 'rtl' + ? { style: { transform: 'scaleX(-1)' } } + : undefined; + +export const getCommonPinnedCellStyles = ({ + column, + table, + theme, +}) => { + const { baseBackgroundColor } = table.options.mrtTheme; + const isPinned = column?.getIsPinned(); + + return { + '&[data-pinned="true"]': { + '&:before': { + backgroundColor: alpha( + darken( + baseBackgroundColor, + theme.palette.mode === 'dark' ? 0.05 : 0.01, + ), + 0.97, + ), + boxShadow: column + ? isPinned === 'left' && column.getIsLastColumn(isPinned) + ? `-4px 0 4px -4px ${alpha(theme.palette.grey[700], 0.5)} inset` + : isPinned === 'right' && column.getIsFirstColumn(isPinned) + ? `4px 0 4px -4px ${alpha(theme.palette.grey[700], 0.5)} inset` + : undefined + : undefined, + ...commonCellBeforeAfterStyles, + }, + }, + }; +}; + +export const getCommonMRTCellStyles = ({ + column, + header, + table, + tableCellProps, + theme, +}) => { + const { + getState, + options: { enableColumnVirtualization, layoutMode }, + } = table; + const { draggingColumn } = getState(); + const { columnDef } = column; + const { columnDefType } = columnDef; + + const isColumnPinned = + columnDef.columnDefType !== 'group' && column.getIsPinned(); + + const widthStyles = { + minWidth: `max(calc(var(--${header ? 'header' : 'col'}-${parseCSSVarId( + header?.id ?? column.id, + )}-size) * 1px), ${columnDef.minSize ?? 30}px)`, + width: `calc(var(--${header ? 'header' : 'col'}-${parseCSSVarId( + header?.id ?? column.id, + )}-size) * 1px)`, + }; + + if (layoutMode === 'grid') { + widthStyles.flex = `${[0, false].includes(columnDef.grow) + ? 0 + : `var(--${header ? 'header' : 'col'}-${parseCSSVarId( + header?.id ?? column.id, + )}-size)` + } 0 auto`; + } else if (layoutMode === 'grid-no-grow') { + widthStyles.flex = `${+(columnDef.grow || 0)} 0 auto`; + } + + const pinnedStyles = isColumnPinned + ? { + ...getCommonPinnedCellStyles({ column, table, theme }), + left: + isColumnPinned === 'left' + ? `${column.getStart('left')}px` + : undefined, + opacity: 0.97, + position: 'sticky', + right: + isColumnPinned === 'right' + ? `${column.getAfter('right')}px` + : undefined, + } + : {}; + + return { + backgroundColor: 'inherit', + backgroundImage: 'inherit', + display: layoutMode?.startsWith('grid') ? 'flex' : undefined, + justifyContent: + columnDefType === 'group' + ? 'center' + : layoutMode?.startsWith('grid') + ? tableCellProps.align + : undefined, + opacity: + table.getState().draggingColumn?.id === column.id || + table.getState().hoveredColumn?.id === column.id + ? 0.5 + : 1, + position: 'relative', + transition: enableColumnVirtualization + ? 'none' + : `padding 150ms ease-in-out`, + zIndex: + column.getIsResizing() || draggingColumn?.id === column.id + ? 2 + : columnDefType !== 'group' && isColumnPinned + ? 1 + : 0, + ...pinnedStyles, + ...widthStyles, + ...(parseFromValuesOrFunc(tableCellProps?.sx, theme)), + }; +}; + +export const commonCellBeforeAfterStyles = { + content: '""', + height: '100%', + left: 0, + position: 'absolute', + top: 0, + width: '100%', + zIndex: -1, +}; \ No newline at end of file diff --git a/src/lib/contexts/tableSetting.jsx b/src/lib/contexts/tableSetting.jsx new file mode 100644 index 0000000..0c2262d --- /dev/null +++ b/src/lib/contexts/tableSetting.jsx @@ -0,0 +1,129 @@ +'use client' +import {createContext, useCallback, useEffect, useState} from "react"; +import {flattenArrayOfObjects} from "@/core/utils/flattenArrayOfObjects"; + +export const TableSettingContext = createContext(); +export const TableSettingProvider = ({children}) => { + const [settingStore, setSettingStore] = useState({}); + + useEffect(() => { + const _localStorage = localStorage.getItem("_setting-app") + if (_localStorage) { + setSettingStore(JSON.parse(_localStorage)) + } + }, []); + + const hideAction = useCallback((user_id, page_name, table_name, settingValue, columns) => { + updateSettingStorage(user_id, page_name, table_name, "hides", settingValue, columns); + }, []) + const sortAction = useCallback((user_id, page_name, table_name, settingValue, columns) => { + updateSettingStorage(user_id, page_name, table_name, "sorts", settingValue, columns); + }, []) + const filterAction = (user_id, page_name, table_name, settingValue, columns) => { + updateSettingStorage(user_id, page_name, table_name, "filters", settingValue, columns); + } + const updateSettingStorage = (user_id, page_name, table_name, settingType, settingValue, columns) => { + const prevLocalStorage = JSON.parse(localStorage.getItem("_setting-app")) || {}; + const userSettings = prevLocalStorage[user_id] || {}; + const pageSettings = userSettings[page_name] || {}; + const tableSettings = pageSettings[table_name] || {}; + let newSettings; + if (settingType === "sorts" || settingType === "filters") { + newSettings = [...settingValue]; + } else { + newSettings = {...tableSettings[settingType] || {}, ...settingValue}; + } + const updatedLocalStorage = { + ...prevLocalStorage, + [user_id]: { + ...userSettings, + [page_name]: { + ...pageSettings, + [table_name]: { + ...tableSettings, + [settingType]: Array.isArray(settingValue) ? [...newSettings] : {...newSettings}, + summary: SummaryTextMessage({ + ...tableSettings, + [settingType]: Array.isArray(settingValue) ? [...newSettings] : {...newSettings}, + }, columns) + }, + }, + }, + }; + localStorage.setItem("_setting-app", JSON.stringify(updatedLocalStorage)); + setSettingStore(updatedLocalStorage); + } + + const SummaryTextMessage = (values, columns) => { + let SummaryText = ""; + let HideText = ""; + let FilterText = ""; + let SortText = ""; + + if (values.sorts && values.sorts.length > 0) { + const _list = [] + for (const sort of values.sorts) { + const _column = columns.find(column => column.id == sort.id) + if (_column) + _list.push(`${_column.header}` + `(${sort.desc ? "نزولی" : "صعودی"})`) + } + if (_list.length > 0) + SortText = " مرتب شده بر اساس : " + _list.join(", ") + } + if (values.filters && values.filters.length > 0) { + const _list = [] + for (const filter of values.filters) { + const _column = columns.find(column => column.id == filter.id) + if (_column) + _list.push(`${_column.header}`) + } + if (_list.length > 0) + FilterText = " فیلتر شده بر اساس : " + _list.join(", ") + } + + if (values.hides) { + const hidesArray = Object.entries(values.hides).map((e) => ({id: e[0], value: e[1]})) + if (hidesArray.length > 0) { + const _list = [] + for (const hide of hidesArray) { + if (hide.value) continue + const _column = columns.find(c => c.id == hide.id) + if (_column) + _list.push(_column.header) + } + if (_list.length > 0) + HideText = "ستون های مخفی شده : " + _list.join(", "); + } + } + + if (HideText || SortText || FilterText) { + if (SortText) { + SummaryText += SortText; + if (HideText) { + SummaryText += " ; "; + } + if (FilterText) { + SummaryText += " ; "; + } + } + if (HideText) { + SummaryText += HideText; + if (FilterText) { + SummaryText += " ; "; + } + } + if (FilterText) { + SummaryText += FilterText + } + } + + return SummaryText; + }; + + return ( + + {children} + + ); +}; \ No newline at end of file diff --git a/src/lib/hooks/useTableSetting.jsx b/src/lib/hooks/useTableSetting.jsx new file mode 100644 index 0000000..14d9d4e --- /dev/null +++ b/src/lib/hooks/useTableSetting.jsx @@ -0,0 +1,10 @@ +import { useContext } from "react"; +import { TableSettingContext } from "../contexts/tableSetting"; + +const useTableSetting = () => { + const { settingStore, hideAction, sortAction, filterAction } = + useContext(TableSettingContext); + return { settingStore, hideAction, sortAction, filterAction }; +}; + +export default useTableSetting;