| @@ -16,7 +16,10 @@ | |||
| "@mui/icons-material": "^5.15.0", | |||
| "@mui/material": "^5.15.0", | |||
| "@mui/material-nextjs": "^5.15.0", | |||
| "@mui/x-data-grid": "^6.18.7", | |||
| "@mui/x-date-pickers": "^6.18.7", | |||
| "@unly/universal-language-detector": "^2.0.3", | |||
| "dayjs": "^1.11.10", | |||
| "i18next": "^23.7.11", | |||
| "i18next-resources-to-backend": "^1.2.0", | |||
| "next": "14.0.4", | |||
| @@ -24,7 +27,8 @@ | |||
| "react": "^18", | |||
| "react-dom": "^18", | |||
| "react-hook-form": "^7.49.2", | |||
| "react-i18next": "^13.5.0" | |||
| "react-i18next": "^13.5.0", | |||
| "react-intl": "^6.5.5" | |||
| }, | |||
| "devDependencies": { | |||
| "@types/node": "^20", | |||
| @@ -371,6 +375,92 @@ | |||
| "resolved": "https://registry.npmjs.org/@fontsource/plus-jakarta-sans/-/plus-jakarta-sans-5.0.18.tgz", | |||
| "integrity": "sha512-poMuIcQ8F7WGXF4mNUviDk49Ewdf0pU7wmCzWQNbWEtus+L46BSp+4OqbWy0LWJEmMLI9F5hUHaSo2maLJwrQw==" | |||
| }, | |||
| "node_modules/@formatjs/ecma402-abstract": { | |||
| "version": "1.18.0", | |||
| "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.0.tgz", | |||
| "integrity": "sha512-PEVLoa3zBevWSCZzPIM/lvPCi8P5l4G+NXQMc/CjEiaCWgyHieUoo0nM7Bs0n/NbuQ6JpXEolivQ9pKSBHaDlA==", | |||
| "dependencies": { | |||
| "@formatjs/intl-localematcher": "0.5.2", | |||
| "tslib": "^2.4.0" | |||
| } | |||
| }, | |||
| "node_modules/@formatjs/fast-memoize": { | |||
| "version": "2.2.0", | |||
| "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz", | |||
| "integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==", | |||
| "dependencies": { | |||
| "tslib": "^2.4.0" | |||
| } | |||
| }, | |||
| "node_modules/@formatjs/icu-messageformat-parser": { | |||
| "version": "2.7.3", | |||
| "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.3.tgz", | |||
| "integrity": "sha512-X/jy10V9S/vW+qlplqhMUxR8wErQ0mmIYSq4mrjpjDl9mbuGcCILcI1SUYkL5nlM4PJqpc0KOS0bFkkJNPxYRw==", | |||
| "dependencies": { | |||
| "@formatjs/ecma402-abstract": "1.18.0", | |||
| "@formatjs/icu-skeleton-parser": "1.7.0", | |||
| "tslib": "^2.4.0" | |||
| } | |||
| }, | |||
| "node_modules/@formatjs/icu-skeleton-parser": { | |||
| "version": "1.7.0", | |||
| "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.7.0.tgz", | |||
| "integrity": "sha512-Cfdo/fgbZzpN/jlN/ptQVe0lRHora+8ezrEeg2RfrNjyp+YStwBy7cqDY8k5/z2LzXg6O0AdzAV91XS0zIWv+A==", | |||
| "dependencies": { | |||
| "@formatjs/ecma402-abstract": "1.18.0", | |||
| "tslib": "^2.4.0" | |||
| } | |||
| }, | |||
| "node_modules/@formatjs/intl": { | |||
| "version": "2.9.9", | |||
| "resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.9.9.tgz", | |||
| "integrity": "sha512-JI3CNgL2Zdg5lv9ncT2sYKqbAj2RGrCbdzaCckIxMPxn4QuHuOVvYUGmBAXVusBmfG/0sxLmMrnwnBioz+QKdA==", | |||
| "dependencies": { | |||
| "@formatjs/ecma402-abstract": "1.18.0", | |||
| "@formatjs/fast-memoize": "2.2.0", | |||
| "@formatjs/icu-messageformat-parser": "2.7.3", | |||
| "@formatjs/intl-displaynames": "6.6.4", | |||
| "@formatjs/intl-listformat": "7.5.3", | |||
| "intl-messageformat": "10.5.8", | |||
| "tslib": "^2.4.0" | |||
| }, | |||
| "peerDependencies": { | |||
| "typescript": "5" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "typescript": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@formatjs/intl-displaynames": { | |||
| "version": "6.6.4", | |||
| "resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-6.6.4.tgz", | |||
| "integrity": "sha512-ET8KQ+L9Q0K8x1SnJQa4DNssUcbATlMopWqYvGGR8yAvw5qwAQc1fv+DshCoZNIE9pbcue0IGC4kWNAkWqlFag==", | |||
| "dependencies": { | |||
| "@formatjs/ecma402-abstract": "1.18.0", | |||
| "@formatjs/intl-localematcher": "0.5.2", | |||
| "tslib": "^2.4.0" | |||
| } | |||
| }, | |||
| "node_modules/@formatjs/intl-listformat": { | |||
| "version": "7.5.3", | |||
| "resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.5.3.tgz", | |||
| "integrity": "sha512-l7EOr0Yh1m8KagytukB90yw81uyzrM7amKFrgxXqphz4KeSIL0KPa68lPsdtZ+JmQB73GaDQRwLOwUKFZ1VZPQ==", | |||
| "dependencies": { | |||
| "@formatjs/ecma402-abstract": "1.18.0", | |||
| "@formatjs/intl-localematcher": "0.5.2", | |||
| "tslib": "^2.4.0" | |||
| } | |||
| }, | |||
| "node_modules/@formatjs/intl-localematcher": { | |||
| "version": "0.5.2", | |||
| "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.2.tgz", | |||
| "integrity": "sha512-txaaE2fiBMagLrR4jYhxzFO6wEdEG4TPMqrzBAcbr4HFUYzH/YC+lg6OIzKCHm8WgDdyQevxbAAV1OgcXctuGw==", | |||
| "dependencies": { | |||
| "tslib": "^2.4.0" | |||
| } | |||
| }, | |||
| "node_modules/@humanwhocodes/config-array": { | |||
| "version": "0.11.13", | |||
| "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", | |||
| @@ -782,6 +872,96 @@ | |||
| "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", | |||
| "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" | |||
| }, | |||
| "node_modules/@mui/x-data-grid": { | |||
| "version": "6.18.7", | |||
| "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-6.18.7.tgz", | |||
| "integrity": "sha512-K1A3pMUPxI4/Mt5A4vrK45fBBQK5rZvBVqRMrB5n8zX++Bj+WLWKvLTtfCmlriUtzuadr/Hl7Z+FDRXUJAx6qg==", | |||
| "dependencies": { | |||
| "@babel/runtime": "^7.23.2", | |||
| "@mui/utils": "^5.14.16", | |||
| "clsx": "^2.0.0", | |||
| "prop-types": "^15.8.1", | |||
| "reselect": "^4.1.8" | |||
| }, | |||
| "engines": { | |||
| "node": ">=14.0.0" | |||
| }, | |||
| "funding": { | |||
| "type": "opencollective", | |||
| "url": "https://opencollective.com/mui" | |||
| }, | |||
| "peerDependencies": { | |||
| "@mui/material": "^5.4.1", | |||
| "@mui/system": "^5.4.1", | |||
| "react": "^17.0.0 || ^18.0.0", | |||
| "react-dom": "^17.0.0 || ^18.0.0" | |||
| } | |||
| }, | |||
| "node_modules/@mui/x-date-pickers": { | |||
| "version": "6.18.7", | |||
| "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.18.7.tgz", | |||
| "integrity": "sha512-4NoapaCT3jvEk2cuAUjG0ReZvTEk1i4dGDz94Gt1Oc08GuC1AuzYRwCR1/1tdmbDynwkR8ilkKL6AyS3NL1H4A==", | |||
| "dependencies": { | |||
| "@babel/runtime": "^7.23.2", | |||
| "@mui/base": "^5.0.0-beta.22", | |||
| "@mui/utils": "^5.14.16", | |||
| "@types/react-transition-group": "^4.4.8", | |||
| "clsx": "^2.0.0", | |||
| "prop-types": "^15.8.1", | |||
| "react-transition-group": "^4.4.5" | |||
| }, | |||
| "engines": { | |||
| "node": ">=14.0.0" | |||
| }, | |||
| "funding": { | |||
| "type": "opencollective", | |||
| "url": "https://opencollective.com/mui" | |||
| }, | |||
| "peerDependencies": { | |||
| "@emotion/react": "^11.9.0", | |||
| "@emotion/styled": "^11.8.1", | |||
| "@mui/material": "^5.8.6", | |||
| "@mui/system": "^5.8.0", | |||
| "date-fns": "^2.25.0", | |||
| "date-fns-jalali": "^2.13.0-0", | |||
| "dayjs": "^1.10.7", | |||
| "luxon": "^3.0.2", | |||
| "moment": "^2.29.4", | |||
| "moment-hijri": "^2.1.2", | |||
| "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", | |||
| "react": "^17.0.0 || ^18.0.0", | |||
| "react-dom": "^17.0.0 || ^18.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 | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@next/env": { | |||
| "version": "14.0.4", | |||
| "resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.4.tgz", | |||
| @@ -1039,6 +1219,15 @@ | |||
| "tslib": "^2.4.0" | |||
| } | |||
| }, | |||
| "node_modules/@types/hoist-non-react-statics": { | |||
| "version": "3.3.5", | |||
| "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", | |||
| "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", | |||
| "dependencies": { | |||
| "@types/react": "*", | |||
| "hoist-non-react-statics": "^3.3.0" | |||
| } | |||
| }, | |||
| "node_modules/@types/json5": { | |||
| "version": "0.0.29", | |||
| "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", | |||
| @@ -1885,6 +2074,11 @@ | |||
| "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", | |||
| "dev": true | |||
| }, | |||
| "node_modules/dayjs": { | |||
| "version": "1.11.10", | |||
| "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", | |||
| "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" | |||
| }, | |||
| "node_modules/debug": { | |||
| "version": "4.3.4", | |||
| "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", | |||
| @@ -3257,6 +3451,17 @@ | |||
| "node": ">= 0.4" | |||
| } | |||
| }, | |||
| "node_modules/intl-messageformat": { | |||
| "version": "10.5.8", | |||
| "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.8.tgz", | |||
| "integrity": "sha512-NRf0jpBWV0vd671G5b06wNofAN8tp7WWDogMZyaU8GUAsmbouyvgwmFJI7zLjfAMpm3zK+vSwRP3jzaoIcMbaA==", | |||
| "dependencies": { | |||
| "@formatjs/ecma402-abstract": "1.18.0", | |||
| "@formatjs/fast-memoize": "2.2.0", | |||
| "@formatjs/icu-messageformat-parser": "2.7.3", | |||
| "tslib": "^2.4.0" | |||
| } | |||
| }, | |||
| "node_modules/is-array-buffer": { | |||
| "version": "3.0.2", | |||
| "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", | |||
| @@ -4697,6 +4902,32 @@ | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/react-intl": { | |||
| "version": "6.5.5", | |||
| "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.5.5.tgz", | |||
| "integrity": "sha512-cI5UKvBh4tc1zxLIziHBYGMX3dhYWDEFlvUDVN6NfT2i96zTXz/zH2AmM8+2waqgOhwkFUzd+7kK1G9q7fiC2g==", | |||
| "dependencies": { | |||
| "@formatjs/ecma402-abstract": "1.18.0", | |||
| "@formatjs/icu-messageformat-parser": "2.7.3", | |||
| "@formatjs/intl": "2.9.9", | |||
| "@formatjs/intl-displaynames": "6.6.4", | |||
| "@formatjs/intl-listformat": "7.5.3", | |||
| "@types/hoist-non-react-statics": "^3.3.1", | |||
| "@types/react": "16 || 17 || 18", | |||
| "hoist-non-react-statics": "^3.3.2", | |||
| "intl-messageformat": "10.5.8", | |||
| "tslib": "^2.4.0" | |||
| }, | |||
| "peerDependencies": { | |||
| "react": "^16.6.0 || 17 || 18", | |||
| "typescript": "5" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "typescript": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/react-is": { | |||
| "version": "16.13.1", | |||
| "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", | |||
| @@ -4780,6 +5011,11 @@ | |||
| "url": "https://github.com/sponsors/ljharb" | |||
| } | |||
| }, | |||
| "node_modules/reselect": { | |||
| "version": "4.1.8", | |||
| "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", | |||
| "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==" | |||
| }, | |||
| "node_modules/resolve": { | |||
| "version": "1.22.8", | |||
| "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", | |||
| @@ -5567,7 +5803,7 @@ | |||
| "version": "5.3.3", | |||
| "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", | |||
| "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", | |||
| "dev": true, | |||
| "devOptional": true, | |||
| "bin": { | |||
| "tsc": "bin/tsc", | |||
| "tsserver": "bin/tsserver" | |||
| @@ -17,7 +17,10 @@ | |||
| "@mui/icons-material": "^5.15.0", | |||
| "@mui/material": "^5.15.0", | |||
| "@mui/material-nextjs": "^5.15.0", | |||
| "@mui/x-data-grid": "^6.18.7", | |||
| "@mui/x-date-pickers": "^6.18.7", | |||
| "@unly/universal-language-detector": "^2.0.3", | |||
| "dayjs": "^1.11.10", | |||
| "i18next": "^23.7.11", | |||
| "i18next-resources-to-backend": "^1.2.0", | |||
| "next": "14.0.4", | |||
| @@ -25,7 +28,8 @@ | |||
| "react": "^18", | |||
| "react-dom": "^18", | |||
| "react-hook-form": "^7.49.2", | |||
| "react-i18next": "^13.5.0" | |||
| "react-i18next": "^13.5.0", | |||
| "react-intl": "^6.5.5" | |||
| }, | |||
| "devDependencies": { | |||
| "@types/node": "^20", | |||
| @@ -0,0 +1,161 @@ | |||
| import * as React from 'react'; | |||
| import { Card, CardHeader, CardContent, SxProps, Theme } from '@mui/material'; | |||
| import { DataGrid, GridColDef } from '@mui/x-data-grid'; | |||
| import { darken, lighten, styled } from '@mui/material/styles'; | |||
| interface CustomDatagridProps { | |||
| Title?: string; | |||
| rows: any[]; | |||
| columns: any[]; | |||
| columnWidth?: number; | |||
| Style?: boolean; | |||
| sx?: SxProps<Theme>; | |||
| dataGridHeight?: number; | |||
| [key: string]: any; | |||
| } | |||
| const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||
| Title, | |||
| rows, | |||
| columns, | |||
| columnWidth, | |||
| Style = true, | |||
| sx, | |||
| dataGridHeight, | |||
| ...props | |||
| }) => { | |||
| const modifiedColumns = columns.map((column) => { | |||
| return { | |||
| ...column, | |||
| width: columnWidth ?? 150, | |||
| }; | |||
| }); | |||
| const rowsWithDefaultValues = rows.map((row) => { | |||
| return { ...row }; | |||
| }); | |||
| const getBackgroundColor = (color: string, mode: 'light' | 'dark') => | |||
| mode === 'dark' ? darken(color, 0.7) : lighten(color, 0.7); | |||
| const getHoverBackgroundColor = (color: string, mode: 'light' | 'dark') => | |||
| mode === 'dark' ? darken(color, 0.6) : lighten(color, 0.6); | |||
| const getSelectedBackgroundColor = (color: string, mode: 'light' | 'dark') => | |||
| mode === 'dark' ? darken(color, 0.5) : lighten(color, 0.5); | |||
| const getSelectedHoverBackgroundColor = (color: string, mode: 'light' | 'dark') => | |||
| mode === 'dark' ? darken(color, 0.4) : lighten(color, 0.4); | |||
| const StyledDataGrid = styled(DataGrid)(({ theme }) => ({ | |||
| '& .super-app-theme--Open': { | |||
| backgroundColor: getBackgroundColor(theme.palette.info.main, theme.palette.mode), | |||
| '&:hover': { | |||
| backgroundColor: getHoverBackgroundColor(theme.palette.info.main, theme.palette.mode) | |||
| }, | |||
| '&.Mui-selected': { | |||
| backgroundColor: getSelectedBackgroundColor(theme.palette.info.main, theme.palette.mode), | |||
| '&:hover': { | |||
| backgroundColor: getSelectedHoverBackgroundColor(theme.palette.info.main, theme.palette.mode) | |||
| } | |||
| } | |||
| }, | |||
| '& .super-app-theme--finish': { | |||
| backgroundColor: getBackgroundColor(theme.palette.success.main, theme.palette.mode), | |||
| '&:hover': { | |||
| backgroundColor: getHoverBackgroundColor(theme.palette.success.main, theme.palette.mode) | |||
| }, | |||
| '&.Mui-selected': { | |||
| backgroundColor: getSelectedBackgroundColor(theme.palette.success.main, theme.palette.mode), | |||
| '&:hover': { | |||
| backgroundColor: getSelectedHoverBackgroundColor(theme.palette.success.main, theme.palette.mode) | |||
| } | |||
| } | |||
| }, | |||
| '& .super-app-theme--danger': { | |||
| backgroundColor: getBackgroundColor(theme.palette.warning.main, theme.palette.mode), | |||
| '&:hover': { | |||
| backgroundColor: getHoverBackgroundColor(theme.palette.warning.main, theme.palette.mode) | |||
| }, | |||
| '&.Mui-selected': { | |||
| backgroundColor: getSelectedBackgroundColor(theme.palette.warning.main, theme.palette.mode), | |||
| '&:hover': { | |||
| backgroundColor: getSelectedHoverBackgroundColor(theme.palette.warning.main, theme.palette.mode) | |||
| } | |||
| } | |||
| }, | |||
| '& .super-app-theme--warning': { | |||
| backgroundColor: getBackgroundColor(theme.palette.error.main, theme.palette.mode), | |||
| '&:hover': { | |||
| backgroundColor: getHoverBackgroundColor(theme.palette.error.main, theme.palette.mode) | |||
| }, | |||
| '&.Mui-selected': { | |||
| backgroundColor: getSelectedBackgroundColor(theme.palette.error.main, theme.palette.mode), | |||
| '&:hover': { | |||
| backgroundColor: getSelectedHoverBackgroundColor(theme.palette.error.main, theme.palette.mode) | |||
| } | |||
| } | |||
| } | |||
| })); | |||
| return ( | |||
| <div className="mt-5" style={{ height: dataGridHeight ?? 400, width: '100%' }}> | |||
| <Card> | |||
| {Title && <CardHeader title={Title} />} | |||
| <CardContent style={{ display: "flex", alignItems: "center", justifyContent: "center" }}> | |||
| {Style ? ( | |||
| <StyledDataGrid | |||
| rows={rowsWithDefaultValues} | |||
| columns={modifiedColumns} | |||
| editMode="row" | |||
| initialState={{ | |||
| pagination: { paginationModel: { pageSize: 10 } }, | |||
| }} | |||
| className="customDataGrid" | |||
| sx={{ | |||
| boxShadow: 2, | |||
| border: 2, | |||
| borderColor: 'primary.light', | |||
| '& .MuiDataGrid-cell:hover': { | |||
| color: 'primary.main' | |||
| }, | |||
| height: dataGridHeight ?? 400, | |||
| '& .MuiDataGrid-root': { | |||
| overflow: 'auto', | |||
| }, | |||
| ...sx | |||
| }} | |||
| {...props} | |||
| /> | |||
| ) : ( | |||
| <DataGrid | |||
| rows={rowsWithDefaultValues} | |||
| columns={modifiedColumns} | |||
| editMode="row" | |||
| initialState={{ | |||
| pagination: { paginationModel: { pageSize: 10 } }, | |||
| }} | |||
| className="customDataGrid" | |||
| sx={{ | |||
| boxShadow: 2, | |||
| border: 2, | |||
| borderColor: 'primary.light', | |||
| '& .MuiDataGrid-cell:hover': { | |||
| color: 'primary.main' | |||
| }, | |||
| height: 400, | |||
| '& .MuiDataGrid-root': { | |||
| overflow: 'auto', | |||
| }, | |||
| ...sx | |||
| }} | |||
| {...props} | |||
| /> | |||
| )} | |||
| </CardContent> | |||
| </Card> | |||
| </div> | |||
| ); | |||
| }; | |||
| export default CustomDatagrid; | |||
| @@ -0,0 +1 @@ | |||
| export { default } from "./CustomDatagrid"; | |||
| @@ -0,0 +1,263 @@ | |||
| "use client"; | |||
| import React, { useState, FC } from 'react'; | |||
| import { | |||
| Stack, | |||
| Typography, | |||
| FormControlLabel, | |||
| Checkbox, | |||
| Autocomplete, | |||
| Button, | |||
| Grid, | |||
| TextField, | |||
| CardHeader, | |||
| Card, | |||
| FormControl, | |||
| InputLabel, | |||
| Select, | |||
| MenuItem, | |||
| ThemeProvider | |||
| } from '@mui/material'; | |||
| import { useForm, Controller } from 'react-hook-form'; | |||
| import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; | |||
| import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; | |||
| import dayjs from 'dayjs'; | |||
| import SearchIcon from '@mui/icons-material/Search'; | |||
| import RefreshIcon from '@mui/icons-material/Refresh'; | |||
| import { DemoItem } from '@mui/x-date-pickers/internals/demo'; | |||
| import { DatePicker } from '@mui/x-date-pickers/DatePicker'; | |||
| interface Field { | |||
| id: any; | |||
| label: any; | |||
| type: string; | |||
| options?: Array<{ id: string; label: string }>; | |||
| setValue: ((value: any) => void) | Array<(value: any) => void>; | |||
| value?: any; | |||
| required?: boolean; | |||
| } | |||
| interface FormComponentProps { | |||
| fields: Field[]; | |||
| onSubmit: (data: any) => void; | |||
| resetForm: () => void; | |||
| sx?: any; | |||
| } | |||
| interface SearchFormProps { | |||
| applySearch: (data: any) => void; | |||
| fields: Field[]; | |||
| title?: string; | |||
| sx?: any; | |||
| } | |||
| const FormComponent: FC<FormComponentProps> = ({ fields, onSubmit, resetForm, sx }) => { | |||
| const { reset, register, handleSubmit, control } = useForm(); | |||
| const [fromDate, setFromDate] = useState<dayjs.Dayjs | null>(null); | |||
| const [dayRangeFromDate, setDayRangeFromDate] = useState<dayjs.Dayjs | null>(null); | |||
| const [dayRangeToDate, setDayRangeToDate] = useState<dayjs.Dayjs | null>(null); | |||
| const [value, setValue] = useState<{ [key: string]: any }>({}); | |||
| const [checkbox1, setCheckbox1] = useState(false); | |||
| const handleFormSubmit = (data: any) => { | |||
| if (fromDate != null || fromDate != undefined) { | |||
| data.fromDate = dayjs(fromDate).format('YYYY-MM-DD'); | |||
| } | |||
| if (value !== null) { | |||
| data.dropdownCombo = value | |||
| } | |||
| if (value !== null) { | |||
| data.checkbox = checkbox1; | |||
| } | |||
| if (dayRangeFromDate != null || dayRangeFromDate != undefined) { | |||
| data.dayRangeFromDate = dayjs(dayRangeFromDate).format('YYYY-MM-DD'); | |||
| } | |||
| if (dayRangeToDate != null || dayRangeToDate != undefined) { | |||
| data.dayRangeToDate = dayjs(dayRangeToDate).format('YYYY-MM-DD'); | |||
| } | |||
| onSubmit(data); | |||
| }; | |||
| const handleFormReset = () => { | |||
| reset(); | |||
| resetForm(); | |||
| setFromDate(null); | |||
| fields.forEach((field) => { | |||
| if (typeof(field.setValue) === "function") { | |||
| field.setValue(typeof (field.value) === "boolean" ? false : null); | |||
| } else if (Array.isArray(field.setValue)) { | |||
| field.setValue.forEach((setFunc) => { | |||
| setFunc(null); | |||
| }); | |||
| } | |||
| }); | |||
| }; | |||
| return ( | |||
| <form onSubmit={handleSubmit(handleFormSubmit)}> | |||
| <Grid container alignItems="center"> | |||
| {fields.map((field) => { | |||
| if (field.type === 'dropdown') { | |||
| return ( | |||
| <Grid item xs={12} sm={5.5} md={5.5} lg={5.5} sx={{ ml: 3, mr: 3, mb: 3 }} key={field.id}> | |||
| <FormControl fullWidth> | |||
| <InputLabel id={`${field.id}-label`}>{field.label}</InputLabel> | |||
| <Controller | |||
| name={field.id} | |||
| control={control} | |||
| defaultValue="" | |||
| render={({ field: { onChange, value } }) => ( | |||
| <Select | |||
| labelId={`${field.id}-label`} | |||
| id={field.id} | |||
| value={value} | |||
| onChange={(e) => { | |||
| onChange(e.target.value); | |||
| }} | |||
| > | |||
| {field.options?.map((option) => ( | |||
| <MenuItem | |||
| value={option.id ?? JSON.stringify(option)} | |||
| key={option.id ?? JSON.stringify(option)} | |||
| > | |||
| {option.id !== undefined ? option.label : JSON.stringify(option)} | |||
| </MenuItem> | |||
| ))} | |||
| </Select> | |||
| )} | |||
| /> | |||
| </FormControl> | |||
| </Grid> | |||
| ); | |||
| } else if (field.type === 'date') { | |||
| return ( | |||
| <Grid item xs={12} sm={5.5} md={5.5} lg={5.5} sx={{ ml: 3, mr: 3, mb: 3 }} key={field.id}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <DemoItem> | |||
| <DatePicker | |||
| slotProps={{ | |||
| textField: { | |||
| id:field.id, | |||
| }, | |||
| }} | |||
| label={field.label} | |||
| value={fromDate === null ? null : dayjs(fromDate)} | |||
| onChange={(newValue) => { | |||
| setFromDate(newValue); | |||
| }} | |||
| /> | |||
| </DemoItem> | |||
| </LocalizationProvider> | |||
| </Grid> | |||
| ); | |||
| } else if (field.type === 'checkbox') { | |||
| return ( | |||
| <Grid item xs={12} sm={5.5} md={5.5} lg={5.5} sx={{ ml: 3, mr: 3, mb: 3 }} key={field.id}> | |||
| <FormControlLabel | |||
| control={ | |||
| <Checkbox | |||
| id={field.id} | |||
| checked={field.value} | |||
| onChange={(event) => { | |||
| if (typeof field.setValue === 'function') { | |||
| field.setValue(event.target.checked); | |||
| setCheckbox1(event.target.checked); | |||
| } | |||
| }} | |||
| color="primary" | |||
| /> | |||
| } | |||
| label={<Typography style={{fontSize:"1.15em"}}>{field.label}</Typography>} | |||
| /> | |||
| </Grid> | |||
| ) | |||
| } else if (field.type === 'dateRange') { | |||
| return ( | |||
| <Grid container key={field.id[0]}> | |||
| <Grid item xs={12} sm={7} md={7} lg={7} sx={{ ml: 3, mr: 3, mb: 3 }}> | |||
| <Grid container> | |||
| <Grid> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <DatePicker | |||
| label={field.label[0]} | |||
| value={field.value[0]} | |||
| onChange={(newValue) => setDayRangeFromDate(newValue)} | |||
| /> | |||
| </LocalizationProvider> | |||
| </Grid> | |||
| <Grid item xs={1.5} sm={1.5} md={1.5} lg={1.5} sx={{ display: 'flex', justifyContent: "center", alignItems: 'center' }}> | |||
| To | |||
| </Grid> | |||
| <Grid item xs={5.25} sm={5.25} md={5.25} lg={5.25}> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <DatePicker | |||
| label={field.label[1]} | |||
| value={field.value[1]} | |||
| onChange={(newValue) => setDayRangeToDate(newValue)} | |||
| /> | |||
| </LocalizationProvider> | |||
| </Grid> | |||
| </Grid> | |||
| </Grid> | |||
| </Grid> | |||
| ) | |||
| } | |||
| return ( | |||
| <Grid item xs={12} sm={5.5} md={5.5} lg={5.5} sx={{ ml: 3, mr: 3, mb: 3 }} key={field.id}> | |||
| <TextField | |||
| fullWidth | |||
| {...register(field.id)} | |||
| id={field.id} | |||
| label={field.label} | |||
| defaultValue={field.value !== undefined && field.value !== null ? `${field.value}` : ''} | |||
| required={field.required === true ? field.required : false} | |||
| sx={{ ...sx }} | |||
| /> | |||
| </Grid> | |||
| ); | |||
| })} | |||
| </Grid> | |||
| <Grid container maxWidth="lg" justifyContent="space-between" style={{marginTop:-20}}> | |||
| <Stack direction="row"> | |||
| <Grid item sx={{ ml: 3, mr: 3, mb: 3, mt: 3 }}> | |||
| <Button className="h-12 w-32" style={{backgroundColor:"#92c1e9",color:"white",fontSize:"1.15em",fontWeight:100}} type="submit"> | |||
| <SearchIcon/> Search | |||
| </Button> | |||
| </Grid> | |||
| <Grid item sx={{ ml: 3, mr: 3, mb: 3, mt: 3 }}> | |||
| <Button className="h-12 w-32" style={{backgroundColor:"#f890a5",color:"white",fontSize:"1.15em",fontWeight:100}} onClick={handleFormReset}> | |||
| <RefreshIcon/> Reset | |||
| </Button> | |||
| </Grid> | |||
| </Stack> | |||
| </Grid> | |||
| </form> | |||
| ); | |||
| }; | |||
| const CustomSearchForm: FC<SearchFormProps> = ({ applySearch, fields, title, sx }) => { | |||
| const Title = title || "Searching Criteria"; | |||
| const handleSubmit = (data: any) => { | |||
| if (applySearch) { | |||
| applySearch(data); | |||
| } else { | |||
| console.log('applySearch function is null'); | |||
| } | |||
| }; | |||
| const handleFormReset = () => { | |||
| console.log('Form Reset'); | |||
| }; | |||
| return ( | |||
| <Card style={{marginRight:20}}> | |||
| <CardHeader className="text-slate-500" style={{marginTop:-20}} titleTypographyProps={{ variant: 'h5' }} title={Title}></CardHeader> | |||
| <FormComponent fields={fields} onSubmit={handleSubmit} resetForm={handleFormReset} sx={sx} /> | |||
| </Card> | |||
| ); | |||
| }; | |||
| export default CustomSearchForm; | |||
| @@ -0,0 +1 @@ | |||
| export { default } from "./CustomSearchForm"; | |||
| @@ -5,6 +5,7 @@ import Paper from "@mui/material/Paper"; | |||
| import { TFunction } from "i18next"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import PageTitle from "../PageTitle/PageTitle"; | |||
| import ProgressByClient from "./ProgressByClient"; | |||
| import '../../app/global.css'; | |||
| const DashboardTabButton: React.FC = () => { | |||
| @@ -16,8 +17,8 @@ const DashboardTabButton: React.FC = () => { | |||
| return <div>Project Financial Summary</div>; | |||
| case 'cashFlow': | |||
| return <div>Project Cash Flow</div>; | |||
| case 'projectProgress': | |||
| return <div>Project Progress by Client</div>; | |||
| case 'progressByClient': | |||
| return <ProgressByClient/>; | |||
| case 'resourceUtilization': | |||
| return <div>Project Resource Utilization</div>; | |||
| case 'staffUtilization': | |||
| @@ -0,0 +1,115 @@ | |||
| "use client"; | |||
| import * as React from "react"; | |||
| import Grid from "@mui/material/Grid"; | |||
| import { useState } from 'react' | |||
| import Paper from "@mui/material/Paper"; | |||
| import { TFunction } from "i18next"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import {Card,CardHeader} from '@mui/material'; | |||
| import CustomSearchForm from "../CustomSearchForm/CustomSearchForm"; | |||
| import CustomDatagrid from '../CustomDatagrid/CustomDatagrid'; | |||
| import '../../app/global.css'; | |||
| const ProgressByClient: React.FC = () => { | |||
| const [activeTab, setActiveTab] = useState('financialSummary'); | |||
| const [SearchCriteria, setSearchCriteria] = React.useState({}) | |||
| const { t } = useTranslation("dashboard"); | |||
| const [clientCode, setClientCode] = useState(''); | |||
| const [clientName, setClientName] = useState(''); | |||
| const [subsidiaryClientCode, setSubsidiaryClientCode] = useState(''); | |||
| const [subsidiaryClientName, setSubsidiaryClientName] = useState(''); | |||
| const [dropdownDemo, setDropdownDemo] = useState(''); | |||
| const [dateDemo, setDateDemo] = useState(null); | |||
| const [checkboxDemo, setCheckboxDemo] = useState(false); | |||
| const [receiptFromDate, setReceiptFromDate] = useState(null); | |||
| const [receiptToDate, setReceiptToDate] = useState(null); | |||
| const rows = [{id: 1,processNo:"1",processDescription:"把豬頸背肉絲解凍及洗淨隔乾水份", mat:"豬頸背肉絲", matLot:"Lot001", actlQty:"11.30",unit: "kg", equipment:"解凍盤",operator:"HKPC01"}, | |||
| {id: 2,processNo:"2",processDescription:"加入調味料作腌製", mat:"洋葱", matLot:"Lot002", actlQty:"2.20",unit: "kg", equipment:"調味盤",operator:"HKPC01"}, | |||
| {id: 3,processNo:"3",processDescription:"加入調味料作腌製", mat:"茄膏", matLot:"Lot003", actlQty:"50.00",unit: "g", equipment:"調味盤",operator:"HKPC01"}, | |||
| {id: 4,processNo:"4",processDescription:"加入調味料作腌製", mat:"家樂牌黃汁粉", matLot:"Lot004", actlQty:"500.00",unit: "g", equipment:"調味盤",operator:"HKPC01"}, | |||
| {id: 5,processNo:"5",processDescription:"放入0-4度雪櫃暫存備用", mat:"-", matLot:"-", actlQty:"-",unit: "-", equipment:"雪櫃",operator:"HKPC01"}, | |||
| {id: 6,processNo:"6",processDescription:"洋葱洗淨切碎備用", mat:"洋葱", matLot:"Lot002", actlQty:"131.00",unit: "g", equipment:"切割機",operator:"HKPC01"}, | |||
| {id: 7,processNo:"7",processDescription:"用蒸焗爐煮至熟透", mat:"-", matLot:"-", actlQty:"-",unit: "-", equipment:"蒸焗爐",operator:"HKPC01"},] | |||
| const columns = [ | |||
| { | |||
| id: 'processNo', | |||
| field: 'processNo', | |||
| headerName: "工序次序", | |||
| flex: 0.7, | |||
| }, | |||
| { | |||
| id: 'processDescription', | |||
| field: 'processDescription', | |||
| headerName: "工序描述", | |||
| flex: 1, | |||
| }, | |||
| { | |||
| id: 'mat', | |||
| field: 'mat', | |||
| headerName: "原料", | |||
| flex: 1, | |||
| }, | |||
| { | |||
| id: 'matLot', | |||
| field: 'matLot', | |||
| headerName: "原料批次", | |||
| flex: 1, | |||
| }, | |||
| { | |||
| id: 'actlQty', | |||
| field: 'actlQty', | |||
| headerName: "投入數量", | |||
| flex: 0.7, | |||
| align: "right", | |||
| headerAlign: 'right', | |||
| }, | |||
| { | |||
| id: 'unit', | |||
| field: 'unit', | |||
| headerName: "單位", | |||
| flex: 0.7, | |||
| align: "left", | |||
| headerAlign: 'left', | |||
| }, | |||
| { | |||
| id: 'equipment', | |||
| field: 'equipment', | |||
| headerName: "設備", | |||
| flex: 1, | |||
| }, | |||
| { | |||
| id: 'operator', | |||
| field: 'operator', | |||
| headerName: "操作人員", | |||
| flex: 1, | |||
| }, | |||
| ]; | |||
| const InputFields = [ | |||
| { id: "clientCode", label: "Client Code", type: 'text', value: clientCode, setValue: setClientCode }, | |||
| { id: "clientName", label: "Client Name", type: 'text', value: clientName, setValue: setClientName }, | |||
| { id: "subsidiaryClientCode", label: "Subsidiary Client Code", type:'text', value:subsidiaryClientCode, setValue: setSubsidiaryClientCode}, | |||
| { id: "subsidiaryClientName", label: "Subsidiary Client Name", type:'text', value:subsidiaryClientName, setValue: setSubsidiaryClientName}, | |||
| // { id: 'dropdownDemo', label: "dropdownDemo", type: 'dropdown', options: [{id:"1", label:"1"}], value: dropdownDemo, setValue: setDropdownDemo }, | |||
| // { id: 'dateDemo', label:'dateDemo', type: 'date', value: dateDemo, setValue: setDateDemo }, | |||
| // { id: 'checkboxDemo', label:'checkboxDemo', type: 'checkbox', value: checkboxDemo, setValue: setCheckboxDemo }, | |||
| // { id: ['receiptFromDate','receiptToDate'], label: ["收貨日期","收貨日期"], value: [receiptFromDate ? receiptFromDate : null, receiptToDate ? receiptToDate : null], | |||
| // setValue: [setReceiptFromDate, setReceiptToDate],type: 'dateRange' }, | |||
| ]; | |||
| const applySearch = (data: any) => { | |||
| console.log(data) | |||
| setSearchCriteria(data) | |||
| } | |||
| return ( | |||
| <Grid item sm> | |||
| <CustomSearchForm applySearch={applySearch} fields={InputFields}/> | |||
| <CustomDatagrid Title={"工單工序詳情"} rows={rows} columns={columns} columnWidth={200} /> | |||
| </Grid> | |||
| ); | |||
| }; | |||
| export default ProgressByClient; | |||