From a006218bfd900cb0b0b5c32c3a9f0afa6ff600f1 Mon Sep 17 00:00:00 2001 From: "MSI\\User" Date: Thu, 11 Jan 2024 18:24:03 +0800 Subject: [PATCH] update dashboard and added custom search form and data grid --- package-lock.json | 240 +++++++++++++++- package.json | 6 +- .../CustomDatagrid/CustomDatagrid.tsx | 161 +++++++++++ src/components/CustomDatagrid/index.ts | 1 + .../CustomSearchForm/CustomSearchForm.tsx | 263 ++++++++++++++++++ src/components/CustomSearchForm/index.ts | 1 + .../DashboardPage/DashboardTabButton.tsx | 5 +- .../DashboardPage/ProgressByClient.tsx | 115 ++++++++ 8 files changed, 787 insertions(+), 5 deletions(-) create mode 100644 src/components/CustomDatagrid/CustomDatagrid.tsx create mode 100644 src/components/CustomDatagrid/index.ts create mode 100644 src/components/CustomSearchForm/CustomSearchForm.tsx create mode 100644 src/components/CustomSearchForm/index.ts create mode 100644 src/components/DashboardPage/ProgressByClient.tsx diff --git a/package-lock.json b/package-lock.json index cdc02c5..578b006 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" diff --git a/package.json b/package.json index 7cb96da..2348c90 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/CustomDatagrid/CustomDatagrid.tsx b/src/components/CustomDatagrid/CustomDatagrid.tsx new file mode 100644 index 0000000..5eb5719 --- /dev/null +++ b/src/components/CustomDatagrid/CustomDatagrid.tsx @@ -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; + dataGridHeight?: number; + [key: string]: any; +} + +const CustomDatagrid: React.FC = ({ + 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 ( +
+ + {Title && } + + {Style ? ( + + ) : ( + + )} + + +
+ ); +}; + +export default CustomDatagrid; diff --git a/src/components/CustomDatagrid/index.ts b/src/components/CustomDatagrid/index.ts new file mode 100644 index 0000000..d34e5f0 --- /dev/null +++ b/src/components/CustomDatagrid/index.ts @@ -0,0 +1 @@ +export { default } from "./CustomDatagrid"; diff --git a/src/components/CustomSearchForm/CustomSearchForm.tsx b/src/components/CustomSearchForm/CustomSearchForm.tsx new file mode 100644 index 0000000..54a7c81 --- /dev/null +++ b/src/components/CustomSearchForm/CustomSearchForm.tsx @@ -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 = ({ fields, onSubmit, resetForm, sx }) => { + const { reset, register, handleSubmit, control } = useForm(); + const [fromDate, setFromDate] = useState(null); + const [dayRangeFromDate, setDayRangeFromDate] = useState(null); + const [dayRangeToDate, setDayRangeToDate] = useState(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 ( +
+ + {fields.map((field) => { + if (field.type === 'dropdown') { + return ( + + + {field.label} + ( + + )} + /> + + + ); + } else if (field.type === 'date') { + return ( + + + + { + setFromDate(newValue); + }} + /> + + + + ); + } else if (field.type === 'checkbox') { + return ( + + { + if (typeof field.setValue === 'function') { + field.setValue(event.target.checked); + setCheckbox1(event.target.checked); + } + }} + color="primary" + /> + } + label={{field.label}} + /> + + ) + } else if (field.type === 'dateRange') { + return ( + + + + + + setDayRangeFromDate(newValue)} + /> + + + + + To + + + + + setDayRangeToDate(newValue)} + /> + + + + + + + ) + } + return ( + + + + ); + })} + + + + + + + + + + + +
+ ); +}; + +const CustomSearchForm: FC = ({ 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 ( + + + + + ); +}; + +export default CustomSearchForm; diff --git a/src/components/CustomSearchForm/index.ts b/src/components/CustomSearchForm/index.ts new file mode 100644 index 0000000..51718f8 --- /dev/null +++ b/src/components/CustomSearchForm/index.ts @@ -0,0 +1 @@ +export { default } from "./CustomSearchForm"; diff --git a/src/components/DashboardPage/DashboardTabButton.tsx b/src/components/DashboardPage/DashboardTabButton.tsx index 25e2863..c97ca91 100644 --- a/src/components/DashboardPage/DashboardTabButton.tsx +++ b/src/components/DashboardPage/DashboardTabButton.tsx @@ -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
Project Financial Summary
; case 'cashFlow': return
Project Cash Flow
; - case 'projectProgress': - return
Project Progress by Client
; + case 'progressByClient': + return ; case 'resourceUtilization': return
Project Resource Utilization
; case 'staffUtilization': diff --git a/src/components/DashboardPage/ProgressByClient.tsx b/src/components/DashboardPage/ProgressByClient.tsx new file mode 100644 index 0000000..6388bf6 --- /dev/null +++ b/src/components/DashboardPage/ProgressByClient.tsx @@ -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 ( + + + + + ); +}; + +export default ProgressByClient;