浏览代码

update dashboard and added custom search form and data grid

tags/Baseline_30082024_FRONTEND_UAT
MSI\User 1年前
父节点
当前提交
a006218bfd
共有 8 个文件被更改,包括 787 次插入5 次删除
  1. +238
    -2
      package-lock.json
  2. +5
    -1
      package.json
  3. +161
    -0
      src/components/CustomDatagrid/CustomDatagrid.tsx
  4. +1
    -0
      src/components/CustomDatagrid/index.ts
  5. +263
    -0
      src/components/CustomSearchForm/CustomSearchForm.tsx
  6. +1
    -0
      src/components/CustomSearchForm/index.ts
  7. +3
    -2
      src/components/DashboardPage/DashboardTabButton.tsx
  8. +115
    -0
      src/components/DashboardPage/ProgressByClient.tsx

+ 238
- 2
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"


+ 5
- 1
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",


+ 161
- 0
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<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;

+ 1
- 0
src/components/CustomDatagrid/index.ts 查看文件

@@ -0,0 +1 @@
export { default } from "./CustomDatagrid";

+ 263
- 0
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<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/>&nbsp;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/>&nbsp;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;

+ 1
- 0
src/components/CustomSearchForm/index.ts 查看文件

@@ -0,0 +1 @@
export { default } from "./CustomSearchForm";

+ 3
- 2
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 <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':


+ 115
- 0
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 (
<Grid item sm>
<CustomSearchForm applySearch={applySearch} fields={InputFields}/>
<CustomDatagrid Title={"工單工序詳情"} rows={rows} columns={columns} columnWidth={200} />
</Grid>
);
};

export default ProgressByClient;

正在加载...
取消
保存