| @@ -19,6 +19,7 @@ | |||||
| "@mui/x-data-grid": "^6.18.7", | "@mui/x-data-grid": "^6.18.7", | ||||
| "@mui/x-date-pickers": "^6.18.7", | "@mui/x-date-pickers": "^6.18.7", | ||||
| "@unly/universal-language-detector": "^2.0.3", | "@unly/universal-language-detector": "^2.0.3", | ||||
| "apexcharts": "^3.45.1", | |||||
| "dayjs": "^1.11.10", | "dayjs": "^1.11.10", | ||||
| "i18next": "^23.7.11", | "i18next": "^23.7.11", | ||||
| "i18next-resources-to-backend": "^1.2.0", | "i18next-resources-to-backend": "^1.2.0", | ||||
| @@ -26,6 +27,7 @@ | |||||
| "next": "14.0.4", | "next": "14.0.4", | ||||
| "next-auth": "^4.24.5", | "next-auth": "^4.24.5", | ||||
| "react": "^18", | "react": "^18", | ||||
| "react-apexcharts": "^1.4.1", | |||||
| "react-dom": "^18", | "react-dom": "^18", | ||||
| "react-hook-form": "^7.49.2", | "react-hook-form": "^7.49.2", | ||||
| "react-i18next": "^13.5.0", | "react-i18next": "^13.5.0", | ||||
| @@ -1525,6 +1527,11 @@ | |||||
| "resolved": "https://registry.npmjs.org/@unly/utils/-/utils-1.0.3.tgz", | "resolved": "https://registry.npmjs.org/@unly/utils/-/utils-1.0.3.tgz", | ||||
| "integrity": "sha512-QTRknIDX56FvzGcIpBum5D/oRSlX3dkZ+l1op1jsFlYCTd925OGUb991V7zsFv3ePcqFfvfqfR5cNVv+w4JAOw==" | "integrity": "sha512-QTRknIDX56FvzGcIpBum5D/oRSlX3dkZ+l1op1jsFlYCTd925OGUb991V7zsFv3ePcqFfvfqfR5cNVv+w4JAOw==" | ||||
| }, | }, | ||||
| "node_modules/@yr/monotone-cubic-spline": { | |||||
| "version": "1.0.3", | |||||
| "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", | |||||
| "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==" | |||||
| }, | |||||
| "node_modules/accept-language-parser": { | "node_modules/accept-language-parser": { | ||||
| "version": "1.5.0", | "version": "1.5.0", | ||||
| "resolved": "https://registry.npmjs.org/accept-language-parser/-/accept-language-parser-1.5.0.tgz", | "resolved": "https://registry.npmjs.org/accept-language-parser/-/accept-language-parser-1.5.0.tgz", | ||||
| @@ -1619,6 +1626,20 @@ | |||||
| "node": ">= 8" | "node": ">= 8" | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/apexcharts": { | |||||
| "version": "3.45.1", | |||||
| "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.45.1.tgz", | |||||
| "integrity": "sha512-pPjj/SA6dfPvR/IKRZF0STdfBGpBh3WRt7K0DFuW9P8erypYkX17EHu3/molPRfo2zSiQwTVpshHC5ncysqfkA==", | |||||
| "dependencies": { | |||||
| "@yr/monotone-cubic-spline": "^1.0.3", | |||||
| "svg.draggable.js": "^2.2.2", | |||||
| "svg.easing.js": "^2.0.0", | |||||
| "svg.filter.js": "^2.0.2", | |||||
| "svg.pathmorphing.js": "^0.1.3", | |||||
| "svg.resize.js": "^1.4.3", | |||||
| "svg.select.js": "^3.0.1" | |||||
| } | |||||
| }, | |||||
| "node_modules/arg": { | "node_modules/arg": { | ||||
| "version": "5.0.2", | "version": "5.0.2", | ||||
| "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", | "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", | ||||
| @@ -5410,6 +5431,18 @@ | |||||
| "node": ">=0.10.0" | "node": ">=0.10.0" | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/react-apexcharts": { | |||||
| "version": "1.4.1", | |||||
| "resolved": "https://registry.npmjs.org/react-apexcharts/-/react-apexcharts-1.4.1.tgz", | |||||
| "integrity": "sha512-G14nVaD64Bnbgy8tYxkjuXEUp/7h30Q0U33xc3AwtGFijJB9nHqOt1a6eG0WBn055RgRg+NwqbKGtqPxy15d0Q==", | |||||
| "dependencies": { | |||||
| "prop-types": "^15.8.1" | |||||
| }, | |||||
| "peerDependencies": { | |||||
| "apexcharts": "^3.41.0", | |||||
| "react": ">=0.13" | |||||
| } | |||||
| }, | |||||
| "node_modules/react-dom": { | "node_modules/react-dom": { | ||||
| "version": "18.2.0", | "version": "18.2.0", | ||||
| "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", | ||||
| @@ -6118,6 +6151,89 @@ | |||||
| "url": "https://github.com/sponsors/ljharb" | "url": "https://github.com/sponsors/ljharb" | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/svg.draggable.js": { | |||||
| "version": "2.2.2", | |||||
| "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", | |||||
| "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", | |||||
| "dependencies": { | |||||
| "svg.js": "^2.0.1" | |||||
| }, | |||||
| "engines": { | |||||
| "node": ">= 0.8.0" | |||||
| } | |||||
| }, | |||||
| "node_modules/svg.easing.js": { | |||||
| "version": "2.0.0", | |||||
| "resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz", | |||||
| "integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==", | |||||
| "dependencies": { | |||||
| "svg.js": ">=2.3.x" | |||||
| }, | |||||
| "engines": { | |||||
| "node": ">= 0.8.0" | |||||
| } | |||||
| }, | |||||
| "node_modules/svg.filter.js": { | |||||
| "version": "2.0.2", | |||||
| "resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz", | |||||
| "integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==", | |||||
| "dependencies": { | |||||
| "svg.js": "^2.2.5" | |||||
| }, | |||||
| "engines": { | |||||
| "node": ">= 0.8.0" | |||||
| } | |||||
| }, | |||||
| "node_modules/svg.js": { | |||||
| "version": "2.7.1", | |||||
| "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", | |||||
| "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==" | |||||
| }, | |||||
| "node_modules/svg.pathmorphing.js": { | |||||
| "version": "0.1.3", | |||||
| "resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz", | |||||
| "integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==", | |||||
| "dependencies": { | |||||
| "svg.js": "^2.4.0" | |||||
| }, | |||||
| "engines": { | |||||
| "node": ">= 0.8.0" | |||||
| } | |||||
| }, | |||||
| "node_modules/svg.resize.js": { | |||||
| "version": "1.4.3", | |||||
| "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", | |||||
| "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", | |||||
| "dependencies": { | |||||
| "svg.js": "^2.6.5", | |||||
| "svg.select.js": "^2.1.2" | |||||
| }, | |||||
| "engines": { | |||||
| "node": ">= 0.8.0" | |||||
| } | |||||
| }, | |||||
| "node_modules/svg.resize.js/node_modules/svg.select.js": { | |||||
| "version": "2.1.2", | |||||
| "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", | |||||
| "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", | |||||
| "dependencies": { | |||||
| "svg.js": "^2.2.5" | |||||
| }, | |||||
| "engines": { | |||||
| "node": ">= 0.8.0" | |||||
| } | |||||
| }, | |||||
| "node_modules/svg.select.js": { | |||||
| "version": "3.0.1", | |||||
| "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", | |||||
| "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", | |||||
| "dependencies": { | |||||
| "svg.js": "^2.6.5" | |||||
| }, | |||||
| "engines": { | |||||
| "node": ">= 0.8.0" | |||||
| } | |||||
| }, | |||||
| "node_modules/synckit": { | "node_modules/synckit": { | ||||
| "version": "0.8.6", | "version": "0.8.6", | ||||
| "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.6.tgz", | "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.6.tgz", | ||||
| @@ -20,6 +20,7 @@ | |||||
| "@mui/x-data-grid": "^6.18.7", | "@mui/x-data-grid": "^6.18.7", | ||||
| "@mui/x-date-pickers": "^6.18.7", | "@mui/x-date-pickers": "^6.18.7", | ||||
| "@unly/universal-language-detector": "^2.0.3", | "@unly/universal-language-detector": "^2.0.3", | ||||
| "apexcharts": "^3.45.1", | |||||
| "dayjs": "^1.11.10", | "dayjs": "^1.11.10", | ||||
| "i18next": "^23.7.11", | "i18next": "^23.7.11", | ||||
| "i18next-resources-to-backend": "^1.2.0", | "i18next-resources-to-backend": "^1.2.0", | ||||
| @@ -27,6 +28,7 @@ | |||||
| "next": "14.0.4", | "next": "14.0.4", | ||||
| "next-auth": "^4.24.5", | "next-auth": "^4.24.5", | ||||
| "react": "^18", | "react": "^18", | ||||
| "react-apexcharts": "^1.4.1", | |||||
| "react-dom": "^18", | "react-dom": "^18", | ||||
| "react-hook-form": "^7.49.2", | "react-hook-form": "^7.49.2", | ||||
| "react-i18next": "^13.5.0", | "react-i18next": "^13.5.0", | ||||
| @@ -0,0 +1,31 @@ | |||||
| import { Metadata } from "next"; | |||||
| import { I18nProvider } from "@/i18n"; | |||||
| import DashboardPage from "@/components/DashboardPage/DashboardPage"; | |||||
| import DashboardPageButton from "@/components/DashboardPage/DashboardTabButton"; | |||||
| import ProgressByClientSearch from "@/components/ProgressByClientSearch"; | |||||
| import { Suspense} from "react"; | |||||
| import Tabs, { TabsProps } from "@mui/material/Tabs"; | |||||
| import Tab from "@mui/material/Tab"; | |||||
| import Typography from "@mui/material/Typography"; | |||||
| import ProgressByClient from "@/components/ProgressByClient"; | |||||
| export const metadata: Metadata = { | |||||
| title: "Project Status by Client", | |||||
| }; | |||||
| const ProjectStatusByClient: React.FC = () => { | |||||
| return ( | |||||
| <I18nProvider namespaces={["dashboard"]}> | |||||
| <Typography variant="h4" marginInlineEnd={2}> | |||||
| Project Status by Client | |||||
| </Typography> | |||||
| <Suspense fallback={<ProgressByClientSearch.Loading />}> | |||||
| <ProgressByClientSearch/> | |||||
| </Suspense> | |||||
| <ProgressByClient/> | |||||
| </I18nProvider> | |||||
| ); | |||||
| }; | |||||
| export default ProjectStatusByClient; | |||||
| @@ -1,15 +1,22 @@ | |||||
| import { Metadata } from "next"; | import { Metadata } from "next"; | ||||
| import { I18nProvider } from "@/i18n"; | import { I18nProvider } from "@/i18n"; | ||||
| import DashboardPage from "@/components/DashboardPage/DashboardPage"; | import DashboardPage from "@/components/DashboardPage/DashboardPage"; | ||||
| import DashboardPageButton from "@/components/DashboardPage/DashboardTabButton"; | |||||
| import ProgressByClientSearch from "@/components/ProgressByClientSearch"; | |||||
| import { Suspense} from "react"; | |||||
| import Tabs, { TabsProps } from "@mui/material/Tabs"; | |||||
| import Tab from "@mui/material/Tab"; | |||||
| export const metadata: Metadata = { | export const metadata: Metadata = { | ||||
| title: "Dashboard", | title: "Dashboard", | ||||
| }; | }; | ||||
| const Dashboard: React.FC = async () => { | |||||
| const Dashboard: React.FC = () => { | |||||
| return ( | return ( | ||||
| <I18nProvider namespaces={["dashboard"]}> | <I18nProvider namespaces={["dashboard"]}> | ||||
| <DashboardPage /> | |||||
| {/* <DashboardPage /> */} | |||||
| </I18nProvider> | </I18nProvider> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -1,5 +1,6 @@ | |||||
| import { preloadProjects } from "@/app/api/projects"; | import { preloadProjects } from "@/app/api/projects"; | ||||
| import ProjectSearch from "@/components/ProjectSearch"; | import ProjectSearch from "@/components/ProjectSearch"; | ||||
| import ProgressByClientSearch from "@/components/ProgressByClientSearch"; | |||||
| import { getServerI18n } from "@/i18n"; | import { getServerI18n } from "@/i18n"; | ||||
| import Add from "@mui/icons-material/Add"; | import Add from "@mui/icons-material/Add"; | ||||
| import Button from "@mui/material/Button"; | import Button from "@mui/material/Button"; | ||||
| @@ -0,0 +1,53 @@ | |||||
| import { cache } from "react"; | |||||
| export interface ClientProjectResult { | |||||
| id: number; | |||||
| clientCode: string; | |||||
| clientName: string; | |||||
| SubsidiaryClientCode: string; | |||||
| SubsidiaryClientName: string; | |||||
| NoOfProjects: number; | |||||
| } | |||||
| export const preloadProjects = () => { | |||||
| fetchClientProjects(); | |||||
| }; | |||||
| export const fetchClientProjects = cache(async () => { | |||||
| return mockProjects; | |||||
| }); | |||||
| const mockProjects: ClientProjectResult[] = [ | |||||
| { | |||||
| id: 1, | |||||
| clientCode: "CUST-001", | |||||
| clientName: "Client A", | |||||
| SubsidiaryClientCode: "N/A", | |||||
| SubsidiaryClientName: "N/A", | |||||
| NoOfProjects: 5, | |||||
| }, | |||||
| { | |||||
| id: 1, | |||||
| clientCode: "CUST-001", | |||||
| clientName: "Client A", | |||||
| SubsidiaryClientCode: "SUBS-001", | |||||
| SubsidiaryClientName: "Subsidiary A", | |||||
| NoOfProjects: 5, | |||||
| }, | |||||
| { | |||||
| id: 1, | |||||
| clientCode: "CUST-001", | |||||
| clientName: "Client A", | |||||
| SubsidiaryClientCode: "SUBS-002", | |||||
| SubsidiaryClientName: "Subsidiary B", | |||||
| NoOfProjects: 3, | |||||
| }, | |||||
| { | |||||
| id: 1, | |||||
| clientCode: "CUST-001", | |||||
| clientName: "Client A", | |||||
| SubsidiaryClientCode: "SUBS-003", | |||||
| SubsidiaryClientName: "Subsidiary C", | |||||
| NoOfProjects: 1, | |||||
| }, | |||||
| ]; | |||||
| @@ -16,7 +16,7 @@ const AppBar: React.FC<AppBarProps> = ({ avatarImageSrc, profileName }) => { | |||||
| <I18nProvider namespaces={["common"]}> | <I18nProvider namespaces={["common"]}> | ||||
| <MUIAppBar position="sticky" color="default" elevation={4}> | <MUIAppBar position="sticky" color="default" elevation={4}> | ||||
| <Toolbar> | <Toolbar> | ||||
| <NavigationToggle /> | |||||
| <NavigationToggle/> | |||||
| <Box | <Box | ||||
| sx={{ flexGrow: 1, display: "flex", justifyContent: "flex-end" }} | sx={{ flexGrow: 1, display: "flex", justifyContent: "flex-end" }} | ||||
| > | > | ||||
| @@ -1,5 +1,4 @@ | |||||
| "use client"; | "use client"; | ||||
| import IconButton from "@mui/material/IconButton"; | import IconButton from "@mui/material/IconButton"; | ||||
| import MenuIcon from "@mui/icons-material/Menu"; | import MenuIcon from "@mui/icons-material/Menu"; | ||||
| import NavigationContent from "../NavigationContent"; | import NavigationContent from "../NavigationContent"; | ||||
| @@ -18,21 +17,21 @@ const NavigationToggle: React.FC = () => { | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <Drawer variant="permanent" sx={{ display: { xs: "none", lg: "block" } }}> | |||||
| <NavigationContent /> | |||||
| <Drawer variant="permanent" sx={{ display: { xs: "none", xl: "block" } }}> | |||||
| <NavigationContent/> | |||||
| </Drawer> | </Drawer> | ||||
| <Drawer | <Drawer | ||||
| sx={{ display: { lg: "none" } }} | |||||
| sx={{ display: { xl: "none" } }} | |||||
| open={isOpened} | open={isOpened} | ||||
| onClose={closeNavigation} | onClose={closeNavigation} | ||||
| ModalProps={{ | ModalProps={{ | ||||
| keepMounted: true, | keepMounted: true, | ||||
| }} | }} | ||||
| > | > | ||||
| <NavigationContent /> | |||||
| <NavigationContent/> | |||||
| </Drawer> | </Drawer> | ||||
| <IconButton | <IconButton | ||||
| sx={{ display: { lg: "none" } }} | |||||
| sx={{ display: { xl: "none" } }} | |||||
| onClick={openNavigation} | onClick={openNavigation} | ||||
| edge="start" | edge="start" | ||||
| aria-label="menu" | aria-label="menu" | ||||
| @@ -1,7 +1,9 @@ | |||||
| "use client"; | |||||
| import * as React from 'react'; | import * as React from 'react'; | ||||
| import { Card, CardHeader, CardContent, SxProps, Theme } from '@mui/material'; | import { Card, CardHeader, CardContent, SxProps, Theme } from '@mui/material'; | ||||
| import { DataGrid, GridColDef } from '@mui/x-data-grid'; | |||||
| import { DataGrid, GridColDef, GridRowSelectionModel} from '@mui/x-data-grid'; | |||||
| import { darken, lighten, styled } from '@mui/material/styles'; | import { darken, lighten, styled } from '@mui/material/styles'; | ||||
| import { useState } from 'react' | |||||
| interface CustomDatagridProps { | interface CustomDatagridProps { | ||||
| Title?: string; | Title?: string; | ||||
| @@ -12,6 +14,9 @@ interface CustomDatagridProps { | |||||
| sx?: SxProps<Theme>; | sx?: SxProps<Theme>; | ||||
| dataGridHeight?: number; | dataGridHeight?: number; | ||||
| [key: string]: any; | [key: string]: any; | ||||
| checkboxSelection?: boolean; | |||||
| onRowSelectionModelChange?: (newSelectionModel: GridRowSelectionModel) => void; | |||||
| selectionModel?: any; | |||||
| } | } | ||||
| const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | ||||
| @@ -19,9 +24,12 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||||
| rows, | rows, | ||||
| columns, | columns, | ||||
| columnWidth, | columnWidth, | ||||
| Style = true, | |||||
| Style = false, | |||||
| sx, | sx, | ||||
| dataGridHeight, | dataGridHeight, | ||||
| checkboxSelection, // Destructure the new prop | |||||
| onRowSelectionModelChange, // Destructure the new prop | |||||
| selectionModel, | |||||
| ...props | ...props | ||||
| }) => { | }) => { | ||||
| const modifiedColumns = columns.map((column) => { | const modifiedColumns = columns.map((column) => { | ||||
| @@ -35,6 +43,16 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||||
| return { ...row }; | return { ...row }; | ||||
| }); | }); | ||||
| // Event handler to be called when the selection changes | |||||
| const handleSelectionModelChange = (newSelectionModel: GridRowSelectionModel) => { | |||||
| // setSelectionModel(newSelectionModel); | |||||
| // To log selected row data, filter rows based on the new selection model | |||||
| const selectedRowsData = rows.filter((row) => | |||||
| newSelectionModel.includes(row.id) | |||||
| ); | |||||
| console.log(selectedRowsData); | |||||
| }; | |||||
| const getBackgroundColor = (color: string, mode: 'light' | 'dark') => | const getBackgroundColor = (color: string, mode: 'light' | 'dark') => | ||||
| mode === 'dark' ? darken(color, 0.7) : lighten(color, 0.7); | mode === 'dark' ? darken(color, 0.7) : lighten(color, 0.7); | ||||
| @@ -99,15 +117,47 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||||
| })); | })); | ||||
| return ( | return ( | ||||
| <div className="mt-5" style={{ height: dataGridHeight ?? 400, width: '100%' }}> | |||||
| <Card> | |||||
| {Title && <CardHeader title={Title} />} | |||||
| <CardContent style={{ display: "flex", alignItems: "center", justifyContent: "center" }}> | |||||
| <div className="mt-5 mb-5" style={{ height: dataGridHeight ?? 400, width: '100%'}}> | |||||
| {Title ? ( | |||||
| <Card style={{marginRight:20}}> | |||||
| {Title && <CardHeader className="text-slate-500" title={Title} />} | |||||
| <CardContent style={{ display: "flex", alignItems: "center", justifyContent: "center", marginTop:-20 }}> | |||||
| {Style ? ( | {Style ? ( | ||||
| <StyledDataGrid | <StyledDataGrid | ||||
| rows={rowsWithDefaultValues} | rows={rowsWithDefaultValues} | ||||
| columns={modifiedColumns} | columns={modifiedColumns} | ||||
| editMode="row" | editMode="row" | ||||
| checkboxSelection={checkboxSelection} | |||||
| onRowSelectionModelChange={onRowSelectionModelChange} | |||||
| initialState={{ | |||||
| pagination: { paginationModel: { pageSize: 10 } }, | |||||
| }} | |||||
| className="customDataGrid" | |||||
| sx={{ | |||||
| boxShadow: 1, | |||||
| border: 0, | |||||
| borderColor: 'primary.light', | |||||
| '& .MuiDataGrid-cell:hover': { | |||||
| color: 'primary.main' | |||||
| }, | |||||
| height: dataGridHeight ?? 400, | |||||
| '& .MuiDataGrid-root': { | |||||
| overflow: 'auto', | |||||
| }, | |||||
| '& .MuiDataGrid-columnHeaderTitle': { | |||||
| fontWeight: 'bold', | |||||
| }, | |||||
| ...sx | |||||
| }} | |||||
| {...props} | |||||
| /> | |||||
| ) : ( | |||||
| <DataGrid | |||||
| rows={rowsWithDefaultValues} | |||||
| columns={modifiedColumns} | |||||
| editMode="row" | |||||
| checkboxSelection={checkboxSelection} | |||||
| onRowSelectionModelChange={onRowSelectionModelChange} | |||||
| initialState={{ | initialState={{ | ||||
| pagination: { paginationModel: { pageSize: 10 } }, | pagination: { paginationModel: { pageSize: 10 } }, | ||||
| }} | }} | ||||
| @@ -119,10 +169,43 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||||
| '& .MuiDataGrid-cell:hover': { | '& .MuiDataGrid-cell:hover': { | ||||
| color: 'primary.main' | color: 'primary.main' | ||||
| }, | }, | ||||
| height: 300, | |||||
| '& .MuiDataGrid-root': { | |||||
| overflow: 'auto', | |||||
| }, | |||||
| ...sx | |||||
| }} | |||||
| {...props} | |||||
| /> | |||||
| )} | |||||
| </CardContent> | |||||
| </Card>) | |||||
| : (Style ? ( | |||||
| <StyledDataGrid | |||||
| rows={rowsWithDefaultValues} | |||||
| columns={modifiedColumns} | |||||
| editMode="row" | |||||
| checkboxSelection={checkboxSelection} | |||||
| onRowSelectionModelChange={onRowSelectionModelChange} | |||||
| style={{marginRight:20}} | |||||
| initialState={{ | |||||
| pagination: { paginationModel: { pageSize: 10 } }, | |||||
| }} | |||||
| className="customDataGrid" | |||||
| sx={{ | |||||
| boxShadow: 1, | |||||
| border: 0, | |||||
| borderColor: 'primary.light', | |||||
| '& .MuiDataGrid-cell:hover': { | |||||
| color: 'primary.main' | |||||
| }, | |||||
| height: dataGridHeight ?? 400, | height: dataGridHeight ?? 400, | ||||
| '& .MuiDataGrid-root': { | '& .MuiDataGrid-root': { | ||||
| overflow: 'auto', | overflow: 'auto', | ||||
| }, | }, | ||||
| '& .MuiDataGrid-columnHeaderTitle': { | |||||
| fontWeight: 'bold', | |||||
| }, | |||||
| ...sx | ...sx | ||||
| }} | }} | ||||
| {...props} | {...props} | ||||
| @@ -132,6 +215,9 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||||
| rows={rowsWithDefaultValues} | rows={rowsWithDefaultValues} | ||||
| columns={modifiedColumns} | columns={modifiedColumns} | ||||
| editMode="row" | editMode="row" | ||||
| style={{marginRight:20}} | |||||
| checkboxSelection={checkboxSelection} | |||||
| onRowSelectionModelChange={onRowSelectionModelChange} | |||||
| initialState={{ | initialState={{ | ||||
| pagination: { paginationModel: { pageSize: 10 } }, | pagination: { paginationModel: { pageSize: 10 } }, | ||||
| }} | }} | ||||
| @@ -143,7 +229,7 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||||
| '& .MuiDataGrid-cell:hover': { | '& .MuiDataGrid-cell:hover': { | ||||
| color: 'primary.main' | color: 'primary.main' | ||||
| }, | }, | ||||
| height: 400, | |||||
| height: 300, | |||||
| '& .MuiDataGrid-root': { | '& .MuiDataGrid-root': { | ||||
| overflow: 'auto', | overflow: 'auto', | ||||
| }, | }, | ||||
| @@ -151,9 +237,7 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||||
| }} | }} | ||||
| {...props} | {...props} | ||||
| /> | /> | ||||
| )} | |||||
| </CardContent> | |||||
| </Card> | |||||
| ))} | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -214,6 +214,11 @@ const FormComponent: FC<FormComponentProps> = ({ fields, onSubmit, resetForm, sx | |||||
| defaultValue={field.value !== undefined && field.value !== null ? `${field.value}` : ''} | defaultValue={field.value !== undefined && field.value !== null ? `${field.value}` : ''} | ||||
| required={field.required === true ? field.required : false} | required={field.required === true ? field.required : false} | ||||
| sx={{ ...sx }} | sx={{ ...sx }} | ||||
| InputProps={{ | |||||
| style: { | |||||
| borderRadius: "10px", | |||||
| } | |||||
| }} | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| ); | ); | ||||
| @@ -222,12 +227,12 @@ const FormComponent: FC<FormComponentProps> = ({ fields, onSubmit, resetForm, sx | |||||
| <Grid container maxWidth="lg" justifyContent="space-between" style={{marginTop:-20}}> | <Grid container maxWidth="lg" justifyContent="space-between" style={{marginTop:-20}}> | ||||
| <Stack direction="row"> | <Stack direction="row"> | ||||
| <Grid item sx={{ ml: 3, mr: 3, mb: 3, mt: 3 }}> | <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"> | |||||
| <Button className="h-12 w-32" style={{backgroundColor:"#92c1e9",color:"white",fontSize:"1.15em",fontWeight:100,borderRadius:10}} type="submit"> | |||||
| <SearchIcon/> Search | <SearchIcon/> Search | ||||
| </Button> | </Button> | ||||
| </Grid> | </Grid> | ||||
| <Grid item sx={{ ml: 3, mr: 3, mb: 3, mt: 3 }}> | <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}> | |||||
| <Button className="h-12 w-32" style={{backgroundColor:"#f890a5",color:"white",fontSize:"1.15em",fontWeight:100,borderRadius:10}} onClick={handleFormReset}> | |||||
| <RefreshIcon/> Reset | <RefreshIcon/> Reset | ||||
| </Button> | </Button> | ||||
| </Grid> | </Grid> | ||||
| @@ -254,7 +259,7 @@ const CustomSearchForm: FC<SearchFormProps> = ({ applySearch, fields, title, sx | |||||
| return ( | return ( | ||||
| <Card style={{marginRight:20}}> | <Card style={{marginRight:20}}> | ||||
| <CardHeader className="text-slate-500" style={{marginTop:-20}} titleTypographyProps={{ variant: 'h5' }} title={Title}></CardHeader> | |||||
| <CardHeader className="text-slate-500 " style={{marginTop:-5}} title={Title}></CardHeader> | |||||
| <FormComponent fields={fields} onSubmit={handleSubmit} resetForm={handleFormReset} sx={sx} /> | <FormComponent fields={fields} onSubmit={handleSubmit} resetForm={handleFormReset} sx={sx} /> | ||||
| </Card> | </Card> | ||||
| ); | ); | ||||
| @@ -1,20 +1,50 @@ | |||||
| "use client"; | |||||
| "use client" | |||||
| import Grid from "@mui/material/Grid"; | import Grid from "@mui/material/Grid"; | ||||
| import Paper from "@mui/material/Paper"; | import Paper from "@mui/material/Paper"; | ||||
| import { TFunction } from "i18next"; | import { TFunction } from "i18next"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import PageTitle from "../PageTitle/PageTitle"; | import PageTitle from "../PageTitle/PageTitle"; | ||||
| import DashboardTabButton from "./DashboardTabButton"; | import DashboardTabButton from "./DashboardTabButton"; | ||||
| import { ThemeProvider } from '@mui/material/styles'; | |||||
| import theme from '../../theme'; | |||||
| import Tabs, { TabsProps } from "@mui/material/Tabs"; | |||||
| import Tab from "@mui/material/Tab"; | |||||
| import React, { useCallback, useState } from "react"; | |||||
| import { useRouter } from "next/navigation"; | |||||
| import ProgressByClient from "./ProgressByClient"; | |||||
| import ProgressByClientSearch from "@/components/ProgressByClientSearch"; | |||||
| import { Suspense } from "react"; | |||||
| const DashboardPage: React.FC = () => { | const DashboardPage: React.FC = () => { | ||||
| const [tabIndex, setTabIndex] = useState(0); | |||||
| const { t } = useTranslation("dashboard"); | const { t } = useTranslation("dashboard"); | ||||
| const router = useRouter(); | |||||
| const handleCancel = () => { | |||||
| router.back(); | |||||
| }; | |||||
| const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | |||||
| (_e, newValue) => { | |||||
| setTabIndex(newValue); | |||||
| }, | |||||
| [], | |||||
| ); | |||||
| return ( | return ( | ||||
| <Grid container height="100vh"> | |||||
| <ThemeProvider theme={theme}> | |||||
| <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | |||||
| <Tab label="Project Financial Summary" /> | |||||
| <Tab label="Project Cash Flow" /> | |||||
| <Tab label="Project Progress by Client" /> | |||||
| <Tab label="Project Resource Utilization" /> | |||||
| <Tab label="Staff Utilization" /> | |||||
| </Tabs> | |||||
| {tabIndex === 2 && <ProgressByClient />} | |||||
| {/* <Grid container height="100vh" style={{ backgroundColor: theme.palette.background.default}}> | |||||
| <Grid item sm> | <Grid item sm> | ||||
| <PageTitle BigTitle={"Dashboards"}/> | <PageTitle BigTitle={"Dashboards"}/> | ||||
| <DashboardTabButton/> | <DashboardTabButton/> | ||||
| </Grid> | </Grid> | ||||
| </Grid> | |||||
| </Grid> */} | |||||
| </ThemeProvider> | |||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -1,11 +1,13 @@ | |||||
| "use client"; | "use client"; | ||||
| import Grid from "@mui/material/Grid"; | import Grid from "@mui/material/Grid"; | ||||
| import { useState } from 'react' | |||||
| import { useState,useCallback} from 'react' | |||||
| import Paper from "@mui/material/Paper"; | import Paper from "@mui/material/Paper"; | ||||
| import { TFunction } from "i18next"; | import { TFunction } from "i18next"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import PageTitle from "../PageTitle/PageTitle"; | import PageTitle from "../PageTitle/PageTitle"; | ||||
| import ProgressByClient from "./ProgressByClient"; | import ProgressByClient from "./ProgressByClient"; | ||||
| import Tabs, { TabsProps } from "@mui/material/Tabs"; | |||||
| import Tab from "@mui/material/Tab"; | |||||
| import '../../app/global.css'; | import '../../app/global.css'; | ||||
| const DashboardTabButton: React.FC = () => { | const DashboardTabButton: React.FC = () => { | ||||
| @@ -27,34 +29,48 @@ const DashboardTabButton: React.FC = () => { | |||||
| return <div>Project Financial Summary</div>; | return <div>Project Financial Summary</div>; | ||||
| } | } | ||||
| }; | }; | ||||
| const [tabIndex, setTabIndex] = useState(0); | |||||
| const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | |||||
| (_e, newValue) => { | |||||
| setTabIndex(newValue); | |||||
| }, | |||||
| [], | |||||
| ); | |||||
| return ( | return ( | ||||
| <Grid item sm> | |||||
| <div style={{marginLeft:20}}> | |||||
| {activeTab !== 'financialSummary' ? | |||||
| <button onClick={() => setActiveTab('financialSummary')}className="hover:bg-sky-100 hover:cursor-pointer rounded-lg bg-transparent border-cyan-500 border-solid text-cyan-500 ml-0.5 mt-0.5" style={{height:40,width:250,fontSize:18}}>Project Financial Summary</button> : | |||||
| <button onClick={() => setActiveTab('financialSummary')}className="rounded-lg bg-sky-100 border-cyan-500 border-solid text-cyan-500 ml-0.5 mt-0.5" style={{height:40,width:250,fontSize:18}}>Project Financial Summary</button> | |||||
| } | |||||
| {activeTab !== 'cashFlow' ? | |||||
| <button onClick={() => setActiveTab('cashFlow')} className="hover:bg-sky-100 hover:cursor-pointer rounded-lg bg-transparent border-cyan-500 border-solid text-cyan-500 ml-0.5 mt-0.5" style={{height:39,width:250,fontSize:18}}>Project Cash Flow</button> : | |||||
| <button onClick={() => setActiveTab('cashFlow')} className="rounded-lg bg-sky-100 border-cyan-500 border-solid text-cyan-500 ml-0.5 mt-0.5" style={{height:39,width:250,fontSize:18}}>Project Cash Flow</button> | |||||
| } | |||||
| {activeTab !== 'progressByClient' ? | |||||
| <button onClick={() => setActiveTab('progressByClient')} className="hover:bg-sky-100 hover:cursor-pointer rounded-lg bg-transparent border-cyan-500 border-solid text-cyan-500 ml-0.5 mt-0.5" style={{height:39,width:250,fontSize:18}}>Project Progress by Client</button> : | |||||
| <button onClick={() => setActiveTab('progressByClient')} className="rounded-lg bg-sky-100 border-cyan-500 border-solid text-cyan-500 ml-0.5 mt-0.5" style={{height:39,width:250,fontSize:18}}>Project Progress by Client</button> | |||||
| } | |||||
| {activeTab !== 'resourceUtilization' ? | |||||
| <button onClick={() => setActiveTab('resourceUtilization')} className="hover:bg-sky-100 hover:cursor-pointer rounded-lg bg-transparent border-cyan-500 border-solid text-cyan-500 ml-0.5 mt-0.5" style={{height:39,width:250,fontSize:18}}>Project Resource Utilization</button> : | |||||
| <button onClick={() => setActiveTab('resourceUtilization')} className="rounded-lg bg-sky-100 border-cyan-500 border-solid text-cyan-500 ml-0.5 mt-0.5" style={{height:39,width:250,fontSize:18}}>Project Resource Utilization</button> | |||||
| } | |||||
| {activeTab !== 'staffUtilization' ? | |||||
| <button onClick={() => setActiveTab('staffUtilization')} className="hover:bg-sky-100 hover:cursor-pointer rounded-lg bg-transparent border-cyan-500 border-solid text-cyan-500 ml-0.5 mt-0.5" style={{height:39,width:250,fontSize:18}}>Staff Utilization</button> : | |||||
| <button onClick={() => setActiveTab('staffUtilization')} className="rounded-lg bg-sky-100 border-cyan-500 border-solid text-cyan-500 ml-0.5 mt-0.5" style={{height:39,width:250,fontSize:18}}>Staff Utilization</button> | |||||
| } | |||||
| </div> | |||||
| <div style={{marginLeft:20,marginTop:20}}> | |||||
| {renderContent()} | |||||
| </div> | |||||
| </Grid> | |||||
| // <Grid item sm> | |||||
| // <div style={{marginLeft:20}}> | |||||
| // {activeTab !== 'financialSummary' ? | |||||
| // <button onClick={() => setActiveTab('financialSummary')}className="hover:bg-sky-100 hover:cursor-pointer rounded-lg bg-transparent border-slate-400 border-solid text-slate-400 ml-0.5 mt-0.5" style={{height:40,width:250,fontSize:18}}>Project Financial Summary</button> : | |||||
| // <button onClick={() => setActiveTab('financialSummary')}className="rounded-lg bg-sky-100 border-cyan-500 border-solid text-cyan-500 ml-0.5 mt-0.5" style={{height:40,width:250,fontSize:18}}>Project Financial Summary</button> | |||||
| // } | |||||
| // {activeTab !== 'cashFlow' ? | |||||
| // <button onClick={() => setActiveTab('cashFlow')} className="hover:bg-sky-100 hover:cursor-pointer rounded-lg bg-transparent border-slate-400 border-solid text-slate-400 ml-0.5 mt-0.5" style={{height:39,width:250,fontSize:18}}>Project Cash Flow</button> : | |||||
| // <button onClick={() => setActiveTab('cashFlow')} className="rounded-lg bg-sky-100 border-cyan-500 border-solid text-cyan-500 ml-0.5 mt-0.5" style={{height:39,width:250,fontSize:18}}>Project Cash Flow</button> | |||||
| // } | |||||
| // {activeTab !== 'progressByClient' ? | |||||
| // <button onClick={() => setActiveTab('progressByClient')} className="hover:bg-sky-100 hover:cursor-pointer rounded-lg bg-transparent border-slate-400 border-solid text-slate-400 ml-0.5 mt-0.5" style={{height:39,width:250,fontSize:18}}>Project Progress by Client</button> : | |||||
| // <button onClick={() => setActiveTab('progressByClient')} className="rounded-lg bg-sky-100 border-cyan-500 border-solid text-cyan-500 ml-0.5 mt-0.5" style={{height:39,width:250,fontSize:18}}>Project Progress by Client</button> | |||||
| // } | |||||
| // {activeTab !== 'resourceUtilization' ? | |||||
| // <button onClick={() => setActiveTab('resourceUtilization')} className="hover:bg-sky-100 hover:cursor-pointer rounded-lg bg-transparent border-slate-400 border-solid text-slate-400 ml-0.5 mt-0.5" style={{height:39,width:250,fontSize:18}}>Project Resource Utilization</button> : | |||||
| // <button onClick={() => setActiveTab('resourceUtilization')} className="rounded-lg bg-sky-100 border-cyan-500 border-solid text-cyan-500 ml-0.5 mt-0.5" style={{height:39,width:250,fontSize:18}}>Project Resource Utilization</button> | |||||
| // } | |||||
| // {activeTab !== 'staffUtilization' ? | |||||
| // <button onClick={() => setActiveTab('staffUtilization')} className="hover:bg-sky-100 hover:cursor-pointer rounded-lg bg-transparent border-slate-400 border-solid text-slate-400 ml-0.5 mt-0.5" style={{height:39,width:250,fontSize:18}}>Staff Utilization</button> : | |||||
| // <button onClick={() => setActiveTab('staffUtilization')} className="rounded-lg bg-sky-100 border-cyan-500 border-solid text-cyan-500 ml-0.5 mt-0.5" style={{height:39,width:250,fontSize:18}}>Staff Utilization</button> | |||||
| // } | |||||
| // </div> | |||||
| // <div style={{marginLeft:20,marginTop:20}}> | |||||
| // {renderContent()} | |||||
| // </div> | |||||
| // </Grid> | |||||
| <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | |||||
| <Tab label="Project Financial Summary" /> | |||||
| <Tab label="Project Cash Flow" /> | |||||
| <Tab label="Project Progress by Client" /> | |||||
| <Tab label="Project Resource Utilization" /> | |||||
| <Tab label="Staff Utilization" /> | |||||
| </Tabs> | |||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -1,14 +1,23 @@ | |||||
| "use client"; | |||||
| import * as React from "react"; | import * as React from "react"; | ||||
| import Grid from "@mui/material/Grid"; | import Grid from "@mui/material/Grid"; | ||||
| import { useState } from 'react' | |||||
| import { useState,useEffect, useMemo } from 'react' | |||||
| import Paper from "@mui/material/Paper"; | import Paper from "@mui/material/Paper"; | ||||
| import { TFunction } from "i18next"; | import { TFunction } from "i18next"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import {Card,CardHeader} from '@mui/material'; | import {Card,CardHeader} from '@mui/material'; | ||||
| import CustomSearchForm from "../CustomSearchForm/CustomSearchForm"; | import CustomSearchForm from "../CustomSearchForm/CustomSearchForm"; | ||||
| import CustomDatagrid from '../CustomDatagrid/CustomDatagrid'; | import CustomDatagrid from '../CustomDatagrid/CustomDatagrid'; | ||||
| import ReactApexChart from 'react-apexcharts'; | |||||
| import { ApexOptions } from 'apexcharts'; | |||||
| import { GridColDef, GridRowSelectionModel} from '@mui/x-data-grid'; | |||||
| import ReportProblemIcon from '@mui/icons-material/ReportProblem'; | |||||
| import dynamic from 'next/dynamic'; | |||||
| import '../../app/global.css'; | import '../../app/global.css'; | ||||
| import { AnyARecord } from "dns"; | |||||
| import SearchBox, { Criterion } from "../SearchBox"; | |||||
| import ProgressByClientSearch from "@/components/ProgressByClientSearch"; | |||||
| import { Suspense } from "react"; | |||||
| const ProgressByClient: React.FC = () => { | const ProgressByClient: React.FC = () => { | ||||
| const [activeTab, setActiveTab] = useState('financialSummary'); | const [activeTab, setActiveTab] = useState('financialSummary'); | ||||
| @@ -19,74 +28,143 @@ const ProgressByClient: React.FC = () => { | |||||
| const [clientName, setClientName] = useState(''); | const [clientName, setClientName] = useState(''); | ||||
| const [subsidiaryClientCode, setSubsidiaryClientCode] = useState(''); | const [subsidiaryClientCode, setSubsidiaryClientCode] = useState(''); | ||||
| const [subsidiaryClientName, setSubsidiaryClientName] = useState(''); | const [subsidiaryClientName, setSubsidiaryClientName] = useState(''); | ||||
| const [projectArray, setProjectArray] : any[] = useState([]); | |||||
| const [percentageArray, setPercentageArray] : any[] = useState([]); | |||||
| const [selectionModel, setSelectionModel] : any[] = React.useState([]); | |||||
| const [dropdownDemo, setDropdownDemo] = useState(''); | const [dropdownDemo, setDropdownDemo] = useState(''); | ||||
| const [dateDemo, setDateDemo] = useState(null); | const [dateDemo, setDateDemo] = useState(null); | ||||
| const [checkboxDemo, setCheckboxDemo] = useState(false); | const [checkboxDemo, setCheckboxDemo] = useState(false); | ||||
| const [receiptFromDate, setReceiptFromDate] = useState(null); | const [receiptFromDate, setReceiptFromDate] = useState(null); | ||||
| const [receiptToDate, setReceiptToDate] = 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 [selectedRows, setSelectedRows] = useState([]); | |||||
| const rows = [{id: 1,clientCode:"CUST-001",clientName:"Client A", clientSubsidiaryCode:"N/A", clientSubsidiaryName:"N/A", noOfProjects:"5"}, | |||||
| {id: 2,clientCode:"CUST-001",clientName:"Client A", clientSubsidiaryCode:"SUBS-001", clientSubsidiaryName:"Subsidiary A", noOfProjects:"5"}, | |||||
| {id: 3,clientCode:"CUST-001",clientName:"Client A", clientSubsidiaryCode:"SUBS-002", clientSubsidiaryName:"Subsidiary B", noOfProjects:"3"}, | |||||
| {id: 4,clientCode:"CUST-001",clientName:"Client A", clientSubsidiaryCode:"SUBS-003", clientSubsidiaryName:"Subsidiary C", noOfProjects:"1"} | |||||
| ] | |||||
| const rows2 = [{id: 1,project:"Consultancy Project 123",team:"XXX", teamLeader:"XXX", currentStage:"Contract Documentation", budgetedManhour:"200.00",spentManhour:"120.00",remainedManhour:"80.00",comingPaymentMilestone:"31/03/2024",alert:false}, | |||||
| {id: 2,project:"Consultancy Project 456",team:"XXX", teamLeader:"XXX", currentStage:"Report Preparation", budgetedManhour:"400.00",spentManhour:"200.00",remainedManhour:"200.00",comingPaymentMilestone:"20/02/2024",alert:false}, | |||||
| {id: 3,project:"Construction Project A",team:"YYY", teamLeader:"YYY", currentStage:"Construction", budgetedManhour:"187.50",spentManhour:"200.00",remainedManhour:"12.50",comingPaymentMilestone:"13/12/2023",alert:true}, | |||||
| {id: 4,project:"Construction Project B",team:"XXX", teamLeader:"XXX", currentStage:"Post Construction", budgetedManhour:"100.00",spentManhour:"40.00",remainedManhour:"60.00",comingPaymentMilestone:"05/01/2024",alert:false}, | |||||
| {id: 5,project:"Construction Project C",team:"YYY", teamLeader:"YYY", currentStage:"Construction", budgetedManhour:"300.00",spentManhour:"150.00",remainedManhour:"150.00",comingPaymentMilestone:"31/03/2024",alert:false}, | |||||
| ] | |||||
| const columns = [ | const columns = [ | ||||
| { | { | ||||
| id: 'processNo', | |||||
| field: 'processNo', | |||||
| headerName: "工序次序", | |||||
| flex: 0.7, | |||||
| id: 'clientCode', | |||||
| field: 'clientCode', | |||||
| headerName: "Client Code", | |||||
| flex: 1, | |||||
| }, | }, | ||||
| { | { | ||||
| id: 'processDescription', | |||||
| field: 'processDescription', | |||||
| headerName: "工序描述", | |||||
| id: 'clientName', | |||||
| field: 'clientName', | |||||
| headerName: "Client Name", | |||||
| flex: 1, | flex: 1, | ||||
| }, | }, | ||||
| { | { | ||||
| id: 'mat', | |||||
| field: 'mat', | |||||
| headerName: "原料", | |||||
| id: 'clientSubsidiaryCode', | |||||
| field: 'clientSubsidiaryCode', | |||||
| headerName: "Client Subsidiary Code", | |||||
| flex: 1, | flex: 1, | ||||
| }, | }, | ||||
| { | { | ||||
| id: 'matLot', | |||||
| field: 'matLot', | |||||
| headerName: "原料批次", | |||||
| id: 'noOfProjects', | |||||
| field: 'noOfProjects', | |||||
| headerName: "No. of Projects", | |||||
| flex: 1, | flex: 1, | ||||
| }, | }, | ||||
| ]; | |||||
| const columns2 = [ | |||||
| { | { | ||||
| 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: "設備", | |||||
| id: 'project', | |||||
| field: 'project', | |||||
| headerName: "Project", | |||||
| flex: 1, | flex: 1, | ||||
| }, | |||||
| { | |||||
| id: 'operator', | |||||
| field: 'operator', | |||||
| headerName: "操作人員", | |||||
| }, | |||||
| { | |||||
| id: 'team', | |||||
| field: 'team', | |||||
| headerName: "Team", | |||||
| flex: 1, | flex: 1, | ||||
| }, | |||||
| { | |||||
| id: 'teamLeader', | |||||
| field: 'teamLeader', | |||||
| headerName: "Team Leader", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: 'currentStage', | |||||
| field: 'currentStage', | |||||
| headerName: "Current Stage", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: 'budgetedManhour', | |||||
| field: 'budgetedManhour', | |||||
| headerName: "Budgeted Manhour", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: 'spentManhour', | |||||
| field: 'spentManhour', | |||||
| headerName: "Spent Manhour", | |||||
| renderCell: (params:any) => { | |||||
| if (params.row.budgetedManhour - params.row.spentManhour <= 0) { | |||||
| return( | |||||
| <span className="text-red-300">{params.row.spentManhour}</span> | |||||
| ) | |||||
| } else { | |||||
| return ( | |||||
| <span>{params.row.spentManhour}</span> | |||||
| ) | |||||
| } | |||||
| }, | }, | ||||
| ]; | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: 'remainedManhour', | |||||
| field: 'remainedManhour', | |||||
| headerName: "Remained Manhour", | |||||
| renderCell: (params:any) => { | |||||
| if (params.row.budgetedManhour - params.row.spentManhour <= 0) { | |||||
| return( | |||||
| <span className="text-red-300">({params.row.remainedManhour})</span> | |||||
| ) | |||||
| } else { | |||||
| return ( | |||||
| <span>{params.row.remainedManhour}</span> | |||||
| ) | |||||
| } | |||||
| }, | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: 'comingPaymentMilestone', | |||||
| field: 'comingPaymentMilestone', | |||||
| headerName: "Coming Payment Milestone", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: 'alert', | |||||
| field: 'alert', | |||||
| headerName: "Alert", | |||||
| renderCell: (params:any) => { | |||||
| if (params.row.alert === true) { | |||||
| return ( | |||||
| <span className="text-red-300 text-center"><ReportProblemIcon/></span> | |||||
| ) | |||||
| } else { | |||||
| return ( | |||||
| <span></span> | |||||
| ) | |||||
| } | |||||
| }, | |||||
| flex: 1, | |||||
| }, | |||||
| ]; | |||||
| const InputFields = [ | const InputFields = [ | ||||
| { id: "clientCode", label: "Client Code", type: 'text', value: clientCode, setValue: setClientCode }, | { id: "clientCode", label: "Client Code", type: 'text', value: clientCode, setValue: setClientCode }, | ||||
| @@ -100,14 +178,173 @@ const ProgressByClient: React.FC = () => { | |||||
| // setValue: [setReceiptFromDate, setReceiptToDate],type: 'dateRange' }, | // setValue: [setReceiptFromDate, setReceiptToDate],type: 'dateRange' }, | ||||
| ]; | ]; | ||||
| const stageDeadline = ["31/03/2024","20/02/2024","01/12/2023","05/01/2024","31/03/2023"] | |||||
| const series2: ApexAxisChartSeries | ApexNonAxisChartSeries = [{ | |||||
| data: [17.1, 28.6, 5.7, 48.6], | |||||
| }]; | |||||
| const series: ApexAxisChartSeries | ApexNonAxisChartSeries = [{ | |||||
| name: 'Current Stage Completion Percentage', | |||||
| data: [80, 55, 40, 65, 70], | |||||
| }]; | |||||
| const options2 : ApexOptions = { | |||||
| chart: { | |||||
| type: 'donut', | |||||
| }, | |||||
| plotOptions: { | |||||
| pie: { | |||||
| donut:{ | |||||
| labels:{ | |||||
| show:false, | |||||
| } | |||||
| } | |||||
| }, | |||||
| }, | |||||
| labels: [projectArray], | |||||
| legend: { | |||||
| show: false, | |||||
| }, | |||||
| responsive: [{ | |||||
| breakpoint: 480, | |||||
| options: { | |||||
| chart: { | |||||
| width: 200 | |||||
| }, | |||||
| legend: { | |||||
| position: 'bottom', | |||||
| show:false | |||||
| } | |||||
| } | |||||
| }] | |||||
| } | |||||
| const options: ApexOptions = { | |||||
| chart: { | |||||
| type: 'bar', | |||||
| height: 350 | |||||
| }, | |||||
| colors: ['#FF4560', '#00E396', '#008FFB', '#775DD0', '#FEB019'], | |||||
| plotOptions: { | |||||
| bar: { | |||||
| horizontal: true, | |||||
| distributed: true, | |||||
| }, | |||||
| }, | |||||
| dataLabels: { | |||||
| enabled: false | |||||
| }, | |||||
| xaxis: { | |||||
| categories: [ | |||||
| 'Consultancy Project 123', | |||||
| 'Consultancy Project 456', | |||||
| 'Construction Project A', | |||||
| 'Construction Project B', | |||||
| 'Construction Project C', | |||||
| ], | |||||
| }, | |||||
| yaxis: { | |||||
| title: { | |||||
| text: 'Projects' | |||||
| }, | |||||
| labels: { | |||||
| maxWidth: 200, | |||||
| style: { | |||||
| cssClass: 'apexcharts-yaxis-label', | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| title: { | |||||
| text: 'Current Stage Completion Percentage', | |||||
| align: 'center' | |||||
| }, | |||||
| grid: { | |||||
| borderColor: '#f1f1f1', | |||||
| }, | |||||
| annotations: { | |||||
| } | |||||
| }; | |||||
| const handleSelectionChange = (newSelectionModel: GridRowSelectionModel) => { | |||||
| const selectedRowsData = rows2.filter((row) => | |||||
| newSelectionModel.includes(row.id) | |||||
| ); | |||||
| console.log(selectedRowsData) | |||||
| const projectArray:any[] = [] | |||||
| let otherPercentage = 100 | |||||
| let totalBudgetManhour = 0 | |||||
| const percentageArray = [] | |||||
| for (let i = 0; i <= selectedRowsData.length; i++) { | |||||
| if (i === selectedRowsData.length) { | |||||
| projectArray.push("Other") | |||||
| } else { | |||||
| projectArray.push(selectedRowsData[i].project) | |||||
| totalBudgetManhour += Number(selectedRowsData[i].budgetedManhour) | |||||
| } | |||||
| } | |||||
| for (let i = 0; i <= selectedRowsData.length; i++) { | |||||
| if (i === selectedRowsData.length) { | |||||
| percentageArray.push(otherPercentage) | |||||
| } else { | |||||
| let percentage = ((Number(selectedRowsData[i].spentManhour) / totalBudgetManhour) * 100).toFixed(1) | |||||
| percentageArray.push(Number(percentage)) | |||||
| otherPercentage -= Number(percentage) | |||||
| } | |||||
| } | |||||
| setSelectionModel(newSelectionModel) | |||||
| setProjectArray(projectArray) | |||||
| setPercentageArray(percentageArray) | |||||
| }; | |||||
| const applySearch = (data: any) => { | const applySearch = (data: any) => { | ||||
| console.log(data) | console.log(data) | ||||
| setSearchCriteria(data) | setSearchCriteria(data) | ||||
| } | } | ||||
| return ( | return ( | ||||
| <Grid item sm> | <Grid item sm> | ||||
| <CustomSearchForm applySearch={applySearch} fields={InputFields}/> | |||||
| <CustomDatagrid Title={"工單工序詳情"} rows={rows} columns={columns} columnWidth={200} /> | |||||
| {/* <CustomSearchForm applySearch={applySearch} fields={InputFields}/> */} | |||||
| {/* <CustomDatagrid rows={rows} columns={columns} columnWidth={200} dataGridHeight={300}/> */} | |||||
| <div style={{display:"inline-block",width:"70%"}}> | |||||
| <Grid item xs={12} md={12} lg={12}> | |||||
| <Card> | |||||
| <CardHeader className="text-slate-500" title="Project Progress"/> | |||||
| <div style={{display:"inline-block",width:"99%"}}> | |||||
| <ReactApexChart | |||||
| options={options} | |||||
| series={series} | |||||
| type="bar" | |||||
| height={350} | |||||
| /> | |||||
| </div> | |||||
| {/* <div style={{display:"inline-block",width:"20%",verticalAlign:"top",textAlign:"center"}}> | |||||
| <p><strong><u>Stage Deadline</u></strong></p> | |||||
| {stageDeadline.map((date, index) => { | |||||
| const marginTop = index === 0 ? 25 : 20; | |||||
| return ( | |||||
| <p style={{marginTop:marginTop}} key={index}>{date}</p> | |||||
| ); | |||||
| })} | |||||
| </div> */} | |||||
| <CardHeader className="text-slate-500" title="Current Stage Due Date"/> | |||||
| <div style={{display:"inline-block",width:"99%",marginLeft:10}}> | |||||
| <CustomDatagrid rows={rows2} columns={columns2} columnWidth={200} dataGridHeight={300} checkboxSelection={true} onRowSelectionModelChange={handleSelectionChange} selectionModel={selectionModel}/> | |||||
| </div> | |||||
| </Card> | |||||
| </Grid> | |||||
| </div> | |||||
| <div style={{display:"inline-block",width:"30%",verticalAlign:"top",marginLeft:0}}> | |||||
| <Grid item xs={12} md={12} lg={12}> | |||||
| <Card style={{marginLeft:15,marginRight:20}}> | |||||
| <CardHeader className="text-slate-500" title="Overall Progress per Project"/> | |||||
| <ReactApexChart | |||||
| options={options2} | |||||
| series={percentageArray} | |||||
| type="donut" | |||||
| /> | |||||
| </Card> | |||||
| </Grid> | |||||
| </div> | |||||
| </Grid> | </Grid> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -18,16 +18,22 @@ import Typography from "@mui/material/Typography"; | |||||
| import { usePathname } from "next/navigation"; | import { usePathname } from "next/navigation"; | ||||
| import Link from "next/link"; | import Link from "next/link"; | ||||
| import { NAVIGATION_CONTENT_WIDTH } from "@/config/uiConfig"; | import { NAVIGATION_CONTENT_WIDTH } from "@/config/uiConfig"; | ||||
| import ArrowCircleLeftOutlinedIcon from '@mui/icons-material/ArrowCircleLeftOutlined'; | |||||
| import ArrowCircleLeftRoundedIcon from '@mui/icons-material/ArrowCircleLeftRounded'; | |||||
| interface NavigationItem { | interface NavigationItem { | ||||
| icon: React.ReactNode; | icon: React.ReactNode; | ||||
| label: string; | label: string; | ||||
| path: string; | path: string; | ||||
| children?: NavigationItem[]; | |||||
| } | } | ||||
| const navigationItems: NavigationItem[] = [ | const navigationItems: NavigationItem[] = [ | ||||
| { icon: <WorkHistory />, label: "User Workspace", path: "/home" }, | { icon: <WorkHistory />, label: "User Workspace", path: "/home" }, | ||||
| { icon: <Dashboard />, label: "Dashboard", path: "/dashboard" }, | |||||
| { icon: <Dashboard />, label: "Dashboard", path: "", children: [ | |||||
| { icon: <ArrowCircleLeftOutlinedIcon />, label: "Project Status by Client", path: "/dashboard/ProjectStatusByClient" }, | |||||
| { icon: <ArrowCircleLeftRoundedIcon />, label: "Subitem 2", path: "/dashboard/subitem2" }, | |||||
| ]}, | |||||
| { icon: <RequestQuote />, label: "Expense Claim", path: "/claim" }, | { icon: <RequestQuote />, label: "Expense Claim", path: "/claim" }, | ||||
| { icon: <Assignment />, label: "Project Management", path: "/projects" }, | { icon: <Assignment />, label: "Project Management", path: "/projects" }, | ||||
| { icon: <Task />, label: "Task Template", path: "/tasks" }, | { icon: <Task />, label: "Task Template", path: "/tasks" }, | ||||
| @@ -40,15 +46,54 @@ const NavigationContent: React.FC = () => { | |||||
| const { t } = useTranslation("common"); | const { t } = useTranslation("common"); | ||||
| const pathname = usePathname(); | const pathname = usePathname(); | ||||
| const [openItems, setOpenItems] = React.useState<string[]>([]); | |||||
| const toggleItem = (path: string) => { | |||||
| setOpenItems(prevOpenItems => | |||||
| prevOpenItems.includes(path) | |||||
| ? prevOpenItems.filter(item => item !== path) | |||||
| : [...prevOpenItems, path] | |||||
| ); | |||||
| }; | |||||
| const renderNavigationItem = (item: NavigationItem) => { | |||||
| const isOpen = openItems.includes(item.path); | |||||
| return ( | |||||
| <Box | |||||
| key={`${item.label}-${item.path}`} | |||||
| component={Link} | |||||
| href={item.path} | |||||
| sx={{ textDecoration: "none", color: "inherit" }} | |||||
| > | |||||
| <ListItemButton | |||||
| selected={pathname.includes(item.path)} | |||||
| onClick={() => item.children && toggleItem(item.path)} | |||||
| > | |||||
| <ListItemIcon>{item.icon}</ListItemIcon> | |||||
| <ListItemText primary={t(item.label)} /> | |||||
| </ListItemButton> | |||||
| {item.children && isOpen && ( | |||||
| <List sx={{ pl: 2 }}> | |||||
| {item.children.map(child => renderNavigationItem(child))} | |||||
| </List> | |||||
| )} | |||||
| </Box> | |||||
| ); | |||||
| }; | |||||
| return ( | return ( | ||||
| <Box sx={{ width: NAVIGATION_CONTENT_WIDTH }}> | <Box sx={{ width: NAVIGATION_CONTENT_WIDTH }}> | ||||
| <Box sx={{ p: "1.5rem" }}> | <Box sx={{ p: "1.5rem" }}> | ||||
| {/* Replace this with company logo and/or name */} | {/* Replace this with company logo and/or name */} | ||||
| <Typography variant="h4">TSMS</Typography> | |||||
| <Typography style={{display:"inline-block"}}variant="h4">TSMS</Typography> | |||||
| {/* <button className="float-right bg-transparent border-transparent" > | |||||
| <ArrowCircleLeftRoundedIcon className="text-slate-400 hover:text-blue-400 hover:cursor-pointer " style={{ fontSize: '35px' }} /> | |||||
| </button> */} | |||||
| </Box> | </Box> | ||||
| <Divider /> | <Divider /> | ||||
| <List component="nav"> | <List component="nav"> | ||||
| {navigationItems.map(({ icon, label, path }, index) => { | |||||
| {navigationItems.map(item => renderNavigationItem(item))} | |||||
| {/* {navigationItems.map(({ icon, label, path }, index) => { | |||||
| return ( | return ( | ||||
| <Box | <Box | ||||
| key={`${label}-${index}`} | key={`${label}-${index}`} | ||||
| @@ -62,7 +107,7 @@ const NavigationContent: React.FC = () => { | |||||
| </ListItemButton> | </ListItemButton> | ||||
| </Box> | </Box> | ||||
| ); | ); | ||||
| })} | |||||
| })} */} | |||||
| </List> | </List> | ||||
| </Box> | </Box> | ||||
| ); | ); | ||||
| @@ -0,0 +1,434 @@ | |||||
| "use client"; | |||||
| import * as React from "react"; | |||||
| import Grid from "@mui/material/Grid"; | |||||
| import { useState,useEffect, useMemo } 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 ReactApexChart from 'react-apexcharts'; | |||||
| import { ApexOptions } from 'apexcharts'; | |||||
| import { GridColDef, GridRowSelectionModel} from '@mui/x-data-grid'; | |||||
| import ReportProblemIcon from '@mui/icons-material/ReportProblem'; | |||||
| import dynamic from 'next/dynamic'; | |||||
| import '../../app/global.css'; | |||||
| import { AnyARecord, AnyCnameRecord } from "dns"; | |||||
| import SearchBox, { Criterion } from "../SearchBox"; | |||||
| import ProgressByClientSearch from "@/components/ProgressByClientSearch"; | |||||
| import { Suspense } from "react"; | |||||
| 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 [projectArray, setProjectArray] : any[] = useState([]); | |||||
| const [percentageArray, setPercentageArray] : any[] = useState([]); | |||||
| const [colorArray, setColorArray] : any[] = useState([]); | |||||
| const [selectionModel, setSelectionModel] : any[] = React.useState([]); | |||||
| const [pieChartColor, setPieChartColor] : any[] = React.useState([]); | |||||
| const [totalSpentPercentage, setTotalSpentPercentage] : any = React.useState(); | |||||
| const [projectBudgetManhour, setProjectBudgetManhour] :any = React.useState('-'); | |||||
| const [actualManhourSpent, setActualManhourSpent] :any = React.useState('-'); | |||||
| const [remainedManhour, setRemainedManhour] :any = React.useState('-'); | |||||
| const [lastUpdate, setLastUpdate] :any = React.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 [selectedRows, setSelectedRows] = useState([]); | |||||
| const rows = [{id: 1,clientCode:"CUST-001",clientName:"Client A", clientSubsidiaryCode:"N/A", clientSubsidiaryName:"N/A", noOfProjects:"5"}, | |||||
| {id: 2,clientCode:"CUST-001",clientName:"Client A", clientSubsidiaryCode:"SUBS-001", clientSubsidiaryName:"Subsidiary A", noOfProjects:"5"}, | |||||
| {id: 3,clientCode:"CUST-001",clientName:"Client A", clientSubsidiaryCode:"SUBS-002", clientSubsidiaryName:"Subsidiary B", noOfProjects:"3"}, | |||||
| {id: 4,clientCode:"CUST-001",clientName:"Client A", clientSubsidiaryCode:"SUBS-003", clientSubsidiaryName:"Subsidiary C", noOfProjects:"1"} | |||||
| ] | |||||
| //['#f57f90', '#94f7d6', '#87c5f5', '#ab95f5', '#fcd68b'] | |||||
| const rows2 = [{id: 1,project:"Consultancy Project 123",team:"XXX", teamLeader:"XXX", currentStage:"Contract Documentation", budgetedManhour:"200.00",spentManhour:"120.00",remainedManhour:"80.00",comingPaymentMilestone:"31/03/2024",alert:false,color:"#f57f90"}, | |||||
| {id: 2,project:"Consultancy Project 456",team:"XXX", teamLeader:"XXX", currentStage:"Report Preparation", budgetedManhour:"400.00",spentManhour:"200.00",remainedManhour:"200.00",comingPaymentMilestone:"20/02/2024",alert:false,color:"#94f7d6"}, | |||||
| {id: 3,project:"Construction Project A",team:"YYY", teamLeader:"YYY", currentStage:"Construction", budgetedManhour:"187.50",spentManhour:"200.00",remainedManhour:"12.50",comingPaymentMilestone:"13/12/2023",alert:true,color:"#87c5f5"}, | |||||
| {id: 4,project:"Construction Project B",team:"XXX", teamLeader:"XXX", currentStage:"Post Construction", budgetedManhour:"100.00",spentManhour:"40.00",remainedManhour:"60.00",comingPaymentMilestone:"05/01/2024",alert:false,color:"#ab95f5"}, | |||||
| {id: 5,project:"Construction Project C",team:"YYY", teamLeader:"YYY", currentStage:"Construction", budgetedManhour:"300.00",spentManhour:"150.00",remainedManhour:"150.00",comingPaymentMilestone:"31/03/2024",alert:false,color:"#fcd68b"}, | |||||
| ] | |||||
| const columns = [ | |||||
| { | |||||
| id: 'clientCode', | |||||
| field: 'clientCode', | |||||
| headerName: "Client Code", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: 'clientName', | |||||
| field: 'clientName', | |||||
| headerName: "Client Name", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: 'clientSubsidiaryCode', | |||||
| field: 'clientSubsidiaryCode', | |||||
| headerName: "Client Subsidiary Code", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: 'noOfProjects', | |||||
| field: 'noOfProjects', | |||||
| headerName: "No. of Projects", | |||||
| flex: 1, | |||||
| }, | |||||
| ]; | |||||
| const columns2 = [ | |||||
| { | |||||
| id:"color", | |||||
| field:'color', | |||||
| headerName:'', | |||||
| renderCell: (params:any) => { | |||||
| return ( | |||||
| <span className="dot" style={{height:"15px",width:"15px",borderRadius:"50%",backgroundColor:`${params.row.color}`,display:"inline-block"}}></span> | |||||
| ) | |||||
| }, | |||||
| flex:0.1, | |||||
| }, | |||||
| { | |||||
| id: 'project', | |||||
| field: 'project', | |||||
| headerName: "Project", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: 'team', | |||||
| field: 'team', | |||||
| headerName: "Team", | |||||
| flex: 0.8, | |||||
| }, | |||||
| { | |||||
| id: 'teamLeader', | |||||
| field: 'teamLeader', | |||||
| headerName: "Team Leader", | |||||
| flex: 0.8, | |||||
| }, | |||||
| { | |||||
| id: 'currentStage', | |||||
| field: 'currentStage', | |||||
| headerName: "Current Stage", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: 'budgetedManhour', | |||||
| field: 'budgetedManhour', | |||||
| headerName: "Budgeted Manhour", | |||||
| flex: 0.8, | |||||
| }, | |||||
| { | |||||
| id: 'spentManhour', | |||||
| field: 'spentManhour', | |||||
| headerName: "Spent Manhour", | |||||
| renderCell: (params:any) => { | |||||
| if (params.row.budgetedManhour - params.row.spentManhour <= 0) { | |||||
| return( | |||||
| <span className="text-red-300">{params.row.spentManhour}</span> | |||||
| ) | |||||
| } else { | |||||
| return ( | |||||
| <span>{params.row.spentManhour}</span> | |||||
| ) | |||||
| } | |||||
| }, | |||||
| flex: 0.8, | |||||
| }, | |||||
| { | |||||
| id: 'remainedManhour', | |||||
| field: 'remainedManhour', | |||||
| headerName: "Remained Manhour", | |||||
| renderCell: (params:any) => { | |||||
| if (params.row.budgetedManhour - params.row.spentManhour <= 0) { | |||||
| return( | |||||
| <span className="text-red-300">({params.row.remainedManhour})</span> | |||||
| ) | |||||
| } else { | |||||
| return ( | |||||
| <span>{params.row.remainedManhour}</span> | |||||
| ) | |||||
| } | |||||
| }, | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: 'comingPaymentMilestone', | |||||
| field: 'comingPaymentMilestone', | |||||
| headerName: "Coming Payment Milestone", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: 'alert', | |||||
| field: 'alert', | |||||
| headerName: "Alert", | |||||
| renderCell: (params:any) => { | |||||
| if (params.row.alert === true) { | |||||
| return ( | |||||
| <span className="text-red-300 text-center"><ReportProblemIcon/></span> | |||||
| ) | |||||
| } else { | |||||
| return ( | |||||
| <span></span> | |||||
| ) | |||||
| } | |||||
| }, | |||||
| flex: 0.2, | |||||
| }, | |||||
| ]; | |||||
| 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 stageDeadline = ["31/03/2024","20/02/2024","01/12/2023","05/01/2024","31/03/2023"] | |||||
| const series2: ApexAxisChartSeries | ApexNonAxisChartSeries = [{ | |||||
| data: [17.1, 28.6, 5.7, 48.6], | |||||
| }]; | |||||
| const series: ApexAxisChartSeries | ApexNonAxisChartSeries = [{ | |||||
| name: 'Current Stage Completion Percentage', | |||||
| data: [80, 55, 40, 65, 70], | |||||
| }]; | |||||
| const options2 : ApexOptions = { | |||||
| chart: { | |||||
| type: 'donut', | |||||
| }, | |||||
| colors: colorArray, | |||||
| plotOptions: { | |||||
| pie: { | |||||
| donut:{ | |||||
| labels:{ | |||||
| show:true, | |||||
| name:{ | |||||
| show:true, | |||||
| }, | |||||
| value:{ | |||||
| show:true, | |||||
| fontWeight: 500, | |||||
| fontSize: '30px', | |||||
| color: "#3e98c7", | |||||
| }, | |||||
| total:{ | |||||
| show: true, | |||||
| showAlways: true, | |||||
| label: 'Spent', | |||||
| fontFamily: 'sans-serif', | |||||
| formatter: function (val) { | |||||
| return totalSpentPercentage + "%" | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| }, | |||||
| labels: projectArray, | |||||
| legend: { | |||||
| show: false, | |||||
| }, | |||||
| responsive: [{ | |||||
| breakpoint: 480, | |||||
| options: { | |||||
| chart: { | |||||
| width: 200 | |||||
| }, | |||||
| legend: { | |||||
| position: 'bottom', | |||||
| show:false | |||||
| } | |||||
| } | |||||
| }] | |||||
| } | |||||
| const options: ApexOptions = { | |||||
| chart: { | |||||
| type: 'bar', | |||||
| height: 350 | |||||
| }, | |||||
| colors: ['#f57f90', '#94f7d6', '#87c5f5', '#ab95f5', '#fcd68b'], | |||||
| plotOptions: { | |||||
| bar: { | |||||
| horizontal: true, | |||||
| distributed: true, | |||||
| }, | |||||
| }, | |||||
| dataLabels: { | |||||
| enabled: false | |||||
| }, | |||||
| xaxis: { | |||||
| categories: [ | |||||
| 'Consultancy Project 123', | |||||
| 'Consultancy Project 456', | |||||
| 'Construction Project A', | |||||
| 'Construction Project B', | |||||
| 'Construction Project C', | |||||
| ], | |||||
| }, | |||||
| yaxis: { | |||||
| title: { | |||||
| text: 'Projects' | |||||
| }, | |||||
| labels: { | |||||
| maxWidth: 200, | |||||
| style: { | |||||
| cssClass: 'apexcharts-yaxis-label', | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| title: { | |||||
| text: 'Current Stage Completion Percentage', | |||||
| align: 'center' | |||||
| }, | |||||
| grid: { | |||||
| borderColor: '#f1f1f1', | |||||
| }, | |||||
| annotations: { | |||||
| } | |||||
| }; | |||||
| const handleSelectionChange = (newSelectionModel: GridRowSelectionModel) => { | |||||
| const selectedRowsData = rows2.filter((row) => | |||||
| newSelectionModel.includes(row.id) | |||||
| ); | |||||
| console.log(selectedRowsData) | |||||
| const projectArray = [] | |||||
| const pieChartColorArray = [] | |||||
| let totalSpent = 0 | |||||
| let totalBudgetManhour = 0 | |||||
| const percentageArray = [] | |||||
| for (let i = 0; i <= selectedRowsData.length; i++) { | |||||
| if (i === selectedRowsData.length && i > 0) { | |||||
| projectArray.push("Remained") | |||||
| } else if (selectedRowsData.length > 0) { | |||||
| projectArray.push(selectedRowsData[i].project) | |||||
| totalBudgetManhour += Number(selectedRowsData[i].budgetedManhour) | |||||
| totalSpent += Number(selectedRowsData[i].spentManhour) | |||||
| pieChartColorArray.push(selectedRowsData[i].color) | |||||
| } | |||||
| } | |||||
| for (let i = 0; i <= selectedRowsData.length; i++) { | |||||
| if (i === selectedRowsData.length && i > 0) { | |||||
| const remainedManhour = (totalBudgetManhour - totalSpent) | |||||
| percentageArray.push(Number(((remainedManhour / totalBudgetManhour) * 100).toFixed(1))) | |||||
| } else if (selectedRowsData.length > 0) { | |||||
| let percentage = ((Number(selectedRowsData[i].spentManhour) / totalBudgetManhour) * 100).toFixed(1) | |||||
| percentageArray.push(Number(percentage)) | |||||
| } | |||||
| } | |||||
| setProjectBudgetManhour(totalBudgetManhour.toFixed(2)) | |||||
| setActualManhourSpent(totalSpent.toFixed(2)) | |||||
| setRemainedManhour((totalBudgetManhour - totalSpent).toFixed(2)) | |||||
| setLastUpdate(new Date().toLocaleDateString('en-GB')) | |||||
| setSelectionModel(newSelectionModel) | |||||
| console.log(projectArray) | |||||
| setProjectArray(projectArray) | |||||
| setPercentageArray(percentageArray) | |||||
| console.log(percentageArray) | |||||
| setTotalSpentPercentage(((totalSpent/totalBudgetManhour)*100).toFixed(1)) | |||||
| if (projectArray.length > 0 && projectArray.includes("Remained")) { | |||||
| const nonLastRecordColors = pieChartColorArray; | |||||
| setColorArray([...nonLastRecordColors.slice(0, projectArray.length - 1), "#a3a3a3"]); | |||||
| } else { | |||||
| setColorArray(pieChartColorArray); | |||||
| } | |||||
| }; | |||||
| const applySearch = (data: any) => { | |||||
| console.log(data) | |||||
| setSearchCriteria(data) | |||||
| } | |||||
| return ( | |||||
| <Grid item sm> | |||||
| {/* <CustomSearchForm applySearch={applySearch} fields={InputFields}/> */} | |||||
| {/* <CustomDatagrid rows={rows} columns={columns} columnWidth={200} dataGridHeight={300}/> */} | |||||
| <div style={{display:"inline-block",width:"70%"}}> | |||||
| <Grid item xs={12} md={12} lg={12}> | |||||
| <Card> | |||||
| <CardHeader className="text-slate-500" title="Project Progress"/> | |||||
| <div style={{display:"inline-block",width:"99%"}}> | |||||
| <ReactApexChart | |||||
| options={options} | |||||
| series={series} | |||||
| type="bar" | |||||
| height={350} | |||||
| /> | |||||
| </div> | |||||
| {/* <div style={{display:"inline-block",width:"20%",verticalAlign:"top",textAlign:"center"}}> | |||||
| <p><strong><u>Stage Deadline</u></strong></p> | |||||
| {stageDeadline.map((date, index) => { | |||||
| const marginTop = index === 0 ? 25 : 20; | |||||
| return ( | |||||
| <p style={{marginTop:marginTop}} key={index}>{date}</p> | |||||
| ); | |||||
| })} | |||||
| </div> */} | |||||
| <CardHeader className="text-slate-500" title="Current Stage Due Date"/> | |||||
| <div style={{display:"inline-block",width:"99%",marginLeft:10}}> | |||||
| <CustomDatagrid rows={rows2} columns={columns2} columnWidth={200} dataGridHeight={300} checkboxSelection={true} onRowSelectionModelChange={handleSelectionChange} selectionModel={selectionModel}/> | |||||
| </div> | |||||
| </Card> | |||||
| </Grid> | |||||
| </div> | |||||
| <div style={{display:"inline-block",width:"30%",verticalAlign:"top",marginLeft:0}}> | |||||
| <Grid item xs={12} md={12} lg={12}> | |||||
| <Card style={{marginLeft:15,marginRight:20}}> | |||||
| <CardHeader className="text-slate-500" title="Overall Progress per Project"/> | |||||
| {percentageArray.length === 0 &&( | |||||
| <div className="mt-10 mb-10 ml-5 mr-5 text-lg font-medium text-center" style={{color:"#898d8d"}}>Please select the project you want to check.</div> | |||||
| )} | |||||
| {percentageArray.length > 0 &&( | |||||
| <ReactApexChart | |||||
| options={options2} | |||||
| series={percentageArray} | |||||
| type="donut" | |||||
| /> | |||||
| )} | |||||
| </Card> | |||||
| </Grid> | |||||
| <Grid item xs={12} md={12} lg={12}> | |||||
| <Card style={{marginLeft:15,marginRight:20,marginTop:20}}> | |||||
| <div> | |||||
| <div className="mt-5 text-lg font-medium" style={{color:"#898d8d"}}><span style={{marginLeft:"5%"}}>Project Budget Manhour</span></div> | |||||
| <div className="mt-2 text-2xl font-extrabold" style={{color:"#6b87cf"}}><span style={{marginLeft:"5%"}}>{projectBudgetManhour}</span></div> | |||||
| </div> | |||||
| <hr/> | |||||
| <div> | |||||
| <div className="mt-2 text-lg font-medium" style={{color:"#898d8d"}}><span style={{marginLeft:"5%"}}>Actual Manhour Spent</span></div> | |||||
| <div className="mt-2 text-2xl font-extrabold" style={{color:"#6b87cf"}}><span style={{marginLeft:"5%"}}>{actualManhourSpent}</span></div> | |||||
| </div> | |||||
| <hr/> | |||||
| <div> | |||||
| <div className="mt-2 text-lg font-medium" style={{color:"#898d8d"}}><span style={{marginLeft:"5%"}}>Remained Manhour</span></div> | |||||
| <div className="mt-2 text-2xl font-extrabold" style={{color:"#6b87cf"}}><span style={{marginLeft:"5%"}}>{remainedManhour}</span></div> | |||||
| </div> | |||||
| <hr/> | |||||
| <div> | |||||
| <div className="mt-2 text-lg font-medium" style={{color:"#898d8d"}}><span style={{marginLeft:"5%"}}>Last Update</span></div> | |||||
| <div className="mt-2 mb-5 text-2xl font-extrabold" style={{color:"#6b87cf"}}><span style={{marginLeft:"5%"}}>{lastUpdate}</span></div> | |||||
| </div> | |||||
| </Card> | |||||
| </Grid> | |||||
| </div> | |||||
| </Grid> | |||||
| ); | |||||
| }; | |||||
| export default ProgressByClient; | |||||
| @@ -0,0 +1 @@ | |||||
| export { default } from "./ProgressByClient"; | |||||
| @@ -0,0 +1,57 @@ | |||||
| "use client"; | |||||
| import { ProjectResult } from "@/app/api/projects"; | |||||
| import React, { useMemo, useState } from "react"; | |||||
| import SearchBox, { Criterion } from "../SearchBox"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| import SearchResults, { Column } from "../SearchResults"; | |||||
| import { ClientProjectResult } from "@/app/api/clientprojects"; | |||||
| interface Props { | |||||
| projects: ClientProjectResult[]; | |||||
| } | |||||
| type SearchQuery = Partial<Omit<ClientProjectResult, "id">>; | |||||
| type SearchParamNames = keyof SearchQuery; | |||||
| const ProgressByClientSearch: React.FC<Props> = ({ projects }) => { | |||||
| const { t } = useTranslation("projects"); | |||||
| // If project searching is done on the server-side, then no need for this. | |||||
| const [filteredProjects, setFilteredProjects] = useState(projects); | |||||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | |||||
| () => [ | |||||
| { label: "Client Code", paramName: "clientCode", type: "text" }, | |||||
| { label: "Client Name", paramName: "clientName", type: "text" }, | |||||
| ], | |||||
| [t], | |||||
| ); | |||||
| const columns = useMemo<Column<ClientProjectResult>[]>( | |||||
| () => [ | |||||
| { name: "clientCode", label: t("Project Code") }, | |||||
| { name: "clientName", label: t("Project Name") }, | |||||
| { name: "SubsidiaryClientCode", label: t("Project Category") }, | |||||
| { name: "SubsidiaryClientName", label: t("Team") }, | |||||
| { name: "NoOfProjects", label: t("Client") }, | |||||
| ], | |||||
| [t], | |||||
| ); | |||||
| return ( | |||||
| <> | |||||
| <SearchBox | |||||
| criteria={searchCriteria} | |||||
| onSearch={(query) => { | |||||
| console.log(query); | |||||
| }} | |||||
| /> | |||||
| <SearchResults<ClientProjectResult> | |||||
| items={filteredProjects} | |||||
| columns={columns} | |||||
| /> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default ProgressByClientSearch; | |||||
| @@ -0,0 +1,40 @@ | |||||
| import Card from "@mui/material/Card"; | |||||
| import CardContent from "@mui/material/CardContent"; | |||||
| import Skeleton from "@mui/material/Skeleton"; | |||||
| import Stack from "@mui/material/Stack"; | |||||
| import React from "react"; | |||||
| // Can make this nicer | |||||
| export const ProgressByClientSearchLoading: React.FC = () => { | |||||
| return ( | |||||
| <> | |||||
| <Card> | |||||
| <CardContent> | |||||
| <Stack spacing={2}> | |||||
| <Skeleton variant="rounded" height={60} /> | |||||
| <Skeleton variant="rounded" height={60} /> | |||||
| <Skeleton variant="rounded" height={60} /> | |||||
| <Skeleton | |||||
| variant="rounded" | |||||
| height={50} | |||||
| width={100} | |||||
| sx={{ alignSelf: "flex-end" }} | |||||
| /> | |||||
| </Stack> | |||||
| </CardContent> | |||||
| </Card> | |||||
| <Card> | |||||
| <CardContent> | |||||
| <Stack spacing={2}> | |||||
| <Skeleton variant="rounded" height={40} /> | |||||
| <Skeleton variant="rounded" height={40} /> | |||||
| <Skeleton variant="rounded" height={40} /> | |||||
| <Skeleton variant="rounded" height={40} /> | |||||
| </Stack> | |||||
| </CardContent> | |||||
| </Card> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default ProgressByClientSearchLoading; | |||||
| @@ -0,0 +1,18 @@ | |||||
| import { fetchClientProjects } from "@/app/api/clientprojects"; | |||||
| import React from "react"; | |||||
| import ProgressByClientSearch from "./ProgressByClientSearch"; | |||||
| import ProgressByClientSearchLoading from "./ProgressByClientSearchLoading"; | |||||
| interface SubComponents { | |||||
| Loading: typeof ProgressByClientSearchLoading; | |||||
| } | |||||
| const ProgressByClientSearchWrapper: React.FC & SubComponents = async () => { | |||||
| const clentprojects = await fetchClientProjects(); | |||||
| return <ProgressByClientSearch projects={clentprojects} />; | |||||
| }; | |||||
| ProgressByClientSearchWrapper.Loading = ProgressByClientSearchLoading; | |||||
| export default ProgressByClientSearchWrapper; | |||||
| @@ -0,0 +1 @@ | |||||
| export { default } from "./ProgressByClientSearchWrapper"; | |||||
| @@ -77,7 +77,7 @@ function SearchBox<T extends string>({ criteria, onSearch }: Props<T>) { | |||||
| return ( | return ( | ||||
| <Card> | <Card> | ||||
| <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}> | <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}> | ||||
| <Typography variant="overline">{t("Search Criteria")}</Typography> | |||||
| <Typography variant="overline" style={{fontWeight:"600"}}>{t("Search Criteria")}</Typography> | |||||
| <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | ||||
| {criteria.map((c) => { | {criteria.map((c) => { | ||||
| return ( | return ( | ||||
| @@ -0,0 +1,12 @@ | |||||
| import { createTheme } from '@mui/material/styles'; | |||||
| const theme = createTheme({ | |||||
| palette: { | |||||
| background: { | |||||
| default: '#fcfcfc' | |||||
| } | |||||
| } | |||||
| }); | |||||
| export default theme; | |||||
| @@ -189,7 +189,7 @@ const components: ThemeOptions["components"] = { | |||||
| }, | }, | ||||
| [`&.Mui-focused`]: { | [`&.Mui-focused`]: { | ||||
| backgroundColor: "transparent", | backgroundColor: "transparent", | ||||
| borderColor: palette.primary.main, | |||||
| borderColor: "palette.primary.main", | |||||
| boxShadow: `${palette.primary.main} 0 0 0 2px`, | boxShadow: `${palette.primary.main} 0 0 0 2px`, | ||||
| }, | }, | ||||
| [`&.Mui-error`]: { | [`&.Mui-error`]: { | ||||
| @@ -216,7 +216,7 @@ const components: ThemeOptions["components"] = { | |||||
| [`&.Mui-focused`]: { | [`&.Mui-focused`]: { | ||||
| backgroundColor: "transparent", | backgroundColor: "transparent", | ||||
| [`& .MuiOutlinedInput-notchedOutline`]: { | [`& .MuiOutlinedInput-notchedOutline`]: { | ||||
| borderColor: palette.primary.main, | |||||
| borderColor: "palette.primary.main", | |||||
| boxShadow: `${palette.primary.main} 0 0 0 2px`, | boxShadow: `${palette.primary.main} 0 0 0 2px`, | ||||
| }, | }, | ||||
| }, | }, | ||||