Merge branch 'release/v2.2.0'
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
NEXT_PUBLIC_VERSION = "2.1.5"
|
NEXT_PUBLIC_VERSION = "2.2.0"
|
||||||
|
|
||||||
NEXT_PUBLIC_API_URL = "https://crm.witel.ir/server"
|
NEXT_PUBLIC_API_URL = "https://crm.witel.ir/server"
|
||||||
NEXT_PUBLIC_SERVER_SOCKET_URL = "wss://crm.witel.ir"
|
NEXT_PUBLIC_SERVER_SOCKET_URL = "wss://crm.witel.ir"
|
||||||
|
|||||||
@@ -17,9 +17,14 @@
|
|||||||
"@mui/icons-material": "^7.0.2",
|
"@mui/icons-material": "^7.0.2",
|
||||||
"@mui/material": "^7.0.2",
|
"@mui/material": "^7.0.2",
|
||||||
"@mui/material-nextjs": "^7.0.2",
|
"@mui/material-nextjs": "^7.0.2",
|
||||||
|
"@mui/x-date-pickers": "^8.2.0",
|
||||||
|
"@mui/x-tree-view": "^8.2.0",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
|
"date-fns-jalali": "4.1.0-0",
|
||||||
"jalali-moment": "^3.3.11",
|
"jalali-moment": "^3.3.11",
|
||||||
"lz-string": "^1.5.0",
|
"lz-string": "^1.5.0",
|
||||||
|
"material-react-table": "^3.2.1",
|
||||||
|
"moment": "^2.30.1",
|
||||||
"next": "15.3.1",
|
"next": "15.3.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
|||||||
215
pnpm-lock.yaml
generated
215
pnpm-lock.yaml
generated
@@ -29,15 +29,30 @@ importers:
|
|||||||
'@mui/material-nextjs':
|
'@mui/material-nextjs':
|
||||||
specifier: ^7.0.2
|
specifier: ^7.0.2
|
||||||
version: 7.0.2(@emotion/cache@11.14.0)(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(next@15.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react@19.1.0)
|
version: 7.0.2(@emotion/cache@11.14.0)(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(next@15.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react@19.1.0)
|
||||||
|
'@mui/x-date-pickers':
|
||||||
|
specifier: ^8.2.0
|
||||||
|
version: 8.2.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@mui/material@7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mui/system@7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(date-fns-jalali@4.1.0-0)(moment@2.30.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@mui/x-tree-view':
|
||||||
|
specifier: ^8.2.0
|
||||||
|
version: 8.2.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@mui/material@7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mui/system@7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
axios:
|
axios:
|
||||||
specifier: ^1.9.0
|
specifier: ^1.9.0
|
||||||
version: 1.9.0
|
version: 1.9.0
|
||||||
|
date-fns-jalali:
|
||||||
|
specifier: 4.1.0-0
|
||||||
|
version: 4.1.0-0
|
||||||
jalali-moment:
|
jalali-moment:
|
||||||
specifier: ^3.3.11
|
specifier: ^3.3.11
|
||||||
version: 3.3.11
|
version: 3.3.11
|
||||||
lz-string:
|
lz-string:
|
||||||
specifier: ^1.5.0
|
specifier: ^1.5.0
|
||||||
version: 1.5.0
|
version: 1.5.0
|
||||||
|
material-react-table:
|
||||||
|
specifier: ^3.2.1
|
||||||
|
version: 3.2.1(ec2ae31a6c05688f49c8e8bdcad7fbc8)
|
||||||
|
moment:
|
||||||
|
specifier: ^2.30.1
|
||||||
|
version: 2.30.1
|
||||||
next:
|
next:
|
||||||
specifier: 15.3.1
|
specifier: 15.3.1
|
||||||
version: 15.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0)
|
version: 15.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0)
|
||||||
@@ -495,6 +510,65 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@mui/x-date-pickers@8.2.0':
|
||||||
|
resolution: {integrity: sha512-nkFzKkaDr4a9uto4pSOsNBMh+M2YQWiW9iYyBDOi5wOa983e2xMeb7zFjHGJ0xaycg1kfxNcbvJd7tm4Ub+RhA==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@emotion/react': ^11.9.0
|
||||||
|
'@emotion/styled': ^11.8.1
|
||||||
|
'@mui/material': ^5.15.14 || ^6.0.0 || ^7.0.0
|
||||||
|
'@mui/system': ^5.15.14 || ^6.0.0 || ^7.0.0
|
||||||
|
date-fns: ^2.25.0 || ^3.2.0 || ^4.0.0
|
||||||
|
date-fns-jalali: ^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0
|
||||||
|
dayjs: ^1.10.7
|
||||||
|
luxon: ^3.0.2
|
||||||
|
moment: ^2.29.4
|
||||||
|
moment-hijri: ^2.1.2 || ^3.0.0
|
||||||
|
moment-jalaali: ^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0
|
||||||
|
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@emotion/react':
|
||||||
|
optional: true
|
||||||
|
'@emotion/styled':
|
||||||
|
optional: true
|
||||||
|
date-fns:
|
||||||
|
optional: true
|
||||||
|
date-fns-jalali:
|
||||||
|
optional: true
|
||||||
|
dayjs:
|
||||||
|
optional: true
|
||||||
|
luxon:
|
||||||
|
optional: true
|
||||||
|
moment:
|
||||||
|
optional: true
|
||||||
|
moment-hijri:
|
||||||
|
optional: true
|
||||||
|
moment-jalaali:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@mui/x-internals@8.2.0':
|
||||||
|
resolution: {integrity: sha512-qV4Qr+m4sAPBSuqu8/Ofi5m+nMMvIybGno6cp757bHSmwxkqrn5SKaGyFnH5kB58fOhYA9hG1UivFp7mO1dE4A==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
|
||||||
|
'@mui/x-tree-view@8.2.0':
|
||||||
|
resolution: {integrity: sha512-58ZRFXsUfJaqZ/1kiXaAUILfxQOZHZErZbApK3dj0O7jGHgj4mLdjetwlTmwHjZiEO//ryckaDxl4K83sntANQ==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@emotion/react': ^11.9.0
|
||||||
|
'@emotion/styled': ^11.8.1
|
||||||
|
'@mui/material': ^5.15.14 || ^6.0.0 || ^7.0.0
|
||||||
|
'@mui/system': ^5.15.14 || ^6.0.0 || ^7.0.0
|
||||||
|
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@emotion/react':
|
||||||
|
optional: true
|
||||||
|
'@emotion/styled':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/wasm-runtime@0.2.9':
|
'@napi-rs/wasm-runtime@0.2.9':
|
||||||
resolution: {integrity: sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg==}
|
resolution: {integrity: sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg==}
|
||||||
|
|
||||||
@@ -671,6 +745,30 @@ packages:
|
|||||||
'@swc/helpers@0.5.15':
|
'@swc/helpers@0.5.15':
|
||||||
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
|
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
|
||||||
|
|
||||||
|
'@tanstack/match-sorter-utils@8.19.4':
|
||||||
|
resolution: {integrity: sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
'@tanstack/react-table@8.20.6':
|
||||||
|
resolution: {integrity: sha512-w0jluT718MrOKthRcr2xsjqzx+oEM7B7s/XXyfs19ll++hlId3fjTm+B2zrR3ijpANpkzBAr15j1XGVOMxpggQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8'
|
||||||
|
react-dom: '>=16.8'
|
||||||
|
|
||||||
|
'@tanstack/react-virtual@3.11.2':
|
||||||
|
resolution: {integrity: sha512-OuFzMXPF4+xZgx8UzJha0AieuMihhhaWG0tCqpp6tDzlFwOmNBPYMuLOtMJ1Tr4pXLHmgjcWhG6RlknY2oNTdQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
|
||||||
|
'@tanstack/table-core@8.20.5':
|
||||||
|
resolution: {integrity: sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
'@tanstack/virtual-core@3.11.2':
|
||||||
|
resolution: {integrity: sha512-vTtpNt7mKCiZ1pwU9hfKPhpdVO2sVzFQsxoVBGtOSHxlrRRzYr8iQ2TlwbAcRYCcEiZ9ECAM8kBzH0v2+VzfKw==}
|
||||||
|
|
||||||
'@tybys/wasm-util@0.9.0':
|
'@tybys/wasm-util@0.9.0':
|
||||||
resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==}
|
resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==}
|
||||||
|
|
||||||
@@ -1091,6 +1189,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
|
resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
date-fns-jalali@4.1.0-0:
|
||||||
|
resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==}
|
||||||
|
|
||||||
debug@3.2.7:
|
debug@3.2.7:
|
||||||
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1533,6 +1634,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
highlight-words@2.0.0:
|
||||||
|
resolution: {integrity: sha512-If5n+IhSBRXTScE7wl16VPmd+44Vy7kof24EdqhjsZsDuHikpv1OCagVcJFpB4fS4UPUniedlWqrjIO8vWOsIQ==}
|
||||||
|
engines: {node: '>= 20', npm: '>= 9'}
|
||||||
|
|
||||||
hoist-non-react-statics@3.3.2:
|
hoist-non-react-statics@3.3.2:
|
||||||
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
|
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
|
||||||
|
|
||||||
@@ -1786,6 +1891,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
|
resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
material-react-table@3.2.1:
|
||||||
|
resolution: {integrity: sha512-sQtTf7bETpkPN2Hm5BVtz89wrfXCVQguz6XlwMChSnfKFO5QCKAJJC5aSIKnUc3S0AvTz/k/ILi00FnnY1Gixw==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
peerDependencies:
|
||||||
|
'@emotion/react': '>=11.13'
|
||||||
|
'@emotion/styled': '>=11.13'
|
||||||
|
'@mui/icons-material': '>=6'
|
||||||
|
'@mui/material': '>=6'
|
||||||
|
'@mui/x-date-pickers': '>=7.15'
|
||||||
|
react: '>=18.0'
|
||||||
|
react-dom: '>=18.0'
|
||||||
|
|
||||||
math-intrinsics@1.1.0:
|
math-intrinsics@1.1.0:
|
||||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -2097,6 +2214,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
|
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
remove-accents@0.5.0:
|
||||||
|
resolution: {integrity: sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==}
|
||||||
|
|
||||||
|
reselect@5.1.1:
|
||||||
|
resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
|
||||||
|
|
||||||
resolve-from@4.0.0:
|
resolve-from@4.0.0:
|
||||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -2927,6 +3050,56 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
|
'@mui/x-date-pickers@8.2.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@mui/material@7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mui/system@7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(date-fns-jalali@4.1.0-0)(moment@2.30.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.27.1
|
||||||
|
'@mui/material': 7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@mui/system': 7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@mui/utils': 7.0.2(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@mui/x-internals': 8.2.0(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@types/react-transition-group': 4.4.12(@types/react@19.1.2)
|
||||||
|
clsx: 2.1.1
|
||||||
|
prop-types: 15.8.1
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
react-transition-group: 4.4.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@emotion/react': 11.14.0(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
date-fns-jalali: 4.1.0-0
|
||||||
|
moment: 2.30.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
|
||||||
|
'@mui/x-internals@8.2.0(@types/react@19.1.2)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.27.1
|
||||||
|
'@mui/utils': 7.0.2(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
|
||||||
|
'@mui/x-tree-view@8.2.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@mui/material@7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mui/system@7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.27.1
|
||||||
|
'@mui/material': 7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@mui/system': 7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@mui/utils': 7.0.2(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@mui/x-internals': 8.2.0(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@types/react-transition-group': 4.4.12(@types/react@19.1.2)
|
||||||
|
clsx: 2.1.1
|
||||||
|
prop-types: 15.8.1
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
react-transition-group: 4.4.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
reselect: 5.1.1
|
||||||
|
use-sync-external-store: 1.5.0(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@emotion/react': 11.14.0(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
|
||||||
'@napi-rs/wasm-runtime@0.2.9':
|
'@napi-rs/wasm-runtime@0.2.9':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/core': 1.4.3
|
'@emnapi/core': 1.4.3
|
||||||
@@ -3055,6 +3228,26 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@tanstack/match-sorter-utils@8.19.4':
|
||||||
|
dependencies:
|
||||||
|
remove-accents: 0.5.0
|
||||||
|
|
||||||
|
'@tanstack/react-table@8.20.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@tanstack/table-core': 8.20.5
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
|
||||||
|
'@tanstack/react-virtual@3.11.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@tanstack/virtual-core': 3.11.2
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
|
||||||
|
'@tanstack/table-core@8.20.5': {}
|
||||||
|
|
||||||
|
'@tanstack/virtual-core@3.11.2': {}
|
||||||
|
|
||||||
'@tybys/wasm-util@0.9.0':
|
'@tybys/wasm-util@0.9.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
@@ -3508,6 +3701,8 @@ snapshots:
|
|||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
is-data-view: 1.0.2
|
is-data-view: 1.0.2
|
||||||
|
|
||||||
|
date-fns-jalali@4.1.0-0: {}
|
||||||
|
|
||||||
debug@3.2.7:
|
debug@3.2.7:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
@@ -4110,6 +4305,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind: 1.1.2
|
function-bind: 1.1.2
|
||||||
|
|
||||||
|
highlight-words@2.0.0: {}
|
||||||
|
|
||||||
hoist-non-react-statics@3.3.2:
|
hoist-non-react-statics@3.3.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
react-is: 16.13.1
|
react-is: 16.13.1
|
||||||
@@ -4375,6 +4572,20 @@ snapshots:
|
|||||||
|
|
||||||
lz-string@1.5.0: {}
|
lz-string@1.5.0: {}
|
||||||
|
|
||||||
|
material-react-table@3.2.1(ec2ae31a6c05688f49c8e8bdcad7fbc8):
|
||||||
|
dependencies:
|
||||||
|
'@emotion/react': 11.14.0(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@mui/icons-material': 7.0.2(@mui/material@7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@mui/material': 7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@mui/x-date-pickers': 8.2.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@mui/material@7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mui/system@7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(date-fns-jalali@4.1.0-0)(moment@2.30.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@tanstack/match-sorter-utils': 8.19.4
|
||||||
|
'@tanstack/react-table': 8.20.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@tanstack/react-virtual': 3.11.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
highlight-words: 2.0.0
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
|
||||||
math-intrinsics@1.1.0: {}
|
math-intrinsics@1.1.0: {}
|
||||||
|
|
||||||
media-typer@1.1.0: {}
|
media-typer@1.1.0: {}
|
||||||
@@ -4679,6 +4890,10 @@ snapshots:
|
|||||||
gopd: 1.2.0
|
gopd: 1.2.0
|
||||||
set-function-name: 2.0.2
|
set-function-name: 2.0.2
|
||||||
|
|
||||||
|
remove-accents@0.5.0: {}
|
||||||
|
|
||||||
|
reselect@5.1.1: {}
|
||||||
|
|
||||||
resolve-from@4.0.0: {}
|
resolve-from@4.0.0: {}
|
||||||
|
|
||||||
resolve-pkg-maps@1.0.0: {}
|
resolve-pkg-maps@1.0.0: {}
|
||||||
|
|||||||
11
src/app/(withAuth)/(dashboardLayout)/dashboard/users/page.js
Normal file
11
src/app/(withAuth)/(dashboardLayout)/dashboard/users/page.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import UsersPage from "@/components/dashboard/Users";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "کاربران",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
return <UsersPage></UsersPage>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -1,16 +1,23 @@
|
|||||||
|
"use client";
|
||||||
import HeaderWithSidebar from "@/components/layouts/dashboard/headerWithSidebar";
|
import HeaderWithSidebar from "@/components/layouts/dashboard/headerWithSidebar";
|
||||||
import CallWidget from "@/components/widget/call";
|
import CallWidget from "@/components/widget/call";
|
||||||
import WithWidgetMiddleware from "@/core/middlewares/withWidget";
|
import WithWidgetMiddleware from "@/core/middlewares/withWidget";
|
||||||
import { SocketProvider } from "@/lib/contexts/socket";
|
import { SocketProvider } from "@/lib/contexts/socket";
|
||||||
|
import { usePermissions } from "@/lib/hooks/usePermissions";
|
||||||
import { Stack } from "@mui/material";
|
import { Stack } from "@mui/material";
|
||||||
|
|
||||||
const Layout = ({ children }) => {
|
const Layout = ({ children }) => {
|
||||||
|
const { data: permissions } = usePermissions();
|
||||||
|
const isCallWidgetEnabled = permissions?.some(
|
||||||
|
(permission) => permission === "manage_calls",
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SocketProvider>
|
<SocketProvider>
|
||||||
<Stack sx={{ width: "100%", height: "100%" }}>
|
<Stack sx={{ width: "100%", height: "100%" }}>
|
||||||
<HeaderWithSidebar>{children}</HeaderWithSidebar>
|
<HeaderWithSidebar>{children}</HeaderWithSidebar>
|
||||||
</Stack>
|
</Stack>
|
||||||
<WithWidgetMiddleware enable={true}>
|
<WithWidgetMiddleware enable={isCallWidgetEnabled}>
|
||||||
<CallWidget />
|
<CallWidget />
|
||||||
</WithWidgetMiddleware>
|
</WithWidgetMiddleware>
|
||||||
</SocketProvider>
|
</SocketProvider>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const Template = ({ children }) => {
|
|||||||
styles={{
|
styles={{
|
||||||
"*": {
|
"*": {
|
||||||
scrollbarWidth: "thin",
|
scrollbarWidth: "thin",
|
||||||
scrollbarColor: `#155175 transparent`,
|
scrollbarColor: `${theme.palette.primary.dark} transparent`,
|
||||||
},
|
},
|
||||||
"*&::-webkit-scrollbar": {
|
"*&::-webkit-scrollbar": {
|
||||||
width: "4px",
|
width: "4px",
|
||||||
@@ -23,7 +23,7 @@ const Template = ({ children }) => {
|
|||||||
borderRadius: "4px",
|
borderRadius: "4px",
|
||||||
},
|
},
|
||||||
"*&::-webkit-scrollbar-thumb": {
|
"*&::-webkit-scrollbar-thumb": {
|
||||||
background: "#155175",
|
background: `${theme.palette.primary.dark}`,
|
||||||
borderRadius: "4px",
|
borderRadius: "4px",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|||||||
205
src/components/dashboard/Users/DataTable.jsx
Normal file
205
src/components/dashboard/Users/DataTable.jsx
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
"use client";
|
||||||
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
|
import { GET_USER_LIST } from "@/core/utils/routes";
|
||||||
|
import { Box, Stack } from "@mui/material";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import RowActions from "./RowActions";
|
||||||
|
import Toolbar from "./Toolbar";
|
||||||
|
import useProvinces from "@/lib/hooks/useProvince";
|
||||||
|
import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency";
|
||||||
|
import { Power, PowerOff } from "@mui/icons-material";
|
||||||
|
import useRoles from "@/lib/hooks/useRoles";
|
||||||
|
import OnlineCell from "./RowActions/Online";
|
||||||
|
import TelephoneId from "./RowActions/TelephoneId";
|
||||||
|
|
||||||
|
const DataTable = () => {
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: "id",
|
||||||
|
header: "کد یکتا",
|
||||||
|
id: "id",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "استان",
|
||||||
|
id: "province_id",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "numeric",
|
||||||
|
filterMode: "equals",
|
||||||
|
grow: false,
|
||||||
|
size: 130,
|
||||||
|
ColumnSelectComponent: (props) => {
|
||||||
|
const { provinces, errorProvinces, loadingProvinces } =
|
||||||
|
useProvinces();
|
||||||
|
const getColumnSelectOptions = useMemo(() => {
|
||||||
|
if (loadingProvinces) {
|
||||||
|
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||||
|
}
|
||||||
|
if (errorProvinces) {
|
||||||
|
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{ value: "", label: "کل کشور" },
|
||||||
|
...provinces.map((province) => ({
|
||||||
|
value: province.id,
|
||||||
|
label: province.name,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
}, [provinces, errorProvinces, loadingProvinces]);
|
||||||
|
return (
|
||||||
|
<CustomSelectByDependency
|
||||||
|
{...props}
|
||||||
|
value={
|
||||||
|
loadingProvinces ? "loading" : props.filterParameters.value
|
||||||
|
}
|
||||||
|
columnSelectOption={getColumnSelectOptions}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Cell: ({ renderedCellValue, row }) => <>{row.original.province_fa}</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "شماره تماس داخلی",
|
||||||
|
id: "telephone_id",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => <TelephoneId user_id={row.original.id} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "username",
|
||||||
|
header: "نام کاربری",
|
||||||
|
id: "username",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "position",
|
||||||
|
header: "سمت",
|
||||||
|
id: "position",
|
||||||
|
enableColumnFilter: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "phone_number",
|
||||||
|
header: "شماره همراه",
|
||||||
|
id: "phone_number",
|
||||||
|
enableColumnFilter: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "نقش",
|
||||||
|
id: "role__id",
|
||||||
|
enableColumnFilter: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "numeric",
|
||||||
|
filterMode: "equals",
|
||||||
|
grow: false,
|
||||||
|
size: 130,
|
||||||
|
ColumnSelectComponent: (props) => {
|
||||||
|
const { roles, errorRoles, loadingRoles } = useRoles();
|
||||||
|
const getColumnSelectOptions = useMemo(() => {
|
||||||
|
if (loadingRoles) {
|
||||||
|
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||||
|
}
|
||||||
|
if (errorRoles) {
|
||||||
|
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{ value: "", label: "همه نقش ها" },
|
||||||
|
...roles.map((role) => ({
|
||||||
|
value: role.id,
|
||||||
|
label: role.name_fa,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
}, [roles, errorRoles, loadingRoles]);
|
||||||
|
return (
|
||||||
|
<CustomSelectByDependency
|
||||||
|
{...props}
|
||||||
|
value={loadingRoles ? "loading" : props.filterParameters.value}
|
||||||
|
columnSelectOption={getColumnSelectOptions}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Cell: ({ row }) => <>{row.original.roles[0].name_fa}</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "national_id",
|
||||||
|
header: "کد ملی",
|
||||||
|
id: "national_id",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "is_online",
|
||||||
|
header: "وضعیت برخط",
|
||||||
|
id: "is_online",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => <OnlineCell user_id={row.original.id} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "gender",
|
||||||
|
header: "جنسیت",
|
||||||
|
id: "gender",
|
||||||
|
enableColumnFilter: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ renderedCellValue }) => (
|
||||||
|
<>{renderedCellValue == "male" ? "آقا" : "خانم"}</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
|
<DataTableWithAuth
|
||||||
|
need_filter={true}
|
||||||
|
columns={columns}
|
||||||
|
sorting={[{ id: "id", desc: true }]}
|
||||||
|
table_url={GET_USER_LIST}
|
||||||
|
page_name={"usersPage"}
|
||||||
|
table_name={"usersList"}
|
||||||
|
TableToolbar={Toolbar}
|
||||||
|
enableRowActions
|
||||||
|
positionActionsColumn={"last"}
|
||||||
|
RowActions={RowActions}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable;
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { DELETE_USER } from "@/core/utils/routes";
|
||||||
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
Stack,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => {
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
const requestServer = useRequest({ notificationSuccess: true });
|
||||||
|
const handleClick = async () => {
|
||||||
|
setSubmitting(true);
|
||||||
|
try {
|
||||||
|
await requestServer(`${DELETE_USER}/${rowId}`, "delete");
|
||||||
|
mutate();
|
||||||
|
setOpenDeleteDialog(false);
|
||||||
|
} catch (error) {
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DialogContent>
|
||||||
|
<Stack alignItems="center" spacing={2}>
|
||||||
|
<Typography mt={2}>آیا از حذف کاربر اطمینان دارید؟</Typography>
|
||||||
|
</Stack>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions
|
||||||
|
sx={{ justifyContent: "center", paddingBottom: 2, width: "100%" }}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => setOpenDeleteDialog(false)}
|
||||||
|
variant="outlined"
|
||||||
|
color="secondary"
|
||||||
|
>
|
||||||
|
خیر !
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleClick}
|
||||||
|
variant="contained"
|
||||||
|
color="error"
|
||||||
|
disabled={submitting}
|
||||||
|
>
|
||||||
|
{submitting ? "درحال حذف..." : "بله اطمینان دارم"}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteContent;
|
||||||
37
src/components/dashboard/Users/RowActions/Delete/index.jsx
Normal file
37
src/components/dashboard/Users/RowActions/Delete/index.jsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
|
import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material";
|
||||||
|
import { useState } from "react";
|
||||||
|
import DeleteContent from "./DeleteContent";
|
||||||
|
const Delete = ({ rowId, mutate }) => {
|
||||||
|
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tooltip title="حذف">
|
||||||
|
<IconButton color="error" onClick={() => setOpenDeleteDialog(true)}>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Dialog
|
||||||
|
open={openDeleteDialog}
|
||||||
|
onClose={() => setOpenDeleteDialog(false)}
|
||||||
|
PaperProps={{
|
||||||
|
sx: {
|
||||||
|
boxShadow:
|
||||||
|
"rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
dir="rtl"
|
||||||
|
maxWidth={"md"}
|
||||||
|
>
|
||||||
|
<DialogTitle>حذف</DialogTitle>
|
||||||
|
<DeleteContent
|
||||||
|
rowId={rowId}
|
||||||
|
mutate={mutate}
|
||||||
|
setOpenDeleteDialog={setOpenDeleteDialog}
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default Delete;
|
||||||
369
src/components/dashboard/Users/RowActions/Edit/Form/index.jsx
Normal file
369
src/components/dashboard/Users/RowActions/Edit/Form/index.jsx
Normal file
@@ -0,0 +1,369 @@
|
|||||||
|
import LtrTextField from "@/core/components/LtrTextField";
|
||||||
|
import StyledForm from "@/core/components/StyledForm";
|
||||||
|
import validateNationalCode from "@/core/utils/nationalCodeValidation";
|
||||||
|
import { UPDATE_USER } from "@/core/utils/routes";
|
||||||
|
import useProvinces from "@/lib/hooks/useProvince";
|
||||||
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
|
import useRoles from "@/lib/hooks/useRoles";
|
||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Chip,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
Divider,
|
||||||
|
FormControl,
|
||||||
|
FormHelperText,
|
||||||
|
Grid,
|
||||||
|
InputLabel,
|
||||||
|
MenuItem,
|
||||||
|
OutlinedInput,
|
||||||
|
Select,
|
||||||
|
Stack,
|
||||||
|
TextField,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { object, string } from "yup";
|
||||||
|
|
||||||
|
const validationSchema = object().shape({
|
||||||
|
full_name: string().required("نام و نام خانوادگی خود را وارد کنید"),
|
||||||
|
username: string().required("نام کاربری خود را وارد کنید"),
|
||||||
|
phone_number: string()
|
||||||
|
.matches(/^09\d{9}$/, "شماره همراه باید با 09 شروع شود و 11 رقم باشد.")
|
||||||
|
.required("شماره همراه خود را وارد کنید"),
|
||||||
|
gender: string().required("جنسیت خود را وارد کنید"),
|
||||||
|
national_id: string()
|
||||||
|
.test(
|
||||||
|
"max",
|
||||||
|
"کد ملی باید شامل 10 رقم باشد",
|
||||||
|
(value) => value.toString().length === 10,
|
||||||
|
)
|
||||||
|
.test("validation", "کد ملی صحیح نمی باشد", (value) =>
|
||||||
|
validateNationalCode(value),
|
||||||
|
)
|
||||||
|
.required("کد ملی خود را وارد کنید"),
|
||||||
|
position: string().required("سمت خود را وارد کنید"),
|
||||||
|
province_id: string().required("استان خود را وارد کنید"),
|
||||||
|
role_id: string().required("نقش خود را وارد کنید"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const UpdateUserForm = ({ values, setOpen, mutate }) => {
|
||||||
|
const requestServer = useRequest({ notificationSuccess: true });
|
||||||
|
const { provinces, errorProvinces, loadingProvinces } = useProvinces();
|
||||||
|
const { roles, errorRoles, loadingRoles } = useRoles();
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
full_name: values.full_name,
|
||||||
|
username: values.username,
|
||||||
|
position: values.position,
|
||||||
|
gender: values.gender,
|
||||||
|
national_id: values.national_id,
|
||||||
|
phone_number: values.phone_number,
|
||||||
|
province_id: values.province_id,
|
||||||
|
role_id: values.roles[0].id,
|
||||||
|
};
|
||||||
|
|
||||||
|
const provinceSelectOptions = useMemo(() => {
|
||||||
|
if (loadingProvinces) {
|
||||||
|
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||||
|
}
|
||||||
|
if (errorProvinces) {
|
||||||
|
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{ value: "", label: "انتخاب استان" },
|
||||||
|
...provinces.map((province) => ({
|
||||||
|
value: province.id,
|
||||||
|
label: province.name,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
}, [provinces, errorProvinces, loadingProvinces]);
|
||||||
|
|
||||||
|
const roleSelectOptions = useMemo(() => {
|
||||||
|
if (loadingRoles) {
|
||||||
|
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||||
|
}
|
||||||
|
if (errorRoles) {
|
||||||
|
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{ value: "", label: "انتخاب نقش" },
|
||||||
|
...roles.map((role) => ({
|
||||||
|
value: role.id,
|
||||||
|
label: role.name_fa,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
}, [roles, errorRoles, loadingRoles]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { isSubmitting, errors },
|
||||||
|
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||||
|
|
||||||
|
const onSubmit = async (data) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
Object.keys(data).forEach((key) => {
|
||||||
|
formData.append(key, data[key]);
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await requestServer(`${UPDATE_USER}/${values.id}`, "post", {
|
||||||
|
data: formData,
|
||||||
|
});
|
||||||
|
setOpen(false);
|
||||||
|
mutate();
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DialogContent dividers>
|
||||||
|
<Divider sx={{ mb: 3 }}>
|
||||||
|
<Chip color="primary" variant="outlined" label="کاربر جدید" />
|
||||||
|
</Divider>
|
||||||
|
<StyledForm
|
||||||
|
id="UpdateUserForm"
|
||||||
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
|
style={{ width: "100%", height: "100%" }}
|
||||||
|
>
|
||||||
|
<Stack spacing={2}>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
label="نام کاربری"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"username"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<TextField
|
||||||
|
{...field}
|
||||||
|
label="نام و نام خانوادگی"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"full_name"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<TextField
|
||||||
|
{...field}
|
||||||
|
label="سمت"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"position"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
label="شماره همراه"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
type="tel"
|
||||||
|
onChange={(e) => {
|
||||||
|
const inputValue = event.target.value;
|
||||||
|
if (isNaN(Number(inputValue))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (inputValue.length > 11) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
field.onChange(inputValue);
|
||||||
|
}}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"phone_number"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth error={!!error}>
|
||||||
|
<InputLabel id={`label-phone-number`} shrink>
|
||||||
|
جنسیت
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId={`label-phone-number`}
|
||||||
|
id={"phone-number"}
|
||||||
|
name={`gender`}
|
||||||
|
value={field.value}
|
||||||
|
input={<OutlinedInput label={"جنسیت"} />}
|
||||||
|
onChange={(e) => field.onChange(e.target.value)}
|
||||||
|
displayEmpty
|
||||||
|
>
|
||||||
|
<MenuItem value={"male"}>مرد</MenuItem>
|
||||||
|
<MenuItem value={"female"}>زن</MenuItem>
|
||||||
|
</Select>
|
||||||
|
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||||
|
{!!error && error.message}
|
||||||
|
</FormHelperText>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"gender"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
label="شماره ملی"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
type="tel"
|
||||||
|
onChange={(e) => {
|
||||||
|
const inputValue = event.target.value;
|
||||||
|
if (isNaN(Number(inputValue))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (inputValue.length > 10) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
field.onChange(inputValue);
|
||||||
|
}}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"national_id"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth error={!!error}>
|
||||||
|
<InputLabel id={`label-province`} shrink>
|
||||||
|
استان
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId={`label-province`}
|
||||||
|
id={"province"}
|
||||||
|
name={`province_id`}
|
||||||
|
value={loadingProvinces ? "loading" : field.value}
|
||||||
|
input={<OutlinedInput label={"استان"} />}
|
||||||
|
onChange={(e) => field.onChange(e.target.value)}
|
||||||
|
displayEmpty
|
||||||
|
>
|
||||||
|
{provinceSelectOptions.map((option) => (
|
||||||
|
<MenuItem key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||||
|
{!!error && error.message}
|
||||||
|
</FormHelperText>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"province_id"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth error={!!error}>
|
||||||
|
<InputLabel id={`label-role`} shrink>
|
||||||
|
نقش
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId={`label-role`}
|
||||||
|
id={"role"}
|
||||||
|
name={`role_id`}
|
||||||
|
value={loadingRoles ? "loading" : field.value}
|
||||||
|
input={<OutlinedInput label={"نقش"} />}
|
||||||
|
onChange={(e) => field.onChange(e.target.value)}
|
||||||
|
displayEmpty
|
||||||
|
>
|
||||||
|
{roleSelectOptions.map((option) => (
|
||||||
|
<MenuItem key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||||
|
{!!error && error.message}
|
||||||
|
</FormHelperText>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"role_id"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Stack>
|
||||||
|
</StyledForm>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
disabled={isSubmitting}
|
||||||
|
variant="outlined"
|
||||||
|
color="secondary"
|
||||||
|
size="large"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
>
|
||||||
|
بستن
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
form="UpdateUserForm"
|
||||||
|
size="large"
|
||||||
|
autoFocus
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
{isSubmitting ? "در حال ویرایش کاربر..." : "ویرایش کاربر"}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UpdateUserForm;
|
||||||
32
src/components/dashboard/Users/RowActions/Edit/index.jsx
Normal file
32
src/components/dashboard/Users/RowActions/Edit/index.jsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
"use client";
|
||||||
|
import { Edit } from "@mui/icons-material";
|
||||||
|
import { Dialog, IconButton, Tooltip } from "@mui/material";
|
||||||
|
import { useState } from "react";
|
||||||
|
import UpdateUserForm from "./Form";
|
||||||
|
|
||||||
|
const UpdateUser = ({ values, mutate }) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleOpen = () => {
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tooltip title="ویرایش">
|
||||||
|
<IconButton onClick={handleOpen}>
|
||||||
|
<Edit />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Dialog maxWidth="sm" fullWidth={true} open={open}>
|
||||||
|
<UpdateUserForm
|
||||||
|
open={open}
|
||||||
|
setOpen={setOpen}
|
||||||
|
mutate={mutate}
|
||||||
|
values={values}
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default UpdateUser;
|
||||||
17
src/components/dashboard/Users/RowActions/Online/index.jsx
Normal file
17
src/components/dashboard/Users/RowActions/Online/index.jsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { useSocket } from "@/lib/contexts/socket";
|
||||||
|
import { Power, PowerOff } from "@mui/icons-material";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
|
||||||
|
const OnlineCell = ({ user_id }) => {
|
||||||
|
const { clientsOnline } = useSocket();
|
||||||
|
return (
|
||||||
|
<Stack alignItems={"center"}>
|
||||||
|
{clientsOnline.some((co) => co.user_id == user_id) ? (
|
||||||
|
<Power color="success" />
|
||||||
|
) : (
|
||||||
|
<PowerOff color="error" />
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default OnlineCell;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { useSocket } from "@/lib/contexts/socket";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
|
||||||
|
const TelephoneId = ({ user_id }) => {
|
||||||
|
const { clientsOnline } = useSocket();
|
||||||
|
const telephone_id = clientsOnline.find(
|
||||||
|
(co) => co.user_id == user_id,
|
||||||
|
)?.telephone_id;
|
||||||
|
return <Stack alignItems={"center"}>{telephone_id}</Stack>;
|
||||||
|
};
|
||||||
|
export default TelephoneId;
|
||||||
12
src/components/dashboard/Users/RowActions/index.jsx
Normal file
12
src/components/dashboard/Users/RowActions/index.jsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Box } from "@mui/material";
|
||||||
|
import Delete from "./Delete";
|
||||||
|
import UpdateUser from "./Edit";
|
||||||
|
const RowActions = ({ row, mutate }) => {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", gap: 1, alignItems: "center" }}>
|
||||||
|
<Delete rowId={row.original.id} mutate={mutate} />
|
||||||
|
<UpdateUser values={row.original} mutate={mutate} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default RowActions;
|
||||||
416
src/components/dashboard/Users/Toolbar/Create/Form/index.jsx
Normal file
416
src/components/dashboard/Users/Toolbar/Create/Form/index.jsx
Normal file
@@ -0,0 +1,416 @@
|
|||||||
|
import LtrTextField from "@/core/components/LtrTextField";
|
||||||
|
import StyledForm from "@/core/components/StyledForm";
|
||||||
|
import validateNationalCode from "@/core/utils/nationalCodeValidation";
|
||||||
|
import useProvinces from "@/lib/hooks/useProvince";
|
||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Chip,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
Divider,
|
||||||
|
FormControl,
|
||||||
|
FormHelperText,
|
||||||
|
Grid,
|
||||||
|
IconButton,
|
||||||
|
InputAdornment,
|
||||||
|
InputLabel,
|
||||||
|
MenuItem,
|
||||||
|
OutlinedInput,
|
||||||
|
Select,
|
||||||
|
Stack,
|
||||||
|
TextField,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { object, string } from "yup";
|
||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import useRoles from "@/lib/hooks/useRoles";
|
||||||
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
|
import { CREATE_USER } from "@/core/utils/routes";
|
||||||
|
import { Visibility, VisibilityOff } from "@mui/icons-material";
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
full_name: "",
|
||||||
|
username: "",
|
||||||
|
position: "",
|
||||||
|
gender: "male",
|
||||||
|
national_id: "",
|
||||||
|
phone_number: "",
|
||||||
|
province_id: "",
|
||||||
|
password: "",
|
||||||
|
role_id: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const validationSchema = object().shape({
|
||||||
|
full_name: string().required("نام و نام خانوادگی خود را وارد کنید"),
|
||||||
|
username: string().required("نام کاربری خود را وارد کنید"),
|
||||||
|
phone_number: string()
|
||||||
|
.matches(/^09\d{9}$/, "شماره همراه باید با 09 شروع شود و 11 رقم باشد.")
|
||||||
|
.required("شماره همراه خود را وارد کنید"),
|
||||||
|
gender: string().required("جنسیت خود را وارد کنید"),
|
||||||
|
national_id: string()
|
||||||
|
.test(
|
||||||
|
"max",
|
||||||
|
"کد ملی باید شامل 10 رقم باشد",
|
||||||
|
(value) => value.toString().length === 10,
|
||||||
|
)
|
||||||
|
.test("validation", "کد ملی صحیح نمی باشد", (value) =>
|
||||||
|
validateNationalCode(value),
|
||||||
|
)
|
||||||
|
.required("کد ملی خود را وارد کنید"),
|
||||||
|
password: string()
|
||||||
|
.required("رمز عبور خود را وارد کنید")
|
||||||
|
.matches(
|
||||||
|
/^(?=.*[a-zA-Z])(?=.*\d).{8,}$/,
|
||||||
|
"رمز عبور باید حداقل 8 کاراکتر و متشکل از عدد، حرف یا سمبل باشد",
|
||||||
|
),
|
||||||
|
position: string().required("سمت خود را وارد کنید"),
|
||||||
|
province_id: string().required("استان خود را وارد کنید"),
|
||||||
|
role_id: string().required("نقش خود را وارد کنید"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const CreateUserForm = ({ setOpen, mutate }) => {
|
||||||
|
const requestServer = useRequest({ notificationSuccess: true });
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const { provinces, errorProvinces, loadingProvinces } = useProvinces();
|
||||||
|
const { roles, errorRoles, loadingRoles } = useRoles();
|
||||||
|
|
||||||
|
const handleClickShowPassword = () => setShowPassword((show) => !show);
|
||||||
|
|
||||||
|
const provinceSelectOptions = useMemo(() => {
|
||||||
|
if (loadingProvinces) {
|
||||||
|
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||||
|
}
|
||||||
|
if (errorProvinces) {
|
||||||
|
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{ value: "", label: "انتخاب استان" },
|
||||||
|
...provinces.map((province) => ({
|
||||||
|
value: province.id,
|
||||||
|
label: province.name,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
}, [provinces, errorProvinces, loadingProvinces]);
|
||||||
|
|
||||||
|
const roleSelectOptions = useMemo(() => {
|
||||||
|
if (loadingRoles) {
|
||||||
|
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||||
|
}
|
||||||
|
if (errorRoles) {
|
||||||
|
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{ value: "", label: "انتخاب نقش" },
|
||||||
|
...roles.map((role) => ({
|
||||||
|
value: role.id,
|
||||||
|
label: role.name_fa,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
}, [roles, errorRoles, loadingRoles]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { isSubmitting, errors },
|
||||||
|
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||||
|
|
||||||
|
const onSubmit = async (data) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
Object.keys(data).forEach((key) => {
|
||||||
|
formData.append(key, data[key]);
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await requestServer(CREATE_USER, "post", {
|
||||||
|
data: formData,
|
||||||
|
});
|
||||||
|
setOpen(false);
|
||||||
|
mutate();
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DialogContent dividers>
|
||||||
|
<Divider sx={{ mb: 3 }}>
|
||||||
|
<Chip color="primary" variant="outlined" label="کاربر جدید" />
|
||||||
|
</Divider>
|
||||||
|
<StyledForm
|
||||||
|
id="createUserForm"
|
||||||
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
|
style={{ width: "100%", height: "100%" }}
|
||||||
|
>
|
||||||
|
<Stack spacing={2}>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
label="نام کاربری"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"username"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
label="رمز عبور"
|
||||||
|
variant="outlined"
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
slotProps={{
|
||||||
|
input: {
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">
|
||||||
|
<IconButton onClick={handleClickShowPassword}>
|
||||||
|
{showPassword ? (
|
||||||
|
<VisibilityOff />
|
||||||
|
) : (
|
||||||
|
<Visibility />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"password"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<TextField
|
||||||
|
{...field}
|
||||||
|
label="نام و نام خانوادگی"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"full_name"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<TextField
|
||||||
|
{...field}
|
||||||
|
label="سمت"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"position"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
label="شماره همراه"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
type="tel"
|
||||||
|
onChange={(e) => {
|
||||||
|
const inputValue = event.target.value;
|
||||||
|
if (isNaN(Number(inputValue))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (inputValue.length > 11) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
field.onChange(inputValue);
|
||||||
|
}}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"phone_number"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth error={!!error}>
|
||||||
|
<InputLabel id={`label-phone-number`} shrink>
|
||||||
|
جنسیت
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId={`label-phone-number`}
|
||||||
|
id={"phone-number"}
|
||||||
|
name={`gender`}
|
||||||
|
value={field.value}
|
||||||
|
input={<OutlinedInput label={"جنسیت"} />}
|
||||||
|
onChange={(e) => field.onChange(e.target.value)}
|
||||||
|
displayEmpty
|
||||||
|
>
|
||||||
|
<MenuItem value={"male"}>مرد</MenuItem>
|
||||||
|
<MenuItem value={"female"}>زن</MenuItem>
|
||||||
|
</Select>
|
||||||
|
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||||
|
{!!error && error.message}
|
||||||
|
</FormHelperText>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"gender"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
label="شماره ملی"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
type="tel"
|
||||||
|
onChange={(e) => {
|
||||||
|
const inputValue = event.target.value;
|
||||||
|
if (isNaN(Number(inputValue))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (inputValue.length > 10) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
field.onChange(inputValue);
|
||||||
|
}}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"national_id"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth error={!!error}>
|
||||||
|
<InputLabel id={`label-province`} shrink>
|
||||||
|
استان
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId={`label-province`}
|
||||||
|
id={"province"}
|
||||||
|
name={`province_id`}
|
||||||
|
value={field.value}
|
||||||
|
input={<OutlinedInput label={"استان"} />}
|
||||||
|
onChange={(e) => field.onChange(e.target.value)}
|
||||||
|
displayEmpty
|
||||||
|
>
|
||||||
|
{provinceSelectOptions.map((option) => (
|
||||||
|
<MenuItem key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||||
|
{!!error && error.message}
|
||||||
|
</FormHelperText>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"province_id"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth error={!!error}>
|
||||||
|
<InputLabel id={`label-role`} shrink>
|
||||||
|
نقش
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId={`label-role`}
|
||||||
|
id={"role"}
|
||||||
|
name={`role_id`}
|
||||||
|
value={field.value}
|
||||||
|
input={<OutlinedInput label={"نقش"} />}
|
||||||
|
onChange={(e) => field.onChange(e.target.value)}
|
||||||
|
displayEmpty
|
||||||
|
>
|
||||||
|
{roleSelectOptions.map((option) => (
|
||||||
|
<MenuItem key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||||
|
{!!error && error.message}
|
||||||
|
</FormHelperText>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"role_id"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Stack>
|
||||||
|
</StyledForm>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
disabled={isSubmitting}
|
||||||
|
variant="outlined"
|
||||||
|
color="secondary"
|
||||||
|
size="large"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
>
|
||||||
|
بستن
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
form="createUserForm"
|
||||||
|
size="large"
|
||||||
|
autoFocus
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
{isSubmitting ? "در حال ثبت کاربر جدید..." : "ثبت کاربر جدید"}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateUserForm;
|
||||||
49
src/components/dashboard/Users/Toolbar/Create/index.jsx
Normal file
49
src/components/dashboard/Users/Toolbar/Create/index.jsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
"use client";
|
||||||
|
import { AddCircle } from "@mui/icons-material";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
IconButton,
|
||||||
|
useMediaQuery,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
import CreateUserForm from "./Form";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
const CreateUser = ({ mutate }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleOpen = () => {
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isMobile ? (
|
||||||
|
<IconButton
|
||||||
|
aria-label="کاربر جدید"
|
||||||
|
color="primary"
|
||||||
|
onClick={handleOpen}
|
||||||
|
>
|
||||||
|
<AddCircle sx={{ fontSize: "25px" }} />
|
||||||
|
</IconButton>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
size={"small"}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
startIcon={<AddCircle />}
|
||||||
|
onClick={handleOpen}
|
||||||
|
>
|
||||||
|
کاربر جدید
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Dialog maxWidth="sm" fullWidth={true} open={open}>
|
||||||
|
<CreateUserForm open={open} setOpen={setOpen} mutate={mutate} />
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default CreateUser;
|
||||||
10
src/components/dashboard/Users/Toolbar/index.jsx
Normal file
10
src/components/dashboard/Users/Toolbar/index.jsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import CreateUser from "./Create";
|
||||||
|
|
||||||
|
const Toolbar = ({ mutate }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CreateUser mutate={mutate} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default Toolbar;
|
||||||
14
src/components/dashboard/Users/index.jsx
Normal file
14
src/components/dashboard/Users/index.jsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import PageTitle from "@/core/components/PageTitle";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
import DataTable from "./DataTable";
|
||||||
|
|
||||||
|
const UsersPage = () => {
|
||||||
|
return (
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<PageTitle title={"کاربران"} />
|
||||||
|
<DataTable />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UsersPage;
|
||||||
@@ -33,7 +33,7 @@ const CallHistory = ({ tab }) => {
|
|||||||
<ErrorOrEmpty isErrorOrEmpty="empty" />
|
<ErrorOrEmpty isErrorOrEmpty="empty" />
|
||||||
) : (
|
) : (
|
||||||
<Box>
|
<Box>
|
||||||
<ListItemOfCalls historyItem={firstItemOfHistory} />
|
<ListItemOfCalls key={"first"} historyItem={firstItemOfHistory} />
|
||||||
{historyList && (
|
{historyList && (
|
||||||
<Divider>
|
<Divider>
|
||||||
<Chip
|
<Chip
|
||||||
|
|||||||
@@ -1,23 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { CallProvider } from "@/lib/contexts/call";
|
import { CallProvider } from "@/lib/contexts/call";
|
||||||
import { CategoriesProvider } from "@/lib/contexts/category";
|
import { CategoriesProvider } from "@/lib/contexts/category";
|
||||||
import { useSocket } from "@/lib/contexts/socket";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import CallWidgetDialog from "./CallWidgetDialog";
|
import CallWidgetDialog from "./CallWidgetDialog";
|
||||||
|
|
||||||
const CallWidget = () => {
|
const CallWidget = () => {
|
||||||
const { socket, status } = useSocket();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (status == "connected") return;
|
|
||||||
|
|
||||||
socket.connect();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
socket.disconnect();
|
|
||||||
};
|
|
||||||
}, [socket]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CategoriesProvider>
|
<CategoriesProvider>
|
||||||
<CallProvider>
|
<CallProvider>
|
||||||
|
|||||||
60
src/core/components/CustomDatePicker.jsx
Normal file
60
src/core/components/CustomDatePicker.jsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { AdapterDateFnsJalali } from "@mui/x-date-pickers/AdapterDateFnsJalali";
|
||||||
|
import { faIR } from "@mui/x-date-pickers/locales";
|
||||||
|
import { LocalizationProvider, MobileDatePicker } from "@mui/x-date-pickers";
|
||||||
|
import { IconButton, InputAdornment } from "@mui/material";
|
||||||
|
import ClearIcon from "@mui/icons-material/Clear";
|
||||||
|
|
||||||
|
const CustomDatePicker = ({
|
||||||
|
dateValue,
|
||||||
|
setDateValue,
|
||||||
|
placeholder = "انتخاب تاریخ",
|
||||||
|
size = "small",
|
||||||
|
}) => {
|
||||||
|
const handleDateChange = (newValue) => {
|
||||||
|
setDateValue(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LocalizationProvider
|
||||||
|
dateAdapter={AdapterDateFnsJalali}
|
||||||
|
localeText={
|
||||||
|
faIR.components.MuiLocalizationProvider.defaultProps.localeText
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MobileDatePicker
|
||||||
|
value={dateValue || null}
|
||||||
|
onChange={handleDateChange}
|
||||||
|
slotProps={{
|
||||||
|
textField: {
|
||||||
|
size: size,
|
||||||
|
placeholder: placeholder,
|
||||||
|
InputProps: {
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
size={size}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
setDateValue(null);
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
color: "#bfbfbf",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "rgba(189, 189, 189, 0.1)",
|
||||||
|
color: "#363434",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ClearIcon />
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
></MobileDatePicker>
|
||||||
|
</LocalizationProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default CustomDatePicker;
|
||||||
199
src/core/components/DataTable/Main.js
Normal file
199
src/core/components/DataTable/Main.js
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
import DataTableFilterDataStructure from "@/core/utils/DataTableFilterDataStructure";
|
||||||
|
import { flattenArrayOfObjects } from "@/core/utils/flattenArrayOfObjects";
|
||||||
|
import { flattenObjectOfObjects } from "@/core/utils/flattenObjectOfObjects";
|
||||||
|
import isArrayEmpty from "@/core/utils/isArrayEmpty";
|
||||||
|
import useDataTable from "@/lib/hooks/useDataTable";
|
||||||
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
|
import { useMaterialReactTable } from "material-react-table";
|
||||||
|
import { useCallback, useEffect, useReducer, useState } from "react";
|
||||||
|
import useSWR from "swr";
|
||||||
|
import { FA_DATATABLE_LOCALIZATION } from "./localization/fa/datatable";
|
||||||
|
import DataTable_Paper from "./table/Paper";
|
||||||
|
|
||||||
|
const rowSelectionReducer = (state, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case "TOGGLE_ROW":
|
||||||
|
return { [action.payload]: true };
|
||||||
|
case "RESET":
|
||||||
|
return {};
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const DataTable_Main = (props) => {
|
||||||
|
const request = useRequest();
|
||||||
|
const [rowSelection, dispatchRowSelection] = useReducer(
|
||||||
|
rowSelectionReducer,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
const { filterData, sortData, setSortData, hideData } = useDataTable();
|
||||||
|
const {
|
||||||
|
need_filter,
|
||||||
|
table_url,
|
||||||
|
user_id,
|
||||||
|
page_name,
|
||||||
|
table_name,
|
||||||
|
columns,
|
||||||
|
initialStateProps,
|
||||||
|
TableToolbar,
|
||||||
|
table_title,
|
||||||
|
RowActions,
|
||||||
|
specific_data,
|
||||||
|
specialFilter,
|
||||||
|
} = props;
|
||||||
|
const flatColumns = flattenArrayOfObjects(columns, "columns");
|
||||||
|
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 });
|
||||||
|
const [totalRowCount, setTotalRowCount] = useState(0);
|
||||||
|
const flattenHideData = flattenObjectOfObjects(hideData);
|
||||||
|
const onSortingChange = (event) => {
|
||||||
|
setSortData(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchUrl = useCallback(() => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.set("start", `${pagination.pageIndex * pagination.pageSize}`);
|
||||||
|
params.set("size", pagination.pageSize);
|
||||||
|
if (specialFilter) {
|
||||||
|
const filteredSpecialFilterData = Object.values(specialFilter).filter(
|
||||||
|
(filter) => !isArrayEmpty(filter.value),
|
||||||
|
);
|
||||||
|
params.set("filters", JSON.stringify(filteredSpecialFilterData || []));
|
||||||
|
} else {
|
||||||
|
const filteredFilterData = DataTableFilterDataStructure(
|
||||||
|
filterData,
|
||||||
|
isArrayEmpty,
|
||||||
|
);
|
||||||
|
params.set(
|
||||||
|
"filters",
|
||||||
|
JSON.stringify(
|
||||||
|
filteredFilterData.length === 0 ? [] : filteredFilterData,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
params.set(
|
||||||
|
"sorting",
|
||||||
|
JSON.stringify(
|
||||||
|
Object.values(sortData).map(({ id, ...rest }) => ({
|
||||||
|
...rest,
|
||||||
|
id: id.replace(/__/g, "."),
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return `${table_url}?${params}`;
|
||||||
|
}, [table_url, filterData, pagination, sortData, specialFilter]);
|
||||||
|
|
||||||
|
const fetcher = async (url) => {
|
||||||
|
try {
|
||||||
|
const response = await request(url);
|
||||||
|
setTotalRowCount(response.data?.meta?.totalRowCount);
|
||||||
|
if (specific_data) {
|
||||||
|
return specific_data(response.data);
|
||||||
|
} else {
|
||||||
|
return response.data?.data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data, isValidating, mutate } = useSWR(
|
||||||
|
props.data ? "" : fetchUrl(),
|
||||||
|
fetcher,
|
||||||
|
{
|
||||||
|
revalidateIfStale: true,
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
revalidateOnReconnect: true,
|
||||||
|
keepPreviousData: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isValidating) return;
|
||||||
|
dispatchRowSelection({ type: "RESET" });
|
||||||
|
}, [isValidating]);
|
||||||
|
|
||||||
|
const table = useMaterialReactTable({
|
||||||
|
localization: FA_DATATABLE_LOCALIZATION,
|
||||||
|
columns,
|
||||||
|
data: data ?? [],
|
||||||
|
muiTableBodyRowProps: ({ row }) => ({
|
||||||
|
onClick: () =>
|
||||||
|
dispatchRowSelection({ type: "TOGGLE_ROW", payload: row.id }),
|
||||||
|
selected: !!rowSelection[row.id],
|
||||||
|
}),
|
||||||
|
initialState: {
|
||||||
|
density: "compact",
|
||||||
|
columnVisibility: flattenHideData,
|
||||||
|
sorting: sortData,
|
||||||
|
...initialStateProps,
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
showProgressBars: isValidating || props.loading,
|
||||||
|
columnVisibility: flattenHideData,
|
||||||
|
sorting: sortData,
|
||||||
|
pagination,
|
||||||
|
rowSelection,
|
||||||
|
},
|
||||||
|
muiTableHeadProps: {
|
||||||
|
sx: {
|
||||||
|
borderTop: "1px solid #e1e1e1",
|
||||||
|
borderBottom: "2px solid #e1e1e1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
muiTableHeadCellProps: {
|
||||||
|
sx: {
|
||||||
|
color: "primary.main",
|
||||||
|
borderLeft: "1px solid #e1e1e1",
|
||||||
|
"&:first-of-type": {
|
||||||
|
borderLeft: "unset",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
muiTableBodyCellProps: {
|
||||||
|
sx: {
|
||||||
|
borderLeft: "1px solid #e1e1e1",
|
||||||
|
"&:first-of-type": {
|
||||||
|
borderLeft: "unset",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
manualFiltering: true,
|
||||||
|
manualPagination: true,
|
||||||
|
manualSorting: true,
|
||||||
|
onPaginationChange: setPagination,
|
||||||
|
onSortingChange: onSortingChange,
|
||||||
|
rowCount: totalRowCount,
|
||||||
|
renderTopToolbarCustomActions: ({ table }) => (
|
||||||
|
<>
|
||||||
|
{TableToolbar && (
|
||||||
|
<TableToolbar
|
||||||
|
table={table}
|
||||||
|
mutate={mutate}
|
||||||
|
filterData={specialFilter || filterData}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
renderRowActions: ({ row }) => <RowActions row={row} mutate={mutate} />,
|
||||||
|
...props,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataTable_Paper
|
||||||
|
table={table}
|
||||||
|
table_title={table_title}
|
||||||
|
need_filter={need_filter}
|
||||||
|
mutate={mutate}
|
||||||
|
table_url={table_url}
|
||||||
|
columns={flatColumns}
|
||||||
|
user_id={user_id}
|
||||||
|
page_name={page_name}
|
||||||
|
table_name={table_name}
|
||||||
|
special_data={props.data}
|
||||||
|
special_filter={specialFilter}
|
||||||
|
setFilterData={props.setFilterData}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable_Main;
|
||||||
201
src/core/components/DataTable/body/TableBody.js
Normal file
201
src/core/components/DataTable/body/TableBody.js
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
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") && (
|
||||||
|
<TableBody
|
||||||
|
{...tableBodyProps}
|
||||||
|
sx={(theme) => ({
|
||||||
|
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" ? (
|
||||||
|
<Memo_DataTable_TableBodyRow key={row.id} {...props} />
|
||||||
|
) : (
|
||||||
|
<DataTable_TableBodyRow key={row.id} {...props} />
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
)}
|
||||||
|
<TableBody
|
||||||
|
{...tableBodyProps}
|
||||||
|
sx={(theme) => ({
|
||||||
|
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 ? (
|
||||||
|
<tr
|
||||||
|
style={{
|
||||||
|
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
colSpan={table.getVisibleLeafColumns().length}
|
||||||
|
style={{
|
||||||
|
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{renderEmptyRowsFallback?.({ table }) ?? (
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
color: "text.secondary",
|
||||||
|
fontStyle: "italic",
|
||||||
|
maxWidth: `min(100vw, ${tablePaperRef.current?.clientWidth ?? 360}px)`,
|
||||||
|
py: "2rem",
|
||||||
|
textAlign: "center",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{globalFilter || columnFilters.length
|
||||||
|
? localization.noResultsFound
|
||||||
|
: localization.noRecordsToDisplay}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{(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" ? (
|
||||||
|
<Memo_DataTable_TableBodyRow key={key} {...props} />
|
||||||
|
) : (
|
||||||
|
<DataTable_TableBodyRow key={key} {...props} />
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
{!rowPinningDisplayMode?.includes("sticky") &&
|
||||||
|
getIsSomeRowsPinned("bottom") && (
|
||||||
|
<TableBody
|
||||||
|
{...tableBodyProps}
|
||||||
|
sx={(theme) => ({
|
||||||
|
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" ? (
|
||||||
|
<Memo_DataTable_TableBodyRow key={row.id} {...props} />
|
||||||
|
) : (
|
||||||
|
<DataTable_TableBodyRow key={row.id} {...props} />
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable_TableBody;
|
||||||
|
|
||||||
|
export const Memo_DataTable_TableBody = memo(
|
||||||
|
DataTable_TableBody,
|
||||||
|
(prev, next) => prev.table.options.data === next.table.options.data,
|
||||||
|
);
|
||||||
308
src/core/components/DataTable/body/TableBodyCell.js
Normal file
308
src/core/components/DataTable/body/TableBodyCell.js
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
import {
|
||||||
|
getCommonMRTCellStyles,
|
||||||
|
parseFromValuesOrFunc,
|
||||||
|
} from "@/core/utils/utils";
|
||||||
|
import { useTheme } from "@emotion/react";
|
||||||
|
import { Skeleton, TableCell } from "@mui/material";
|
||||||
|
import { isCellEditable, openEditingCell } from "material-react-table";
|
||||||
|
import { memo, useEffect, useMemo, useState } from "react";
|
||||||
|
import DataTable_CopyButton from "../buttons/CopyButton";
|
||||||
|
import DataTable_TableBodyCellValue from "./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, baseBackgroundColor },
|
||||||
|
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 (
|
||||||
|
<TableCell
|
||||||
|
data-index={staticColumnIndex}
|
||||||
|
data-pinned={!!isColumnPinned || undefined}
|
||||||
|
{...tableCellProps}
|
||||||
|
onContextMenu={handleContextMenu}
|
||||||
|
onDoubleClick={handleDoubleClick}
|
||||||
|
onDragEnter={handleDragEnter}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
sx={(theme) => ({
|
||||||
|
"&: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,
|
||||||
|
backgroundColor: baseBackgroundColor,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{tableCellProps.children ?? (
|
||||||
|
<>
|
||||||
|
{cell.getIsPlaceholder() ? (
|
||||||
|
(columnDef.PlaceholderCell?.({ cell, column, row, table }) ?? null)
|
||||||
|
) : showSkeletons !== false && (isLoading || showSkeletons) ? (
|
||||||
|
<Skeleton
|
||||||
|
animation="wave"
|
||||||
|
height={20}
|
||||||
|
width={skeletonWidth}
|
||||||
|
{...skeletonProps}
|
||||||
|
/>
|
||||||
|
) : 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 ? (
|
||||||
|
<DataTable_CopyButton cell={cell} table={table}>
|
||||||
|
<DataTable_TableBodyCellValue {...cellValueProps} />
|
||||||
|
</DataTable_CopyButton>
|
||||||
|
) : (
|
||||||
|
<DataTable_TableBodyCellValue {...cellValueProps} />
|
||||||
|
)}
|
||||||
|
{cell.getIsGrouped() && !columnDef.GroupedCell && (
|
||||||
|
<> ({row.subRows?.length})</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable_TableBodyCell;
|
||||||
|
|
||||||
|
export const Memo_DataTable_TableBodyCell = memo(
|
||||||
|
DataTable_TableBodyCell,
|
||||||
|
(prev, next) => next.cell === prev.cell,
|
||||||
|
);
|
||||||
112
src/core/components/DataTable/body/TableBodyCellValue.js
Normal file
112
src/core/components/DataTable/body/TableBodyCellValue.js
Normal file
@@ -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 = (
|
||||||
|
<span aria-label={renderedCellValue} role="note">
|
||||||
|
{chunks?.map(({ key, match, text }) => (
|
||||||
|
<Box
|
||||||
|
aria-hidden="true"
|
||||||
|
component="span"
|
||||||
|
key={key}
|
||||||
|
sx={
|
||||||
|
match
|
||||||
|
? {
|
||||||
|
backgroundColor: matchHighlightColor,
|
||||||
|
borderRadius: "2px",
|
||||||
|
color: (theme) =>
|
||||||
|
theme.palette.mode === "dark"
|
||||||
|
? theme.palette.common.white
|
||||||
|
: theme.palette.common.black,
|
||||||
|
padding: "2px 1px",
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</Box>
|
||||||
|
)) ?? renderedCellValue}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columnDef.Cell && !isGroupedValue) {
|
||||||
|
renderedCellValue = columnDef.Cell({
|
||||||
|
cell,
|
||||||
|
column,
|
||||||
|
renderedCellValue,
|
||||||
|
row,
|
||||||
|
rowRef,
|
||||||
|
staticColumnIndex,
|
||||||
|
staticRowIndex,
|
||||||
|
table,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderedCellValue;
|
||||||
|
};
|
||||||
|
export default DataTable_TableBodyCellValue;
|
||||||
258
src/core/components/DataTable/body/TableBodyRow.js
Normal file
258
src/core/components/DataTable/body/TableBodyRow.js
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
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.2)}`
|
||||||
|
: `${darken(baseBackgroundColor, 0.2)}`
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TableRow
|
||||||
|
data-index={renderDetailPanel ? staticRowIndex * 2 : staticRowIndex}
|
||||||
|
data-pinned={!!isRowPinned || undefined}
|
||||||
|
data-selected={isRowSelected || undefined}
|
||||||
|
onDragEnter={handleDragEnter}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
ref={(node) => {
|
||||||
|
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,
|
||||||
|
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 ? (
|
||||||
|
<td style={{ display: "flex", width: 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 ? (
|
||||||
|
<Memo_DataTable_TableBodyCell key={cell.id} {...props} />
|
||||||
|
) : (
|
||||||
|
<DataTable_TableBodyCell key={cell.id} {...props} />
|
||||||
|
)
|
||||||
|
) : null;
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
{virtualPaddingRight ? (
|
||||||
|
<td style={{ display: "flex", width: virtualPaddingRight }} />
|
||||||
|
) : null}
|
||||||
|
</TableRow>
|
||||||
|
{renderDetailPanel && !row.getIsGrouped() && (
|
||||||
|
<DataTable_TableDetailPanel
|
||||||
|
parentRowRef={rowRef}
|
||||||
|
row={row}
|
||||||
|
rowVirtualizer={rowVirtualizer}
|
||||||
|
staticRowIndex={staticRowIndex}
|
||||||
|
table={table}
|
||||||
|
virtualRow={virtualRow}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable_TableBodyRow;
|
||||||
|
|
||||||
|
export const Memo_DataTable_TableBodyRow = memo(
|
||||||
|
DataTable_TableBodyRow,
|
||||||
|
(prev, next) =>
|
||||||
|
prev.row === next.row && prev.staticRowIndex === next.staticRowIndex,
|
||||||
|
);
|
||||||
91
src/core/components/DataTable/body/TableDetailPanel.js
Normal file
91
src/core/components/DataTable/body/TableDetailPanel.js
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
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 (
|
||||||
|
<TableRow
|
||||||
|
className="Mui-TableBodyCell-DetailPanel"
|
||||||
|
data-index={renderDetailPanel ? staticRowIndex * 2 + 1 : staticRowIndex}
|
||||||
|
ref={(node) => {
|
||||||
|
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),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<TableCell
|
||||||
|
className="Mui-TableBodyCell-DetailPanel"
|
||||||
|
colSpan={getVisibleLeafColumns().length}
|
||||||
|
{...tableCellProps}
|
||||||
|
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
|
||||||
|
) : (
|
||||||
|
<Collapse in={row.getIsExpanded()} mountOnEnter unmountOnExit>
|
||||||
|
{DetailPanel}
|
||||||
|
</Collapse>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable_TableDetailPanel;
|
||||||
74
src/core/components/DataTable/buttons/CopyButton.js
Normal file
74
src/core/components/DataTable/buttons/CopyButton.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
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 (
|
||||||
|
<Tooltip
|
||||||
|
{...getCommonTooltipProps("top")}
|
||||||
|
title={
|
||||||
|
buttonProps?.title ??
|
||||||
|
(copied ? localization.copiedToClipboard : localization.clickToCopy)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={(e) => handleCopy(e, cell.getValue())}
|
||||||
|
size="small"
|
||||||
|
type="button"
|
||||||
|
variant="text"
|
||||||
|
{...buttonProps}
|
||||||
|
sx={(theme) => ({
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
border: "none",
|
||||||
|
color: "inherit",
|
||||||
|
cursor: "copy",
|
||||||
|
fontFamily: "inherit",
|
||||||
|
fontSize: "inherit",
|
||||||
|
letterSpacing: "inherit",
|
||||||
|
m: "-0.25rem",
|
||||||
|
minWidth: "unset",
|
||||||
|
py: 0,
|
||||||
|
textAlign: "inherit",
|
||||||
|
textTransform: "inherit",
|
||||||
|
...parseFromValuesOrFunc(buttonProps?.sx, theme),
|
||||||
|
})}
|
||||||
|
title={undefined}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable_CopyButton;
|
||||||
52
src/core/components/DataTable/buttons/GrabHandleButton.js
Normal file
52
src/core/components/DataTable/buttons/GrabHandleButton.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import {
|
||||||
|
getCommonTooltipProps,
|
||||||
|
parseFromValuesOrFunc,
|
||||||
|
} from "@/core/utils/utils";
|
||||||
|
import { IconButton, Tooltip } from "@mui/material";
|
||||||
|
|
||||||
|
const DataTable_GrabHandleButton = ({ location, table, ...rest }) => {
|
||||||
|
const {
|
||||||
|
options: {
|
||||||
|
icons: { DragHandleIcon },
|
||||||
|
localization,
|
||||||
|
},
|
||||||
|
} = table;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
{...getCommonTooltipProps("top")}
|
||||||
|
title={rest?.title ?? localization.move}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
aria-label={rest.title ?? localization.move}
|
||||||
|
disableRipple
|
||||||
|
draggable="true"
|
||||||
|
size="small"
|
||||||
|
{...rest}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
rest?.onClick?.(e);
|
||||||
|
}}
|
||||||
|
sx={(theme) => ({
|
||||||
|
"&:active": {
|
||||||
|
cursor: "grabbing",
|
||||||
|
},
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
cursor: "grab",
|
||||||
|
m: "0 -0.1rem",
|
||||||
|
opacity: location === "row" ? 1 : 0.5,
|
||||||
|
p: "2px",
|
||||||
|
transition: "all 150ms ease-in-out",
|
||||||
|
...parseFromValuesOrFunc(rest?.sx, theme),
|
||||||
|
})}
|
||||||
|
title={undefined}
|
||||||
|
>
|
||||||
|
<DragHandleIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable_GrabHandleButton;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
const DataTable_ToggleFiltersButton = ({ table, ...rest }) => {
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
|
export default DataTable_ToggleFiltersButton;
|
||||||
133
src/core/components/DataTable/filter/FilterBody.jsx
Normal file
133
src/core/components/DataTable/filter/FilterBody.jsx
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import FilterBodyControllerWithDependency from "@/core/components/DataTable/filter/FilterBodyControllerWithDependency";
|
||||||
|
import FilterHeader from "@/core/components/DataTable/filter/FilterHeader";
|
||||||
|
import useDataTable from "@/lib/hooks/useDataTable";
|
||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
import { Box, Button } from "@mui/material";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import * as Yup from "yup";
|
||||||
|
import ScrollBox from "../../ScrollBox";
|
||||||
|
import FilterBodyController from "./FilterBodyController";
|
||||||
|
|
||||||
|
function FilterBody({ columns, drawerState, setDrawerState }) {
|
||||||
|
const { filterData, setFilterData } = useDataTable();
|
||||||
|
const [validationSchema, setValidationSchema] = useState(Yup.object({}));
|
||||||
|
|
||||||
|
console.log(filterData);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const schema = Yup.object(
|
||||||
|
Object.keys(filterData).reduce((acc, key) => {
|
||||||
|
const initialValue = filterData[key];
|
||||||
|
if (initialValue.filterMode === "between") {
|
||||||
|
acc[key] = Yup.object().shape({
|
||||||
|
value: Yup.array()
|
||||||
|
.of(Yup.string().nullable())
|
||||||
|
.test({
|
||||||
|
test(value, ctx) {
|
||||||
|
const [start, end] = value || ["", ""];
|
||||||
|
if (
|
||||||
|
initialValue.datatype === "numeric" &&
|
||||||
|
parseInt(end, 10) <= parseInt(start, 10)
|
||||||
|
) {
|
||||||
|
return ctx.createError({
|
||||||
|
message: `مقدار وارده باید بیشتر از (${start}) باشد`,
|
||||||
|
});
|
||||||
|
} else if ((start && !end) || (!start && end)) {
|
||||||
|
return ctx.createError({
|
||||||
|
message: "این بخش را تکمیل نمایید",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
|
);
|
||||||
|
|
||||||
|
setValidationSchema(schema);
|
||||||
|
}, [filterData]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
control,
|
||||||
|
reset,
|
||||||
|
formState: { errors, isDirty, isValid },
|
||||||
|
} = useForm({
|
||||||
|
defaultValues: filterData,
|
||||||
|
resolver: yupResolver(validationSchema),
|
||||||
|
mode: "all",
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = (data) => {
|
||||||
|
setDrawerState(false);
|
||||||
|
setFilterData(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FilterHeader setDrawerState={setDrawerState} />
|
||||||
|
{Object.keys(filterData).length > 0 && (
|
||||||
|
<ScrollBox>
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<Box sx={{ px: 2, py: 3 }}>
|
||||||
|
{columns.map((column) =>
|
||||||
|
column.enableColumnFilter ? (
|
||||||
|
column.dependencyId ? (
|
||||||
|
<FilterBodyControllerWithDependency
|
||||||
|
key={column.id}
|
||||||
|
column={column}
|
||||||
|
control={control}
|
||||||
|
errors={errors}
|
||||||
|
reset={reset}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<FilterBodyController
|
||||||
|
key={column.id}
|
||||||
|
column={column}
|
||||||
|
control={control}
|
||||||
|
errors={errors}
|
||||||
|
reset={reset}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
) : null,
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
pb: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
size="large"
|
||||||
|
disabled={!isDirty || !isValid}
|
||||||
|
sx={{
|
||||||
|
px: 8,
|
||||||
|
boxShadow:
|
||||||
|
"rgba(0, 0, 0, 0.23) 0px 6px 6px, rgba(0, 0, 0, 0.19) 10px 0px 20px",
|
||||||
|
backgroundColor: "primary2",
|
||||||
|
":hover": {
|
||||||
|
backgroundColor: "primary2",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
اعمال فیلتر
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</form>
|
||||||
|
</ScrollBox>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FilterBody;
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Controller, useWatch } from "react-hook-form";
|
||||||
|
import FilterBodyField from "./FilterBodyField";
|
||||||
|
|
||||||
|
const FilterBodyController = ({ column, control, reset, errors }) => {
|
||||||
|
return (
|
||||||
|
<Controller
|
||||||
|
name={`${column.id}`}
|
||||||
|
control={control}
|
||||||
|
render={({ field: { value, name, onBlur, onChange } }) => {
|
||||||
|
return (
|
||||||
|
<FilterBodyField
|
||||||
|
column={column}
|
||||||
|
filterParameters={value}
|
||||||
|
handleChange={onChange}
|
||||||
|
handleBlur={onBlur}
|
||||||
|
dependencyFieldValue={null}
|
||||||
|
resetForm={() => reset()}
|
||||||
|
errors={errors}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default FilterBodyController;
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { Controller, useWatch } from "react-hook-form";
|
||||||
|
import FilterBodyField from "./FilterBodyField";
|
||||||
|
|
||||||
|
const FilterBodyControllerWithDependency = ({
|
||||||
|
column,
|
||||||
|
control,
|
||||||
|
reset,
|
||||||
|
errors,
|
||||||
|
}) => {
|
||||||
|
const dependencyField = useWatch({ control, name: column.dependencyId });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Controller
|
||||||
|
name={`${column.id}`}
|
||||||
|
control={control}
|
||||||
|
render={({ field: { value, name, onBlur, onChange } }) => {
|
||||||
|
return (
|
||||||
|
<FilterBodyField
|
||||||
|
column={column}
|
||||||
|
filterParameters={value}
|
||||||
|
handleChange={onChange}
|
||||||
|
handleBlur={onBlur}
|
||||||
|
dependencyFieldValue={dependencyField}
|
||||||
|
resetForm={() => reset()}
|
||||||
|
errors={errors}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default FilterBodyControllerWithDependency;
|
||||||
105
src/core/components/DataTable/filter/FilterBodyField.jsx
Normal file
105
src/core/components/DataTable/filter/FilterBodyField.jsx
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import FilterOptionList from "@/core/components/DataTable/filter/FilterOptionList";
|
||||||
|
import CustomDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/CustomDatePicker";
|
||||||
|
import CustomDatePickerRange from "@/core/components/DataTable/filter/fieldsType/CustomDate/CustomDatePickerRange";
|
||||||
|
import CustomSelect from "@/core/components/DataTable/filter/fieldsType/CustomSelect";
|
||||||
|
import CustomSelectMultiple from "@/core/components/DataTable/filter/fieldsType/CustomSelectMultiple";
|
||||||
|
import CustomTextField from "@/core/components/DataTable/filter/fieldsType/CustomTextField";
|
||||||
|
import CustomTextFieldRange from "@/core/components/DataTable/filter/fieldsType/CustomTextFieldRange";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
const columnFilterModeOptionFa = {
|
||||||
|
equals: "برابر",
|
||||||
|
notEquals: "نابرابر",
|
||||||
|
contains: "شامل",
|
||||||
|
lessThan: "کوچکتر",
|
||||||
|
greaterThan: "بزرگتر",
|
||||||
|
fuzzy: "فازی",
|
||||||
|
between: "مابین",
|
||||||
|
};
|
||||||
|
|
||||||
|
function FilterBodyField({
|
||||||
|
column,
|
||||||
|
filterParameters,
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
dependencyFieldValue,
|
||||||
|
setFieldValue,
|
||||||
|
resetForm,
|
||||||
|
errors,
|
||||||
|
}) {
|
||||||
|
const [anchorEl, setAnchorEl] = useState(null);
|
||||||
|
const defaultFilterTranslation =
|
||||||
|
columnFilterModeOptionFa[filterParameters.filterMode] ||
|
||||||
|
filterParameters.filterMode;
|
||||||
|
|
||||||
|
const handleOpenFilterBox = (event) => {
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderField = () => {
|
||||||
|
const commonProps = {
|
||||||
|
column,
|
||||||
|
filterParameters,
|
||||||
|
defaultFilterTranslation,
|
||||||
|
handleOpenFilterBox,
|
||||||
|
dependencyFieldValue,
|
||||||
|
setFieldValue,
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
errors,
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (filterParameters.datatype) {
|
||||||
|
case "numeric":
|
||||||
|
if (filterParameters.filterMode === "between") {
|
||||||
|
return <CustomTextFieldRange {...commonProps} />;
|
||||||
|
}
|
||||||
|
if (filterParameters.filterMode === "equals") {
|
||||||
|
return column.ColumnSelectComponent ? (
|
||||||
|
<column.ColumnSelectComponent {...commonProps} />
|
||||||
|
) : (
|
||||||
|
<CustomSelect {...commonProps} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "date":
|
||||||
|
if (filterParameters.filterMode === "equals") {
|
||||||
|
return <CustomDatePicker {...commonProps} />;
|
||||||
|
}
|
||||||
|
if (filterParameters.filterMode === "between") {
|
||||||
|
return <CustomDatePickerRange {...commonProps} />;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "array":
|
||||||
|
if (filterParameters.filterMode === "equals") {
|
||||||
|
return <CustomSelectMultiple {...commonProps} />;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return <CustomTextField {...commonProps} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{renderField()}
|
||||||
|
{Array.isArray(column.columnFilterModeOptions) && (
|
||||||
|
<FilterOptionList
|
||||||
|
column={column}
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
setAnchorEl={setAnchorEl}
|
||||||
|
filterParameters={filterParameters}
|
||||||
|
filterType={filterParameters.filterMode}
|
||||||
|
handleChange={handleChange}
|
||||||
|
resetForm={resetForm}
|
||||||
|
filterOption={column.columnFilterModeOptions}
|
||||||
|
columnFilterModeOptionFa={columnFilterModeOptionFa}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FilterBodyField;
|
||||||
39
src/core/components/DataTable/filter/FilterButton.jsx
Normal file
39
src/core/components/DataTable/filter/FilterButton.jsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
"use client";
|
||||||
|
import FilterAltIcon from "@mui/icons-material/FilterAlt";
|
||||||
|
import { IconButton, Tooltip, Badge } from "@mui/material";
|
||||||
|
import useDataTable from "@/lib/hooks/useDataTable";
|
||||||
|
|
||||||
|
function FilterButton({ drawerState, setDrawerState }) {
|
||||||
|
const { filterData } = useDataTable();
|
||||||
|
const isValueEmpty = (value) => {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.length === 0 || value.every((v) => v === "");
|
||||||
|
}
|
||||||
|
return value === "" || value === null || value === undefined;
|
||||||
|
};
|
||||||
|
const filteredFilterData = Object.values(filterData).filter(
|
||||||
|
(filter) => !isValueEmpty(filter.value),
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Tooltip title="فیلتر">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
setDrawerState(!drawerState);
|
||||||
|
}}
|
||||||
|
aria-label="filter table"
|
||||||
|
>
|
||||||
|
<Badge
|
||||||
|
badgeContent={filteredFilterData.length}
|
||||||
|
color="primary"
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: "top",
|
||||||
|
horizontal: "left",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FilterAltIcon />
|
||||||
|
</Badge>
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default FilterButton;
|
||||||
38
src/core/components/DataTable/filter/FilterCustom.jsx
Normal file
38
src/core/components/DataTable/filter/FilterCustom.jsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { useCallback, useState } from "react";
|
||||||
|
import FilterButton from "./FilterButton";
|
||||||
|
import { Box, Drawer } from "@mui/material";
|
||||||
|
import FilterDrawer from "../../FilterDrawer";
|
||||||
|
|
||||||
|
const FilterCustom = ({ filterData, setFilterData }) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const closeDrawer = useCallback(() => setOpen(false), []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FilterButton drawerState={open} setDrawerState={setOpen} />
|
||||||
|
<Drawer
|
||||||
|
open={open}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
sx={{
|
||||||
|
overflowY: "hidden",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
height: "100%",
|
||||||
|
zIndex: "1300",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ width: { xs: 300, sm: 450 } }}>
|
||||||
|
{open && (
|
||||||
|
<FilterDrawer
|
||||||
|
defaultValues={filterData}
|
||||||
|
setFilterData={setFilterData}
|
||||||
|
closeDrawer={closeDrawer}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Drawer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default FilterCustom;
|
||||||
35
src/core/components/DataTable/filter/FilterHeader.jsx
Normal file
35
src/core/components/DataTable/filter/FilterHeader.jsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import CancelIcon from "@mui/icons-material/Cancel";
|
||||||
|
import FilterAltIcon from "@mui/icons-material/FilterAlt";
|
||||||
|
import { Box, IconButton, Typography } from "@mui/material";
|
||||||
|
|
||||||
|
function FilterHeader({ setDrawerState }) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
px: 2,
|
||||||
|
py: 1,
|
||||||
|
backgroundColor: (theme) => theme.palette.primary.main,
|
||||||
|
boxShadow:
|
||||||
|
"rgba(0, 0, 0, 0.19) 0px 10px 20px, rgba(0, 0, 0, 0.23) 0px 6px 6px",
|
||||||
|
maxWidth: "450px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<FilterAltIcon sx={{ color: "#fff", mr: 1 }} />
|
||||||
|
<Typography variant="h6" sx={{ color: "#fff" }}>
|
||||||
|
فیلتر
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<IconButton sx={{ color: "#fff" }} onClick={() => setDrawerState(false)}>
|
||||||
|
<CancelIcon sx={{ fontSize: "1em" }} />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FilterHeader;
|
||||||
58
src/core/components/DataTable/filter/FilterOptionList.jsx
Normal file
58
src/core/components/DataTable/filter/FilterOptionList.jsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { ListItem, Menu } from "@mui/material";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
function FilterOptionList({
|
||||||
|
filterType,
|
||||||
|
filterOption,
|
||||||
|
filterParameters,
|
||||||
|
anchorEl,
|
||||||
|
columnFilterModeOptionFa,
|
||||||
|
setAnchorEl,
|
||||||
|
handleChange,
|
||||||
|
}) {
|
||||||
|
const [selectedFilter, setSelectedFilter] = useState(filterType);
|
||||||
|
|
||||||
|
const handleChangeItem = (event, index) => {
|
||||||
|
handleChange({
|
||||||
|
...filterParameters,
|
||||||
|
value: filterOption[index] === "between" ? ["", ""] : "",
|
||||||
|
filterMode: filterOption[index],
|
||||||
|
});
|
||||||
|
setSelectedFilter(filterOption[index]);
|
||||||
|
setAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseFilterBox = () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
id="simple-menu"
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
keepMounted
|
||||||
|
open={Boolean(anchorEl)}
|
||||||
|
onClose={handleCloseFilterBox}
|
||||||
|
>
|
||||||
|
{filterOption.map((option, index) => (
|
||||||
|
<ListItem
|
||||||
|
key={index}
|
||||||
|
onClick={(event) => handleChangeItem(event, index)}
|
||||||
|
selected={option === selectedFilter}
|
||||||
|
sx={{
|
||||||
|
cursor: "pointer",
|
||||||
|
width: "100px",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{columnFilterModeOptionFa[option]}
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FilterOptionList;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import React from "react";
|
||||||
|
import MuiDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker";
|
||||||
|
import { Typography } from "@mui/material";
|
||||||
|
|
||||||
|
function CustomDatePicker({
|
||||||
|
column,
|
||||||
|
filterParameters,
|
||||||
|
defaultFilterTranslation,
|
||||||
|
handleChange,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<MuiDatePicker
|
||||||
|
name={`${column.id}.value`}
|
||||||
|
value={filterParameters.value}
|
||||||
|
setFieldValue={(name, formattedDate) => {
|
||||||
|
handleChange({ ...filterParameters, value: formattedDate });
|
||||||
|
}}
|
||||||
|
placeholder={column.header}
|
||||||
|
helperText={
|
||||||
|
<Typography
|
||||||
|
variant="button"
|
||||||
|
sx={{ color: (theme) => theme.palette.primary.main }}
|
||||||
|
>
|
||||||
|
نوع فیلتر: {defaultFilterTranslation} (تاریخ)
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomDatePicker;
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { Box, Typography } from "@mui/material";
|
||||||
|
import MuiDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker";
|
||||||
|
|
||||||
|
function CustomDatePickerRange({
|
||||||
|
column,
|
||||||
|
filterParameters,
|
||||||
|
defaultFilterTranslation,
|
||||||
|
handleChange,
|
||||||
|
errors,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", gap: 1 }}>
|
||||||
|
<MuiDatePicker
|
||||||
|
label={column.header}
|
||||||
|
name={`${column.id}.value[0]`}
|
||||||
|
value={filterParameters.value[0]}
|
||||||
|
setFieldValue={(name, formattedDate) => {
|
||||||
|
handleChange({
|
||||||
|
...filterParameters,
|
||||||
|
value: [formattedDate, filterParameters.value[1]],
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
maxDate={filterParameters.value[1]}
|
||||||
|
placeholder={`از تاریخ`}
|
||||||
|
helperText={
|
||||||
|
<Typography
|
||||||
|
variant="button"
|
||||||
|
sx={{ color: (theme) => theme.palette.primary.main }}
|
||||||
|
>
|
||||||
|
نوع فیلتر: {defaultFilterTranslation} (تاریخ)
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<MuiDatePicker
|
||||||
|
label={column.header}
|
||||||
|
name={`${column.id}.value[1]`}
|
||||||
|
value={filterParameters.value[1]}
|
||||||
|
setFieldValue={(name, formattedDate) => {
|
||||||
|
handleChange({
|
||||||
|
...filterParameters,
|
||||||
|
value: [filterParameters.value[0], formattedDate],
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
minDate={filterParameters.value[0]}
|
||||||
|
placeholder={`تا تاریخ`}
|
||||||
|
helperText={
|
||||||
|
errors?.[`${column.id}`]?.value
|
||||||
|
? errors?.[`${column.id}`]?.value.message
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
error={Boolean(errors?.[`${column.id}`]?.value)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomDatePickerRange;
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { LocalizationProvider, MobileDatePicker } from "@mui/x-date-pickers";
|
||||||
|
import { AdapterDateFnsJalali } from "@mui/x-date-pickers/AdapterDateFnsJalali";
|
||||||
|
import { faIR } from "@mui/x-date-pickers/locales";
|
||||||
|
import { Box, FormHelperText, IconButton, InputAdornment } from "@mui/material";
|
||||||
|
import ClearIcon from "@mui/icons-material/Clear";
|
||||||
|
import moment from "jalali-moment";
|
||||||
|
|
||||||
|
function MuiDatePicker({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
setFieldValue,
|
||||||
|
name,
|
||||||
|
minDate,
|
||||||
|
maxDate,
|
||||||
|
helperText,
|
||||||
|
placeholder,
|
||||||
|
error,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column", my: 1 }}>
|
||||||
|
<LocalizationProvider
|
||||||
|
dateAdapter={AdapterDateFnsJalali}
|
||||||
|
localeText={
|
||||||
|
faIR.components.MuiLocalizationProvider.defaultProps.localeText
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MobileDatePicker
|
||||||
|
value={value ? new Date(value) : null}
|
||||||
|
sx={{ width: "100%" }}
|
||||||
|
id={name}
|
||||||
|
label={label}
|
||||||
|
name={name}
|
||||||
|
closeOnSelect={true}
|
||||||
|
aria-describedby="component-helper-text"
|
||||||
|
onChange={(value) => {
|
||||||
|
const date = new Date(value);
|
||||||
|
const formattedDate = moment(date)
|
||||||
|
.locale("en")
|
||||||
|
.format("YYYY-MM-DD");
|
||||||
|
setFieldValue(name, formattedDate);
|
||||||
|
}}
|
||||||
|
minDate={minDate ? new Date(minDate) : null}
|
||||||
|
maxDate={maxDate ? new Date(maxDate) : null}
|
||||||
|
slotProps={{
|
||||||
|
textField: {
|
||||||
|
size: "small",
|
||||||
|
error: error,
|
||||||
|
placeholder: placeholder,
|
||||||
|
InputProps: {
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
setFieldValue(name, "");
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
color: "#bfbfbf",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "rgba(189, 189, 189, 0.1)",
|
||||||
|
color: "#363434",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ClearIcon />
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
InputLabelProps: {
|
||||||
|
shrink: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* <FormHelperText id="component-helper-text" variant="caption" sx={{ ml: 2, color: error ? "#d32f2f" : "unset", fontWeight: 'bold' }}>
|
||||||
|
{helperText ? helperText : ""}
|
||||||
|
</FormHelperText> */}
|
||||||
|
</LocalizationProvider>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MuiDatePicker;
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
InputLabel,
|
||||||
|
MenuItem,
|
||||||
|
OutlinedInput,
|
||||||
|
Select,
|
||||||
|
} from "@mui/material";
|
||||||
|
function CustomSelect({ column, filterParameters, handleChange }) {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth sx={{ marginY: 1 }} size="small">
|
||||||
|
<InputLabel id={`label${column.id}`} shrink>
|
||||||
|
{column.header}
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId={`label${column.id}`}
|
||||||
|
id={column.id}
|
||||||
|
name={`${column.id}.value`}
|
||||||
|
value={filterParameters.value}
|
||||||
|
input={<OutlinedInput label={column.header} />}
|
||||||
|
size="small"
|
||||||
|
onChange={(e) =>
|
||||||
|
handleChange({ ...filterParameters, value: e.target.value })
|
||||||
|
}
|
||||||
|
displayEmpty
|
||||||
|
>
|
||||||
|
{column.columnSelectOption().map((option) => (
|
||||||
|
<MenuItem key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomSelect;
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormHelperText,
|
||||||
|
InputLabel,
|
||||||
|
MenuItem,
|
||||||
|
OutlinedInput,
|
||||||
|
Select,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { useCallback, useEffect } from "react";
|
||||||
|
|
||||||
|
function CustomSelectByDependency({
|
||||||
|
column,
|
||||||
|
filterParameters,
|
||||||
|
value,
|
||||||
|
defaultFilterTranslation,
|
||||||
|
handleChange,
|
||||||
|
columnSelectOption,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth sx={{ my: 1 }} size="small">
|
||||||
|
<InputLabel id={`label${column.id}`} shrink>
|
||||||
|
{column.header}
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId={`label${column.id}`}
|
||||||
|
id={column.id}
|
||||||
|
name={`${column.id}.value`}
|
||||||
|
value={value}
|
||||||
|
size="small"
|
||||||
|
input={<OutlinedInput label={column.header} />}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleChange({ ...filterParameters, value: e.target.value })
|
||||||
|
}
|
||||||
|
displayEmpty
|
||||||
|
>
|
||||||
|
{columnSelectOption.map((option) => (
|
||||||
|
<MenuItem key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomSelectByDependency;
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Chip,
|
||||||
|
FormControl,
|
||||||
|
FormHelperText,
|
||||||
|
InputLabel,
|
||||||
|
MenuItem,
|
||||||
|
OutlinedInput,
|
||||||
|
Select,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
function CustomSelectMultiple({
|
||||||
|
column,
|
||||||
|
filterParameters,
|
||||||
|
defaultFilterTranslation,
|
||||||
|
handleChange,
|
||||||
|
}) {
|
||||||
|
const columnSelectOption = column.columnSelectOption;
|
||||||
|
|
||||||
|
const getLabelForValue = (value) => {
|
||||||
|
const option = columnSelectOption.find((opt) => opt.value === value);
|
||||||
|
return option ? option.label : value;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth sx={{ marginY: 1 }} size="small">
|
||||||
|
<InputLabel htmlFor="uncontrolled-native" id={`label${column.id}`}>
|
||||||
|
{column.header}
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId={`label${column.id}`}
|
||||||
|
id={column.id}
|
||||||
|
value={filterParameters.value}
|
||||||
|
multiple
|
||||||
|
size="small"
|
||||||
|
onChange={(e) =>
|
||||||
|
handleChange({ ...filterParameters, value: e.target.value })
|
||||||
|
}
|
||||||
|
renderValue={(selected) => (
|
||||||
|
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||||
|
{selected.map((value) => (
|
||||||
|
<Chip key={value} label={getLabelForValue(value)} />
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
input={<OutlinedInput label={column.header} />}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
>
|
||||||
|
{columnSelectOption.map((option) => (
|
||||||
|
<MenuItem key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomSelectMultiple;
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { InputAdornment, TextField, Typography } from "@mui/material";
|
||||||
|
import FilterListIcon from "@mui/icons-material/FilterList";
|
||||||
|
|
||||||
|
function CustomTextField({
|
||||||
|
column,
|
||||||
|
defaultFilterTranslation,
|
||||||
|
filterParameters,
|
||||||
|
handleOpenFilterBox,
|
||||||
|
handleBlur,
|
||||||
|
handleChange,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<TextField
|
||||||
|
id={column.id}
|
||||||
|
name={column.id}
|
||||||
|
label={column.header}
|
||||||
|
value={filterParameters.value}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleChange({ ...filterParameters, value: e.target.value })
|
||||||
|
}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
sx={{ my: 1 }}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment
|
||||||
|
position="end"
|
||||||
|
sx={{ cursor: "pointer" }}
|
||||||
|
onClick={handleOpenFilterBox}
|
||||||
|
>
|
||||||
|
<FilterListIcon />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomTextField;
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { Box, InputAdornment, TextField, Typography } from "@mui/material";
|
||||||
|
import FilterListIcon from "@mui/icons-material/FilterList";
|
||||||
|
|
||||||
|
function CustomTextFieldRange({
|
||||||
|
column,
|
||||||
|
defaultFilterTranslation,
|
||||||
|
handleOpenFilterBox,
|
||||||
|
handleChange,
|
||||||
|
filterParameters,
|
||||||
|
handleBlur,
|
||||||
|
errors,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex" }}>
|
||||||
|
<TextField
|
||||||
|
id={`${column.id}.value[0]`}
|
||||||
|
name={`${column.id}.value[0]`}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleChange({
|
||||||
|
...filterParameters,
|
||||||
|
value: [e.target.value, filterParameters.value[1]],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
label={<Typography>از {column.header}</Typography>}
|
||||||
|
value={filterParameters.value[0]}
|
||||||
|
fullWidth
|
||||||
|
error={
|
||||||
|
touched?.[`${column.id}`]?.value &&
|
||||||
|
Boolean(errors?.[`${column.id}`]?.value)
|
||||||
|
}
|
||||||
|
helperText={
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
sx={{
|
||||||
|
color: (theme) => theme.palette.primary.main,
|
||||||
|
fontWeight: "bold",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
نوع فیلتر: {defaultFilterTranslation}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
sx={{ my: 1, marginRight: 1 }}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id={`${column.id}.value[1]`}
|
||||||
|
name={`${column.id}.value[1]`}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleChange({
|
||||||
|
...filterParameters,
|
||||||
|
value: [filterParameters.value[0], e.target.value],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
error={Boolean(errors?.[`${column.id}`]?.value)}
|
||||||
|
helperText={
|
||||||
|
errors?.[`${column.id}`]?.value
|
||||||
|
? errors?.[`${column.id}`]?.value.message
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
label={<Typography>تا {column.header}</Typography>}
|
||||||
|
value={filterParameters.value[1]}
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
sx={{ my: 1 }}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment
|
||||||
|
position="end"
|
||||||
|
sx={{ cursor: "pointer" }}
|
||||||
|
onClick={handleOpenFilterBox}
|
||||||
|
>
|
||||||
|
<FilterListIcon />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomTextFieldRange;
|
||||||
41
src/core/components/DataTable/filter/index.jsx
Normal file
41
src/core/components/DataTable/filter/index.jsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
"use client";
|
||||||
|
import { useState } from "react";
|
||||||
|
import FilterButton from "@/core/components/DataTable/filter/FilterButton";
|
||||||
|
import FilterBody from "@/core/components/DataTable/filter/FilterBody";
|
||||||
|
import { Box, Drawer } from "@mui/material";
|
||||||
|
|
||||||
|
function FilterColumn({ columns, user_id, page_name, table_name }) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FilterButton drawerState={open} setDrawerState={setOpen} />
|
||||||
|
<Drawer
|
||||||
|
open={open}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
sx={{
|
||||||
|
overflowY: "hidden",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
height: "100%",
|
||||||
|
zIndex: "1300",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ width: { xs: 300, sm: 450 } }}>
|
||||||
|
{open && (
|
||||||
|
<FilterBody
|
||||||
|
columns={columns}
|
||||||
|
drawerState={open}
|
||||||
|
setDrawerState={setOpen}
|
||||||
|
user_id={user_id}
|
||||||
|
page_name={page_name}
|
||||||
|
table_name={table_name}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Drawer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FilterColumn;
|
||||||
56
src/core/components/DataTable/head/TableHead.js
Normal file
56
src/core/components/DataTable/head/TableHead.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
|
import { TableHead } from "@mui/material";
|
||||||
|
import DataTable_TableHeadRow from "./TableHeadRow";
|
||||||
|
|
||||||
|
const DataTable_TableHead = ({ columnVirtualizer, table, ...rest }) => {
|
||||||
|
const {
|
||||||
|
getState,
|
||||||
|
options: {
|
||||||
|
enableStickyHeader,
|
||||||
|
layoutMode,
|
||||||
|
muiTableHeadProps,
|
||||||
|
positionToolbarAlertBanner,
|
||||||
|
},
|
||||||
|
refs: { tableHeadRef },
|
||||||
|
} = table;
|
||||||
|
const { isFullScreen, showAlertBanner } = getState();
|
||||||
|
|
||||||
|
const tableHeadProps = {
|
||||||
|
...parseFromValuesOrFunc(muiTableHeadProps, { table }),
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
|
||||||
|
const stickyHeader = enableStickyHeader || isFullScreen;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableHead
|
||||||
|
{...tableHeadProps}
|
||||||
|
ref={(ref) => {
|
||||||
|
tableHeadRef.current = ref;
|
||||||
|
if (tableHeadProps?.ref) {
|
||||||
|
// @ts-ignore
|
||||||
|
tableHeadProps.ref.current = ref;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
sx={(theme) => ({
|
||||||
|
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||||
|
opacity: 0.97,
|
||||||
|
position: stickyHeader ? "sticky" : "relative",
|
||||||
|
top: stickyHeader && layoutMode?.startsWith("grid") ? 0 : undefined,
|
||||||
|
zIndex: stickyHeader ? 2 : undefined,
|
||||||
|
...parseFromValuesOrFunc(tableHeadProps?.sx, theme),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{table.getHeaderGroups().map((headerGroup, index) => (
|
||||||
|
<DataTable_TableHeadRow
|
||||||
|
columnVirtualizer={columnVirtualizer}
|
||||||
|
headerGroup={headerGroup}
|
||||||
|
key={headerGroup.id}
|
||||||
|
table={table}
|
||||||
|
index={index}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</TableHead>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable_TableHead;
|
||||||
290
src/core/components/DataTable/head/TableHeadCell.js
Normal file
290
src/core/components/DataTable/head/TableHeadCell.js
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
import {
|
||||||
|
getCommonMRTCellStyles,
|
||||||
|
parseFromValuesOrFunc,
|
||||||
|
} from "@/core/utils/utils";
|
||||||
|
import { useTheme } from "@emotion/react";
|
||||||
|
import { Box, TableCell } from "@mui/material";
|
||||||
|
import { MRT_TableHeadCellSortLabel } from "material-react-table";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
const DataTable_TableHeadCell = ({
|
||||||
|
columnVirtualizer,
|
||||||
|
header,
|
||||||
|
staticColumnIndex,
|
||||||
|
backgroundColor,
|
||||||
|
table,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const {
|
||||||
|
getState,
|
||||||
|
options: {
|
||||||
|
columnResizeDirection,
|
||||||
|
columnResizeMode,
|
||||||
|
enableColumnActions,
|
||||||
|
enableColumnDragging,
|
||||||
|
enableColumnOrdering,
|
||||||
|
enableColumnPinning,
|
||||||
|
enableGrouping,
|
||||||
|
enableMultiSort,
|
||||||
|
layoutMode,
|
||||||
|
mrtTheme: { draggingBorderColor },
|
||||||
|
muiTableHeadCellProps,
|
||||||
|
},
|
||||||
|
refs: { tableHeadCellRefs },
|
||||||
|
setHoveredColumn,
|
||||||
|
} = table;
|
||||||
|
const {
|
||||||
|
columnSizingInfo,
|
||||||
|
density,
|
||||||
|
draggingColumn,
|
||||||
|
grouping,
|
||||||
|
hoveredColumn,
|
||||||
|
showColumnFilters,
|
||||||
|
} = getState();
|
||||||
|
const { column } = header;
|
||||||
|
const { columnDef } = column;
|
||||||
|
const { columnDefType } = columnDef;
|
||||||
|
|
||||||
|
const tableCellProps = {
|
||||||
|
...parseFromValuesOrFunc(muiTableHeadCellProps, { column, table }),
|
||||||
|
...parseFromValuesOrFunc(columnDef.muiTableHeadCellProps, {
|
||||||
|
column,
|
||||||
|
table,
|
||||||
|
}),
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
|
||||||
|
const isColumnPinned =
|
||||||
|
enableColumnPinning &&
|
||||||
|
columnDef.columnDefType !== "group" &&
|
||||||
|
column.getIsPinned();
|
||||||
|
|
||||||
|
const showColumnActions =
|
||||||
|
(enableColumnActions || columnDef.enableColumnActions) &&
|
||||||
|
columnDef.enableColumnActions !== false;
|
||||||
|
|
||||||
|
const showDragHandle =
|
||||||
|
enableColumnDragging !== false &&
|
||||||
|
columnDef.enableColumnDragging !== false &&
|
||||||
|
(enableColumnDragging ||
|
||||||
|
(enableColumnOrdering && columnDef.enableColumnOrdering !== false) ||
|
||||||
|
(enableGrouping &&
|
||||||
|
columnDef.enableGrouping !== false &&
|
||||||
|
!grouping.includes(column.id)));
|
||||||
|
|
||||||
|
const headerPL = useMemo(() => {
|
||||||
|
let pl = 0;
|
||||||
|
if (column.getCanSort()) pl += 1;
|
||||||
|
if (showColumnActions) pl += 1.75;
|
||||||
|
if (showDragHandle) pl += 1.5;
|
||||||
|
return pl;
|
||||||
|
}, [showColumnActions, showDragHandle]);
|
||||||
|
|
||||||
|
const draggingBorders = useMemo(() => {
|
||||||
|
const showResizeBorder =
|
||||||
|
columnSizingInfo.isResizingColumn === column.id &&
|
||||||
|
columnResizeMode === "onChange" &&
|
||||||
|
!header.subHeaders.length;
|
||||||
|
|
||||||
|
const borderStyle = showResizeBorder
|
||||||
|
? `2px solid ${draggingBorderColor} !important`
|
||||||
|
: draggingColumn?.id === column.id
|
||||||
|
? `1px dashed ${theme.palette.grey[500]}`
|
||||||
|
: hoveredColumn?.id === column.id
|
||||||
|
? `2px dashed ${draggingBorderColor}`
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (showResizeBorder) {
|
||||||
|
return columnResizeDirection === "ltr"
|
||||||
|
? { borderRight: borderStyle }
|
||||||
|
: { borderLeft: borderStyle };
|
||||||
|
}
|
||||||
|
const draggingBorders = borderStyle
|
||||||
|
? {
|
||||||
|
borderLeft: borderStyle,
|
||||||
|
borderRight: borderStyle,
|
||||||
|
borderTop: borderStyle,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return draggingBorders;
|
||||||
|
}, [draggingColumn, hoveredColumn, columnSizingInfo.isResizingColumn]);
|
||||||
|
|
||||||
|
const handleDragEnter = (_e) => {
|
||||||
|
if (enableGrouping && hoveredColumn?.id === "drop-zone") {
|
||||||
|
setHoveredColumn(null);
|
||||||
|
}
|
||||||
|
if (enableColumnOrdering && draggingColumn && columnDefType !== "group") {
|
||||||
|
setHoveredColumn(
|
||||||
|
columnDef.enableColumnOrdering !== false ? column : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragOver = (e) => {
|
||||||
|
if (columnDef.enableColumnOrdering !== false) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const HeaderElement =
|
||||||
|
parseFromValuesOrFunc(columnDef.Header, {
|
||||||
|
column,
|
||||||
|
header,
|
||||||
|
table,
|
||||||
|
}) ?? columnDef.header;
|
||||||
|
|
||||||
|
const columnRelativeDepth = header.depth - column.depth;
|
||||||
|
|
||||||
|
if (columnRelativeDepth > 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let rowSpan = 1;
|
||||||
|
if (header.isPlaceholder) {
|
||||||
|
const leafs = header.getLeafHeaders();
|
||||||
|
rowSpan = leafs[leafs.length - 1].depth - header.depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableCell
|
||||||
|
align={
|
||||||
|
columnDefType === "group"
|
||||||
|
? "center"
|
||||||
|
: theme.direction === "rtl"
|
||||||
|
? "right"
|
||||||
|
: "left"
|
||||||
|
}
|
||||||
|
colSpan={header.colSpan}
|
||||||
|
rowSpan={rowSpan}
|
||||||
|
data-index={staticColumnIndex}
|
||||||
|
data-pinned={!!isColumnPinned || undefined}
|
||||||
|
onDragEnter={handleDragEnter}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
ref={(node) => {
|
||||||
|
if (node) {
|
||||||
|
tableHeadCellRefs.current[column.id] = node;
|
||||||
|
if (columnDefType !== "group") {
|
||||||
|
columnVirtualizer?.measureElement?.(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
{...tableCellProps}
|
||||||
|
sx={(theme) => ({
|
||||||
|
"& :hover": {
|
||||||
|
".MuiButtonBase-root": {
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
flexDirection: layoutMode?.startsWith("grid") ? "column" : undefined,
|
||||||
|
fontWeight: "bold",
|
||||||
|
overflow: "visible",
|
||||||
|
p:
|
||||||
|
density === "compact"
|
||||||
|
? "0.5rem"
|
||||||
|
: density === "comfortable"
|
||||||
|
? columnDefType === "display"
|
||||||
|
? "0.75rem"
|
||||||
|
: "1rem"
|
||||||
|
: columnDefType === "display"
|
||||||
|
? "1rem 1.25rem"
|
||||||
|
: "1.5rem",
|
||||||
|
pb:
|
||||||
|
columnDefType === "display"
|
||||||
|
? 0
|
||||||
|
: showColumnFilters || density === "compact"
|
||||||
|
? "0.4rem"
|
||||||
|
: "0.6rem",
|
||||||
|
pt:
|
||||||
|
columnDefType === "group" || density === "compact"
|
||||||
|
? "0.25rem"
|
||||||
|
: density === "comfortable"
|
||||||
|
? ".75rem"
|
||||||
|
: "1.25rem",
|
||||||
|
userSelect: enableMultiSort && column.getCanSort() ? "none" : undefined,
|
||||||
|
verticalAlign: "middle",
|
||||||
|
...getCommonMRTCellStyles({
|
||||||
|
column,
|
||||||
|
header,
|
||||||
|
table,
|
||||||
|
tableCellProps,
|
||||||
|
theme,
|
||||||
|
}),
|
||||||
|
...draggingBorders,
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{tableCellProps.children ?? (
|
||||||
|
<Box
|
||||||
|
className="Mui-TableHeadCell-Content"
|
||||||
|
sx={{
|
||||||
|
alignItems: "center",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection:
|
||||||
|
tableCellProps?.align === "right" ? "row-reverse" : "row",
|
||||||
|
justifyContent:
|
||||||
|
columnDefType === "group" || tableCellProps?.align === "center"
|
||||||
|
? "center"
|
||||||
|
: column.getCanResize()
|
||||||
|
? "space-between"
|
||||||
|
: "flex-start",
|
||||||
|
position: "relative",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
className="Mui-TableHeadCell-Content-Labels"
|
||||||
|
onClick={column.getToggleSortingHandler()}
|
||||||
|
sx={{
|
||||||
|
alignItems: "center",
|
||||||
|
cursor:
|
||||||
|
column.getCanSort() && columnDefType !== "group"
|
||||||
|
? "pointer"
|
||||||
|
: undefined,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection:
|
||||||
|
tableCellProps?.align === "right" ? "row-reverse" : "row",
|
||||||
|
overflow: columnDefType === "data" ? "hidden" : undefined,
|
||||||
|
pl:
|
||||||
|
tableCellProps?.align === "center"
|
||||||
|
? `${headerPL}rem`
|
||||||
|
: undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
className="Mui-TableHeadCell-Content-Wrapper"
|
||||||
|
sx={{
|
||||||
|
"&:hover": {
|
||||||
|
textOverflow: "clip",
|
||||||
|
},
|
||||||
|
minWidth: `${Math.min(columnDef.header?.length ?? 0, 4)}ch`,
|
||||||
|
overflow: columnDefType === "data" ? "hidden" : undefined,
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
textAlign: "start",
|
||||||
|
color: "#fff",
|
||||||
|
fontWeight: "400",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{HeaderElement}
|
||||||
|
</Box>
|
||||||
|
{column.getCanSort() && columnDefType !== "group" && (
|
||||||
|
<MRT_TableHeadCellSortLabel
|
||||||
|
sx={{
|
||||||
|
width: 20,
|
||||||
|
"& .MuiTableSortLabel-icon": {
|
||||||
|
color: `#fff !important`,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
header={header}
|
||||||
|
table={table}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable_TableHeadCell;
|
||||||
69
src/core/components/DataTable/head/TableHeadRow.js
Normal file
69
src/core/components/DataTable/head/TableHeadRow.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
|
import { TableRow, useTheme } from "@mui/material";
|
||||||
|
import DataTable_TableHeadCell from "./TableHeadCell";
|
||||||
|
|
||||||
|
const DataTable_TableHeadRow = ({
|
||||||
|
columnVirtualizer,
|
||||||
|
headerGroup,
|
||||||
|
table,
|
||||||
|
index, // Add index prop
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const { palette } = useTheme();
|
||||||
|
|
||||||
|
const {
|
||||||
|
options: { enableStickyHeader, layoutMode, muiTableHeadRowProps },
|
||||||
|
} = table;
|
||||||
|
const { virtualColumns, virtualPaddingLeft, virtualPaddingRight } =
|
||||||
|
columnVirtualizer ?? {};
|
||||||
|
|
||||||
|
const backgroundColor =
|
||||||
|
index % 2 === 0 ? palette.primary.main : palette.secondary.main;
|
||||||
|
|
||||||
|
const tableRowProps = {
|
||||||
|
...parseFromValuesOrFunc(muiTableHeadRowProps, {
|
||||||
|
headerGroup,
|
||||||
|
table,
|
||||||
|
}),
|
||||||
|
...rest,
|
||||||
|
sx: (theme) => ({
|
||||||
|
// Access theme from the sx function
|
||||||
|
...(parseFromValuesOrFunc(muiTableHeadRowProps?.sx, theme) || {}),
|
||||||
|
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
||||||
|
position:
|
||||||
|
enableStickyHeader && layoutMode === "semantic" ? "sticky" : "relative",
|
||||||
|
top: 0,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow {...tableRowProps}>
|
||||||
|
{virtualPaddingLeft && (
|
||||||
|
<th style={{ display: "flex", width: virtualPaddingLeft }} />
|
||||||
|
)}
|
||||||
|
{(virtualColumns ?? headerGroup.headers).map(
|
||||||
|
(headerOrVirtualHeader, staticColumnIndex) => {
|
||||||
|
const header = columnVirtualizer
|
||||||
|
? headerGroup.headers[headerOrVirtualHeader.index]
|
||||||
|
: headerOrVirtualHeader;
|
||||||
|
|
||||||
|
return header ? (
|
||||||
|
<DataTable_TableHeadCell
|
||||||
|
columnVirtualizer={columnVirtualizer}
|
||||||
|
header={header}
|
||||||
|
backgroundColor={backgroundColor}
|
||||||
|
key={header.id}
|
||||||
|
staticColumnIndex={staticColumnIndex}
|
||||||
|
table={table}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
{virtualPaddingRight && (
|
||||||
|
<th style={{ display: "flex", width: virtualPaddingRight }} />
|
||||||
|
)}
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DataTable_TableHeadRow;
|
||||||
43
src/core/components/DataTable/hide/HideBody.jsx
Normal file
43
src/core/components/DataTable/hide/HideBody.jsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Box, Drawer, styled } from "@mui/material";
|
||||||
|
import HideBodyField from "@/core/components/DataTable/hide/HideBodyField";
|
||||||
|
import HideHeader from "@/core/components/DataTable/hide/HideHeader";
|
||||||
|
import useDataTable from "@/lib/hooks/useDataTable";
|
||||||
|
import HideOrShowAll from "@/core/components/DataTable/hide/HideOrShowAll";
|
||||||
|
import ScrollBox from "../../ScrollBox";
|
||||||
|
|
||||||
|
function HideBody({ columns, drawerState, setDrawerState }) {
|
||||||
|
const { hideData, setHideData } = useDataTable();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
open={drawerState}
|
||||||
|
onClose={() => setDrawerState(false)}
|
||||||
|
sx={{
|
||||||
|
overflowY: "hidden",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
height: "100%",
|
||||||
|
zIndex: "1300",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<HideHeader setDrawerState={setDrawerState} />
|
||||||
|
<HideOrShowAll hideData={hideData} setHideData={setHideData} />
|
||||||
|
<ScrollBox>
|
||||||
|
<Box sx={{ px: 1, py: 2, display: "flex", flexDirection: "column" }}>
|
||||||
|
{columns.map((column) => (
|
||||||
|
<HideBodyField
|
||||||
|
key={column.id}
|
||||||
|
column={column}
|
||||||
|
hideData={hideData}
|
||||||
|
setHideData={setHideData}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</ScrollBox>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HideBody;
|
||||||
80
src/core/components/DataTable/hide/HideBodyField.jsx
Normal file
80
src/core/components/DataTable/hide/HideBodyField.jsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { alpha, Box, Checkbox, styled, Typography } from "@mui/material";
|
||||||
|
import { SimpleTreeView } from "@mui/x-tree-view";
|
||||||
|
import { TreeItem, treeItemClasses } from "@mui/x-tree-view/TreeItem";
|
||||||
|
|
||||||
|
const CustomTreeItem = styled(TreeItem)(({ theme }) => ({
|
||||||
|
[`& .${treeItemClasses.content}`]: {
|
||||||
|
padding: theme.spacing(0.5, 0.5),
|
||||||
|
margin: theme.spacing(0.2, 0),
|
||||||
|
gap: 0,
|
||||||
|
},
|
||||||
|
[`& .${treeItemClasses.iconContainer}`]: {
|
||||||
|
"& .close": {
|
||||||
|
opacity: 0.3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[`& .${treeItemClasses.groupTransition}`]: {
|
||||||
|
marginLeft: 16,
|
||||||
|
paddingLeft: 16,
|
||||||
|
borderLeft: `1px dashed ${alpha(theme.palette.text.primary, 0.4)}`,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
function HideBodyField({ column, hideData, setHideData }) {
|
||||||
|
const handleCheckboxChange = () => {
|
||||||
|
setHideData((prevData) => {
|
||||||
|
const updateHideData = (data, id) => {
|
||||||
|
if (data.hasOwnProperty(id)) {
|
||||||
|
return { ...data, [id]: !data[id] };
|
||||||
|
}
|
||||||
|
const updatedData = { ...data };
|
||||||
|
for (const key in data) {
|
||||||
|
if (data[key] && typeof data[key] === "object") {
|
||||||
|
updatedData[key] = updateHideData(data[key], id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return updatedData;
|
||||||
|
};
|
||||||
|
|
||||||
|
return updateHideData(prevData, column.id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const labelContent = (() => {
|
||||||
|
if (typeof hideData[column.id] === "boolean") {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<Checkbox
|
||||||
|
checked={hideData[column.id]}
|
||||||
|
onChange={handleCheckboxChange}
|
||||||
|
name={column.id}
|
||||||
|
/>
|
||||||
|
<Typography variant="subtitle1">{column.header}</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center", p: 1 }}>
|
||||||
|
<Typography variant="subtitle1">{column.header}</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SimpleTreeView disableSelection>
|
||||||
|
<CustomTreeItem itemId={column.id} label={labelContent}>
|
||||||
|
{column.columns?.map((subColumn) => (
|
||||||
|
<HideBodyField
|
||||||
|
key={subColumn.id}
|
||||||
|
column={subColumn}
|
||||||
|
hideData={hideData[column.id]}
|
||||||
|
setHideData={setHideData}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</CustomTreeItem>
|
||||||
|
</SimpleTreeView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HideBodyField;
|
||||||
37
src/core/components/DataTable/hide/HideButton.jsx
Normal file
37
src/core/components/DataTable/hide/HideButton.jsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
"use client";
|
||||||
|
import ViewColumnIcon from "@mui/icons-material/ViewColumn";
|
||||||
|
import { IconButton, Tooltip, Badge } from "@mui/material";
|
||||||
|
import useDataTable from "@/lib/hooks/useDataTable";
|
||||||
|
import { flattenObjectOfObjects } from "@/core/utils/flattenObjectOfObjects";
|
||||||
|
|
||||||
|
function HideButton({ drawerState, setDrawerState }) {
|
||||||
|
const { hideData } = useDataTable();
|
||||||
|
const flattenHideData = flattenObjectOfObjects(hideData);
|
||||||
|
const falseCount = Object.values(flattenHideData).filter(
|
||||||
|
(value) => value === false,
|
||||||
|
).length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip title="نمایش/مخفی کردن ستون ها">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
setDrawerState(!drawerState);
|
||||||
|
}}
|
||||||
|
aria-label="hide table column"
|
||||||
|
>
|
||||||
|
<Badge
|
||||||
|
badgeContent={falseCount}
|
||||||
|
color="primary"
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: "top",
|
||||||
|
horizontal: "left",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ViewColumnIcon />
|
||||||
|
</Badge>
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HideButton;
|
||||||
35
src/core/components/DataTable/hide/HideHeader.jsx
Normal file
35
src/core/components/DataTable/hide/HideHeader.jsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Box, IconButton, Typography } from "@mui/material";
|
||||||
|
import CancelIcon from "@mui/icons-material/Cancel";
|
||||||
|
import ViewColumnIcon from "@mui/icons-material/ViewColumn";
|
||||||
|
|
||||||
|
function FilterHeader({ setDrawerState }) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
px: 2,
|
||||||
|
py: 1,
|
||||||
|
backgroundColor: "#155175",
|
||||||
|
boxShadow:
|
||||||
|
"rgba(0, 0, 0, 0.19) 0px 10px 20px, rgba(0, 0, 0, 0.23) 0px 6px 6px",
|
||||||
|
maxWidth: "450px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<ViewColumnIcon sx={{ color: "#fff", mr: 1 }} />
|
||||||
|
<Typography variant="h6" sx={{ color: "#fff" }}>
|
||||||
|
نمایش/مخفی کردن ستون ها
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<IconButton sx={{ color: "#fff" }} onClick={() => setDrawerState(false)}>
|
||||||
|
<CancelIcon sx={{ fontSize: "1em" }} />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FilterHeader;
|
||||||
70
src/core/components/DataTable/hide/HideOrShowAll.jsx
Normal file
70
src/core/components/DataTable/hide/HideOrShowAll.jsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Box, Button } from "@mui/material";
|
||||||
|
import VisibilityIcon from "@mui/icons-material/Visibility";
|
||||||
|
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
|
||||||
|
|
||||||
|
const setAllValues = (obj, value) => {
|
||||||
|
const result = {};
|
||||||
|
for (const key in obj) {
|
||||||
|
if (typeof obj[key] === "object" && obj[key] !== null) {
|
||||||
|
result[key] = setAllValues(obj[key], value);
|
||||||
|
} else {
|
||||||
|
result[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const allValuesAre = (obj, value) => {
|
||||||
|
for (const key in obj) {
|
||||||
|
if (typeof obj[key] === "object" && obj[key] !== null) {
|
||||||
|
if (!allValuesAre(obj[key], value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (obj[key] !== value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
function HideOrShowAll({ hideData, setHideData }) {
|
||||||
|
const handleShowAll = () => {
|
||||||
|
const newHideData = setAllValues(hideData, true);
|
||||||
|
setHideData(newHideData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleHideAll = () => {
|
||||||
|
const newHideData = setAllValues(hideData, false);
|
||||||
|
setHideData(newHideData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const allHidden = allValuesAre(hideData, false);
|
||||||
|
const allVisible = allValuesAre(hideData, true);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ my: 1, display: "flex" }}>
|
||||||
|
<Button
|
||||||
|
sx={{ flex: 1 }}
|
||||||
|
color="info"
|
||||||
|
disabled={allVisible}
|
||||||
|
startIcon={<VisibilityIcon />}
|
||||||
|
onClick={handleShowAll}
|
||||||
|
>
|
||||||
|
نمایش همه
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
sx={{ flex: 1 }}
|
||||||
|
color="error"
|
||||||
|
disabled={allHidden}
|
||||||
|
startIcon={<VisibilityOffIcon />}
|
||||||
|
onClick={handleHideAll}
|
||||||
|
>
|
||||||
|
مخفی کردن همه
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HideOrShowAll;
|
||||||
24
src/core/components/DataTable/hide/index.jsx
Normal file
24
src/core/components/DataTable/hide/index.jsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import HideButton from "@/core/components/DataTable/hide/HideButton";
|
||||||
|
import HideBody from "@/core/components/DataTable/hide/HideBody";
|
||||||
|
|
||||||
|
function HideColumn({ columns, user_id, page_name, table_name }) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<HideButton drawerState={open} setDrawerState={setOpen} />
|
||||||
|
<HideBody
|
||||||
|
columns={columns}
|
||||||
|
drawerState={open}
|
||||||
|
setDrawerState={setOpen}
|
||||||
|
user_id={user_id}
|
||||||
|
page_name={page_name}
|
||||||
|
table_name={table_name}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HideColumn;
|
||||||
18
src/core/components/DataTable/index.js
Normal file
18
src/core/components/DataTable/index.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import DataTable_Main from "./Main";
|
||||||
|
import DataTableProvider from "@/lib/contexts/DataTable";
|
||||||
|
|
||||||
|
const DataTable = (props) => {
|
||||||
|
return (
|
||||||
|
<DataTableProvider
|
||||||
|
user_id={props.user_id}
|
||||||
|
page_name={props.page_name}
|
||||||
|
table_name={props.table_name}
|
||||||
|
columns={props.columns}
|
||||||
|
initialSort={props.sorting}
|
||||||
|
>
|
||||||
|
<DataTable_Main {...props} />
|
||||||
|
</DataTableProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DataTable;
|
||||||
92
src/core/components/DataTable/localization/fa/datatable.js
Normal file
92
src/core/components/DataTable/localization/fa/datatable.js
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
export const FA_DATATABLE_LOCALIZATION = {
|
||||||
|
actions: "عملیات",
|
||||||
|
and: "و",
|
||||||
|
cancel: "لغو",
|
||||||
|
changeFilterMode: "تغییر حالت فیلتر",
|
||||||
|
changeSearchMode: "تغییر حالت جستجو",
|
||||||
|
clearFilter: "پاک کردن فیلتر",
|
||||||
|
clearSearch: "پاک کردن جستجو",
|
||||||
|
clearSort: "پاک کردن مرتب سازی",
|
||||||
|
clickToCopy: "کلیک برای کپی",
|
||||||
|
collapse: "جمع شدن",
|
||||||
|
collapseAll: "جمع شدن همه",
|
||||||
|
columnActions: "عملیات ستون",
|
||||||
|
copiedToClipboard: "کپی شد",
|
||||||
|
dropToGroupBy: "رها کردن برای گروه بندی بر اساس {column}",
|
||||||
|
edit: "ویرایش",
|
||||||
|
expand: "باز شدن",
|
||||||
|
expandAll: "باز شدن همه",
|
||||||
|
filterArrIncludes: "شامل",
|
||||||
|
filterArrIncludesAll: "شامل همه",
|
||||||
|
filterArrIncludesSome: "شامل",
|
||||||
|
filterBetween: "میان",
|
||||||
|
filterBetweenInclusive: "میان با احتساب هر دو",
|
||||||
|
filterByColumn: "فیلتر بر اساس {column}",
|
||||||
|
filterContains: "شامل",
|
||||||
|
filterEmpty: "خالی",
|
||||||
|
filterEndsWith: "به پایان میرسد با",
|
||||||
|
filterEquals: "برابر",
|
||||||
|
filterEqualsString: "برابر",
|
||||||
|
filterFuzzy: "نزدیک",
|
||||||
|
filterGreaterThan: "بزرگتر از",
|
||||||
|
filterGreaterThanOrEqualTo: "بزرگتر یا مساوی",
|
||||||
|
filterInNumberRange: "میان",
|
||||||
|
filterIncludesString: "شامل",
|
||||||
|
filterIncludesStringSensitive: "شامل",
|
||||||
|
filterLessThan: "کوچکتر از",
|
||||||
|
filterLessThanOrEqualTo: "کوچکتر یا مساوی",
|
||||||
|
filterMode: "حالت فیلتر: {filterType}",
|
||||||
|
filterNotEmpty: "غیر خالی",
|
||||||
|
filterNotEquals: "نا برابر",
|
||||||
|
filterStartsWith: "شروع میشود با",
|
||||||
|
filterWeakEquals: "برابر",
|
||||||
|
filteringByColumn: "فیلتر بر اساس {column} - {filterType} {filterValue}",
|
||||||
|
goToFirstPage: "رفتن به صفحه اول",
|
||||||
|
goToLastPage: "رفتن به صفحه آخر",
|
||||||
|
goToNextPage: "رفتن به صفحه بعدی",
|
||||||
|
goToPreviousPage: "رفتن به صفحه قبلی",
|
||||||
|
grab: "گرفتن",
|
||||||
|
groupByColumn: "گروه بندی بر اساس {column}",
|
||||||
|
groupedBy: "گروه بندی شده بر اساس",
|
||||||
|
hideAll: "پنهان کردن همه",
|
||||||
|
hideColumn: "پنهان کردن ستون {column}",
|
||||||
|
max: "حداکثر",
|
||||||
|
min: "حداقل",
|
||||||
|
move: "انتقال",
|
||||||
|
noRecordsToDisplay: "هیچ رکوردی برای نمایش وجود ندارد",
|
||||||
|
noResultsFound: "نتیجهای یافت نشد",
|
||||||
|
of: "از",
|
||||||
|
or: "یا",
|
||||||
|
pinToLeft: "چسباندن به سمت چپ",
|
||||||
|
pinToRight: "چسباندن به سمت راست",
|
||||||
|
resetColumnSize: "بازنشانی اندازه ستون",
|
||||||
|
resetOrder: "بازنشانی ترتیب",
|
||||||
|
rowActions: "عملیات ردیف",
|
||||||
|
rowNumber: "#",
|
||||||
|
rowNumbers: "شماره ردیف",
|
||||||
|
rowsPerPage: "ردیف در هر صفحه",
|
||||||
|
save: "ذخیره",
|
||||||
|
search: "جستجو",
|
||||||
|
selectedCountOfRowCountRowsSelected:
|
||||||
|
"{selectedCount} از {rowCount} ردیف انتخاب شده",
|
||||||
|
select: "انتخاب",
|
||||||
|
showAll: "نمایش همه",
|
||||||
|
showAllColumns: "نمایش همه ستونها",
|
||||||
|
showHideColumns: "نمایش/مخفی کردن ستونها",
|
||||||
|
showHideFilters: "نمایش/مخفی کردن فیلترها",
|
||||||
|
showHideSearch: "نمایش/مخفی کردن جستجو",
|
||||||
|
sortByColumnAsc: "مرتب سازی بر اساس {column} صعودی",
|
||||||
|
sortByColumnDesc: "مرتب سازی بر اساس {column} نزولی",
|
||||||
|
sortedByColumnAsc: "مرتب شده بر اساس {column} صعودی",
|
||||||
|
sortedByColumnDesc: "مرتب شده بر اساس {column} نزولی",
|
||||||
|
thenBy: "، سپس بر اساس ",
|
||||||
|
toggleDensity: "تغییر تراکم",
|
||||||
|
toggleFullScreen: "تغییر حالت تمام صفحه",
|
||||||
|
toggleSelectAll: "انتخاب/عدم انتخاب همه",
|
||||||
|
toggleSelectRow: "انتخاب/عدم انتخاب ردیف",
|
||||||
|
toggleVisibility: "تغییر پیدا/پنهان",
|
||||||
|
ungroupByColumn: "لغو گروه بندی بر اساس {column}",
|
||||||
|
unpin: "رها کردن",
|
||||||
|
unpinAll: "رها کردن همه",
|
||||||
|
unsorted: "بدون مرتب سازی",
|
||||||
|
};
|
||||||
49
src/core/components/DataTable/menus/ActionMenuItem.js
Normal file
49
src/core/components/DataTable/menus/ActionMenuItem.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { Box, IconButton, ListItemIcon, MenuItem } from "@mui/material";
|
||||||
|
|
||||||
|
const DataTable_ActionMenuItem = ({
|
||||||
|
icon,
|
||||||
|
label,
|
||||||
|
onOpenSubMenu,
|
||||||
|
table,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
options: {
|
||||||
|
icons: { ArrowRightIcon },
|
||||||
|
},
|
||||||
|
} = table;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
sx={{
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
minWidth: "120px",
|
||||||
|
my: 0,
|
||||||
|
py: "6px",
|
||||||
|
}}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
alignItems: "center",
|
||||||
|
display: "flex",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemIcon>{icon}</ListItemIcon>
|
||||||
|
{label}
|
||||||
|
</Box>
|
||||||
|
{onOpenSubMenu && (
|
||||||
|
<IconButton
|
||||||
|
onClick={onOpenSubMenu}
|
||||||
|
onMouseEnter={onOpenSubMenu}
|
||||||
|
size="small"
|
||||||
|
sx={{ p: 0 }}
|
||||||
|
>
|
||||||
|
<ArrowRightIcon />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable_ActionMenuItem;
|
||||||
96
src/core/components/DataTable/menus/CellActionMenu.js
Normal file
96
src/core/components/DataTable/menus/CellActionMenu.js
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
|
import { Menu } from "@mui/material";
|
||||||
|
import DataTable_ActionMenuItem from "./ActionMenuItem";
|
||||||
|
|
||||||
|
const DataTable_CellActionMenu = ({ table, ...rest }) => {
|
||||||
|
const {
|
||||||
|
getState,
|
||||||
|
options: {
|
||||||
|
editDisplayMode,
|
||||||
|
enableClickToCopy,
|
||||||
|
enableEditing,
|
||||||
|
icons: { ContentCopy, EditIcon },
|
||||||
|
localization,
|
||||||
|
mrtTheme: { menuBackgroundColor },
|
||||||
|
renderCellActionMenuItems,
|
||||||
|
},
|
||||||
|
refs: { actionCellRef },
|
||||||
|
} = table;
|
||||||
|
const { actionCell, density } = getState();
|
||||||
|
const cell = actionCell || null;
|
||||||
|
const { row } = cell;
|
||||||
|
const { column } = cell;
|
||||||
|
const { columnDef } = column;
|
||||||
|
|
||||||
|
const handleClose = (event) => {
|
||||||
|
event?.stopPropagation();
|
||||||
|
table.setActionCell(null);
|
||||||
|
actionCellRef.current = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const internalMenuItems = [
|
||||||
|
(parseFromValuesOrFunc(enableClickToCopy, cell) === "context-menu" ||
|
||||||
|
parseFromValuesOrFunc(columnDef.enableClickToCopy, cell) ===
|
||||||
|
"context-menu") && (
|
||||||
|
<DataTable_ActionMenuItem
|
||||||
|
icon={<ContentCopy />}
|
||||||
|
key={"mrt-copy"}
|
||||||
|
label={localization.copy}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
navigator.clipboard.writeText(cell.getValue());
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
table={table}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
parseFromValuesOrFunc(enableEditing, row) && editDisplayMode === "cell" && (
|
||||||
|
<DataTable_ActionMenuItem
|
||||||
|
icon={<EditIcon />}
|
||||||
|
key={"mrt-edit"}
|
||||||
|
label={localization.edit}
|
||||||
|
onClick={() => {
|
||||||
|
openEditingCell({ cell, table });
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
table={table}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
const renderActionProps = {
|
||||||
|
cell,
|
||||||
|
closeMenu: handleClose,
|
||||||
|
column,
|
||||||
|
internalMenuItems,
|
||||||
|
row,
|
||||||
|
table,
|
||||||
|
};
|
||||||
|
|
||||||
|
const menuItems =
|
||||||
|
columnDef.renderCellActionMenuItems?.(renderActionProps) ??
|
||||||
|
renderCellActionMenuItems?.(renderActionProps);
|
||||||
|
|
||||||
|
return (
|
||||||
|
(!!menuItems?.length || !!internalMenuItems?.length) && (
|
||||||
|
<Menu
|
||||||
|
MenuListProps={{
|
||||||
|
dense: density === "compact",
|
||||||
|
sx: {
|
||||||
|
backgroundColor: menuBackgroundColor,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
anchorEl={actionCellRef.current}
|
||||||
|
disableScrollLock
|
||||||
|
onClick={(event) => event.stopPropagation()}
|
||||||
|
onClose={handleClose}
|
||||||
|
open={!!cell}
|
||||||
|
transformOrigin={{ horizontal: -100, vertical: 8 }}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{menuItems ?? internalMenuItems}
|
||||||
|
</Menu>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable_CellActionMenu;
|
||||||
36
src/core/components/DataTable/reset/ResetStorage.jsx
Normal file
36
src/core/components/DataTable/reset/ResetStorage.jsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
"use client";
|
||||||
|
import { IconButton, Tooltip, Badge } from "@mui/material";
|
||||||
|
import AutorenewIcon from "@mui/icons-material/Autorenew";
|
||||||
|
import useTableSetting from "@/lib/hooks/useTableSetting";
|
||||||
|
import useDataTable from "@/lib/hooks/useDataTable";
|
||||||
|
|
||||||
|
function ResetStorage({ user_id, page_name, table_name }) {
|
||||||
|
const { resetAction } = useTableSetting();
|
||||||
|
const { isAnyDirty } = useDataTable();
|
||||||
|
const reset = () => {
|
||||||
|
resetAction(user_id, page_name, table_name);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip title="بازنشانی اطلاعات">
|
||||||
|
<IconButton onClick={reset} aria-label="reset table data">
|
||||||
|
{isAnyDirty ? (
|
||||||
|
<Badge
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: "top",
|
||||||
|
horizontal: "left",
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
variant="dot"
|
||||||
|
>
|
||||||
|
<AutorenewIcon />
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
|
<AutorenewIcon />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ResetStorage;
|
||||||
102
src/core/components/DataTable/table/Paper.js
Normal file
102
src/core/components/DataTable/table/Paper.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
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,
|
||||||
|
table_name,
|
||||||
|
columns,
|
||||||
|
user_id,
|
||||||
|
page_name,
|
||||||
|
mutate,
|
||||||
|
need_filter,
|
||||||
|
table_url,
|
||||||
|
table_title,
|
||||||
|
setFilterData,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
getState,
|
||||||
|
options: {
|
||||||
|
enableBottomToolbar,
|
||||||
|
enableTopToolbar,
|
||||||
|
mrtTheme: { baseBackgroundColor },
|
||||||
|
muiTablePaperProps,
|
||||||
|
renderBottomToolbar,
|
||||||
|
renderTopToolbar,
|
||||||
|
},
|
||||||
|
refs: { tablePaperRef },
|
||||||
|
} = table;
|
||||||
|
|
||||||
|
const { isFullScreen } = getState();
|
||||||
|
|
||||||
|
const paperProps = {
|
||||||
|
...parseFromValuesOrFunc(muiTablePaperProps, { table }),
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
elevation={0}
|
||||||
|
{...paperProps}
|
||||||
|
ref={(ref) => {
|
||||||
|
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 }) ?? (
|
||||||
|
<DataTable_TopToolbar
|
||||||
|
need_filter={need_filter}
|
||||||
|
table={table}
|
||||||
|
mutate={mutate}
|
||||||
|
columns={columns}
|
||||||
|
table_url={table_url}
|
||||||
|
user_id={user_id}
|
||||||
|
page_name={page_name}
|
||||||
|
table_name={table_name}
|
||||||
|
table_title={table_title}
|
||||||
|
setFilterData={setFilterData}
|
||||||
|
{...paperProps}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<DataTable_TableContainer table={table} />
|
||||||
|
{enableBottomToolbar &&
|
||||||
|
(parseFromValuesOrFunc(renderBottomToolbar, { table }) ?? (
|
||||||
|
<DataTable_BottomToolbar table={table} />
|
||||||
|
))}
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable_Paper;
|
||||||
76
src/core/components/DataTable/table/Table.js
Normal file
76
src/core/components/DataTable/table/Table.js
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
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 (
|
||||||
|
<Table
|
||||||
|
stickyHeader={enableStickyHeader || isFullScreen}
|
||||||
|
{...tableProps}
|
||||||
|
style={{ ...columnSizeVars, ...tableProps?.style }}
|
||||||
|
sx={(theme) => ({
|
||||||
|
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||||
|
position: "relative",
|
||||||
|
...parseFromValuesOrFunc(tableProps?.sx, theme),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{!!Caption && <caption>{Caption}</caption>}
|
||||||
|
{enableTableHead && <DataTable_TableHead {...commonTableGroupProps} />}
|
||||||
|
{memoMode === "table-body" || columnSizingInfo.isResizingColumn ? (
|
||||||
|
<Memo_DataTable_TableBody {...commonTableGroupProps} />
|
||||||
|
) : (
|
||||||
|
<DataTable_TableBody {...commonTableGroupProps} />
|
||||||
|
)}
|
||||||
|
{/* {enableTableFooter && <MRT_TableFooter {...commonTableGroupProps} />} */}
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable_Table;
|
||||||
84
src/core/components/DataTable/table/TableContainer.js
Normal file
84
src/core/components/DataTable/table/TableContainer.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
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 (
|
||||||
|
<TableContainer
|
||||||
|
aria-busy={loading}
|
||||||
|
aria-describedby={loading ? "mrt-progress" : undefined}
|
||||||
|
{...tableContainerProps}
|
||||||
|
ref={(node) => {
|
||||||
|
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 ? <DataTable_TableLoadingOverlay table={table} /> : null}
|
||||||
|
<DataTable_Table table={table} />
|
||||||
|
{enableCellActions && actionCell && (
|
||||||
|
<DataTable_CellActionMenu table={table} />
|
||||||
|
)}
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable_TableContainer;
|
||||||
44
src/core/components/DataTable/table/TableLoadingOverlay.js
Normal file
44
src/core/components/DataTable/table/TableLoadingOverlay.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
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 (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
alignItems: "center",
|
||||||
|
backgroundColor: alpha(baseBackgroundColor, 0.5),
|
||||||
|
bottom: 0,
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
left: 0,
|
||||||
|
maxHeight: "100vh",
|
||||||
|
position: "absolute",
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
width: "100%",
|
||||||
|
zIndex: 3,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{circularProgressProps?.Component ?? (
|
||||||
|
<CircularProgress
|
||||||
|
aria-label={localization.noRecordsToDisplay}
|
||||||
|
id="mrt-progress"
|
||||||
|
{...circularProgressProps}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable_TableLoadingOverlay;
|
||||||
85
src/core/components/DataTable/toolbar/BottomToolbar.js
Normal file
85
src/core/components/DataTable/toolbar/BottomToolbar.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
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 (
|
||||||
|
<Box
|
||||||
|
{...toolbarProps}
|
||||||
|
ref={(node) => {
|
||||||
|
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),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<DataTable_LinearProgressBar isTopToolbar={false} table={table} />
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
alignItems: "center",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
p: "0.5rem",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{renderBottomToolbarCustomActions ? (
|
||||||
|
renderBottomToolbarCustomActions({ table })
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
position: stackAlertBanner ? "relative" : "absolute",
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{enablePagination &&
|
||||||
|
["both", "bottom"].includes(positionPagination ?? "") && (
|
||||||
|
<DataTable_TablePagination position="bottom" table={table} />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable_BottomToolbar;
|
||||||
40
src/core/components/DataTable/toolbar/LinearProgressBar.js
Normal file
40
src/core/components/DataTable/toolbar/LinearProgressBar.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
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 (
|
||||||
|
<Collapse
|
||||||
|
in={showProgressBars !== false && (showProgressBars || isSaving)}
|
||||||
|
mountOnEnter
|
||||||
|
sx={{
|
||||||
|
bottom: isTopToolbar ? 0 : undefined,
|
||||||
|
position: "absolute",
|
||||||
|
top: !isTopToolbar ? 0 : undefined,
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
unmountOnExit
|
||||||
|
>
|
||||||
|
<LinearProgress
|
||||||
|
aria-busy="true"
|
||||||
|
aria-label="Loading"
|
||||||
|
sx={{ position: "relative" }}
|
||||||
|
{...linearProgressProps}
|
||||||
|
/>
|
||||||
|
</Collapse>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable_LinearProgressBar;
|
||||||
228
src/core/components/DataTable/toolbar/TablePagination.js
Normal file
228
src/core/components/DataTable/toolbar/TablePagination.js
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
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 (
|
||||||
|
<Box
|
||||||
|
className="MuiTablePagination-root"
|
||||||
|
sx={{
|
||||||
|
alignItems: "center",
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
gap: "8px",
|
||||||
|
justifyContent: { md: "space-between", sm: "center" },
|
||||||
|
justifySelf: "flex-end",
|
||||||
|
mt:
|
||||||
|
position === "top" &&
|
||||||
|
enableToolbarInternalActions &&
|
||||||
|
!showGlobalFilter
|
||||||
|
? "3rem"
|
||||||
|
: undefined,
|
||||||
|
position: "relative",
|
||||||
|
px: "8px",
|
||||||
|
py: "12px",
|
||||||
|
zIndex: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{showRowsPerPage && (
|
||||||
|
<Box sx={{ alignItems: "center", display: "flex", gap: "8px" }}>
|
||||||
|
<InputLabel htmlFor="mrt-rows-per-page" sx={{ mb: 0 }}>
|
||||||
|
{localization.rowsPerPage}
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
MenuProps={{ disableScrollLock: true }}
|
||||||
|
disableUnderline
|
||||||
|
disabled={disabled}
|
||||||
|
inputProps={{
|
||||||
|
"aria-label": localization.rowsPerPage,
|
||||||
|
id: "mrt-rows-per-page",
|
||||||
|
}}
|
||||||
|
label={localization.rowsPerPage}
|
||||||
|
onChange={(event) => table.setPageSize(+event.target.value)}
|
||||||
|
sx={{ mb: 0 }}
|
||||||
|
value={pageSize}
|
||||||
|
variant="standard"
|
||||||
|
{...SelectProps}
|
||||||
|
>
|
||||||
|
{rowsPerPageOptions.map((option) => {
|
||||||
|
const value = typeof option !== "number" ? option.value : option;
|
||||||
|
const label =
|
||||||
|
typeof option !== "number" ? option.label : `${option}`;
|
||||||
|
return (
|
||||||
|
SelectProps?.children ??
|
||||||
|
(SelectProps?.native ? (
|
||||||
|
<option key={value} value={value}>
|
||||||
|
{label}
|
||||||
|
</option>
|
||||||
|
) : (
|
||||||
|
<MenuItem key={value} sx={{ m: 0 }} value={value}>
|
||||||
|
{label}
|
||||||
|
</MenuItem>
|
||||||
|
))
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{paginationDisplayMode === "pages" ? (
|
||||||
|
<Pagination
|
||||||
|
count={numberOfPages}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={(_e, newPageIndex) => table.setPageIndex(newPageIndex - 1)}
|
||||||
|
page={pageIndex + 1}
|
||||||
|
renderItem={(item) => (
|
||||||
|
<PaginationItem
|
||||||
|
slots={{
|
||||||
|
first: FirstPageIcon,
|
||||||
|
last: LastPageIcon,
|
||||||
|
next: ChevronRightIcon,
|
||||||
|
previous: ChevronLeftIcon,
|
||||||
|
}}
|
||||||
|
{...item}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
showFirstButton={showFirstButton}
|
||||||
|
showLastButton={showLastButton}
|
||||||
|
{...restPaginationProps}
|
||||||
|
/>
|
||||||
|
) : paginationDisplayMode === "default" ? (
|
||||||
|
<>
|
||||||
|
<Typography
|
||||||
|
align="center"
|
||||||
|
component="span"
|
||||||
|
sx={{ m: "0 4px", minWidth: "8ch" }}
|
||||||
|
variant="body2"
|
||||||
|
>{`${
|
||||||
|
lastRowIndex === 0 ? 0 : (firstRowIndex + 1).toLocaleString()
|
||||||
|
}-${lastRowIndex.toLocaleString()} ${
|
||||||
|
localization.of
|
||||||
|
} ${totalRowCount.toLocaleString()}`}</Typography>
|
||||||
|
<Box gap="xs">
|
||||||
|
{showFirstButton && (
|
||||||
|
<Tooltip {...tooltipProps} title={localization.goToFirstPage}>
|
||||||
|
<span>
|
||||||
|
<IconButton
|
||||||
|
aria-label={localization.goToFirstPage}
|
||||||
|
disabled={disableBack}
|
||||||
|
onClick={() => table.firstPage()}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<FirstPageIcon {...flipIconStyles(theme)} />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
<Tooltip {...tooltipProps} title={localization.goToPreviousPage}>
|
||||||
|
<span>
|
||||||
|
<IconButton
|
||||||
|
aria-label={localization.goToPreviousPage}
|
||||||
|
disabled={disableBack}
|
||||||
|
onClick={() => table.previousPage()}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<ChevronLeftIcon {...flipIconStyles(theme)} />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip {...tooltipProps} title={localization.goToNextPage}>
|
||||||
|
<span>
|
||||||
|
<IconButton
|
||||||
|
aria-label={localization.goToNextPage}
|
||||||
|
disabled={disableNext}
|
||||||
|
onClick={() => table.nextPage()}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<ChevronRightIcon {...flipIconStyles(theme)} />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
{showLastButton && (
|
||||||
|
<Tooltip {...tooltipProps} title={localization.goToLastPage}>
|
||||||
|
<span>
|
||||||
|
<IconButton
|
||||||
|
aria-label={localization.goToLastPage}
|
||||||
|
disabled={disableNext}
|
||||||
|
onClick={() => table.lastPage()}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<LastPageIcon {...flipIconStyles(theme)} />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable_TablePagination;
|
||||||
156
src/core/components/DataTable/toolbar/TopToolbar.js
Normal file
156
src/core/components/DataTable/toolbar/TopToolbar.js
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
import {
|
||||||
|
getCommonToolbarStyles,
|
||||||
|
parseFromValuesOrFunc,
|
||||||
|
} from "@/core/utils/utils";
|
||||||
|
import { Box, Typography, useMediaQuery } from "@mui/material";
|
||||||
|
import DataTable_LinearProgressBar from "./LinearProgressBar";
|
||||||
|
import DataTable_TablePagination from "./TablePagination";
|
||||||
|
import FilterColumn from "@/core/components/DataTable/filter";
|
||||||
|
import UpdateTable from "@/core/components/DataTable/update/UpdateTable";
|
||||||
|
import ResetStorage from "@/core/components/DataTable/reset/ResetStorage";
|
||||||
|
import HideColumn from "@/core/components/DataTable/hide";
|
||||||
|
import FilterCustom from "../filter/FilterCustom";
|
||||||
|
|
||||||
|
const DataTable_TopToolbar = ({
|
||||||
|
mutate,
|
||||||
|
need_filter,
|
||||||
|
table,
|
||||||
|
columns,
|
||||||
|
table_url,
|
||||||
|
user_id,
|
||||||
|
page_name,
|
||||||
|
table_name,
|
||||||
|
table_title,
|
||||||
|
special_data,
|
||||||
|
special_filter,
|
||||||
|
setFilterData,
|
||||||
|
}) => {
|
||||||
|
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 (
|
||||||
|
<Box
|
||||||
|
{...toolbarProps}
|
||||||
|
ref={(ref) => {
|
||||||
|
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),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flexWrap: { xs: "wrap", sm: "no-wrap" },
|
||||||
|
boxSizing: "border-box",
|
||||||
|
display: "flex",
|
||||||
|
gap: "0.5rem",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
p: "0.5rem",
|
||||||
|
position: stackAlertBanner ? "relative" : "absolute",
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
order: { xs: 2, sm: 1 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{renderTopToolbarCustomActions?.({ table })}
|
||||||
|
</Box>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
textAlign: "center",
|
||||||
|
order: { xs: 1, sm: 2 },
|
||||||
|
width: { xs: "100%", sm: "unset" },
|
||||||
|
}}
|
||||||
|
fontWeight={600}
|
||||||
|
>
|
||||||
|
{table_title || ""}
|
||||||
|
</Typography>
|
||||||
|
{enableToolbarInternalActions && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
gap: "0.5rem",
|
||||||
|
flexWrap: "wrap-reverse",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
order: 3,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{!special_data && (
|
||||||
|
<>
|
||||||
|
<ResetStorage
|
||||||
|
user_id={user_id}
|
||||||
|
page_name={page_name}
|
||||||
|
table_name={table_name}
|
||||||
|
/>
|
||||||
|
<UpdateTable mutate={mutate} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<HideColumn
|
||||||
|
columns={table.options.columns}
|
||||||
|
user_id={user_id}
|
||||||
|
page_name={page_name}
|
||||||
|
table_name={table_name}
|
||||||
|
/>
|
||||||
|
{need_filter && (
|
||||||
|
<FilterColumn
|
||||||
|
columns={columns}
|
||||||
|
table_url={table_url}
|
||||||
|
user_id={user_id}
|
||||||
|
page_name={page_name}
|
||||||
|
table_name={table_name}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{special_filter && setFilterData && (
|
||||||
|
<FilterCustom
|
||||||
|
filterData={special_filter}
|
||||||
|
setFilterData={setFilterData}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
{enablePagination &&
|
||||||
|
["both", "top"].includes(positionPagination ?? "") && (
|
||||||
|
<DataTable_TablePagination position="top" table={table} />
|
||||||
|
)}
|
||||||
|
<DataTable_LinearProgressBar isTopToolbar table={table} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DataTable_TopToolbar;
|
||||||
19
src/core/components/DataTable/update/UpdateTable.jsx
Normal file
19
src/core/components/DataTable/update/UpdateTable.jsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
"use client";
|
||||||
|
import { IconButton, Tooltip } from "@mui/material";
|
||||||
|
import RestartAltIcon from "@mui/icons-material/RestartAlt";
|
||||||
|
|
||||||
|
function UpdateTable({ mutate }) {
|
||||||
|
const update = () => {
|
||||||
|
mutate();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip title="بروزرسانی اطلاعات">
|
||||||
|
<IconButton onClick={update} aria-label="refactor table">
|
||||||
|
<RestartAltIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UpdateTable;
|
||||||
8
src/core/components/DataTableWithAuth.jsx
Normal file
8
src/core/components/DataTableWithAuth.jsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import DataTable from "@/core/components/DataTable";
|
||||||
|
import { useAuth } from "@/lib/contexts/auth";
|
||||||
|
|
||||||
|
const DataTableWithAuth = (props) => {
|
||||||
|
const { user } = useAuth();
|
||||||
|
return <DataTable user_id={user.username} {...props} />;
|
||||||
|
};
|
||||||
|
export default DataTableWithAuth;
|
||||||
25
src/core/components/FilterDrawer/FilterController.jsx
Normal file
25
src/core/components/FilterDrawer/FilterController.jsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Controller } from "react-hook-form";
|
||||||
|
import FilterField from "./FilterField";
|
||||||
|
|
||||||
|
const FilterController = ({ item, control, reset, errors }) => {
|
||||||
|
return (
|
||||||
|
<Controller
|
||||||
|
name={`${item.id}`}
|
||||||
|
control={control}
|
||||||
|
render={({ field: { value, onBlur, onChange } }) => {
|
||||||
|
return (
|
||||||
|
<FilterField
|
||||||
|
item={item}
|
||||||
|
filterParameters={value}
|
||||||
|
handleChange={onChange}
|
||||||
|
handleBlur={onBlur}
|
||||||
|
dependencyFieldValue={null}
|
||||||
|
resetForm={() => reset()}
|
||||||
|
errors={errors}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default FilterController;
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { Controller, useWatch } from "react-hook-form";
|
||||||
|
import FilterField from "./FilterField";
|
||||||
|
|
||||||
|
const FilterControllerWithDependency = ({ item, control, reset, errors }) => {
|
||||||
|
const dependencyField = useWatch({ control, name: item.dependencyId });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Controller
|
||||||
|
name={`${item.id}`}
|
||||||
|
control={control}
|
||||||
|
render={({ field: { value, name, onBlur, onChange } }) => {
|
||||||
|
return (
|
||||||
|
<FilterField
|
||||||
|
item={item}
|
||||||
|
filterParameters={value}
|
||||||
|
handleChange={onChange}
|
||||||
|
handleBlur={onBlur}
|
||||||
|
dependencyFieldValue={dependencyField}
|
||||||
|
resetForm={() => reset()}
|
||||||
|
errors={errors}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default FilterControllerWithDependency;
|
||||||
105
src/core/components/FilterDrawer/FilterField.jsx
Normal file
105
src/core/components/FilterDrawer/FilterField.jsx
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import CustomTextFieldRange from "./fieldsType/CustomTextFieldRange";
|
||||||
|
import CustomSelect from "./fieldsType/CustomSelect";
|
||||||
|
import CustomDatePicker from "../CustomDatePicker";
|
||||||
|
import CustomDatePickerRange from "./fieldsType/CustomDate/CustomDatePickerRange";
|
||||||
|
import CustomSelectMultiple from "./fieldsType/CustomSelectMultiple";
|
||||||
|
import CustomTextField from "./fieldsType/CustomTextField";
|
||||||
|
import FilterOptionList from "./FilterOptionList";
|
||||||
|
|
||||||
|
const filterModeOptionFa = {
|
||||||
|
equals: "برابر",
|
||||||
|
notEquals: "نابرابر",
|
||||||
|
contains: "شامل",
|
||||||
|
lessThan: "کوچکتر",
|
||||||
|
greaterThan: "بزرگتر",
|
||||||
|
fuzzy: "فازی",
|
||||||
|
between: "مابین",
|
||||||
|
};
|
||||||
|
|
||||||
|
function FilterField({
|
||||||
|
item,
|
||||||
|
filterParameters,
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
dependencyFieldValue,
|
||||||
|
setFieldValue,
|
||||||
|
resetForm,
|
||||||
|
errors,
|
||||||
|
}) {
|
||||||
|
const [anchorEl, setAnchorEl] = useState(null);
|
||||||
|
const defaultFilterTranslation =
|
||||||
|
filterModeOptionFa[filterParameters.filterMode] ||
|
||||||
|
filterParameters.filterMode;
|
||||||
|
|
||||||
|
const handleOpenFilterBox = (event) => {
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderField = () => {
|
||||||
|
const commonProps = {
|
||||||
|
item,
|
||||||
|
filterParameters,
|
||||||
|
defaultFilterTranslation,
|
||||||
|
handleOpenFilterBox,
|
||||||
|
dependencyFieldValue,
|
||||||
|
setFieldValue,
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
errors,
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (filterParameters.datatype) {
|
||||||
|
case "numeric":
|
||||||
|
if (filterParameters.filterMode === "between") {
|
||||||
|
return <CustomTextFieldRange {...commonProps} />;
|
||||||
|
}
|
||||||
|
if (filterParameters.filterMode === "equals") {
|
||||||
|
return item.SelectComponent ? (
|
||||||
|
<item.SelectComponent {...commonProps} />
|
||||||
|
) : (
|
||||||
|
<CustomSelect {...commonProps} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "date":
|
||||||
|
if (filterParameters.filterMode === "equals") {
|
||||||
|
return <CustomDatePicker {...commonProps} />;
|
||||||
|
}
|
||||||
|
if (filterParameters.filterMode === "between") {
|
||||||
|
return <CustomDatePickerRange {...commonProps} />;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "array":
|
||||||
|
if (filterParameters.filterMode === "equals") {
|
||||||
|
return <CustomSelectMultiple {...commonProps} />;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return <CustomTextField {...commonProps} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{renderField()}
|
||||||
|
{Array.isArray(item.filterModeOptions) && (
|
||||||
|
<FilterOptionList
|
||||||
|
item={item}
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
setAnchorEl={setAnchorEl}
|
||||||
|
filterParameters={filterParameters}
|
||||||
|
filterType={filterParameters.filterMode}
|
||||||
|
handleChange={handleChange}
|
||||||
|
resetForm={resetForm}
|
||||||
|
filterOption={item.filterModeOptions}
|
||||||
|
filterModeOptionFa={filterModeOptionFa}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FilterField;
|
||||||
58
src/core/components/FilterDrawer/FilterOptionList.jsx
Normal file
58
src/core/components/FilterDrawer/FilterOptionList.jsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { ListItem, Menu } from "@mui/material";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
function FilterOptionList({
|
||||||
|
filterType,
|
||||||
|
filterOption,
|
||||||
|
filterParameters,
|
||||||
|
anchorEl,
|
||||||
|
filterModeOptionFa,
|
||||||
|
setAnchorEl,
|
||||||
|
handleChange,
|
||||||
|
}) {
|
||||||
|
const [selectedFilter, setSelectedFilter] = useState(filterType);
|
||||||
|
|
||||||
|
const handleChangeItem = (event, index) => {
|
||||||
|
handleChange({
|
||||||
|
...filterParameters,
|
||||||
|
value: filterOption[index] === "between" ? ["", ""] : "",
|
||||||
|
filterMode: filterOption[index],
|
||||||
|
});
|
||||||
|
setSelectedFilter(filterOption[index]);
|
||||||
|
setAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseFilterBox = () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
id="simple-menu"
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
keepMounted
|
||||||
|
open={Boolean(anchorEl)}
|
||||||
|
onClose={handleCloseFilterBox}
|
||||||
|
>
|
||||||
|
{filterOption.map((option, index) => (
|
||||||
|
<ListItem
|
||||||
|
key={index}
|
||||||
|
onClick={(event) => handleChangeItem(event, index)}
|
||||||
|
selected={option === selectedFilter}
|
||||||
|
sx={{
|
||||||
|
cursor: "pointer",
|
||||||
|
width: "100px",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{filterModeOptionFa[option]}
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FilterOptionList;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import React from "react";
|
||||||
|
import MuiDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker";
|
||||||
|
import { Typography } from "@mui/material";
|
||||||
|
|
||||||
|
function CustomDatePicker({
|
||||||
|
column,
|
||||||
|
filterParameters,
|
||||||
|
defaultFilterTranslation,
|
||||||
|
handleChange,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<MuiDatePicker
|
||||||
|
name={`${column.id}.value`}
|
||||||
|
value={filterParameters.value}
|
||||||
|
setFieldValue={(name, formattedDate) => {
|
||||||
|
handleChange({ ...filterParameters, value: formattedDate });
|
||||||
|
}}
|
||||||
|
placeholder={column.header}
|
||||||
|
helperText={
|
||||||
|
<Typography
|
||||||
|
variant="button"
|
||||||
|
sx={{ color: (theme) => theme.palette.primary.main }}
|
||||||
|
>
|
||||||
|
نوع فیلتر: {defaultFilterTranslation} (تاریخ)
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomDatePicker;
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { Box, Typography } from "@mui/material";
|
||||||
|
import MuiDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker";
|
||||||
|
|
||||||
|
function CustomDatePickerRange({
|
||||||
|
item,
|
||||||
|
filterParameters,
|
||||||
|
defaultFilterTranslation,
|
||||||
|
handleChange,
|
||||||
|
errors,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", gap: 1 }}>
|
||||||
|
<MuiDatePicker
|
||||||
|
label={item.header}
|
||||||
|
name={`${item.id}.value[0]`}
|
||||||
|
value={filterParameters.value[0]}
|
||||||
|
setFieldValue={(name, formattedDate) => {
|
||||||
|
handleChange({
|
||||||
|
...filterParameters,
|
||||||
|
value: [formattedDate, filterParameters.value[1]],
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
maxDate={filterParameters.value[1]}
|
||||||
|
placeholder={`از تاریخ`}
|
||||||
|
helperText={
|
||||||
|
<Typography
|
||||||
|
variant="button"
|
||||||
|
sx={{ color: (theme) => theme.palette.primary.main }}
|
||||||
|
>
|
||||||
|
نوع فیلتر: {defaultFilterTranslation} (تاریخ)
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<MuiDatePicker
|
||||||
|
label={item.header}
|
||||||
|
name={`${item.id}.value[1]`}
|
||||||
|
value={filterParameters.value[1]}
|
||||||
|
setFieldValue={(name, formattedDate) => {
|
||||||
|
handleChange({
|
||||||
|
...filterParameters,
|
||||||
|
value: [filterParameters.value[0], formattedDate],
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
minDate={filterParameters.value[0]}
|
||||||
|
placeholder={`تا تاریخ`}
|
||||||
|
helperText={
|
||||||
|
errors?.[`${item.id}`]?.value
|
||||||
|
? errors?.[`${item.id}`]?.value.message
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
error={Boolean(errors?.[`${item.id}`]?.value)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomDatePickerRange;
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { LocalizationProvider, MobileDatePicker } from "@mui/x-date-pickers";
|
||||||
|
import { AdapterDateFnsJalali } from "@mui/x-date-pickers/AdapterDateFnsJalali";
|
||||||
|
import { faIR } from "@mui/x-date-pickers/locales";
|
||||||
|
import { Box, FormHelperText, IconButton, InputAdornment } from "@mui/material";
|
||||||
|
import ClearIcon from "@mui/icons-material/Clear";
|
||||||
|
import moment from "jalali-moment";
|
||||||
|
|
||||||
|
function MuiDatePicker({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
setFieldValue,
|
||||||
|
name,
|
||||||
|
minDate,
|
||||||
|
maxDate,
|
||||||
|
helperText,
|
||||||
|
placeholder,
|
||||||
|
error,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column", my: 1 }}>
|
||||||
|
<LocalizationProvider
|
||||||
|
dateAdapter={AdapterDateFnsJalali}
|
||||||
|
localeText={
|
||||||
|
faIR.components.MuiLocalizationProvider.defaultProps.localeText
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MobileDatePicker
|
||||||
|
value={value ? new Date(value) : null}
|
||||||
|
sx={{ width: "100%" }}
|
||||||
|
id={name}
|
||||||
|
label={label}
|
||||||
|
name={name}
|
||||||
|
closeOnSelect={true}
|
||||||
|
aria-describedby="component-helper-text"
|
||||||
|
onChange={(value) => {
|
||||||
|
const date = new Date(value);
|
||||||
|
const formattedDate = moment(date)
|
||||||
|
.locale("en")
|
||||||
|
.format("YYYY-MM-DD");
|
||||||
|
setFieldValue(name, formattedDate);
|
||||||
|
}}
|
||||||
|
minDate={minDate ? new Date(minDate) : null}
|
||||||
|
maxDate={maxDate ? new Date(maxDate) : null}
|
||||||
|
slotProps={{
|
||||||
|
textField: {
|
||||||
|
size: "small",
|
||||||
|
error: error,
|
||||||
|
placeholder: placeholder,
|
||||||
|
InputProps: {
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
setFieldValue(name, "");
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
color: "#bfbfbf",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "rgba(189, 189, 189, 0.1)",
|
||||||
|
color: "#363434",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ClearIcon />
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
InputLabelProps: {
|
||||||
|
shrink: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* <FormHelperText id="component-helper-text" variant="caption" sx={{ ml: 2, color: error ? "#d32f2f" : "unset", fontWeight: 'bold' }}>
|
||||||
|
{helperText ? helperText : ""}
|
||||||
|
</FormHelperText> */}
|
||||||
|
</LocalizationProvider>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MuiDatePicker;
|
||||||
38
src/core/components/FilterDrawer/fieldsType/CustomSelect.jsx
Normal file
38
src/core/components/FilterDrawer/fieldsType/CustomSelect.jsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
InputLabel,
|
||||||
|
MenuItem,
|
||||||
|
OutlinedInput,
|
||||||
|
Select,
|
||||||
|
} from "@mui/material";
|
||||||
|
function CustomSelect({ item, filterParameters, handleChange }) {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth sx={{ marginY: 1 }} size="small">
|
||||||
|
<InputLabel id={`label${item.id}`} shrink>
|
||||||
|
{item.header}
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId={`label${item.id}`}
|
||||||
|
id={item.id}
|
||||||
|
name={`${item.id}.value`}
|
||||||
|
value={filterParameters.value}
|
||||||
|
input={<OutlinedInput label={item.header} />}
|
||||||
|
size="small"
|
||||||
|
onChange={(e) =>
|
||||||
|
handleChange({ ...filterParameters, value: e.target.value })
|
||||||
|
}
|
||||||
|
displayEmpty
|
||||||
|
>
|
||||||
|
{item.selectOption().map((option) => (
|
||||||
|
<MenuItem key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomSelect;
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
InputLabel,
|
||||||
|
MenuItem,
|
||||||
|
OutlinedInput,
|
||||||
|
Select,
|
||||||
|
} from "@mui/material";
|
||||||
|
function CustomSelectByDependency({
|
||||||
|
item,
|
||||||
|
filterParameters,
|
||||||
|
value,
|
||||||
|
handleChange,
|
||||||
|
selectOption,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth sx={{ my: 1 }} size="small">
|
||||||
|
<InputLabel id={`label${item.id}`} shrink>
|
||||||
|
{item.header}
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId={`label${item.id}`}
|
||||||
|
id={item.id}
|
||||||
|
name={`${item.id}.value`}
|
||||||
|
value={value}
|
||||||
|
size="small"
|
||||||
|
input={<OutlinedInput label={item.header} />}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleChange({ ...filterParameters, value: e.target.value })
|
||||||
|
}
|
||||||
|
displayEmpty
|
||||||
|
>
|
||||||
|
{selectOption.map((option) => (
|
||||||
|
<MenuItem key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomSelectByDependency;
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Chip,
|
||||||
|
FormControl,
|
||||||
|
FormHelperText,
|
||||||
|
InputLabel,
|
||||||
|
MenuItem,
|
||||||
|
OutlinedInput,
|
||||||
|
Select,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
function CustomSelectMultiple({ item, filterParameters, handleChange }) {
|
||||||
|
const selectOption = item.selectOption;
|
||||||
|
|
||||||
|
const getLabelForValue = (value) => {
|
||||||
|
const option = selectOption.find((opt) => opt.value === value);
|
||||||
|
return option ? option.label : value;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth sx={{ marginY: 1 }} size="small">
|
||||||
|
<InputLabel htmlFor="uncontrolled-native" id={`label${item.id}`}>
|
||||||
|
{item.header}
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId={`label${item.id}`}
|
||||||
|
id={item.id}
|
||||||
|
value={filterParameters.value}
|
||||||
|
multiple
|
||||||
|
size="small"
|
||||||
|
onChange={(e) =>
|
||||||
|
handleChange({ ...filterParameters, value: e.target.value })
|
||||||
|
}
|
||||||
|
renderValue={(selected) => (
|
||||||
|
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||||
|
{selected.map((value) => (
|
||||||
|
<Chip key={value} label={getLabelForValue(value)} />
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
input={<OutlinedInput label={item.header} />}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
>
|
||||||
|
{selectOption.map((option) => (
|
||||||
|
<MenuItem key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomSelectMultiple;
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { InputAdornment, TextField, Typography } from "@mui/material";
|
||||||
|
import FilterListIcon from "@mui/icons-material/FilterList";
|
||||||
|
|
||||||
|
function CustomTextField({
|
||||||
|
item,
|
||||||
|
filterParameters,
|
||||||
|
handleOpenFilterBox,
|
||||||
|
handleBlur,
|
||||||
|
handleChange,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<TextField
|
||||||
|
id={item.id}
|
||||||
|
name={item.id}
|
||||||
|
label={item.header}
|
||||||
|
value={filterParameters.value}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleChange({ ...filterParameters, value: e.target.value })
|
||||||
|
}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
sx={{ my: 1 }}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment
|
||||||
|
position="end"
|
||||||
|
sx={{ cursor: "pointer" }}
|
||||||
|
onClick={handleOpenFilterBox}
|
||||||
|
>
|
||||||
|
<FilterListIcon />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomTextField;
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { Box, InputAdornment, TextField, Typography } from "@mui/material";
|
||||||
|
import FilterListIcon from "@mui/icons-material/FilterList";
|
||||||
|
|
||||||
|
function CustomTextFieldRange({
|
||||||
|
item,
|
||||||
|
defaultFilterTranslation,
|
||||||
|
handleOpenFilterBox,
|
||||||
|
handleChange,
|
||||||
|
filterParameters,
|
||||||
|
handleBlur,
|
||||||
|
errors,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex" }}>
|
||||||
|
<TextField
|
||||||
|
id={`${item.id}.value[0]`}
|
||||||
|
name={`${item.id}.value[0]`}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleChange({
|
||||||
|
...filterParameters,
|
||||||
|
value: [e.target.value, filterParameters.value[1]],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
label={<Typography>از {item.header}</Typography>}
|
||||||
|
value={filterParameters.value[0]}
|
||||||
|
fullWidth
|
||||||
|
error={
|
||||||
|
touched?.[`${item.id}`]?.value &&
|
||||||
|
Boolean(errors?.[`${item.id}`]?.value)
|
||||||
|
}
|
||||||
|
helperText={
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
sx={{
|
||||||
|
color: (theme) => theme.palette.primary.main,
|
||||||
|
fontWeight: "bold",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
نوع فیلتر: {defaultFilterTranslation}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
sx={{ my: 1, marginRight: 1 }}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id={`${item.id}.value[1]`}
|
||||||
|
name={`${item.id}.value[1]`}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleChange({
|
||||||
|
...filterParameters,
|
||||||
|
value: [filterParameters.value[0], e.target.value],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
error={Boolean(errors?.[`${item.id}`]?.value)}
|
||||||
|
helperText={
|
||||||
|
errors?.[`${item.id}`]?.value
|
||||||
|
? errors?.[`${item.id}`]?.value.message
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
label={<Typography>تا {item.header}</Typography>}
|
||||||
|
value={filterParameters.value[1]}
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
sx={{ my: 1 }}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment
|
||||||
|
position="end"
|
||||||
|
sx={{ cursor: "pointer" }}
|
||||||
|
onClick={handleOpenFilterBox}
|
||||||
|
>
|
||||||
|
<FilterListIcon />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomTextFieldRange;
|
||||||
160
src/core/components/FilterDrawer/index.jsx
Normal file
160
src/core/components/FilterDrawer/index.jsx
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
import { Cancel, FilterAlt } from "@mui/icons-material";
|
||||||
|
import { Box, Button, IconButton, Typography } from "@mui/material";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import * as Yup from "yup";
|
||||||
|
import ScrollBox from "../ScrollBox";
|
||||||
|
import FilterController from "./FilterController";
|
||||||
|
import FilterControllerWithDependency from "./FilterControllerWithDependency";
|
||||||
|
|
||||||
|
const headerSx = {
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
px: 2,
|
||||||
|
py: 1,
|
||||||
|
backgroundColor: (theme) => theme.palette.primary.main,
|
||||||
|
boxShadow:
|
||||||
|
"rgba(0, 0, 0, 0.19) 0px 10px 20px, rgba(0, 0, 0, 0.23) 0px 6px 6px",
|
||||||
|
maxWidth: "450px",
|
||||||
|
};
|
||||||
|
|
||||||
|
const headerTitleSx = { display: "flex", alignItems: "center" };
|
||||||
|
const headerIconSx = { color: "#fff", mr: 1 };
|
||||||
|
const iconButtonSx = { color: "#fff" };
|
||||||
|
|
||||||
|
const formContainerSx = { px: 2, py: 3 };
|
||||||
|
const footerSx = {
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
pb: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitButtonSx = {
|
||||||
|
px: 8,
|
||||||
|
boxShadow:
|
||||||
|
"rgba(0, 0, 0, 0.23) 0px 6px 6px, rgba(0, 0, 0, 0.19) 10px 0px 20px",
|
||||||
|
backgroundColor: "primary2",
|
||||||
|
":hover": { backgroundColor: "primary2" },
|
||||||
|
};
|
||||||
|
|
||||||
|
const validationSchema = Yup.object({
|
||||||
|
activity_date_time: Yup.array()
|
||||||
|
.of(Yup.string().nullable())
|
||||||
|
.test({
|
||||||
|
test(value, ctx) {
|
||||||
|
const [start, end] = value || ["", ""];
|
||||||
|
if ((start && !end) || (!start && end)) {
|
||||||
|
return ctx.createError({ message: "این بخش را تکمیل نمایید" });
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const FilterDrawer = ({ defaultValues, setFilterData, closeDrawer }) => {
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
errors,
|
||||||
|
reset,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { isDirty },
|
||||||
|
} = useForm({
|
||||||
|
defaultValues,
|
||||||
|
resolver: yupResolver(
|
||||||
|
Yup.object(
|
||||||
|
Object.keys(defaultValues).reduce((acc, key) => {
|
||||||
|
const initialValue = defaultValues[key];
|
||||||
|
if (initialValue.filterMode === "between") {
|
||||||
|
acc[key] = Yup.object().shape({
|
||||||
|
value: Yup.array()
|
||||||
|
.of(Yup.string().nullable())
|
||||||
|
.test({
|
||||||
|
test(value, ctx) {
|
||||||
|
const [start, end] = value || ["", ""];
|
||||||
|
if (
|
||||||
|
initialValue.datatype === "numeric" &&
|
||||||
|
parseInt(end, 10) <= parseInt(start, 10)
|
||||||
|
) {
|
||||||
|
return ctx.createError({
|
||||||
|
message: `مقدار وارده باید بیشتر از (${start}) باشد`,
|
||||||
|
});
|
||||||
|
} else if ((start && !end) || (!start && end)) {
|
||||||
|
return ctx.createError({
|
||||||
|
message: "این بخش را تکمیل نمایید",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
mode: "all",
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = (data) => {
|
||||||
|
setFilterData(data);
|
||||||
|
closeDrawer();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box sx={headerSx}>
|
||||||
|
<Box sx={headerTitleSx}>
|
||||||
|
<FilterAlt sx={headerIconSx} />
|
||||||
|
<Typography variant="h6" sx={{ color: "#fff" }}>
|
||||||
|
فیلتر
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<IconButton sx={iconButtonSx} onClick={closeDrawer}>
|
||||||
|
<Cancel sx={{ fontSize: "1em" }} />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
<ScrollBox>
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<Box sx={formContainerSx}>
|
||||||
|
{Object.values(defaultValues).map(
|
||||||
|
(item) =>
|
||||||
|
item.enable &&
|
||||||
|
(item.dependencyId ? (
|
||||||
|
<FilterControllerWithDependency
|
||||||
|
key={item.id}
|
||||||
|
item={item}
|
||||||
|
control={control}
|
||||||
|
errors={errors}
|
||||||
|
reset={reset}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<FilterController
|
||||||
|
key={item.id}
|
||||||
|
item={item}
|
||||||
|
control={control}
|
||||||
|
errors={errors}
|
||||||
|
reset={reset}
|
||||||
|
/>
|
||||||
|
)),
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box sx={footerSx}>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
size="large"
|
||||||
|
disabled={!isDirty}
|
||||||
|
sx={submitButtonSx}
|
||||||
|
>
|
||||||
|
اعمال فیلتر
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</form>
|
||||||
|
</ScrollBox>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default FilterDrawer;
|
||||||
94
src/core/components/NotificationDesign/AskForKeepData.jsx
Normal file
94
src/core/components/NotificationDesign/AskForKeepData.jsx
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import SaveIcon from "@mui/icons-material/Save";
|
||||||
|
import { Box, Button, Typography } from "@mui/material";
|
||||||
|
import DownloadIcon from "@mui/icons-material/Download";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import useTableSetting from "@/lib/hooks/useTableSetting";
|
||||||
|
import { flattenObjectOfObjects } from "@/core/utils/flattenObjectOfObjects";
|
||||||
|
|
||||||
|
function AskForKeepData({
|
||||||
|
filterData,
|
||||||
|
sortData,
|
||||||
|
hideData,
|
||||||
|
user_id,
|
||||||
|
page_name,
|
||||||
|
table_name,
|
||||||
|
columns,
|
||||||
|
}) {
|
||||||
|
const { filterAction, sortAction, hideAction } = useTableSetting();
|
||||||
|
|
||||||
|
const filteredHideData = Object.fromEntries(
|
||||||
|
Object.entries(hideData).filter(([key, value]) => value === false),
|
||||||
|
);
|
||||||
|
|
||||||
|
const flattenHideData = flattenObjectOfObjects(filteredHideData);
|
||||||
|
|
||||||
|
const onSaveFilter = () => {
|
||||||
|
const filteredItems = Object.keys(filterData)
|
||||||
|
.map((key) => {
|
||||||
|
const value = filterData[key].value;
|
||||||
|
if (
|
||||||
|
value !== "" &&
|
||||||
|
!(
|
||||||
|
Array.isArray(value) &&
|
||||||
|
(value.length === 0 ||
|
||||||
|
(value.length === 2 && value[0] === "" && value[1] === ""))
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return filterData[key];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
filterAction(user_id, page_name, table_name, filteredItems, columns);
|
||||||
|
sortAction(user_id, page_name, table_name, sortData, columns);
|
||||||
|
hideAction(user_id, page_name, table_name, flattenHideData, columns);
|
||||||
|
toast.dismiss({ containerId: "datatable" });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDismiss = () => {
|
||||||
|
toast.dismiss({ containerId: "datatable" });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ width: "100%" }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
mb: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
color="primary.main"
|
||||||
|
variant="subtitle1"
|
||||||
|
sx={{ fontWeight: "500" }}
|
||||||
|
>
|
||||||
|
ذخیره سازی تغییرات
|
||||||
|
</Typography>
|
||||||
|
<SaveIcon sx={{ color: "primary.main" }} />
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||||
|
<Typography variant="caption">
|
||||||
|
آیا مایل به ذخیره تغییر های اعمال شده برای دفعات بعد هستید؟
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: "flex", gap: 1, mt: 2 }}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
size="small"
|
||||||
|
endIcon={<DownloadIcon />}
|
||||||
|
onClick={onSaveFilter}
|
||||||
|
>
|
||||||
|
ذخیره
|
||||||
|
</Button>
|
||||||
|
<Button variant="outlined" size="small" onClick={handleDismiss}>
|
||||||
|
رد کردن
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AskForKeepData;
|
||||||
18
src/core/components/PageTitle.jsx
Normal file
18
src/core/components/PageTitle.jsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Chip, Divider, Typography } from "@mui/material";
|
||||||
|
|
||||||
|
const PageTitle = ({ title }) => {
|
||||||
|
return (
|
||||||
|
<Divider>
|
||||||
|
<Chip
|
||||||
|
label={
|
||||||
|
<Typography fontSize={14} fontWeight={500}>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Divider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default PageTitle;
|
||||||
@@ -35,17 +35,17 @@ const validationSchema = object({
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
current_password: "",
|
||||||
|
new_password: "",
|
||||||
|
repeat_new_password: "",
|
||||||
|
};
|
||||||
|
|
||||||
const Form = ({ handleCloseForm, setLoading }) => {
|
const Form = ({ handleCloseForm, setLoading }) => {
|
||||||
const request = useRequest();
|
const request = useRequest();
|
||||||
const [showNewPassword, setShowNewPassword] = useState();
|
const [showNewPassword, setShowNewPassword] = useState();
|
||||||
const [showRepeatNewPassword, setShowRepeatNewPassword] = useState();
|
const [showRepeatNewPassword, setShowRepeatNewPassword] = useState();
|
||||||
|
|
||||||
const defaultValues = {
|
|
||||||
current_password: "",
|
|
||||||
new_password: "",
|
|
||||||
repeat_new_password: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
|
|||||||
20
src/core/components/ScrollBox.jsx
Normal file
20
src/core/components/ScrollBox.jsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { Box, styled } from "@mui/material";
|
||||||
|
|
||||||
|
const ScrollBox = styled(Box)(({ theme }) => ({
|
||||||
|
flexGrow: 1,
|
||||||
|
overflowY: "auto",
|
||||||
|
maxWidth: "450px",
|
||||||
|
"::-webkit-scrollbar": {
|
||||||
|
width: "5px",
|
||||||
|
},
|
||||||
|
"::-webkit-scrollbar-track": {
|
||||||
|
boxShadow: "inset 0 0 5px #fff",
|
||||||
|
borderRadius: "5px",
|
||||||
|
},
|
||||||
|
"::-webkit-scrollbar-thumb": {
|
||||||
|
background: theme.palette.primary.dark,
|
||||||
|
borderRadius: "0px",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default ScrollBox;
|
||||||
10
src/core/utils/DataTableFilterDataStructure.js
Normal file
10
src/core/utils/DataTableFilterDataStructure.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export default function DataTableFilterDataStructure(filterData, isValueEmpty) {
|
||||||
|
return Object.values(filterData)
|
||||||
|
.filter((filter) => !isValueEmpty(filter.value))
|
||||||
|
.map(({ filterMode, id, datatype, value }) => ({
|
||||||
|
id: id.replace(/__/g, "."),
|
||||||
|
fn: filterMode,
|
||||||
|
datatype,
|
||||||
|
value,
|
||||||
|
}));
|
||||||
|
}
|
||||||
9
src/core/utils/flattenArrayOfObjects.js
Normal file
9
src/core/utils/flattenArrayOfObjects.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export const flattenArrayOfObjects = (array, key) => {
|
||||||
|
return array.reduce((acc, obj) => {
|
||||||
|
acc.push(obj);
|
||||||
|
if (Array.isArray(obj[key])) {
|
||||||
|
acc.push(...flattenArrayOfObjects(obj[key], key));
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
12
src/core/utils/flattenObjectOfObjects.js
Normal file
12
src/core/utils/flattenObjectOfObjects.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export const flattenObjectOfObjects = (obj, res = {}) => {
|
||||||
|
for (let key in obj) {
|
||||||
|
if (obj.hasOwnProperty(key)) {
|
||||||
|
if (typeof obj[key] === "object" && obj[key] !== null) {
|
||||||
|
flattenObjectOfObjects(obj[key], res);
|
||||||
|
} else {
|
||||||
|
res[key] = obj[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
6
src/core/utils/isArrayEmpty.js
Normal file
6
src/core/utils/isArrayEmpty.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export default function isArrayEmpty(value) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.length === 0 || value.every((v) => v === "");
|
||||||
|
}
|
||||||
|
return value === "" || value === null || value === undefined;
|
||||||
|
}
|
||||||
30
src/core/utils/nationalCodeValidation.js
Normal file
30
src/core/utils/nationalCodeValidation.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
function validateNationalCode(code) {
|
||||||
|
const invalidCodes = [
|
||||||
|
"1111111111",
|
||||||
|
"2222222222",
|
||||||
|
"3333333333",
|
||||||
|
"4444444444",
|
||||||
|
"5555555555",
|
||||||
|
"6666666666",
|
||||||
|
"7777777777",
|
||||||
|
"8888888888",
|
||||||
|
"9999999999",
|
||||||
|
];
|
||||||
|
if (invalidCodes.includes(code)) return false;
|
||||||
|
|
||||||
|
const L = code.length;
|
||||||
|
if (L < 8 || parseInt(code, 10) === 0) return false;
|
||||||
|
|
||||||
|
code = code.padStart(10, "0");
|
||||||
|
if (parseInt(code.substr(3, 6), 10) === 0) return false;
|
||||||
|
|
||||||
|
const c = parseInt(code.charAt(9), 10);
|
||||||
|
let s = 0;
|
||||||
|
for (let i = 0; i < 9; i++) {
|
||||||
|
s += parseInt(code.charAt(i), 10) * (10 - i);
|
||||||
|
}
|
||||||
|
s = s % 11;
|
||||||
|
return (s < 2 && c === s) || (s >= 2 && c === 11 - s);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default validateNationalCode;
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Settings } from "@mui/icons-material";
|
||||||
import SpaceDashboardIcon from "@mui/icons-material/SpaceDashboard";
|
import SpaceDashboardIcon from "@mui/icons-material/SpaceDashboard";
|
||||||
|
|
||||||
export const pageMenu = [
|
export const pageMenu = [
|
||||||
@@ -9,4 +10,20 @@ export const pageMenu = [
|
|||||||
icon: <SpaceDashboardIcon sx={{ width: "inherit", height: "inherit" }} />,
|
icon: <SpaceDashboardIcon sx={{ width: "inherit", height: "inherit" }} />,
|
||||||
permissions: ["all"],
|
permissions: ["all"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "systemManagment",
|
||||||
|
label: "مدیریت سامانه",
|
||||||
|
type: "menu",
|
||||||
|
icon: <Settings sx={{ width: "inherit", height: "inherit" }} />,
|
||||||
|
hasSubitems: true,
|
||||||
|
Subitems: [
|
||||||
|
{
|
||||||
|
id: "systemManagment-users",
|
||||||
|
label: "کاربران",
|
||||||
|
type: "page",
|
||||||
|
route: "/dashboard/users",
|
||||||
|
permissions: ["manage_users"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -4,10 +4,16 @@ export const GET_USER_ROUTE = api + "/profile/info";
|
|||||||
export const CHANGE_USER_PASSWORD = api + "/profile/change_password";
|
export const CHANGE_USER_PASSWORD = api + "/profile/change_password";
|
||||||
export const GET_USER_LOGIN_ROUTE = api + "/auth/login";
|
export const GET_USER_LOGIN_ROUTE = api + "/auth/login";
|
||||||
export const GET_USER_LOGOUT_ROUTE = api + "/auth/logout";
|
export const GET_USER_LOGOUT_ROUTE = api + "/auth/logout";
|
||||||
export const GET_PERMISSIONS_ROUTE = "";
|
export const GET_PERMISSIONS_ROUTE = api + "/profile/permissions";
|
||||||
export const GET_SIDEBAR_BADGE_ROUTE = "";
|
export const GET_SIDEBAR_BADGE_ROUTE = "";
|
||||||
|
export const GET_PROVINCE_LISTS = api + "/provinces/list";
|
||||||
|
export const GET_ROLE_LISTS = api + "/roles/list";
|
||||||
|
|
||||||
export const GET_CATEGORY = api + "/categories";
|
export const GET_CATEGORY = api + "/categories/list";
|
||||||
|
|
||||||
export const GET_CALLER_HISTORY = api + "/calls/list";
|
export const GET_CALLER_HISTORY = api + "/calls/list";
|
||||||
export const CALL_ACTION = api + "/calls";
|
export const CALL_ACTION = api + "/calls";
|
||||||
|
|
||||||
|
export const GET_USER_LIST = api + "/users";
|
||||||
|
export const CREATE_USER = api + "/users";
|
||||||
|
export const DELETE_USER = api + "/users";
|
||||||
|
export const UPDATE_USER = api + "/users";
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ const theme = createTheme({
|
|||||||
primary: {
|
primary: {
|
||||||
main: "#1b4332",
|
main: "#1b4332",
|
||||||
contrastText: "#FFFFFF",
|
contrastText: "#FFFFFF",
|
||||||
|
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
main: "#1B4043",
|
main: "#1B4043",
|
||||||
|
|||||||
149
src/core/utils/utils.js
Normal file
149
src/core/utils/utils.js
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
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,
|
||||||
|
};
|
||||||
211
src/lib/contexts/DataTable.js
Normal file
211
src/lib/contexts/DataTable.js
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
import { createContext, useEffect, useState } from "react";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import useTableSetting from "@/lib/hooks/useTableSetting";
|
||||||
|
import { flattenArrayOfObjects } from "@/core/utils/flattenArrayOfObjects";
|
||||||
|
import AskForKeepData from "@/core/components/NotificationDesign/AskForKeepData";
|
||||||
|
import { LinearProgress } from "@mui/material";
|
||||||
|
|
||||||
|
export const DataTableContext = createContext();
|
||||||
|
|
||||||
|
const DataTableProvider = ({
|
||||||
|
children,
|
||||||
|
user_id,
|
||||||
|
page_name,
|
||||||
|
table_name,
|
||||||
|
columns,
|
||||||
|
initialSort = [],
|
||||||
|
}) => {
|
||||||
|
const { settingStore } = useTableSetting();
|
||||||
|
const [isInitStates, setInitStates] = useState(false);
|
||||||
|
const flatColumns = flattenArrayOfObjects(columns, "columns");
|
||||||
|
const [initFilter, setInitFilter] = useState({});
|
||||||
|
const [filterData, setFilterData] = useState({});
|
||||||
|
const [initSort, setInitSort] = useState(initialSort || []);
|
||||||
|
const [sortData, setSortData] = useState(initialSort || []);
|
||||||
|
const [initHide, setInitHide] = useState({});
|
||||||
|
const [hideData, setHideData] = useState({});
|
||||||
|
const [isDirty, setIsDirty] = useState(false);
|
||||||
|
const [isAnyDirty, setIsAnyDirty] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let hasAnyConflict = false;
|
||||||
|
const hasConflict =
|
||||||
|
JSON.stringify(hideData) !== JSON.stringify(initHide) ||
|
||||||
|
JSON.stringify(sortData) !== JSON.stringify(initSort) ||
|
||||||
|
JSON.stringify(filterData) !== JSON.stringify(initFilter);
|
||||||
|
|
||||||
|
const CheckFilterValues = Object.values(filterData).every((innerObject) => {
|
||||||
|
if (
|
||||||
|
innerObject.datatype === "date" &&
|
||||||
|
innerObject.filterMode === "between"
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
Array.isArray(innerObject.value) &&
|
||||||
|
innerObject.value.length === 2 &&
|
||||||
|
innerObject.value.every((v) => v === "")
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return innerObject.value === "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const flattenObject = (obj) => {
|
||||||
|
let result = {};
|
||||||
|
for (const key in obj) {
|
||||||
|
if (typeof obj[key] === "object" && obj[key] !== null) {
|
||||||
|
Object.assign(result, flattenObject(obj[key])); // باز کردن اشیای تودرتو
|
||||||
|
} else {
|
||||||
|
result[key] = obj[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// تبدیل آبجکت به آبجکت فلت شده
|
||||||
|
const flatHideData = flattenObject(hideData);
|
||||||
|
|
||||||
|
const CheckHideValues = Object.values(flatHideData).every(
|
||||||
|
(value) => value === true,
|
||||||
|
);
|
||||||
|
hasAnyConflict =
|
||||||
|
!CheckHideValues ||
|
||||||
|
JSON.stringify(sortData) !== JSON.stringify(initialSort) ||
|
||||||
|
!CheckFilterValues;
|
||||||
|
|
||||||
|
setIsDirty(hasConflict);
|
||||||
|
setIsAnyDirty(hasAnyConflict);
|
||||||
|
}, [hideData, sortData, filterData, initHide, initSort, initFilter]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!settingStore) return;
|
||||||
|
const filterValues = flatColumns.reduce((acc, column) => {
|
||||||
|
if (!column.enableColumnFilter) return acc;
|
||||||
|
const storeFilter = settingStore?.[user_id]?.[page_name]?.[table_name]?.[
|
||||||
|
"filters"
|
||||||
|
]?.find((filter) => filter.id === column.id);
|
||||||
|
if (column.datatype === "array") {
|
||||||
|
acc[column.id] = {
|
||||||
|
id: column.id,
|
||||||
|
value: storeFilter?.value || [],
|
||||||
|
filterMode: storeFilter?.filterMode || column.filterMode,
|
||||||
|
datatype: column.datatype,
|
||||||
|
};
|
||||||
|
} else if (column.filterMode === "between") {
|
||||||
|
acc[column.id] = {
|
||||||
|
id: column.id,
|
||||||
|
value: storeFilter?.value || ["", ""],
|
||||||
|
filterMode: storeFilter?.filterMode || column.filterMode,
|
||||||
|
datatype: column.datatype,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
acc[column.id] = {
|
||||||
|
id: column.id,
|
||||||
|
value: storeFilter?.value || "",
|
||||||
|
filterMode: storeFilter?.filterMode || column.filterMode,
|
||||||
|
datatype: column.datatype,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const getHideValues = (columns) => {
|
||||||
|
return columns.reduce((acc, column) => {
|
||||||
|
const columnId = column.id;
|
||||||
|
const storeHide =
|
||||||
|
settingStore?.[user_id]?.[page_name]?.[table_name]?.["hides"]?.[
|
||||||
|
columnId
|
||||||
|
];
|
||||||
|
if (column.columns && column.columns.length > 0) {
|
||||||
|
acc[columnId] = getHideValues(column.columns);
|
||||||
|
} else {
|
||||||
|
acc[columnId] = storeHide !== undefined ? storeHide : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
const hideValues = getHideValues(columns);
|
||||||
|
setHideData(hideValues);
|
||||||
|
setInitHide(hideValues);
|
||||||
|
setFilterData(filterValues);
|
||||||
|
setInitFilter(filterValues);
|
||||||
|
setSortData(
|
||||||
|
settingStore?.[user_id]?.[page_name]?.[table_name]?.["sorts"] ||
|
||||||
|
initialSort,
|
||||||
|
);
|
||||||
|
setInitSort(
|
||||||
|
settingStore?.[user_id]?.[page_name]?.[table_name]?.["sorts"] ||
|
||||||
|
initialSort,
|
||||||
|
);
|
||||||
|
setInitStates(true);
|
||||||
|
}, [settingStore]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
JSON.stringify(initFilter) !== JSON.stringify(filterData) ||
|
||||||
|
JSON.stringify(initSort) !== JSON.stringify(sortData) ||
|
||||||
|
JSON.stringify(initHide) !== JSON.stringify(hideData)
|
||||||
|
) {
|
||||||
|
if (!toast.isActive("keep_data", "filtering")) {
|
||||||
|
toast(
|
||||||
|
<AskForKeepData
|
||||||
|
filterData={filterData}
|
||||||
|
sortData={sortData}
|
||||||
|
hideData={hideData}
|
||||||
|
user_id={user_id}
|
||||||
|
page_name={page_name}
|
||||||
|
table_name={table_name}
|
||||||
|
columns={flatColumns}
|
||||||
|
/>,
|
||||||
|
{
|
||||||
|
containerId: "filtering",
|
||||||
|
toastId: "keep_data",
|
||||||
|
className: "filter-toast",
|
||||||
|
position: "bottom-left",
|
||||||
|
draggable: true,
|
||||||
|
autoClose: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
toast.update("keep_data", {
|
||||||
|
containerId: "filtering",
|
||||||
|
toastId: "keep_data",
|
||||||
|
render: (
|
||||||
|
<AskForKeepData
|
||||||
|
filterData={filterData}
|
||||||
|
sortData={sortData}
|
||||||
|
hideData={hideData}
|
||||||
|
user_id={user_id}
|
||||||
|
page_name={page_name}
|
||||||
|
table_name={table_name}
|
||||||
|
columns={flatColumns}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast.dismiss({ containerId: "datatable" });
|
||||||
|
}
|
||||||
|
}, [filterData, sortData, hideData]);
|
||||||
|
|
||||||
|
if (!isInitStates) return <LinearProgress sx={{ borderRadius: 2 }} />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataTableContext.Provider
|
||||||
|
value={{
|
||||||
|
filterData,
|
||||||
|
setFilterData,
|
||||||
|
sortData,
|
||||||
|
setSortData,
|
||||||
|
hideData,
|
||||||
|
setHideData,
|
||||||
|
isDirty,
|
||||||
|
isAnyDirty,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</DataTableContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DataTableProvider;
|
||||||
@@ -24,6 +24,7 @@ export const SocketProvider = ({ children }) => {
|
|||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const socketToastId = useRef(null);
|
const socketToastId = useRef(null);
|
||||||
const [status, setStatus] = useState("");
|
const [status, setStatus] = useState("");
|
||||||
|
const [clientsOnline, setClientsOnline] = useState([]);
|
||||||
|
|
||||||
const socket = useMemo(() => {
|
const socket = useMemo(() => {
|
||||||
return io(`${process.env.NEXT_PUBLIC_SERVER_SOCKET_URL}`, {
|
return io(`${process.env.NEXT_PUBLIC_SERVER_SOCKET_URL}`, {
|
||||||
@@ -35,7 +36,7 @@ export const SocketProvider = ({ children }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user?.telephone_id) {
|
if (user?.telephone_id) {
|
||||||
socket.auth.token = user.telephone_id;
|
socket.auth.token = { telephone_id: user.telephone_id, user_id: user.id };
|
||||||
}
|
}
|
||||||
}, [user, socket]);
|
}, [user, socket]);
|
||||||
|
|
||||||
@@ -92,7 +93,31 @@ export const SocketProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}, [status]);
|
}, [status]);
|
||||||
|
|
||||||
const contextValue = useMemo(() => ({ socket, status }), [socket, status]);
|
useEffect(() => {
|
||||||
|
if (status == "connected") return;
|
||||||
|
|
||||||
|
socket.connect();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
socket.disconnect();
|
||||||
|
};
|
||||||
|
}, [socket]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleNotification = (data) => {
|
||||||
|
setClientsOnline(data.clients);
|
||||||
|
};
|
||||||
|
socket.on("onlineClientsUpdated", handleNotification);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
socket.off("onlineClientsUpdated", handleNotification);
|
||||||
|
};
|
||||||
|
}, [socket]);
|
||||||
|
|
||||||
|
const contextValue = useMemo(
|
||||||
|
() => ({ socket, status, clientsOnline }),
|
||||||
|
[socket, status, clientsOnline],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SocketContext.Provider value={contextValue}>
|
<SocketContext.Provider value={contextValue}>
|
||||||
|
|||||||
27
src/lib/hooks/useDataTable.js
Normal file
27
src/lib/hooks/useDataTable.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import { DataTableContext } from "@/lib/contexts/DataTable";
|
||||||
|
|
||||||
|
const useTableSetting = () => {
|
||||||
|
const {
|
||||||
|
filterData,
|
||||||
|
setFilterData,
|
||||||
|
sortData,
|
||||||
|
setSortData,
|
||||||
|
hideData,
|
||||||
|
setHideData,
|
||||||
|
isDirty,
|
||||||
|
isAnyDirty,
|
||||||
|
} = useContext(DataTableContext);
|
||||||
|
return {
|
||||||
|
filterData,
|
||||||
|
setFilterData,
|
||||||
|
sortData,
|
||||||
|
setSortData,
|
||||||
|
hideData,
|
||||||
|
setHideData,
|
||||||
|
isDirty,
|
||||||
|
isAnyDirty,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useTableSetting;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user