@@ -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`, | ||||
}, | }, | ||||
}, | }, | ||||