| @@ -1,17 +0,0 @@ | |||||
| import { Metadata } from "next"; | |||||
| import { I18nProvider } from "@/i18n"; | |||||
| import UserWorkspacePage from "@/components/UserWorkspacePage/UserWorkspacePage"; | |||||
| export const metadata: Metadata = { | |||||
| title: "User Workspace", | |||||
| }; | |||||
| const Home: React.FC = async () => { | |||||
| return ( | |||||
| <I18nProvider namespaces={["home"]}> | |||||
| <UserWorkspacePage /> | |||||
| </I18nProvider> | |||||
| ); | |||||
| }; | |||||
| export default Home; | |||||
| @@ -1,26 +1,30 @@ | |||||
| import { Metadata } from "next"; | import { Metadata } from "next"; | ||||
| import { I18nProvider } from "@/i18n"; | import { I18nProvider } from "@/i18n"; | ||||
| import DashboardPageButton from "@/components/DashboardPage/DashboardTabButton"; | |||||
| import ProgressByClientSearch from "@/components/ProgressByClientSearch"; | |||||
| import { Suspense } from "react"; | import { Suspense } from "react"; | ||||
| import Tabs, { TabsProps } from "@mui/material/Tabs"; | |||||
| import Tab from "@mui/material/Tab"; | |||||
| import { getServerI18n } from "@/i18n"; | import { getServerI18n } from "@/i18n"; | ||||
| import DashboardPage from "@/components/DashboardPage"; | import DashboardPage from "@/components/DashboardPage"; | ||||
| import { SearchParams } from "@/app/utils/fetchUtil"; | |||||
| export const metadata: Metadata = { | export const metadata: Metadata = { | ||||
| title: "Dashboard", | title: "Dashboard", | ||||
| }; | }; | ||||
| // type Props = { | |||||
| // test: string | |||||
| // } | |||||
| const Dashboard: React.FC = async () => { | |||||
| type Props = { | |||||
| } & SearchParams | |||||
| const Dashboard: React.FC<Props> = async ({ | |||||
| searchParams | |||||
| }) => { | |||||
| const { t } = await getServerI18n("dashboard"); | const { t } = await getServerI18n("dashboard"); | ||||
| return ( | return ( | ||||
| <I18nProvider namespaces={["dashboard", "common"]}> | <I18nProvider namespaces={["dashboard", "common"]}> | ||||
| <DashboardPage/> | |||||
| <Suspense fallback={<DashboardPage.Loading />}> | |||||
| <DashboardPage | |||||
| searchParams={searchParams} | |||||
| /> | |||||
| </Suspense> | |||||
| </I18nProvider> | </I18nProvider> | ||||
| ) | ) | ||||
| }; | }; | ||||
| @@ -1,26 +0,0 @@ | |||||
| import { Metadata } from "next"; | |||||
| import { I18nProvider } from "@/i18n"; | |||||
| import DashboardPage from "@/components/DashboardPage/DashboardPage"; | |||||
| import DashboardPageButton from "@/components/DashboardPage/DashboardTabButton"; | |||||
| import ProgressCashFlowSearch from "@/components/ProgressCashFlowSearch"; | |||||
| import { Suspense } from "react"; | |||||
| import Tabs, { TabsProps } from "@mui/material/Tabs"; | |||||
| import Tab from "@mui/material/Tab"; | |||||
| import Typography from "@mui/material/Typography"; | |||||
| import CompanyTeamCashFlowComponent from "@/components/CompanyTeamCashFlow"; | |||||
| export const metadata: Metadata = { | |||||
| title: "Project Status by Client", | |||||
| }; | |||||
| const CompanyTeamCashFlow: React.FC = () => { | |||||
| return ( | |||||
| <I18nProvider namespaces={["dashboard"]}> | |||||
| <Typography variant="h4" marginInlineEnd={2}> | |||||
| Company / Team Cash Flow | |||||
| </Typography> | |||||
| <CompanyTeamCashFlowComponent /> | |||||
| </I18nProvider> | |||||
| ); | |||||
| }; | |||||
| export default CompanyTeamCashFlow; | |||||
| @@ -1,29 +0,0 @@ | |||||
| import { Metadata } from "next"; | |||||
| import { I18nProvider } from "@/i18n"; | |||||
| import DashboardPage from "@/components/DashboardPage/DashboardPage"; | |||||
| import DashboardPageButton from "@/components/DashboardPage/DashboardTabButton"; | |||||
| import ProgressCashFlowSearch from "@/components/ProgressCashFlowSearch"; | |||||
| import { Suspense } from "react"; | |||||
| import Tabs, { TabsProps } from "@mui/material/Tabs"; | |||||
| import Tab from "@mui/material/Tab"; | |||||
| import Typography from "@mui/material/Typography"; | |||||
| import ProjectCashFlowComponent from "@/components/ProjectCashFlow"; | |||||
| export const metadata: Metadata = { | |||||
| title: "Project Status by Client", | |||||
| }; | |||||
| const ProjectCashFlow: React.FC = () => { | |||||
| return ( | |||||
| <I18nProvider namespaces={["dashboard"]}> | |||||
| <Typography variant="h4" marginInlineEnd={2}> | |||||
| Project Cash Flow | |||||
| </Typography> | |||||
| {/* <Suspense fallback={<ProgressCashFlowSearch.Loading />}> | |||||
| <ProgressCashFlowSearch/> | |||||
| </Suspense> */} | |||||
| <ProjectCashFlowComponent /> | |||||
| </I18nProvider> | |||||
| ); | |||||
| }; | |||||
| export default ProjectCashFlow; | |||||
| @@ -1,26 +0,0 @@ | |||||
| 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 ProjectFinancialSummaryComponents from "@/components/ProjectFinancialSummary"; | |||||
| export const metadata: Metadata = { | |||||
| title: "Project Status by Client", | |||||
| }; | |||||
| const ProjectFinancialSummary: React.FC = () => { | |||||
| return ( | |||||
| <I18nProvider namespaces={["dashboard"]}> | |||||
| <Typography variant="h4" marginInlineEnd={2}> | |||||
| Project Financial Summary | |||||
| </Typography> | |||||
| <ProjectFinancialSummaryComponents /> | |||||
| </I18nProvider> | |||||
| ); | |||||
| }; | |||||
| export default ProjectFinancialSummary; | |||||
| @@ -1,29 +0,0 @@ | |||||
| 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,26 +0,0 @@ | |||||
| 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 StaffUtilizationComponent from "@/components/StaffUtilization"; | |||||
| export const metadata: Metadata = { | |||||
| title: "Project Status by Client", | |||||
| }; | |||||
| const StaffUtilization: React.FC = () => { | |||||
| return ( | |||||
| <I18nProvider namespaces={["dashboard"]}> | |||||
| <Typography variant="h4" marginInlineEnd={2}> | |||||
| Staff Utilization | |||||
| </Typography> | |||||
| <StaffUtilizationComponent /> | |||||
| </I18nProvider> | |||||
| ); | |||||
| }; | |||||
| export default StaffUtilization; | |||||
| @@ -13,7 +13,6 @@ const User: React.FC = () => { | |||||
| User | User | ||||
| </Typography> | </Typography> | ||||
| <></> | <></> | ||||
| {/* <CompanyTeamCashFlowComponent /> */} | |||||
| </I18nProvider> | </I18nProvider> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -17,7 +17,7 @@ export default async function RootLayout({ | |||||
| return ( | return ( | ||||
| <html lang={lang}> | <html lang={lang}> | ||||
| <body> | <body> | ||||
| <ThemeRegistry>{children}</ThemeRegistry> | |||||
| <ThemeRegistry lang={lang}>{children}</ThemeRegistry> | |||||
| </body> | </body> | ||||
| </html> | </html> | ||||
| ); | ); | ||||
| @@ -1,7 +1,7 @@ | |||||
| import { permanentRedirect } from "next/navigation"; | import { permanentRedirect } from "next/navigation"; | ||||
| const Home: React.FC = async () => { | const Home: React.FC = async () => { | ||||
| permanentRedirect("/home"); | |||||
| permanentRedirect("/dashboard"); | |||||
| }; | }; | ||||
| export default Home; | export default Home; | ||||
| @@ -3,6 +3,10 @@ import { getServerSession } from "next-auth"; | |||||
| import { headers } from "next/headers"; | import { headers } from "next/headers"; | ||||
| import { redirect } from "next/navigation"; | import { redirect } from "next/navigation"; | ||||
| export type SearchParams = { | |||||
| searchParams: { [key: string]: string | string[] | undefined }; | |||||
| } | |||||
| export const serverFetch: typeof fetch = async (input, init) => { | export const serverFetch: typeof fetch = async (input, init) => { | ||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
| const session = await getServerSession<any, SessionWithTokens>(authOptions); | const session = await getServerSession<any, SessionWithTokens>(authOptions); | ||||
| @@ -1,247 +0,0 @@ | |||||
| import * as React from "react"; | |||||
| import { | |||||
| Card, | |||||
| CardHeader, | |||||
| CardContent, | |||||
| SxProps, | |||||
| Theme, | |||||
| Tabs, | |||||
| Tab, | |||||
| Box, | |||||
| Typography, | |||||
| Grid, | |||||
| Link, | |||||
| } from "@mui/material"; | |||||
| import { DataGrid, GridColDef } from "@mui/x-data-grid"; | |||||
| import { darken, lighten, styled } from "@mui/material/styles"; | |||||
| import { ThemeProvider } from "@emotion/react"; | |||||
| import { TAB_THEME } from "@/theme/colorConst"; | |||||
| import AllProjectGrid from "../UserWorkspacePage/ProjectGrid"; | |||||
| interface AssignedProjectGridProps { | |||||
| Title?: string; | |||||
| // rows: any[]; | |||||
| // columns: any[]; | |||||
| columnWidth?: number; | |||||
| Style?: boolean; | |||||
| sx?: SxProps<Theme>; | |||||
| height?: number; | |||||
| [key: string]: any; | |||||
| } | |||||
| interface TabPanelProps { | |||||
| children?: React.ReactNode; | |||||
| index: number; | |||||
| value: number; | |||||
| } | |||||
| function CustomTabPanel(props: TabPanelProps) { | |||||
| const { children, value, index, ...other } = props; | |||||
| return ( | |||||
| <div | |||||
| role="tabpanel" | |||||
| hidden={value !== index} | |||||
| id={`simple-tabpanel-${index}`} | |||||
| aria-labelledby={`simple-tab-${index}`} | |||||
| {...other} | |||||
| > | |||||
| {value === index && ( | |||||
| <Box sx={{ p: 3 }}> | |||||
| <Typography>{children}</Typography> | |||||
| </Box> | |||||
| )} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| function a11yProps(index: number) { | |||||
| return { | |||||
| id: `simple-tab-${index}`, | |||||
| "aria-controls": `simple-tabpanel-${index}`, | |||||
| }; | |||||
| } | |||||
| const AssignedProjectGrid: React.FC<AssignedProjectGridProps> = ({ | |||||
| Title, | |||||
| rows, | |||||
| columns, | |||||
| columnWidth, | |||||
| Style = true, | |||||
| sx, | |||||
| height, | |||||
| ...props | |||||
| }) => { | |||||
| // const modifiedColumns = columns.map((column) => { | |||||
| // return { | |||||
| // ...column, | |||||
| // width: columnWidth ?? 150, | |||||
| // }; | |||||
| // }); | |||||
| // const rowsWithDefaultValues = rows.map((row) => { | |||||
| // return { ...row }; | |||||
| // }); | |||||
| const getBackgroundColor = (color: string, mode: "light" | "dark") => | |||||
| mode === "dark" ? darken(color, 0.7) : lighten(color, 0.7); | |||||
| const getHoverBackgroundColor = (color: string, mode: "light" | "dark") => | |||||
| mode === "dark" ? darken(color, 0.6) : lighten(color, 0.6); | |||||
| const getSelectedBackgroundColor = (color: string, mode: "light" | "dark") => | |||||
| mode === "dark" ? darken(color, 0.5) : lighten(color, 0.5); | |||||
| const getSelectedHoverBackgroundColor = ( | |||||
| color: string, | |||||
| mode: "light" | "dark", | |||||
| ) => (mode === "dark" ? darken(color, 0.4) : lighten(color, 0.4)); | |||||
| const StyledDataGrid = styled(DataGrid)(({ theme }) => ({ | |||||
| "& .super-app-theme--Open": { | |||||
| backgroundColor: getBackgroundColor( | |||||
| theme.palette.info.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getHoverBackgroundColor( | |||||
| theme.palette.info.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| "&.Mui-selected": { | |||||
| backgroundColor: getSelectedBackgroundColor( | |||||
| theme.palette.info.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getSelectedHoverBackgroundColor( | |||||
| theme.palette.info.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| "& .super-app-theme--finish": { | |||||
| backgroundColor: getBackgroundColor( | |||||
| theme.palette.success.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getHoverBackgroundColor( | |||||
| theme.palette.success.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| "&.Mui-selected": { | |||||
| backgroundColor: getSelectedBackgroundColor( | |||||
| theme.palette.success.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getSelectedHoverBackgroundColor( | |||||
| theme.palette.success.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| "& .super-app-theme--danger": { | |||||
| backgroundColor: getBackgroundColor( | |||||
| theme.palette.warning.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getHoverBackgroundColor( | |||||
| theme.palette.warning.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| "&.Mui-selected": { | |||||
| backgroundColor: getSelectedBackgroundColor( | |||||
| theme.palette.warning.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getSelectedHoverBackgroundColor( | |||||
| theme.palette.warning.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| "& .super-app-theme--warning": { | |||||
| backgroundColor: getBackgroundColor( | |||||
| theme.palette.error.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getHoverBackgroundColor( | |||||
| theme.palette.error.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| "&.Mui-selected": { | |||||
| backgroundColor: getSelectedBackgroundColor( | |||||
| theme.palette.error.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getSelectedHoverBackgroundColor( | |||||
| theme.palette.error.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| })); | |||||
| const [value, setValue] = React.useState(0); | |||||
| const handleChange = (event: React.SyntheticEvent, newValue: number) => { | |||||
| setValue(newValue); | |||||
| }; | |||||
| return ( | |||||
| <div style={{ height: height ?? 400, width: "100%" }}> | |||||
| <Card style={{ margin: "auto 20px auto 20px" }}> | |||||
| {Title && <CardHeader title={Title} />} | |||||
| <CardContent | |||||
| style={{ | |||||
| padding: "0px 24px 24px 24px", | |||||
| display: "flex", | |||||
| alignItems: "center", | |||||
| }} | |||||
| > | |||||
| <div> | |||||
| <ThemeProvider theme={TAB_THEME}> | |||||
| <Box sx={{ borderBottom: 4, borderColor: "divider" }}> | |||||
| <Tabs | |||||
| value={value} | |||||
| onChange={handleChange} | |||||
| aria-label="Manage assigned project" | |||||
| > | |||||
| <Tab label="All Projects" {...a11yProps(0)} /> | |||||
| <Tab label="On Track" {...a11yProps(1)} /> | |||||
| <Tab label="Potential Delay" {...a11yProps(2)} /> | |||||
| </Tabs> | |||||
| </Box> | |||||
| {/* <CustomTabPanel value={value} index={0}> | |||||
| Item {value} | |||||
| </CustomTabPanel> | |||||
| <CustomTabPanel value={value} index={1}> | |||||
| Item {value} | |||||
| </CustomTabPanel> | |||||
| <CustomTabPanel value={value} index={2}> | |||||
| Item {value} | |||||
| </CustomTabPanel> */} | |||||
| </ThemeProvider> | |||||
| </div> | |||||
| </CardContent> | |||||
| <AllProjectGrid tab={value} /> | |||||
| </Card> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default AssignedProjectGrid; | |||||
| @@ -1 +0,0 @@ | |||||
| export { default } from "./AssignedProjectGrid"; | |||||
| @@ -1,306 +0,0 @@ | |||||
| "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"; | |||||
| import ProgressCashFlowSearch from "@/components/ProgressCashFlowSearch"; | |||||
| import { Input, Label } from "reactstrap"; | |||||
| import Select, { components } from "react-select"; | |||||
| const CompanyTeamCashFlow: React.FC = () => { | |||||
| const todayDate = new Date(); | |||||
| const [selectionModel, setSelectionModel]: any[] = React.useState([]); | |||||
| const [cashFlowYear, setCashFlowYear]: any[] = React.useState( | |||||
| todayDate.getFullYear(), | |||||
| ); | |||||
| const teamOptions = [ | |||||
| { value: 1, label: "XXX Team" }, | |||||
| { value: 2, label: "YYY Team" }, | |||||
| { value: 3, label: "ZZZ Team" }, | |||||
| ]; | |||||
| const columns = [ | |||||
| { | |||||
| id: "projectCode", | |||||
| field: "projectCode", | |||||
| headerName: "Project Code", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: "projectName", | |||||
| field: "projectName", | |||||
| headerName: "Project Name", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: "team", | |||||
| field: "team", | |||||
| headerName: "Team", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: "teamLeader", | |||||
| field: "teamLeader", | |||||
| headerName: "Team Leader", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: "startDate", | |||||
| field: "startDate", | |||||
| headerName: "Start Date", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: "targetEndDate", | |||||
| field: "targetEndDate", | |||||
| headerName: "Target End Date", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: "client", | |||||
| field: "client", | |||||
| headerName: "Client", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: "subsidiary", | |||||
| field: "subsidiary", | |||||
| headerName: "Subsidiary", | |||||
| flex: 1, | |||||
| }, | |||||
| ]; | |||||
| const ledgerColumns = [ | |||||
| { | |||||
| id: "date", | |||||
| field: "date", | |||||
| headerName: "Date", | |||||
| flex: 0.5, | |||||
| }, | |||||
| { | |||||
| id: "expenditure", | |||||
| field: "expenditure", | |||||
| headerName: "Expenditure (HKD)", | |||||
| flex: 0.6, | |||||
| }, | |||||
| { | |||||
| id: "income", | |||||
| field: "income", | |||||
| headerName: "Income (HKD)", | |||||
| flex: 0.6, | |||||
| }, | |||||
| { | |||||
| id: "cashFlowBalance", | |||||
| field: "cashFlowBalance", | |||||
| headerName: "Cash Flow Balance (HKD)", | |||||
| flex: 0.6, | |||||
| }, | |||||
| { | |||||
| id: "remarks", | |||||
| field: "remarks", | |||||
| headerName: "Remarks", | |||||
| flex: 1, | |||||
| }, | |||||
| ]; | |||||
| const options: ApexOptions = { | |||||
| chart: { | |||||
| height: 350, | |||||
| type: "line", | |||||
| }, | |||||
| stroke: { | |||||
| width: [0, 0, 2, 2], | |||||
| }, | |||||
| plotOptions: { | |||||
| bar: { | |||||
| horizontal: false, | |||||
| distributed: false, | |||||
| }, | |||||
| }, | |||||
| dataLabels: { | |||||
| enabled: false, | |||||
| }, | |||||
| xaxis: { | |||||
| categories: [ | |||||
| "Q1", | |||||
| "Q2", | |||||
| "Q3", | |||||
| "Q4", | |||||
| "Q5", | |||||
| "Q6", | |||||
| "Q7", | |||||
| "Q8", | |||||
| "Q9", | |||||
| "Q10", | |||||
| "Q11", | |||||
| "Q12", | |||||
| ], | |||||
| }, | |||||
| yaxis: [ | |||||
| { | |||||
| title: { | |||||
| text: "Monthly Income and Expenditure(HKD)", | |||||
| }, | |||||
| min: 0, | |||||
| max: 3700000, | |||||
| tickAmount: 5, | |||||
| }, | |||||
| { | |||||
| show: false, | |||||
| seriesName: "Monthly_Expenditure", | |||||
| title: { | |||||
| text: "Monthly Expenditure (HKD)", | |||||
| }, | |||||
| min: 0, | |||||
| max: 3700000, | |||||
| tickAmount: 5, | |||||
| }, | |||||
| { | |||||
| seriesName: "Cumulative_Income", | |||||
| opposite: true, | |||||
| title: { | |||||
| text: "Cumulative Income and Expenditure(HKD)", | |||||
| }, | |||||
| min: 0, | |||||
| max: 21000000, | |||||
| tickAmount: 5, | |||||
| }, | |||||
| { | |||||
| show: false, | |||||
| seriesName: "Cumulative_Expenditure", | |||||
| opposite: true, | |||||
| title: { | |||||
| text: "Cumulative Expenditure (HKD)", | |||||
| }, | |||||
| min: 0, | |||||
| max: 21000000, | |||||
| tickAmount: 5, | |||||
| }, | |||||
| ], | |||||
| grid: { | |||||
| borderColor: "#f1f1f1", | |||||
| }, | |||||
| annotations: {}, | |||||
| series: [ | |||||
| { | |||||
| name: "Monthly_Income", | |||||
| type: "column", | |||||
| color: "#ffde91", | |||||
| data: [ | |||||
| 1280000, 170000, 3600000, 2400000, 1000000, 1800000, 1800000, 1200000, | |||||
| 1250000, 1200000, 600000, 2400000, | |||||
| ], | |||||
| }, | |||||
| { | |||||
| name: "Monthly_Expenditure", | |||||
| type: "column", | |||||
| color: "#82b59a", | |||||
| data: [ | |||||
| 1200000, 1400000, 2000000, 1400000, 1450000, 1800000, 1200000, | |||||
| 1400000, 1200000, 1600000, 2000000, 1600000, | |||||
| ], | |||||
| }, | |||||
| { | |||||
| name: "Cumulative_Income", | |||||
| type: "line", | |||||
| color: "#EE6D7A", | |||||
| data: [ | |||||
| 500000, 3000000, 7000000, 9000000, 10000000, 13000000, 14000000, | |||||
| 16000000, 17000000, 17500000, 18000000, 20000000, | |||||
| ], | |||||
| }, | |||||
| { | |||||
| name: "Cumulative_Expenditure", | |||||
| type: "line", | |||||
| color: "#7cd3f2", | |||||
| data: [ | |||||
| 400000, 2800000, 4000000, 5200000, 7100000, 8000000, 10000000, | |||||
| 11000000, 12100000, 14000000, 15400000, 17200000, | |||||
| ], | |||||
| }, | |||||
| ], | |||||
| }; | |||||
| return ( | |||||
| <> | |||||
| <Grid item sm> | |||||
| <div style={{ display: "inline-block", width: "100%" }}> | |||||
| <Grid item xs={12} md={12} lg={12}> | |||||
| <Card> | |||||
| <CardHeader | |||||
| className="text-slate-500" | |||||
| title="Company and Team Cash Flow by Month" | |||||
| /> | |||||
| <div style={{ display: "inline-block", width: "99%" }}> | |||||
| <div className="inline-block"> | |||||
| <Label className="text-slate-500 font-medium ml-6"> | |||||
| Period: | |||||
| </Label> | |||||
| <Input | |||||
| id={"cashFlowYear"} | |||||
| value={cashFlowYear} | |||||
| readOnly={true} | |||||
| bsSize="lg" | |||||
| className="rounded-md text-base w-12" | |||||
| /> | |||||
| </div> | |||||
| <div className="inline-block ml-1"> | |||||
| <button | |||||
| onClick={() => setCashFlowYear(cashFlowYear - 1)} | |||||
| className="hover:cursor-pointer hover:bg-slate-200 bg-transparent rounded-md w-8 h-8 text-base" | |||||
| > | |||||
| < | |||||
| </button> | |||||
| </div> | |||||
| <div className="inline-block ml-1"> | |||||
| <button | |||||
| onClick={() => setCashFlowYear(cashFlowYear + 1)} | |||||
| className="hover:cursor-pointer hover:bg-slate-200 bg-transparent rounded-md w-8 h-8 text-base" | |||||
| > | |||||
| > | |||||
| </button> | |||||
| </div> | |||||
| <div className="inline-block ml-2"> | |||||
| <Label className="text-slate-500 font-medium"> | |||||
| Team: | |||||
| </Label> | |||||
| </div> | |||||
| <div className="inline-block ml-1 w-60"> | |||||
| <Select | |||||
| placeholder="All Team" | |||||
| options={teamOptions} | |||||
| isClearable={true} | |||||
| /> | |||||
| </div> | |||||
| <ReactApexChart | |||||
| options={options} | |||||
| series={options.series} | |||||
| type="line" | |||||
| height="500" | |||||
| /> | |||||
| </div> | |||||
| </Card> | |||||
| </Grid> | |||||
| </div> | |||||
| </Grid> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default CompanyTeamCashFlow; | |||||
| @@ -1 +0,0 @@ | |||||
| export { default } from "./CompanyTeamCashFlow"; | |||||
| @@ -1,240 +0,0 @@ | |||||
| import * as React from "react"; | |||||
| import { | |||||
| Card, | |||||
| CardHeader, | |||||
| CardContent, | |||||
| SxProps, | |||||
| Theme, | |||||
| Grid, | |||||
| } from "@mui/material"; | |||||
| import { DataGrid, GridColDef } from "@mui/x-data-grid"; | |||||
| import { darken, lighten, styled } from "@mui/material/styles"; | |||||
| import { PROJECT_CARD_STYLE } from "@/theme/colorConst"; | |||||
| import { useRef, useEffect, useState } from "react"; | |||||
| import Swal from "sweetalert2"; | |||||
| import styledcmp from "styled-components"; | |||||
| const CardWrapper = styledcmp.div` | |||||
| /* Styles for the card when not hovered */ | |||||
| background-color: #f0f0f0; | |||||
| padding: 10px; | |||||
| /* ...other styles... */ | |||||
| &:hover { | |||||
| /* Styles for the card when hovered */ | |||||
| background-color: #c0c0c0; | |||||
| /* ...other hover styles... */ | |||||
| } | |||||
| `; | |||||
| interface CustomCardGridProps { | |||||
| Title?: string; | |||||
| cardsPerRow?: number; | |||||
| rows?: any[]; | |||||
| columns?: any[]; | |||||
| items: any[]; | |||||
| columnWidth?: number; | |||||
| Style?: boolean; | |||||
| sx?: SxProps<Theme>; | |||||
| dataGridHeight?: number; | |||||
| cardStyle?: any; | |||||
| [key: string]: any; | |||||
| } | |||||
| const CustomCardGrid: React.FC<CustomCardGridProps> = ({ | |||||
| Title, | |||||
| rows, | |||||
| items, | |||||
| columns, | |||||
| columnWidth, | |||||
| cardsPerRow = 4, | |||||
| Style = true, | |||||
| sx, | |||||
| dataGridHeight, | |||||
| ...props | |||||
| }) => { | |||||
| const getBackgroundColor = (color: string, mode: "light" | "dark") => | |||||
| mode === "dark" ? darken(color, 0.7) : lighten(color, 0.7); | |||||
| const getHoverBackgroundColor = (color: string, mode: "light" | "dark") => | |||||
| mode === "dark" ? darken(color, 0.6) : lighten(color, 0.6); | |||||
| const getSelectedBackgroundColor = (color: string, mode: "light" | "dark") => | |||||
| mode === "dark" ? darken(color, 0.5) : lighten(color, 0.5); | |||||
| const getSelectedHoverBackgroundColor = ( | |||||
| color: string, | |||||
| mode: "light" | "dark", | |||||
| ) => (mode === "dark" ? darken(color, 0.4) : lighten(color, 0.4)); | |||||
| const StyledCard = styled(Card)(({ theme }) => ({ | |||||
| "& .super-app-theme--Open": { | |||||
| backgroundColor: getBackgroundColor( | |||||
| theme.palette.info.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getHoverBackgroundColor( | |||||
| theme.palette.info.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| "&.Mui-selected": { | |||||
| backgroundColor: getSelectedBackgroundColor( | |||||
| theme.palette.info.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getSelectedHoverBackgroundColor( | |||||
| theme.palette.info.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| "& .super-app-theme--finish": { | |||||
| backgroundColor: getBackgroundColor( | |||||
| theme.palette.success.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getHoverBackgroundColor( | |||||
| theme.palette.success.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| "&.Mui-selected": { | |||||
| backgroundColor: getSelectedBackgroundColor( | |||||
| theme.palette.success.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getSelectedHoverBackgroundColor( | |||||
| theme.palette.success.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| "& .super-app-theme--danger": { | |||||
| backgroundColor: getBackgroundColor( | |||||
| theme.palette.warning.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getHoverBackgroundColor( | |||||
| theme.palette.warning.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| "&.Mui-selected": { | |||||
| backgroundColor: getSelectedBackgroundColor( | |||||
| theme.palette.warning.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getSelectedHoverBackgroundColor( | |||||
| theme.palette.warning.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| "& .super-app-theme--warning": { | |||||
| backgroundColor: getBackgroundColor( | |||||
| theme.palette.error.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getHoverBackgroundColor( | |||||
| theme.palette.error.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| "&.Mui-selected": { | |||||
| backgroundColor: getSelectedBackgroundColor( | |||||
| theme.palette.error.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getSelectedHoverBackgroundColor( | |||||
| theme.palette.error.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| })); | |||||
| const CardItem = (item: any) => { | |||||
| const cardItem = item.item as Record<string, string>; | |||||
| return ( | |||||
| props.cardStyle ?? ( | |||||
| // <Grid item sx={{ m: 3 }}> | |||||
| <StyledCard style={PROJECT_CARD_STYLE}> | |||||
| <CardContent> | |||||
| {Object.keys(cardItem).map((key) => ( | |||||
| <p key={key}> | |||||
| {key}: {cardItem[key]} | |||||
| </p> | |||||
| ))} | |||||
| </CardContent> | |||||
| </StyledCard> | |||||
| // </Grid> | |||||
| ) | |||||
| ); | |||||
| }; | |||||
| const containerRef = useRef<HTMLDivElement>(null!); | |||||
| const [cardMargin, setCardMargin] = useState(1.5); | |||||
| useEffect(() => { | |||||
| console.log(CardItem); | |||||
| const resizeHandler = () => { | |||||
| const containerWidth = containerRef.current.offsetWidth; | |||||
| const cardCount = items.length; | |||||
| const rootSize = parseFloat( | |||||
| getComputedStyle(document.documentElement).fontSize, | |||||
| ); | |||||
| setCardMargin( | |||||
| (containerWidth - | |||||
| cardsPerRow * | |||||
| (rootSize * parseInt(PROJECT_CARD_STYLE.width.slice(0, -3), 10))) / | |||||
| (2 * cardsPerRow), | |||||
| ); | |||||
| // Set the cardMargin value using style={{margin: `${cardMargin}px`, ...PROJECT_CARD_STYLE}} | |||||
| }; | |||||
| window.addEventListener("resize", resizeHandler); | |||||
| resizeHandler(); // Initial calculation | |||||
| // Swal.fire({ | |||||
| // title: 'Error! ', | |||||
| // text: `Card Count is ${items.length}`, | |||||
| // icon: 'success', | |||||
| // confirmButtonText: 'Jus Cool' | |||||
| // }) | |||||
| return () => { | |||||
| window.removeEventListener("resize", resizeHandler); | |||||
| }; | |||||
| }, [items]); | |||||
| return ( | |||||
| <div | |||||
| ref={containerRef} | |||||
| style={{ display: "flex", flexWrap: "wrap", alignItems: "flex-start" }} | |||||
| > | |||||
| {/* <p>width is {containerRef.current == null? "idk":containerRef.current.offsetWidth}, margin is {cardMargin}</p> */} | |||||
| {items.map((item, index) => ( | |||||
| <div key={index}> | |||||
| {props.cardStyle ? props.cardStyle(item) : <CardItem item={item} />} | |||||
| </div> | |||||
| ))} | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default CustomCardGrid; | |||||
| @@ -1 +0,0 @@ | |||||
| export { default } from "./CustomCardGrid"; | |||||
| @@ -1,309 +0,0 @@ | |||||
| "use client"; | |||||
| import * as React from "react"; | |||||
| import { Card, CardHeader, CardContent, SxProps, Theme } from "@mui/material"; | |||||
| import { DataGrid, GridColDef, GridRowSelectionModel } from "@mui/x-data-grid"; | |||||
| import { darken, lighten, styled } from "@mui/material/styles"; | |||||
| import { useState } from "react"; | |||||
| interface CustomDatagridProps { | |||||
| Title?: string; | |||||
| rows: any[]; | |||||
| columns: any[]; | |||||
| columnWidth?: number; | |||||
| Style?: boolean; | |||||
| sx?: SxProps<Theme>; | |||||
| dataGridHeight?: number; | |||||
| [key: string]: any; | |||||
| checkboxSelection?: boolean; | |||||
| onRowSelectionModelChange?: ( | |||||
| newSelectionModel: GridRowSelectionModel, | |||||
| ) => void; | |||||
| selectionModel?: any; | |||||
| } | |||||
| const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||||
| Title, | |||||
| rows, | |||||
| columns, | |||||
| columnWidth, | |||||
| Style = false, | |||||
| sx, | |||||
| dataGridHeight, | |||||
| checkboxSelection, // Destructure the new prop | |||||
| onRowSelectionModelChange, // Destructure the new prop | |||||
| selectionModel, | |||||
| ...props | |||||
| }) => { | |||||
| const modifiedColumns = columns.map((column) => { | |||||
| return { | |||||
| ...column, | |||||
| width: columnWidth ?? 150, | |||||
| }; | |||||
| }); | |||||
| const rowsWithDefaultValues = rows.map((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") => | |||||
| mode === "dark" ? darken(color, 0.7) : lighten(color, 0.7); | |||||
| const getHoverBackgroundColor = (color: string, mode: "light" | "dark") => | |||||
| mode === "dark" ? darken(color, 0.6) : lighten(color, 0.6); | |||||
| const getSelectedBackgroundColor = (color: string, mode: "light" | "dark") => | |||||
| mode === "dark" ? darken(color, 0.5) : lighten(color, 0.5); | |||||
| const getSelectedHoverBackgroundColor = ( | |||||
| color: string, | |||||
| mode: "light" | "dark", | |||||
| ) => (mode === "dark" ? darken(color, 0.4) : lighten(color, 0.4)); | |||||
| const StyledDataGrid = styled(DataGrid)(({ theme }) => ({ | |||||
| "& .super-app-theme--Open": { | |||||
| backgroundColor: getBackgroundColor( | |||||
| theme.palette.info.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getHoverBackgroundColor( | |||||
| theme.palette.info.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| "&.Mui-selected": { | |||||
| backgroundColor: getSelectedBackgroundColor( | |||||
| theme.palette.info.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getSelectedHoverBackgroundColor( | |||||
| theme.palette.info.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| "& .super-app-theme--finish": { | |||||
| backgroundColor: getBackgroundColor( | |||||
| theme.palette.success.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getHoverBackgroundColor( | |||||
| theme.palette.success.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| "&.Mui-selected": { | |||||
| backgroundColor: getSelectedBackgroundColor( | |||||
| theme.palette.success.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getSelectedHoverBackgroundColor( | |||||
| theme.palette.success.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| "& .super-app-theme--danger": { | |||||
| backgroundColor: getBackgroundColor( | |||||
| theme.palette.warning.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getHoverBackgroundColor( | |||||
| theme.palette.warning.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| "&.Mui-selected": { | |||||
| backgroundColor: getSelectedBackgroundColor( | |||||
| theme.palette.warning.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getSelectedHoverBackgroundColor( | |||||
| theme.palette.warning.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| "& .super-app-theme--warning": { | |||||
| backgroundColor: getBackgroundColor( | |||||
| theme.palette.error.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getHoverBackgroundColor( | |||||
| theme.palette.error.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| "&.Mui-selected": { | |||||
| backgroundColor: getSelectedBackgroundColor( | |||||
| theme.palette.error.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| "&:hover": { | |||||
| backgroundColor: getSelectedHoverBackgroundColor( | |||||
| theme.palette.error.main, | |||||
| theme.palette.mode, | |||||
| ), | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| })); | |||||
| return ( | |||||
| <div | |||||
| className="mt-5 mb-5" | |||||
| style={{ height: dataGridHeight ?? 400, width: "100%" }} | |||||
| > | |||||
| {Title ? ( | |||||
| <Card style={{ marginRight: 10 }}> | |||||
| {Title && <CardHeader className="text-slate-500" title={Title} />} | |||||
| <CardContent | |||||
| style={{ | |||||
| display: "flex", | |||||
| alignItems: "center", | |||||
| justifyContent: "center", | |||||
| marginTop: -20, | |||||
| }} | |||||
| > | |||||
| {Style ? ( | |||||
| <StyledDataGrid | |||||
| rows={rowsWithDefaultValues} | |||||
| columns={modifiedColumns} | |||||
| 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={{ | |||||
| pagination: { paginationModel: { pageSize: 10 } }, | |||||
| }} | |||||
| className="customDataGrid" | |||||
| sx={{ | |||||
| boxShadow: 2, | |||||
| border: 2, | |||||
| borderColor: "primary.light", | |||||
| "& .MuiDataGrid-cell:hover": { | |||||
| 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, | |||||
| "& .MuiDataGrid-root": { | |||||
| overflow: "auto", | |||||
| }, | |||||
| "& .MuiDataGrid-columnHeaderTitle": { | |||||
| fontWeight: "bold", | |||||
| }, | |||||
| ...sx, | |||||
| }} | |||||
| {...props} | |||||
| /> | |||||
| ) : ( | |||||
| <DataGrid | |||||
| rows={rowsWithDefaultValues} | |||||
| columns={modifiedColumns} | |||||
| editMode="row" | |||||
| style={{ marginRight: 0 }} | |||||
| checkboxSelection={checkboxSelection} | |||||
| onRowSelectionModelChange={onRowSelectionModelChange} | |||||
| initialState={{ | |||||
| pagination: { paginationModel: { pageSize: 10 } }, | |||||
| }} | |||||
| className="customDataGrid" | |||||
| sx={{ | |||||
| boxShadow: 2, | |||||
| border: 2, | |||||
| borderColor: "primary.light", | |||||
| "& .MuiDataGrid-cell:hover": { | |||||
| color: "primary.main", | |||||
| }, | |||||
| height: 300, | |||||
| "& .MuiDataGrid-root": { | |||||
| overflow: "auto", | |||||
| }, | |||||
| ...sx, | |||||
| }} | |||||
| {...props} | |||||
| /> | |||||
| )} | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default CustomDatagrid; | |||||
| @@ -1 +0,0 @@ | |||||
| export { default } from "./CustomDatagrid"; | |||||
| @@ -1,81 +0,0 @@ | |||||
| import * as React from "react"; | |||||
| import { | |||||
| Card, | |||||
| CardHeader, | |||||
| CardContent, | |||||
| SxProps, | |||||
| Theme, | |||||
| Grid, | |||||
| Modal, | |||||
| Typography, | |||||
| Button, | |||||
| } from "@mui/material"; | |||||
| import { DataGrid, GridColDef } from "@mui/x-data-grid"; | |||||
| import { darken, lighten, styled } from "@mui/material/styles"; | |||||
| import { PROJECT_MODAL_STYLE } from "@/theme/colorConst"; | |||||
| import { useRef, useEffect, useState } from "react"; | |||||
| import Swal from "sweetalert2"; | |||||
| import styledcmp from "styled-components"; | |||||
| const CardWrapper = styledcmp.div` | |||||
| /* Styles for the card when not hovered */ | |||||
| background-color: #f0f0f0, | |||||
| padding: 10px, | |||||
| /* ...other styles... */ | |||||
| &:hover { | |||||
| /* Styles for the card when hovered */ | |||||
| background-color: #c0c0c0, | |||||
| /* ...other hover styles... */ | |||||
| } | |||||
| `; | |||||
| interface CustomModalProps { | |||||
| title?: string; | |||||
| isOpen: boolean; | |||||
| onClose: () => void; | |||||
| modalStyle?: any; | |||||
| } | |||||
| const CustomModal: React.FC<CustomModalProps> = ({ ...props }) => { | |||||
| const ModalContent = () => { | |||||
| return ( | |||||
| // <Grid item sx={{ m: 3 }}> | |||||
| <div> | |||||
| <Typography variant="h6" id="modal-title"> | |||||
| {props.title ?? "Modal Title"} | |||||
| </Typography> | |||||
| <Typography | |||||
| variant="h6" | |||||
| id="modal-title" | |||||
| style={{ alignSelf: "flex-start", margin: "10px" }} | |||||
| > | |||||
| Modal Content | |||||
| </Typography> | |||||
| <div | |||||
| style={{ | |||||
| display: "flex", | |||||
| justifyContent: "space-between", | |||||
| width: "100%", | |||||
| }} | |||||
| > | |||||
| <Button variant="contained" onClick={props.onClose}> | |||||
| Confirm | |||||
| </Button> | |||||
| <Button variant="contained" onClick={props.onClose}> | |||||
| Cancel | |||||
| </Button> | |||||
| </div> | |||||
| </div> | |||||
| // </Grid> | |||||
| ); | |||||
| }; | |||||
| return ( | |||||
| <Modal open={props.isOpen} onClose={props.onClose}> | |||||
| {props.modalStyle ? <props.modalStyle props={props} /> : <ModalContent />} | |||||
| </Modal> | |||||
| ); | |||||
| }; | |||||
| export default CustomModal; | |||||
| @@ -1 +0,0 @@ | |||||
| export { default } from "./CustomModal"; | |||||
| @@ -1,379 +0,0 @@ | |||||
| "use client"; | |||||
| import React, { useState, FC } from "react"; | |||||
| import { | |||||
| Stack, | |||||
| Typography, | |||||
| FormControlLabel, | |||||
| Checkbox, | |||||
| Autocomplete, | |||||
| Button, | |||||
| Grid, | |||||
| TextField, | |||||
| CardHeader, | |||||
| Card, | |||||
| FormControl, | |||||
| InputLabel, | |||||
| Select, | |||||
| MenuItem, | |||||
| ThemeProvider, | |||||
| } from "@mui/material"; | |||||
| import { useForm, Controller } from "react-hook-form"; | |||||
| import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; | |||||
| import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | |||||
| import dayjs from "dayjs"; | |||||
| import SearchIcon from "@mui/icons-material/Search"; | |||||
| import RefreshIcon from "@mui/icons-material/Refresh"; | |||||
| import { DemoItem } from "@mui/x-date-pickers/internals/demo"; | |||||
| import { DatePicker } from "@mui/x-date-pickers/DatePicker"; | |||||
| interface Field { | |||||
| id: any; | |||||
| label: any; | |||||
| type: string; | |||||
| options?: Array<{ id: string; label: string }>; | |||||
| setValue: ((value: any) => void) | Array<(value: any) => void>; | |||||
| value?: any; | |||||
| required?: boolean; | |||||
| } | |||||
| interface FormComponentProps { | |||||
| fields: Field[]; | |||||
| onSubmit: (data: any) => void; | |||||
| resetForm: () => void; | |||||
| sx?: any; | |||||
| } | |||||
| interface SearchFormProps { | |||||
| applySearch: (data: any) => void; | |||||
| fields: Field[]; | |||||
| title?: string; | |||||
| sx?: any; | |||||
| } | |||||
| const FormComponent: FC<FormComponentProps> = ({ | |||||
| fields, | |||||
| onSubmit, | |||||
| resetForm, | |||||
| sx, | |||||
| }) => { | |||||
| const { reset, register, handleSubmit, control } = useForm(); | |||||
| const [fromDate, setFromDate] = useState<dayjs.Dayjs | null>(null); | |||||
| const [dayRangeFromDate, setDayRangeFromDate] = useState<dayjs.Dayjs | null>( | |||||
| null, | |||||
| ); | |||||
| const [dayRangeToDate, setDayRangeToDate] = useState<dayjs.Dayjs | null>( | |||||
| null, | |||||
| ); | |||||
| const [value, setValue] = useState<{ [key: string]: any }>({}); | |||||
| const [checkbox1, setCheckbox1] = useState(false); | |||||
| const handleFormSubmit = (data: any) => { | |||||
| if (fromDate != null || fromDate != undefined) { | |||||
| data.fromDate = dayjs(fromDate).format("YYYY-MM-DD"); | |||||
| } | |||||
| if (value !== null) { | |||||
| data.dropdownCombo = value; | |||||
| } | |||||
| if (value !== null) { | |||||
| data.checkbox = checkbox1; | |||||
| } | |||||
| if (dayRangeFromDate != null || dayRangeFromDate != undefined) { | |||||
| data.dayRangeFromDate = dayjs(dayRangeFromDate).format("YYYY-MM-DD"); | |||||
| } | |||||
| if (dayRangeToDate != null || dayRangeToDate != undefined) { | |||||
| data.dayRangeToDate = dayjs(dayRangeToDate).format("YYYY-MM-DD"); | |||||
| } | |||||
| onSubmit(data); | |||||
| }; | |||||
| const handleFormReset = () => { | |||||
| reset(); | |||||
| resetForm(); | |||||
| setFromDate(null); | |||||
| fields.forEach((field) => { | |||||
| if (typeof field.setValue === "function") { | |||||
| field.setValue(typeof field.value === "boolean" ? false : null); | |||||
| } else if (Array.isArray(field.setValue)) { | |||||
| field.setValue.forEach((setFunc) => { | |||||
| setFunc(null); | |||||
| }); | |||||
| } | |||||
| }); | |||||
| }; | |||||
| return ( | |||||
| <form onSubmit={handleSubmit(handleFormSubmit)}> | |||||
| <Grid container alignItems="center"> | |||||
| {fields.map((field) => { | |||||
| if (field.type === "dropdown") { | |||||
| return ( | |||||
| <Grid | |||||
| item | |||||
| xs={12} | |||||
| sm={5.5} | |||||
| md={5.5} | |||||
| lg={5.5} | |||||
| sx={{ ml: 3, mr: 3, mb: 3 }} | |||||
| key={field.id} | |||||
| > | |||||
| <FormControl fullWidth> | |||||
| <InputLabel id={`${field.id}-label`}> | |||||
| {field.label} | |||||
| </InputLabel> | |||||
| <Controller | |||||
| name={field.id} | |||||
| control={control} | |||||
| defaultValue="" | |||||
| render={({ field: { onChange, value } }) => ( | |||||
| <Select | |||||
| labelId={`${field.id}-label`} | |||||
| id={field.id} | |||||
| value={value} | |||||
| onChange={(e) => { | |||||
| onChange(e.target.value); | |||||
| }} | |||||
| > | |||||
| {field.options?.map((option) => ( | |||||
| <MenuItem | |||||
| value={option.id ?? JSON.stringify(option)} | |||||
| key={option.id ?? JSON.stringify(option)} | |||||
| > | |||||
| {option.id !== undefined | |||||
| ? option.label | |||||
| : JSON.stringify(option)} | |||||
| </MenuItem> | |||||
| ))} | |||||
| </Select> | |||||
| )} | |||||
| /> | |||||
| </FormControl> | |||||
| </Grid> | |||||
| ); | |||||
| } else if (field.type === "date") { | |||||
| return ( | |||||
| <Grid | |||||
| item | |||||
| xs={12} | |||||
| sm={5.5} | |||||
| md={5.5} | |||||
| lg={5.5} | |||||
| sx={{ ml: 3, mr: 3, mb: 3 }} | |||||
| key={field.id} | |||||
| > | |||||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||||
| <DemoItem> | |||||
| <DatePicker | |||||
| slotProps={{ | |||||
| textField: { | |||||
| id: field.id, | |||||
| }, | |||||
| }} | |||||
| label={field.label} | |||||
| value={fromDate === null ? null : dayjs(fromDate)} | |||||
| onChange={(newValue) => { | |||||
| setFromDate(newValue); | |||||
| }} | |||||
| /> | |||||
| </DemoItem> | |||||
| </LocalizationProvider> | |||||
| </Grid> | |||||
| ); | |||||
| } else if (field.type === "checkbox") { | |||||
| return ( | |||||
| <Grid | |||||
| item | |||||
| xs={12} | |||||
| sm={5.5} | |||||
| md={5.5} | |||||
| lg={5.5} | |||||
| sx={{ ml: 3, mr: 3, mb: 3 }} | |||||
| key={field.id} | |||||
| > | |||||
| <FormControlLabel | |||||
| control={ | |||||
| <Checkbox | |||||
| id={field.id} | |||||
| checked={field.value} | |||||
| onChange={(event) => { | |||||
| if (typeof field.setValue === "function") { | |||||
| field.setValue(event.target.checked); | |||||
| setCheckbox1(event.target.checked); | |||||
| } | |||||
| }} | |||||
| color="primary" | |||||
| /> | |||||
| } | |||||
| label={ | |||||
| <Typography style={{ fontSize: "1.15em" }}> | |||||
| {field.label} | |||||
| </Typography> | |||||
| } | |||||
| /> | |||||
| </Grid> | |||||
| ); | |||||
| } else if (field.type === "dateRange") { | |||||
| return ( | |||||
| <Grid container key={field.id[0]}> | |||||
| <Grid | |||||
| item | |||||
| xs={12} | |||||
| sm={7} | |||||
| md={7} | |||||
| lg={7} | |||||
| sx={{ ml: 3, mr: 3, mb: 3 }} | |||||
| > | |||||
| <Grid container> | |||||
| <Grid> | |||||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||||
| <DatePicker | |||||
| label={field.label[0]} | |||||
| value={field.value[0]} | |||||
| onChange={(newValue) => setDayRangeFromDate(newValue)} | |||||
| /> | |||||
| </LocalizationProvider> | |||||
| </Grid> | |||||
| <Grid | |||||
| item | |||||
| xs={1.5} | |||||
| sm={1.5} | |||||
| md={1.5} | |||||
| lg={1.5} | |||||
| sx={{ | |||||
| display: "flex", | |||||
| justifyContent: "center", | |||||
| alignItems: "center", | |||||
| }} | |||||
| > | |||||
| To | |||||
| </Grid> | |||||
| <Grid item xs={5.25} sm={5.25} md={5.25} lg={5.25}> | |||||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||||
| <DatePicker | |||||
| label={field.label[1]} | |||||
| value={field.value[1]} | |||||
| onChange={(newValue) => setDayRangeToDate(newValue)} | |||||
| /> | |||||
| </LocalizationProvider> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Grid> | |||||
| ); | |||||
| } | |||||
| return ( | |||||
| <Grid | |||||
| item | |||||
| xs={12} | |||||
| sm={5.5} | |||||
| md={5.5} | |||||
| lg={5.5} | |||||
| sx={{ ml: 3, mr: 3, mb: 3 }} | |||||
| key={field.id} | |||||
| > | |||||
| <TextField | |||||
| fullWidth | |||||
| {...register(field.id)} | |||||
| id={field.id} | |||||
| label={field.label} | |||||
| defaultValue={ | |||||
| field.value !== undefined && field.value !== null | |||||
| ? `${field.value}` | |||||
| : "" | |||||
| } | |||||
| required={field.required === true ? field.required : false} | |||||
| sx={{ ...sx }} | |||||
| InputProps={{ | |||||
| style: { | |||||
| borderRadius: "10px", | |||||
| }, | |||||
| }} | |||||
| /> | |||||
| </Grid> | |||||
| ); | |||||
| })} | |||||
| </Grid> | |||||
| <Grid | |||||
| container | |||||
| maxWidth="lg" | |||||
| justifyContent="space-between" | |||||
| style={{ marginTop: -20 }} | |||||
| > | |||||
| <Stack direction="row"> | |||||
| <Grid item sx={{ ml: 3, mr: 3, mb: 3, mt: 3 }}> | |||||
| <Button | |||||
| className="h-12 w-32" | |||||
| style={{ | |||||
| backgroundColor: "#92c1e9", | |||||
| color: "white", | |||||
| fontSize: "1.15em", | |||||
| fontWeight: 100, | |||||
| borderRadius: 10, | |||||
| }} | |||||
| type="submit" | |||||
| > | |||||
| <SearchIcon /> | |||||
| Search | |||||
| </Button> | |||||
| </Grid> | |||||
| <Grid item sx={{ ml: 3, mr: 3, mb: 3, mt: 3 }}> | |||||
| <Button | |||||
| className="h-12 w-32" | |||||
| style={{ | |||||
| backgroundColor: "#f890a5", | |||||
| color: "white", | |||||
| fontSize: "1.15em", | |||||
| fontWeight: 100, | |||||
| borderRadius: 10, | |||||
| }} | |||||
| onClick={handleFormReset} | |||||
| > | |||||
| <RefreshIcon /> | |||||
| Reset | |||||
| </Button> | |||||
| </Grid> | |||||
| </Stack> | |||||
| </Grid> | |||||
| </form> | |||||
| ); | |||||
| }; | |||||
| const CustomSearchForm: FC<SearchFormProps> = ({ | |||||
| applySearch, | |||||
| fields, | |||||
| title, | |||||
| sx, | |||||
| }) => { | |||||
| const Title = title || "Searching Criteria"; | |||||
| const handleSubmit = (data: any) => { | |||||
| if (applySearch) { | |||||
| applySearch(data); | |||||
| } else { | |||||
| console.log("applySearch function is null"); | |||||
| } | |||||
| }; | |||||
| const handleFormReset = () => { | |||||
| console.log("Form Reset"); | |||||
| }; | |||||
| return ( | |||||
| <Card style={{ marginRight: 20 }}> | |||||
| <CardHeader | |||||
| className="text-slate-500 " | |||||
| style={{ marginTop: -5 }} | |||||
| title={Title} | |||||
| ></CardHeader> | |||||
| <FormComponent | |||||
| fields={fields} | |||||
| onSubmit={handleSubmit} | |||||
| resetForm={handleFormReset} | |||||
| sx={sx} | |||||
| /> | |||||
| </Card> | |||||
| ); | |||||
| }; | |||||
| export default CustomSearchForm; | |||||
| @@ -1 +0,0 @@ | |||||
| export { default } from "./CustomSearchForm"; | |||||
| @@ -5,7 +5,7 @@ import Stack from "@mui/material/Stack"; | |||||
| import React from "react"; | import React from "react"; | ||||
| // Can make this nicer | // Can make this nicer | ||||
| export const ProgressByClientSearchLoading: React.FC = () => { | |||||
| export const DashboardLoading: React.FC = () => { | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <Card> | <Card> | ||||
| @@ -16,14 +16,14 @@ export const ProgressByClientSearchLoading: React.FC = () => { | |||||
| <Skeleton variant="rounded" height={60} /> | <Skeleton variant="rounded" height={60} /> | ||||
| <Skeleton | <Skeleton | ||||
| variant="rounded" | variant="rounded" | ||||
| height={50} | |||||
| height={50} | |||||
| width={100} | width={100} | ||||
| sx={{ alignSelf: "flex-end" }} | sx={{ alignSelf: "flex-end" }} | ||||
| /> | /> | ||||
| </Stack> | </Stack> | ||||
| </CardContent> | </CardContent> | ||||
| </Card> | </Card> | ||||
| <Card> | |||||
| <Card>EditUser | |||||
| <CardContent> | <CardContent> | ||||
| <Stack spacing={2}> | <Stack spacing={2}> | ||||
| <Skeleton variant="rounded" height={40} /> | <Skeleton variant="rounded" height={40} /> | ||||
| @@ -37,4 +37,4 @@ export const ProgressByClientSearchLoading: React.FC = () => { | |||||
| ); | ); | ||||
| }; | }; | ||||
| export default ProgressByClientSearchLoading; | |||||
| export default DashboardLoading; | |||||
| @@ -1,20 +1,10 @@ | |||||
| "use client"; | "use client"; | ||||
| import Grid from "@mui/material/Grid"; | |||||
| import Paper from "@mui/material/Paper"; | |||||
| import { TFunction } from "i18next"; | |||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import PageTitle from "../PageTitle/PageTitle"; | |||||
| import DashboardTabButton from "./DashboardTabButton"; | |||||
| import { ThemeProvider } from "@mui/material/styles"; | import { ThemeProvider } from "@mui/material/styles"; | ||||
| import theme from "../../theme"; | import theme from "../../theme"; | ||||
| import Tabs, { TabsProps } from "@mui/material/Tabs"; | |||||
| import Tab from "@mui/material/Tab"; | |||||
| import React, { useCallback, useState } from "react"; | |||||
| import { TabsProps } from "@mui/material/Tabs"; | |||||
| import React, { useCallback, useEffect, useState } from "react"; | |||||
| import { useRouter } from "next/navigation"; | import { useRouter } from "next/navigation"; | ||||
| import ProgressByClient from "./ProgressByClient"; | |||||
| import ProgressByClientSearch from "@/components/ProgressByClientSearch"; | |||||
| import { Suspense } from "react"; | |||||
| import { getSession } from "next-auth/react"; | |||||
| type Props = { | type Props = { | ||||
| abilities: string[] | abilities: string[] | ||||
| @@ -22,36 +12,18 @@ type Props = { | |||||
| const DashboardPage: React.FC<Props> = ({ | const DashboardPage: React.FC<Props> = ({ | ||||
| abilities | abilities | ||||
| }) => { | }) => { | ||||
| const [tabIndex, setTabIndex] = useState(0); | |||||
| const { t } = useTranslation("dashboard"); | const { t } = useTranslation("dashboard"); | ||||
| const router = useRouter(); | const router = useRouter(); | ||||
| window.localStorage.setItem("abilities", JSON.stringify(abilities)) | |||||
| const handleCancel = () => { | |||||
| router.back(); | |||||
| }; | |||||
| const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | |||||
| (_e, newValue) => { | |||||
| setTabIndex(newValue); | |||||
| }, | |||||
| [], | |||||
| ); | |||||
| useEffect(()=> { | |||||
| window.localStorage.setItem("abilities", JSON.stringify(abilities)) | |||||
| }) | |||||
| return ( | return ( | ||||
| <ThemeProvider theme={theme}> | <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> | |||||
| <PageTitle BigTitle={"Dashboards"}/> | |||||
| <DashboardTabButton/> | |||||
| </Grid> | |||||
| </Grid> */} | |||||
| <> | |||||
| </> | |||||
| </ThemeProvider> | </ThemeProvider> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -1,77 +0,0 @@ | |||||
| "use client"; | |||||
| import Grid from "@mui/material/Grid"; | |||||
| import { useState, useCallback } from "react"; | |||||
| import Paper from "@mui/material/Paper"; | |||||
| import { TFunction } from "i18next"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| import PageTitle from "../PageTitle/PageTitle"; | |||||
| import ProgressByClient from "./ProgressByClient"; | |||||
| import Tabs, { TabsProps } from "@mui/material/Tabs"; | |||||
| import Tab from "@mui/material/Tab"; | |||||
| import "../../app/global.css"; | |||||
| const DashboardTabButton: React.FC = () => { | |||||
| const [activeTab, setActiveTab] = useState("financialSummary"); | |||||
| const { t } = useTranslation("dashboard"); | |||||
| const renderContent = () => { | |||||
| switch (activeTab) { | |||||
| case "financialSummary": | |||||
| return <div>Project Financial Summary</div>; | |||||
| case "cashFlow": | |||||
| return <div>Project Cash Flow</div>; | |||||
| case "progressByClient": | |||||
| return <ProgressByClient />; | |||||
| case "resourceUtilization": | |||||
| return <div>Project Resource Utilization</div>; | |||||
| case "staffUtilization": | |||||
| return <div>Staff Utilization</div>; | |||||
| default: | |||||
| return <div>Project Financial Summary</div>; | |||||
| } | |||||
| }; | |||||
| const [tabIndex, setTabIndex] = useState(0); | |||||
| const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | |||||
| (_e, newValue) => { | |||||
| setTabIndex(newValue); | |||||
| }, | |||||
| [], | |||||
| ); | |||||
| 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-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> | |||||
| ); | |||||
| }; | |||||
| export default DashboardTabButton; | |||||
| @@ -1,18 +1,38 @@ | |||||
| import { authOptions } from "@/config/authConfig" | import { authOptions } from "@/config/authConfig" | ||||
| import { getServerSession, Session } from "next-auth" | import { getServerSession, Session } from "next-auth" | ||||
| import DashboardPage from "./DashboardPage" | import DashboardPage from "./DashboardPage" | ||||
| import { Typography } from "@mui/material" | |||||
| import { I18nProvider, getServerI18n } from "@/i18n"; | |||||
| import DashboardLoading from "./DashboardLoading"; | |||||
| export type SessionWithAbilities = { | export type SessionWithAbilities = { | ||||
| abilities: string[] | abilities: string[] | ||||
| } & Session | null | } & Session | null | ||||
| const DashboardWrapper: React.FC = async () => { | |||||
| const session: SessionWithAbilities = await getServerSession(authOptions) | |||||
| interface SubComponents { | |||||
| Loading: typeof DashboardLoading; | |||||
| } | |||||
| type Props = { | |||||
| searchParams: { [key: string]: string | string[] | undefined }; | |||||
| } | |||||
| const DashboardWrapper: React.FC<Props> & SubComponents = async ({ | |||||
| searchParams | |||||
| }) => { | |||||
| const { t } = await getServerI18n("dashboard"); | |||||
| const session: SessionWithAbilities = await getServerSession(authOptions) | |||||
| return ( | return ( | ||||
| <DashboardPage | |||||
| abilities={session ? session?.abilities : []} | |||||
| /> | |||||
| <> | |||||
| <Typography variant="h4">{t("Dashboard")}</Typography> | |||||
| <DashboardPage | |||||
| abilities={session ? session?.abilities : []} | |||||
| /> | |||||
| </> | |||||
| ) | ) | ||||
| } | } | ||||
| DashboardWrapper.Loading = DashboardLoading; | |||||
| export default DashboardWrapper | export default DashboardWrapper | ||||
| @@ -1,493 +0,0 @@ | |||||
| 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 } 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 [selectionModel, setSelectionModel]: 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", | |||||
| }, | |||||
| ]; | |||||
| 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 = [ | |||||
| { | |||||
| 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: "project", | |||||
| field: "project", | |||||
| headerName: "Project", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: "team", | |||||
| field: "team", | |||||
| headerName: "Team", | |||||
| 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 = [ | |||||
| { | |||||
| 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", | |||||
| }, | |||||
| 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 { | |||||
| const 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) => { | |||||
| 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" | |||||
| /> | |||||
| <ReactApexChart | |||||
| options={options2} | |||||
| series={percentageArray} | |||||
| type="donut" | |||||
| /> | |||||
| </Card> | |||||
| </Grid> | |||||
| </div> | |||||
| </Grid> | |||||
| ); | |||||
| }; | |||||
| export default ProgressByClient; | |||||
| @@ -1,28 +1,9 @@ | |||||
| "use client"; | "use client"; | ||||
| // import { testing } from "@/app/api/timesheets"; | |||||
| import Grid from "@mui/material/Grid"; | |||||
| import Paper from "@mui/material/Paper"; | |||||
| import { useState } from "react"; | import { useState } from "react"; | ||||
| import { useTranslation } from "react-i18next"; | |||||
| import AssignedProjectGrid from "../AssignedProjectGrid/AssignedProjectGrid"; | |||||
| import PageTitle from "../PageTitle/PageTitle"; | |||||
| import { Suspense } from "react"; | |||||
| import Button from "@mui/material/Button"; | import Button from "@mui/material/Button"; | ||||
| import Stack from "@mui/material/Stack"; | |||||
| import { Add } from "@mui/icons-material"; | |||||
| import Link from "next/link"; | |||||
| import { t } from "i18next"; | |||||
| import { Card, Modal, Typography } from "@mui/material"; | import { Card, Modal, Typography } from "@mui/material"; | ||||
| import CustomModal from "../CustomModal/CustomModal"; | |||||
| import { PROJECT_MODAL_STYLE } from "@/theme/colorConst"; | |||||
| import CustomDatagrid from "../CustomDatagrid/CustomDatagrid"; | |||||
| import { DataGrid } from "@mui/x-data-grid"; | |||||
| import TimesheetInputGrid from "./LeaveInputGrid"; | import TimesheetInputGrid from "./LeaveInputGrid"; | ||||
| import { BASE_API_URL } from "@/config/api"; | |||||
| // import { fetchLeaves } from "@/app/api/leave"; | |||||
| interface EnterTimesheetModalProps { | interface EnterTimesheetModalProps { | ||||
| isOpen: boolean; | isOpen: boolean; | ||||
| onClose: () => void; | onClose: () => void; | ||||
| @@ -1,23 +1,9 @@ | |||||
| "use client"; | "use client"; | ||||
| // import { testing } from "@/app/api/timesheets"; | |||||
| import Grid from "@mui/material/Grid"; | |||||
| import Paper from "@mui/material/Paper"; | |||||
| import { useState } from "react"; | import { useState } from "react"; | ||||
| import { useTranslation } from "react-i18next"; | |||||
| import AssignedProjectGrid from "../AssignedProjectGrid/AssignedProjectGrid"; | |||||
| import PageTitle from "../PageTitle/PageTitle"; | |||||
| import { Suspense } from "react"; | |||||
| import Button from "@mui/material/Button"; | import Button from "@mui/material/Button"; | ||||
| import Stack from "@mui/material/Stack"; | |||||
| import { Add } from "@mui/icons-material"; | |||||
| import Link from "next/link"; | |||||
| import { t } from "i18next"; | import { t } from "i18next"; | ||||
| import { Card, Modal, Typography } from "@mui/material"; | import { Card, Modal, Typography } from "@mui/material"; | ||||
| import CustomModal from "../CustomModal/CustomModal"; | |||||
| import { PROJECT_MODAL_STYLE } from "@/theme/colorConst"; | |||||
| import CustomDatagrid from "../CustomDatagrid/CustomDatagrid"; | |||||
| import { DataGrid } from "@mui/x-data-grid"; | |||||
| import TimesheetInputGrid from "./TimesheetInputGrid"; | import TimesheetInputGrid from "./TimesheetInputGrid"; | ||||
| import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
| @@ -33,8 +33,6 @@ interface NavigationItem { | |||||
| } | } | ||||
| const NavigationContent: React.FC = () => { | const NavigationContent: React.FC = () => { | ||||
| // const abilities = window.localStorage.getItem("abilites") | |||||
| // console.log(abilities) | |||||
| const navigationItems: NavigationItem[] = [ | const navigationItems: NavigationItem[] = [ | ||||
| { | { | ||||
| icon: <Dashboard />, | icon: <Dashboard />, | ||||
| @@ -245,16 +243,16 @@ const NavigationContent: React.FC = () => { | |||||
| const pathname = usePathname(); | const pathname = usePathname(); | ||||
| const [openItems, setOpenItems] = React.useState<string[]>([]); | const [openItems, setOpenItems] = React.useState<string[]>([]); | ||||
| const toggleItem = (path: string) => { | |||||
| const toggleItem = (label: string) => { | |||||
| setOpenItems((prevOpenItems) => | setOpenItems((prevOpenItems) => | ||||
| prevOpenItems.includes(path) | |||||
| ? prevOpenItems.filter((item) => item !== path) | |||||
| : [...prevOpenItems, path], | |||||
| prevOpenItems.includes(label) | |||||
| ? prevOpenItems.filter((item) => item !== label) | |||||
| : [...prevOpenItems, label], | |||||
| ); | ); | ||||
| }; | }; | ||||
| const renderNavigationItem = (item: NavigationItem) => { | const renderNavigationItem = (item: NavigationItem) => { | ||||
| const isOpen = openItems.includes(item.path); | |||||
| const isOpen = openItems.includes(item.label); | |||||
| return ( | return ( | ||||
| <Box | <Box | ||||
| @@ -264,8 +262,8 @@ const NavigationContent: React.FC = () => { | |||||
| sx={{ textDecoration: "none", color: "inherit" }} | sx={{ textDecoration: "none", color: "inherit" }} | ||||
| > | > | ||||
| <ListItemButton | <ListItemButton | ||||
| selected={pathname.includes(item.path)} | |||||
| onClick={() => item.children && toggleItem(item.path)} | |||||
| selected={pathname.includes(item.label)} | |||||
| onClick={() => item.children && toggleItem(item.label)} | |||||
| > | > | ||||
| <ListItemIcon>{item.icon}</ListItemIcon> | <ListItemIcon>{item.icon}</ListItemIcon> | ||||
| <ListItemText primary={t(item.label)} /> | <ListItemText primary={t(item.label)} /> | ||||
| @@ -1,643 +0,0 @@ | |||||
| "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) { | |||||
| const 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; | |||||
| @@ -1 +0,0 @@ | |||||
| export { default } from "./ProgressByClient"; | |||||
| @@ -1,57 +0,0 @@ | |||||
| "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; | |||||
| @@ -1,18 +0,0 @@ | |||||
| 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; | |||||
| @@ -1 +0,0 @@ | |||||
| export { default } from "./ProgressByClientSearchWrapper"; | |||||
| @@ -1,156 +0,0 @@ | |||||
| "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 { CashFlow } from "@/app/api/cashflow"; | |||||
| import CustomDatagrid from "../CustomDatagrid/CustomDatagrid"; | |||||
| import { GridColDef, GridRowSelectionModel } from "@mui/x-data-grid"; | |||||
| import ProjectCashFlow from "../ProjectCashFlow"; | |||||
| interface Props { | |||||
| projects: CashFlow[]; | |||||
| } | |||||
| type SearchQuery = Partial<Omit<CashFlow, "id">>; | |||||
| type SearchParamNames = keyof SearchQuery; | |||||
| const ProgressByClientSearch: React.FC<Props> = ({ projects }) => { | |||||
| const { t } = useTranslation("projects"); | |||||
| const [selectionModel, setSelectionModel]: any[] = React.useState([]); | |||||
| const columns = [ | |||||
| { | |||||
| id: "projectCode", | |||||
| field: "projectCode", | |||||
| headerName: "Project Code", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: "projectName", | |||||
| field: "projectName", | |||||
| headerName: "Project Name", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: "team", | |||||
| field: "team", | |||||
| headerName: "Team", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: "teamLeader", | |||||
| field: "teamLeader", | |||||
| headerName: "Team Leader", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: "startDate", | |||||
| field: "startDate", | |||||
| headerName: "Start Date", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: "targetEndDate", | |||||
| field: "targetEndDate", | |||||
| headerName: "Target End Date", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: "client", | |||||
| field: "client", | |||||
| headerName: "Client", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: "subsidiary", | |||||
| field: "subsidiary", | |||||
| headerName: "Subsidiary", | |||||
| flex: 1, | |||||
| }, | |||||
| ]; | |||||
| const rows = [ | |||||
| { | |||||
| id: 1, | |||||
| projectCode: "M1001", | |||||
| projectName: "Consultancy Project A", | |||||
| team: "XXX", | |||||
| teamLeader: "XXX", | |||||
| startDate: "01/07/2022", | |||||
| targetEndDate: "01/04/2024", | |||||
| client: "Client B", | |||||
| subsidiary: "N/A", | |||||
| }, | |||||
| { | |||||
| id: 2, | |||||
| projectCode: "M1301", | |||||
| projectName: "Consultancy Project AAAA", | |||||
| team: "XXX", | |||||
| teamLeader: "XXX", | |||||
| startDate: "01/09/2022", | |||||
| targetEndDate: "20/02/2024", | |||||
| client: "Client C", | |||||
| subsidiary: "Subsidiary A", | |||||
| }, | |||||
| { | |||||
| id: 3, | |||||
| projectCode: "M1354", | |||||
| projectName: "Consultancy Project BBB", | |||||
| team: "YYY", | |||||
| teamLeader: "YYY", | |||||
| startDate: "01/02/2023", | |||||
| targetEndDate: "31/01/2024", | |||||
| client: "Client D", | |||||
| subsidiary: "Subsidiary C", | |||||
| }, | |||||
| ]; | |||||
| const [selectedTeamData, setSelectedTeamData]: any[] = React.useState(rows); | |||||
| const handleSelectionChange = (newSelectionModel: GridRowSelectionModel) => { | |||||
| const selectedRowsData = selectedTeamData.filter((row: any) => | |||||
| newSelectionModel.includes(row.id), | |||||
| ); | |||||
| console.log(selectedRowsData); | |||||
| }; | |||||
| // 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: "Project Code", paramName: "projectCode", type: "text" }, | |||||
| { label: "Project Name", paramName: "projectName", type: "text" }, | |||||
| { | |||||
| label: "Start Date From", | |||||
| label2: "Start Date To", | |||||
| paramName: "startDateFrom", | |||||
| type: "dateRange", | |||||
| }, | |||||
| ], | |||||
| [t], | |||||
| ); | |||||
| // const columns = useMemo<Column<CashFlow>[]>( | |||||
| // () => [ | |||||
| // { 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); | |||||
| }} | |||||
| /> | |||||
| {/* <ProjectCashFlow/> */} | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default ProgressByClientSearch; | |||||
| @@ -1,40 +0,0 @@ | |||||
| 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 ProgressCashFlowSearchLoading: 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 ProgressCashFlowSearchLoading; | |||||
| @@ -1,18 +0,0 @@ | |||||
| import { fetchProjectsCashFlow } from "@/app/api/cashflow"; | |||||
| import React from "react"; | |||||
| import ProgressCashFlowSearch from "./ProgressCashFlowSearch"; | |||||
| import ProgressCashFlowSearchSearchLoading from "./ProgressCashFlowSearchLoading"; | |||||
| interface SubComponents { | |||||
| Loading: typeof ProgressCashFlowSearchSearchLoading; | |||||
| } | |||||
| const ProgressCashFlowSearchWrapper: React.FC & SubComponents = async () => { | |||||
| const clentprojects = await fetchProjectsCashFlow(); | |||||
| return <ProgressCashFlowSearch projects={clentprojects} />; | |||||
| }; | |||||
| ProgressCashFlowSearchWrapper.Loading = ProgressCashFlowSearchSearchLoading; | |||||
| export default ProgressCashFlowSearchWrapper; | |||||
| @@ -1 +0,0 @@ | |||||
| export { default } from "./ProgressCashFlowSearchWrapper"; | |||||
| @@ -1,638 +0,0 @@ | |||||
| "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"; | |||||
| import ProgressCashFlowSearch from "@/components/ProgressCashFlowSearch"; | |||||
| import { Input, Label } from "reactstrap"; | |||||
| const ProjectCashFlow: React.FC = () => { | |||||
| const todayDate = new Date(); | |||||
| const [selectionModel, setSelectionModel]: any[] = React.useState([]); | |||||
| const [cashFlowYear, setCashFlowYear]: any[] = React.useState( | |||||
| todayDate.getFullYear(), | |||||
| ); | |||||
| const columns = [ | |||||
| { | |||||
| id: "projectCode", | |||||
| field: "projectCode", | |||||
| headerName: "Project Code", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: "projectName", | |||||
| field: "projectName", | |||||
| headerName: "Project Name", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: "team", | |||||
| field: "team", | |||||
| headerName: "Team", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: "teamLeader", | |||||
| field: "teamLeader", | |||||
| headerName: "Team Leader", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: "startDate", | |||||
| field: "startDate", | |||||
| headerName: "Start Date", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: "targetEndDate", | |||||
| field: "targetEndDate", | |||||
| headerName: "Target End Date", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: "client", | |||||
| field: "client", | |||||
| headerName: "Client", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: "subsidiary", | |||||
| field: "subsidiary", | |||||
| headerName: "Subsidiary", | |||||
| flex: 1, | |||||
| }, | |||||
| ]; | |||||
| const ledgerColumns = [ | |||||
| { | |||||
| id: "date", | |||||
| field: "date", | |||||
| headerName: "Date", | |||||
| flex: 0.5, | |||||
| }, | |||||
| { | |||||
| id: "expenditure", | |||||
| field: "expenditure", | |||||
| headerName: "Expenditure (HKD)", | |||||
| flex: 0.6, | |||||
| }, | |||||
| { | |||||
| id: "income", | |||||
| field: "income", | |||||
| headerName: "Income (HKD)", | |||||
| flex: 0.6, | |||||
| }, | |||||
| { | |||||
| id: "cashFlowBalance", | |||||
| field: "cashFlowBalance", | |||||
| headerName: "Cash Flow Balance (HKD)", | |||||
| flex: 0.6, | |||||
| }, | |||||
| { | |||||
| id: "remarks", | |||||
| field: "remarks", | |||||
| headerName: "Remarks", | |||||
| flex: 1, | |||||
| }, | |||||
| ]; | |||||
| const options: ApexOptions = { | |||||
| chart: { | |||||
| height: 350, | |||||
| type: "line", | |||||
| }, | |||||
| stroke: { | |||||
| width: [0, 0, 2, 2], | |||||
| }, | |||||
| plotOptions: { | |||||
| bar: { | |||||
| horizontal: false, | |||||
| distributed: false, | |||||
| }, | |||||
| }, | |||||
| dataLabels: { | |||||
| enabled: false, | |||||
| }, | |||||
| xaxis: { | |||||
| categories: [ | |||||
| "Q1", | |||||
| "Q2", | |||||
| "Q3", | |||||
| "Q4", | |||||
| "Q5", | |||||
| "Q6", | |||||
| "Q7", | |||||
| "Q8", | |||||
| "Q9", | |||||
| "Q10", | |||||
| "Q11", | |||||
| "Q12", | |||||
| ], | |||||
| }, | |||||
| yaxis: [ | |||||
| { | |||||
| title: { | |||||
| text: "Monthly Income and Expenditure(HKD)", | |||||
| }, | |||||
| min: 0, | |||||
| max: 350000, | |||||
| tickAmount: 5, | |||||
| }, | |||||
| { | |||||
| show: false, | |||||
| seriesName: "Monthly_Expenditure", | |||||
| title: { | |||||
| text: "Monthly Expenditure (HKD)", | |||||
| }, | |||||
| min: 0, | |||||
| max: 350000, | |||||
| tickAmount: 5, | |||||
| }, | |||||
| { | |||||
| seriesName: "Cumulative_Income", | |||||
| opposite: true, | |||||
| title: { | |||||
| text: "Cumulative Income and Expenditure(HKD)", | |||||
| }, | |||||
| min: 0, | |||||
| max: 850000, | |||||
| tickAmount: 5, | |||||
| }, | |||||
| { | |||||
| show: false, | |||||
| seriesName: "Cumulative_Expenditure", | |||||
| opposite: true, | |||||
| title: { | |||||
| text: "Cumulative Expenditure (HKD)", | |||||
| }, | |||||
| min: 0, | |||||
| max: 850000, | |||||
| tickAmount: 5, | |||||
| }, | |||||
| ], | |||||
| grid: { | |||||
| borderColor: "#f1f1f1", | |||||
| }, | |||||
| annotations: {}, | |||||
| series: [ | |||||
| { | |||||
| name: "Monthly_Income", | |||||
| type: "column", | |||||
| color: "#ffde91", | |||||
| data: [0, 110000, 0, 0, 185000, 0, 0, 189000, 0, 0, 300000, 0], | |||||
| }, | |||||
| { | |||||
| name: "Monthly_Expenditure", | |||||
| type: "column", | |||||
| color: "#82b59a", | |||||
| data: [ | |||||
| 0, 160000, 120000, 120000, 55000, 55000, 55000, 55000, 55000, 70000, | |||||
| 55000, 55000, | |||||
| ], | |||||
| }, | |||||
| { | |||||
| name: "Cumulative_Income", | |||||
| type: "line", | |||||
| color: "#EE6D7A", | |||||
| data: [ | |||||
| 0, 100000, 100000, 100000, 300000, 300000, 300000, 500000, 500000, | |||||
| 500000, 800000, 800000, | |||||
| ], | |||||
| }, | |||||
| { | |||||
| name: "Cumulative_Expenditure", | |||||
| type: "line", | |||||
| color: "#7cd3f2", | |||||
| data: [ | |||||
| 0, 198000, 240000, 400000, 410000, 430000, 510000, 580000, 600000, | |||||
| 710000, 730000, 790000, | |||||
| ], | |||||
| }, | |||||
| ], | |||||
| }; | |||||
| const accountsReceivableOptions: ApexOptions = { | |||||
| colors: ["#20E647"], | |||||
| series: [80], | |||||
| chart: { | |||||
| height: 350, | |||||
| type: "radialBar", | |||||
| }, | |||||
| plotOptions: { | |||||
| radialBar: { | |||||
| hollow: { | |||||
| size: "70%", | |||||
| background: "#ffffff", | |||||
| }, | |||||
| track: { | |||||
| dropShadow: { | |||||
| enabled: true, | |||||
| top: 2, | |||||
| left: 0, | |||||
| blur: 4, | |||||
| opacity: 0.15, | |||||
| }, | |||||
| }, | |||||
| dataLabels: { | |||||
| name: { | |||||
| show: false, | |||||
| }, | |||||
| value: { | |||||
| color: "#3e98c7", | |||||
| fontSize: "3em", | |||||
| show: true, | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| fill: { | |||||
| type: "gradient", | |||||
| gradient: { | |||||
| shade: "dark", | |||||
| type: "vertical", | |||||
| gradientToColors: ["#87D4F9"], | |||||
| stops: [0, 100], | |||||
| }, | |||||
| }, | |||||
| stroke: { | |||||
| lineCap: "round", | |||||
| }, | |||||
| labels: ["AccountsReceivable"], | |||||
| }; | |||||
| const expenditureOptions: ApexOptions = { | |||||
| colors: ["#20E647"], | |||||
| series: [95], | |||||
| chart: { | |||||
| height: 350, | |||||
| type: "radialBar", | |||||
| }, | |||||
| plotOptions: { | |||||
| radialBar: { | |||||
| hollow: { | |||||
| size: "70%", | |||||
| background: "#ffffff", | |||||
| }, | |||||
| track: { | |||||
| dropShadow: { | |||||
| enabled: true, | |||||
| top: 2, | |||||
| left: 0, | |||||
| blur: 4, | |||||
| opacity: 0.15, | |||||
| }, | |||||
| }, | |||||
| dataLabels: { | |||||
| name: { | |||||
| show: false, | |||||
| }, | |||||
| value: { | |||||
| color: "#3e98c7", | |||||
| fontSize: "3em", | |||||
| show: true, | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| fill: { | |||||
| type: "gradient", | |||||
| gradient: { | |||||
| shade: "dark", | |||||
| type: "vertical", | |||||
| gradientToColors: ["#87D4F9"], | |||||
| stops: [0, 100], | |||||
| }, | |||||
| }, | |||||
| stroke: { | |||||
| lineCap: "round", | |||||
| }, | |||||
| labels: ["AccountsReceivable"], | |||||
| }; | |||||
| const rows = [ | |||||
| { | |||||
| id: 1, | |||||
| projectCode: "M1001", | |||||
| projectName: "Consultancy Project A", | |||||
| team: "XXX", | |||||
| teamLeader: "XXX", | |||||
| startDate: "01/07/2022", | |||||
| targetEndDate: "01/04/2024", | |||||
| client: "Client B", | |||||
| subsidiary: "N/A", | |||||
| }, | |||||
| { | |||||
| id: 2, | |||||
| projectCode: "M1301", | |||||
| projectName: "Consultancy Project AAAA", | |||||
| team: "XXX", | |||||
| teamLeader: "XXX", | |||||
| startDate: "01/09/2022", | |||||
| targetEndDate: "20/02/2024", | |||||
| client: "Client C", | |||||
| subsidiary: "Subsidiary A", | |||||
| }, | |||||
| { | |||||
| id: 3, | |||||
| projectCode: "M1354", | |||||
| projectName: "Consultancy Project BBB", | |||||
| team: "YYY", | |||||
| teamLeader: "YYY", | |||||
| startDate: "01/02/2023", | |||||
| targetEndDate: "31/01/2024", | |||||
| client: "Client D", | |||||
| subsidiary: "Subsidiary C", | |||||
| }, | |||||
| ]; | |||||
| const ledgerRows = [ | |||||
| { | |||||
| id: 1, | |||||
| date: "Feb 2023", | |||||
| expenditure: "-", | |||||
| income: "100,000.00", | |||||
| cashFlowBalance: "100,000.00", | |||||
| remarks: "Payment Milestone 1 (10%)", | |||||
| }, | |||||
| { | |||||
| id: 2, | |||||
| date: "Feb 2023", | |||||
| expenditure: "160,000.00", | |||||
| income: "-", | |||||
| cashFlowBalance: "(60,000.00)", | |||||
| remarks: "Monthly Manpower Expenditure", | |||||
| }, | |||||
| { | |||||
| id: 3, | |||||
| date: "Mar 2023", | |||||
| expenditure: "160,000.00", | |||||
| income: "-", | |||||
| cashFlowBalance: "(180,000.00)", | |||||
| remarks: "Monthly Manpower Expenditure", | |||||
| }, | |||||
| { | |||||
| id: 4, | |||||
| date: "Apr 2023", | |||||
| expenditure: "120,000.00", | |||||
| income: "-", | |||||
| cashFlowBalance: "(300,000.00)", | |||||
| remarks: "Monthly Manpower Expenditure", | |||||
| }, | |||||
| { | |||||
| id: 5, | |||||
| date: "May 2023", | |||||
| expenditure: "-", | |||||
| income: "200,000.00", | |||||
| cashFlowBalance: "(100,000.00)", | |||||
| remarks: "Payment Milestone 2 (20%)", | |||||
| }, | |||||
| { | |||||
| id: 6, | |||||
| date: "May 2023", | |||||
| expenditure: "40,000.00", | |||||
| income: "-", | |||||
| cashFlowBalance: "(140,000.00)", | |||||
| remarks: "Monthly Manpower Expenditure", | |||||
| }, | |||||
| ]; | |||||
| const [projectData, setProjectData]: any[] = React.useState(rows); | |||||
| const [ledgerData, setLedgerData]: any[] = React.useState(ledgerRows); | |||||
| const handleSelectionChange = (newSelectionModel: GridRowSelectionModel) => { | |||||
| const selectedRowsData = projectData.filter((row: any) => | |||||
| newSelectionModel.includes(row.id), | |||||
| ); | |||||
| console.log(selectedRowsData); | |||||
| }; | |||||
| return ( | |||||
| <> | |||||
| <Suspense fallback={<ProgressCashFlowSearch.Loading />}> | |||||
| <ProgressCashFlowSearch /> | |||||
| </Suspense> | |||||
| <CustomDatagrid | |||||
| rows={projectData} | |||||
| columns={columns} | |||||
| columnWidth={200} | |||||
| dataGridHeight={300} | |||||
| checkboxSelection={true} | |||||
| onRowSelectionModelChange={handleSelectionChange} | |||||
| selectionModel={selectionModel} | |||||
| /> | |||||
| <Grid item sm> | |||||
| <div style={{ display: "inline-block", width: "50%" }}> | |||||
| <Grid item xs={12} md={12} lg={12}> | |||||
| <Card> | |||||
| <CardHeader | |||||
| className="text-slate-500" | |||||
| title="Project Cash Flow by Month" | |||||
| /> | |||||
| <div style={{ display: "inline-block", width: "99%" }}> | |||||
| <div className="inline-block"> | |||||
| <Label className="text-slate-500 font-medium ml-6"> | |||||
| Period: | |||||
| </Label> | |||||
| <Input | |||||
| id={"cashFlowYear"} | |||||
| value={cashFlowYear} | |||||
| readOnly={true} | |||||
| bsSize="lg" | |||||
| className="rounded-md text-base w-12" | |||||
| /> | |||||
| </div> | |||||
| <div className="inline-block ml-1"> | |||||
| <button | |||||
| onClick={() => setCashFlowYear(cashFlowYear - 1)} | |||||
| className="hover:cursor-pointer hover:bg-slate-200 bg-transparent rounded-md w-8 h-8 text-base" | |||||
| > | |||||
| < | |||||
| </button> | |||||
| </div> | |||||
| <div className="inline-block ml-1"> | |||||
| <button | |||||
| onClick={() => setCashFlowYear(cashFlowYear + 1)} | |||||
| className="hover:cursor-pointer hover:bg-slate-200 bg-transparent rounded-md w-8 h-8 text-base" | |||||
| > | |||||
| > | |||||
| </button> | |||||
| </div> | |||||
| <ReactApexChart | |||||
| options={options} | |||||
| series={options.series} | |||||
| type="line" | |||||
| height="auto" | |||||
| /> | |||||
| </div> | |||||
| </Card> | |||||
| </Grid> | |||||
| </div> | |||||
| <div | |||||
| style={{ | |||||
| display: "inline-block", | |||||
| width: "24%", | |||||
| verticalAlign: "top", | |||||
| marginLeft: 10, | |||||
| }} | |||||
| > | |||||
| <Grid item xs={12} md={12} lg={12}> | |||||
| <Card> | |||||
| <CardHeader | |||||
| className="text-slate-500" | |||||
| title="Accounts Receivable (HKD)" | |||||
| /> | |||||
| <ReactApexChart | |||||
| options={accountsReceivableOptions} | |||||
| series={accountsReceivableOptions.series} | |||||
| type="radialBar" | |||||
| /> | |||||
| <Card className="ml-2 mr-2 mb-4 rounded-none border-solid border-slate-100"> | |||||
| <div | |||||
| className="text-sm font-medium ml-5 mt-2" | |||||
| style={{ color: "#898d8d" }} | |||||
| > | |||||
| Total A. Receivable | |||||
| </div> | |||||
| <div | |||||
| className="text-lg font-medium ml-5" | |||||
| style={{ color: "#6b87cf" }} | |||||
| > | |||||
| 1,000,000.00 | |||||
| </div> | |||||
| <hr /> | |||||
| <div | |||||
| className="text-sm font-medium ml-5" | |||||
| style={{ color: "#898d8d" }} | |||||
| > | |||||
| Amount Received | |||||
| </div> | |||||
| <div | |||||
| className="text-lg font-medium ml-5" | |||||
| style={{ color: "#6b87cf" }} | |||||
| > | |||||
| 800,000.00 | |||||
| </div> | |||||
| <hr /> | |||||
| <div | |||||
| className="text-sm font-medium ml-5" | |||||
| style={{ color: "#898d8d" }} | |||||
| > | |||||
| Remaining Balance | |||||
| </div> | |||||
| <div | |||||
| className="text-lg font-medium ml-5 mb-2" | |||||
| style={{ color: "#6b87cf" }} | |||||
| > | |||||
| 200,000.00 | |||||
| </div> | |||||
| </Card> | |||||
| </Card> | |||||
| </Grid> | |||||
| </div> | |||||
| <div | |||||
| style={{ | |||||
| display: "inline-block", | |||||
| width: "24%", | |||||
| verticalAlign: "top", | |||||
| marginLeft: 10, | |||||
| }} | |||||
| > | |||||
| <Grid item xs={12} md={12} lg={12}> | |||||
| <Card> | |||||
| <CardHeader | |||||
| className="text-slate-500" | |||||
| title="Expenditure (HKD)" | |||||
| /> | |||||
| <ReactApexChart | |||||
| options={expenditureOptions} | |||||
| series={expenditureOptions.series} | |||||
| type="radialBar" | |||||
| /> | |||||
| <Card className="ml-2 mr-2 mb-4 rounded-none border-solid border-slate-100"> | |||||
| <div | |||||
| className="text-sm font-medium ml-5 mt-2" | |||||
| style={{ color: "#898d8d" }} | |||||
| > | |||||
| Budgeted Expenditure | |||||
| </div> | |||||
| <div | |||||
| className="text-lg font-medium ml-5" | |||||
| style={{ color: "#6b87cf" }} | |||||
| > | |||||
| 800,000.00 | |||||
| </div> | |||||
| <hr /> | |||||
| <div | |||||
| className="text-sm font-medium ml-5" | |||||
| style={{ color: "#898d8d" }} | |||||
| > | |||||
| Actual Expenditure | |||||
| </div> | |||||
| <div | |||||
| className="text-lg font-medium ml-5" | |||||
| style={{ color: "#6b87cf" }} | |||||
| > | |||||
| 760,000.00 | |||||
| </div> | |||||
| <hr /> | |||||
| <div | |||||
| className="text-sm font-medium ml-5" | |||||
| style={{ color: "#898d8d" }} | |||||
| > | |||||
| Remaining Balance | |||||
| </div> | |||||
| <div | |||||
| className="text-lg font-medium ml-5 mb-2" | |||||
| style={{ color: "#6b87cf" }} | |||||
| > | |||||
| 40,000.00 | |||||
| </div> | |||||
| </Card> | |||||
| </Card> | |||||
| </Grid> | |||||
| </div> | |||||
| <div | |||||
| className="mt-5" | |||||
| style={{ | |||||
| display: "inline-block", | |||||
| width: "100%", | |||||
| verticalAlign: "top", | |||||
| }} | |||||
| > | |||||
| <Grid item xs={12} md={12} lg={12}> | |||||
| <Card> | |||||
| <CardHeader | |||||
| className="text-slate-500" | |||||
| title="Cash Flow Ledger by Month" | |||||
| /> | |||||
| <div className="ml-4 mr-4"> | |||||
| <CustomDatagrid | |||||
| rows={ledgerData} | |||||
| columns={ledgerColumns} | |||||
| columnWidth={200} | |||||
| dataGridHeight={300} | |||||
| /> | |||||
| </div> | |||||
| </Card> | |||||
| </Grid> | |||||
| </div> | |||||
| </Grid> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default ProjectCashFlow; | |||||
| @@ -1 +0,0 @@ | |||||
| export { default } from "./ProjectCashFlow"; | |||||
| @@ -1,173 +0,0 @@ | |||||
| 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"; | |||||
| interface Props { | |||||
| Title: string; | |||||
| TotalActiveProjectNumber: string; | |||||
| TotalFees: string; | |||||
| TotalBudget: string; | |||||
| TotalCumulative: string; | |||||
| TotalInvoicedAmount: string; | |||||
| TotalReceivedAmount: string; | |||||
| CashFlowStatus: string; | |||||
| CostPerformanceIndex: string; | |||||
| ClickedIndex: number; | |||||
| Index: number; | |||||
| } | |||||
| const ProjectFinancialCard: React.FC<Props> = ({ | |||||
| Title, | |||||
| TotalActiveProjectNumber, | |||||
| TotalFees, | |||||
| TotalBudget, | |||||
| TotalCumulative, | |||||
| TotalInvoicedAmount, | |||||
| TotalReceivedAmount, | |||||
| CashFlowStatus, | |||||
| CostPerformanceIndex, | |||||
| ClickedIndex, | |||||
| Index, | |||||
| }) => { | |||||
| const [SearchCriteria, setSearchCriteria] = React.useState({}); | |||||
| const { t } = useTranslation("dashboard"); | |||||
| const borderColor = | |||||
| CashFlowStatus === "Negative" | |||||
| ? "border-red-300 border-solid" | |||||
| : "border-green-200 border-solid"; | |||||
| const selectedBackgroundColor = | |||||
| ClickedIndex === Index ? "rgb(235 235 235)" : "rgb(255 255 255)"; | |||||
| console.log(ClickedIndex); | |||||
| console.log(Index); | |||||
| return ( | |||||
| <Card | |||||
| style={{ | |||||
| maxWidth: "25%", | |||||
| minWidth: "280px", | |||||
| boxShadow: | |||||
| "0 0px 10px 0 rgba(0, 0, 0, 0.08), 0 0px 10px 0 rgba(0, 0, 0, 0.08)", | |||||
| backgroundColor: selectedBackgroundColor, | |||||
| }} | |||||
| className={`${borderColor}`} | |||||
| > | |||||
| <div | |||||
| className="text-xl mt-2 font-medium" | |||||
| style={{ width: "100%", textAlign: "center", color: "#898d8d" }} | |||||
| > | |||||
| {Title} | |||||
| </div> | |||||
| <hr /> | |||||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||||
| Total Active Project | |||||
| </div> | |||||
| <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||||
| {TotalActiveProjectNumber} | |||||
| </div> | |||||
| <hr /> | |||||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||||
| Total Fees | |||||
| </div> | |||||
| <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||||
| {TotalFees} | |||||
| </div> | |||||
| <hr /> | |||||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||||
| Total Budget | |||||
| </div> | |||||
| <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||||
| {TotalBudget} | |||||
| </div> | |||||
| <hr /> | |||||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||||
| Total Cumulative Expenditure | |||||
| </div> | |||||
| <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||||
| {TotalCumulative} | |||||
| </div> | |||||
| <hr /> | |||||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||||
| Total Invoiced Amount | |||||
| </div> | |||||
| <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||||
| {TotalInvoicedAmount} | |||||
| </div> | |||||
| <hr /> | |||||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||||
| Total Received Amount | |||||
| </div> | |||||
| <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||||
| {TotalReceivedAmount} | |||||
| </div> | |||||
| <hr /> | |||||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||||
| Cash Flow Status | |||||
| </div> | |||||
| {CashFlowStatus === "Negative" && ( | |||||
| <> | |||||
| <div | |||||
| className="text-lg font-medium ml-5" | |||||
| style={{ color: "#f896aa" }} | |||||
| > | |||||
| {CashFlowStatus} | |||||
| </div> | |||||
| <hr /> | |||||
| </> | |||||
| )} | |||||
| {CashFlowStatus === "Positive" && ( | |||||
| <> | |||||
| <div | |||||
| className="text-lg font-medium ml-5" | |||||
| style={{ color: "#71d19e" }} | |||||
| > | |||||
| {CashFlowStatus} | |||||
| </div> | |||||
| <hr /> | |||||
| </> | |||||
| )} | |||||
| <div | |||||
| className="text-sm mt-2 font-medium ml-5" | |||||
| style={{ color: "#898d8d" }} | |||||
| > | |||||
| Cost Performance Index (CPI) | |||||
| </div> | |||||
| {Number(CostPerformanceIndex) < 1 && ( | |||||
| <> | |||||
| <div | |||||
| className="text-lg font-medium ml-5 mb-2" | |||||
| style={{ color: "#f896aa" }} | |||||
| > | |||||
| {CostPerformanceIndex} | |||||
| </div> | |||||
| </> | |||||
| )} | |||||
| {Number(CostPerformanceIndex) >= 1 && ( | |||||
| <> | |||||
| <div | |||||
| className="text-lg font-medium ml-5 mb-2" | |||||
| style={{ color: "#71d19e" }} | |||||
| > | |||||
| {CostPerformanceIndex} | |||||
| </div> | |||||
| </> | |||||
| )} | |||||
| </Card> | |||||
| ); | |||||
| }; | |||||
| export default ProjectFinancialCard; | |||||
| @@ -1,465 +0,0 @@ | |||||
| "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"; | |||||
| import ProjectFinancialCard from "./ProjectFinancialCard"; | |||||
| const ProjectFinancialSummary: React.FC = () => { | |||||
| const [SearchCriteria, setSearchCriteria] = React.useState({}); | |||||
| const { t } = useTranslation("dashboard"); | |||||
| const [selectionModel, setSelectionModel]: any[] = React.useState([]); | |||||
| const projectFinancialData = [ | |||||
| { | |||||
| id: 1, | |||||
| title: "All Teams", | |||||
| activeProject: "147", | |||||
| fees: "22,800,000.00", | |||||
| budget: "18,240,000.00", | |||||
| cumulativeExpenditure: "17,950,000.00", | |||||
| invoicedAmount: "18,240,000.00", | |||||
| receivedAmount: "10,900,000.00", | |||||
| cashFlowStatus: "Negative", | |||||
| CPI: "0.69", | |||||
| }, | |||||
| { | |||||
| id: 2, | |||||
| title: "XXX Team", | |||||
| activeProject: "25", | |||||
| fees: "1,500,000.00", | |||||
| budget: "1,200,000.00", | |||||
| cumulativeExpenditure: "1,250,000.00", | |||||
| invoicedAmount: "900,000.00", | |||||
| receivedAmount: "650,000.00", | |||||
| cashFlowStatus: "Negative", | |||||
| CPI: "0.72", | |||||
| }, | |||||
| { | |||||
| id: 3, | |||||
| title: "YYY Team", | |||||
| activeProject: "35", | |||||
| fees: "5,000,000.00", | |||||
| budget: "4,000,000.00", | |||||
| cumulativeExpenditure: "3,200,000.00", | |||||
| invoicedAmount: "3,500,000.00", | |||||
| receivedAmount: "3,500,000.00", | |||||
| cashFlowStatus: "Positive", | |||||
| CPI: "1.09", | |||||
| }, | |||||
| { | |||||
| id: 4, | |||||
| title: "ZZZ Team", | |||||
| activeProject: "50", | |||||
| fees: "3,500,000.00", | |||||
| budget: "2,800,000.00", | |||||
| cumulativeExpenditure: "5,600,000.00", | |||||
| invoicedAmount: "2,500,000.00", | |||||
| receivedAmount: "2,200,000.00", | |||||
| cashFlowStatus: "Negative", | |||||
| CPI: "0.45", | |||||
| }, | |||||
| { | |||||
| id: 5, | |||||
| title: "AAA Team", | |||||
| activeProject: "15", | |||||
| fees: "4,800,000.00", | |||||
| budget: "3,840,000.00", | |||||
| cumulativeExpenditure: "2,500,000.00", | |||||
| invoicedAmount: "1,500,000.00", | |||||
| receivedAmount: "750,000.00", | |||||
| cashFlowStatus: "Negative", | |||||
| CPI: "0.60", | |||||
| }, | |||||
| { | |||||
| id: 6, | |||||
| title: "BBB Team", | |||||
| activeProject: "22", | |||||
| fees: "8,000,000.00", | |||||
| budget: "6,400,000.00", | |||||
| cumulativeExpenditure: "5,400,000.00", | |||||
| invoicedAmount: "4,000,000.00", | |||||
| receivedAmount: "3,800,000.00", | |||||
| cashFlowStatus: "Negative", | |||||
| CPI: "0.74", | |||||
| }, | |||||
| ]; | |||||
| const rows0 = [{id: 1,projectCode:"M1201",projectName:"Consultancy Project C", team:"XXX", teamLeader:"XXX", startDate:"01/08/2022", targetEndDate: "01/05/2024", client:"Client A", subsidiary:"N/A"}, | |||||
| {id: 2,projectCode:"M1321",projectName:"Consultancy Project CCC", team:"XXX", teamLeader:"XXX", startDate:"01/08/2022", targetEndDate: "20/01/2024", client:"Client E", subsidiary:"Subsidiary B"}, | |||||
| {id: 3,projectCode:"M1001",projectName:"Consultancy Project A", team:"YYY", teamLeader:"YYY", startDate:"01/07/2022", targetEndDate: "01/04/2024", client:"Client B", subsidiary:"N/A"}, | |||||
| {id: 4,projectCode:"M1301",projectName:"Consultancy Project AAAA", team:"YYY", teamLeader:"YYY", startDate:"01/09/2022", targetEndDate: "20/02/2024", client:"Client C", subsidiary:"Subsidiary A"}, | |||||
| {id: 5,projectCode:"M1354",projectName:"Consultancy Project BBB", team:"YYY", teamLeader:"YYY", startDate:"01/02/2023", targetEndDate: "31/01/2024", client:"Client D", subsidiary:"Subsidiary C"} | |||||
| ] | |||||
| const rows1 = [{id: 1,projectCode:"M1201",projectName:"Consultancy Project C", team:"XXX", teamLeader:"XXX", startDate:"01/08/2022", targetEndDate: "01/05/2024", client:"Client A", subsidiary:"N/A"}, | |||||
| {id: 2,projectCode:"M1321",projectName:"Consultancy Project CCC", team:"XXX", teamLeader:"XXX", startDate:"01/08/2022", targetEndDate: "20/01/2024", client:"Client E", subsidiary:"Subsidiary B"}, | |||||
| ] | |||||
| const rows2 = [{id: 3,projectCode:"M1001",projectName:"Consultancy Project A", team:"YYY", teamLeader:"YYY", startDate:"01/07/2022", targetEndDate: "01/04/2024", client:"Client B", subsidiary:"N/A"}, | |||||
| {id: 4,projectCode:"M1301",projectName:"Consultancy Project AAAA", team:"YYY", teamLeader:"YYY", startDate:"01/09/2022", targetEndDate: "20/02/2024", client:"Client C", subsidiary:"Subsidiary A"}, | |||||
| {id: 5,projectCode:"M1354",projectName:"Consultancy Project BBB", team:"YYY", teamLeader:"YYY", startDate:"01/02/2023", targetEndDate: "31/01/2024", client:"Client D", subsidiary:"Subsidiary C"} | |||||
| ] | |||||
| const projectFinancialRows = [{id: 1,projectCode:"M1354",projectName:"Consultanct Project BBB",clientName:"Client D",cashFlowStatus:"Positive",cpi:"1.25", totalFees:"500,000.00", totalBudget:"400,000.00", totalCumulativeExpenditure:"280,000.00", totalInvoicedAmount: "350,000.00", totalUnInvoicedAmount:"150,000.00", totalReceivedAmount:"350,000.00"} | |||||
| ] | |||||
| const clientFinancialRows =[{id: 1,clientCode:"Cust-02",clientName:"Client B",totalProjectInvolved:"1",cashFlowStatus:"Positive",cpi:"1.25", totalFees:"500,000.00", totalBudget:"400,000.00", totalCumulativeExpenditure:"280,000.00", totalInvoicedAmount: "350,000.00", totalUnInvoicedAmount:"150,000.00", totalReceivedAmount:"350,000.00"}, | |||||
| {id: 2,clientCode:"Cust-03",clientName:"Client C",totalProjectInvolved:"1",cashFlowStatus:"Positive",cpi:"1.25", totalFees:"500,000.00", totalBudget:"400,000.00", totalCumulativeExpenditure:"280,000.00", totalInvoicedAmount: "350,000.00", totalUnInvoicedAmount:"150,000.00", totalReceivedAmount:"350,000.00"}, | |||||
| {id: 3,clientCode:"Cust-04",clientName:"Client D",totalProjectInvolved:"4",cashFlowStatus:"Positive",cpi:"1.25", totalFees:"500,000.00", totalBudget:"400,000.00", totalCumulativeExpenditure:"280,000.00", totalInvoicedAmount: "350,000.00", totalUnInvoicedAmount:"150,000.00", totalReceivedAmount:"350,000.00"} | |||||
| ] | |||||
| const [isCardClickedIndex, setIsCardClickedIndex] = React.useState(0); | |||||
| const [selectedTeamData, setSelectedTeamData]: any[] = React.useState(rows0); | |||||
| const handleCardClick = (r: any) => { | |||||
| setIsCardClickedIndex(r); | |||||
| if (r === 0) { | |||||
| setSelectedTeamData(rows0); | |||||
| } else if (r === 1) { | |||||
| setSelectedTeamData(rows1); | |||||
| } else if (r === 2) { | |||||
| setSelectedTeamData(rows2); | |||||
| } | |||||
| }; | |||||
| const columns = [ | |||||
| { | |||||
| id: 'clientCode', | |||||
| field: 'clientCode', | |||||
| headerName: "Client Code", | |||||
| flex: 0.7, | |||||
| }, | |||||
| { | |||||
| id: 'clientName', | |||||
| field: 'clientName', | |||||
| headerName: "Client Name", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: 'totalProjectInvolved', | |||||
| field: 'totalProjectInvolved', | |||||
| headerName: "Total Project Involved", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: 'cashFlowStatus', | |||||
| field: 'cashFlowStatus', | |||||
| headerName: "Cash Flow Status", | |||||
| flex: 1, | |||||
| renderCell: (params:any) => { | |||||
| if (params.row.cashFlowStatus === "Positive") { | |||||
| return ( | |||||
| <span className="text-lime-500">{params.row.cashFlowStatus}</span> | |||||
| ) | |||||
| } else if (params.row.cashFlowStatus === "Negative") { | |||||
| return ( | |||||
| <span className="text-red-500">{params.row.cashFlowStatus}</span> | |||||
| ) | |||||
| } | |||||
| }, | |||||
| }, | |||||
| { | |||||
| id: 'cpi', | |||||
| field: 'cpi', | |||||
| headerName: "CPI", | |||||
| flex: 0.7, | |||||
| renderCell: (params:any) => { | |||||
| if (params.row.cpi >= 1) { | |||||
| return ( | |||||
| <span className="text-lime-500">{params.row.cpi}</span> | |||||
| ) | |||||
| } else if (params.row.cpi < 1) { | |||||
| return ( | |||||
| <span className="text-red-500">{params.row.cpi}</span> | |||||
| ) | |||||
| } | |||||
| }, | |||||
| }, | |||||
| { | |||||
| id: 'totalFees', | |||||
| field: 'totalFees', | |||||
| headerName: "Total Fees (HKD)", | |||||
| flex: 1, | |||||
| renderCell: (params:any) => { | |||||
| return ( | |||||
| <span>${params.row.totalFees}</span> | |||||
| ) | |||||
| }, | |||||
| }, | |||||
| { | |||||
| id: 'totalBudget', | |||||
| field: 'totalBudget', | |||||
| headerName: "Total Budget (HKD)", | |||||
| flex: 1, | |||||
| renderCell: (params:any) => { | |||||
| return ( | |||||
| <span>${params.row.totalBudget}</span> | |||||
| ) | |||||
| }, | |||||
| }, | |||||
| { | |||||
| id: 'totalCumulativeExpenditure', | |||||
| field: 'totalCumulativeExpenditure', | |||||
| headerName: "Total Cumulative Expenditure (HKD)", | |||||
| flex: 1, | |||||
| renderCell: (params:any) => { | |||||
| return ( | |||||
| <span>${params.row.totalCumulativeExpenditure}</span> | |||||
| ) | |||||
| }, | |||||
| }, | |||||
| { | |||||
| id: 'totalInvoicedAmount', | |||||
| field: 'totalInvoicedAmount', | |||||
| headerName: "Total Invoiced Amount (HKD)", | |||||
| flex: 1, | |||||
| renderCell: (params:any) => { | |||||
| return ( | |||||
| <span>${params.row.totalInvoicedAmount}</span> | |||||
| ) | |||||
| }, | |||||
| }, | |||||
| { | |||||
| id: 'totalUnInvoicedAmount', | |||||
| field: 'totalUnInvoicedAmount', | |||||
| headerName: "Total Un-invoiced Amount (HKD)", | |||||
| flex: 1, | |||||
| renderCell: (params:any) => { | |||||
| return ( | |||||
| <span>${params.row.totalUnInvoicedAmount}</span> | |||||
| ) | |||||
| }, | |||||
| }, | |||||
| { | |||||
| id: 'totalReceivedAmount', | |||||
| field: 'totalReceivedAmount', | |||||
| headerName: "Total Received Amount (HKD)", | |||||
| flex: 1, | |||||
| renderCell: (params:any) => { | |||||
| return ( | |||||
| <span>${params.row.totalReceivedAmount}</span> | |||||
| ) | |||||
| }, | |||||
| }, | |||||
| // { | |||||
| // id: 'projectCode', | |||||
| // field: 'projectCode', | |||||
| // headerName: "Project Code", | |||||
| // flex: 1, | |||||
| // }, | |||||
| // { | |||||
| // id: 'projectName', | |||||
| // field: 'projectName', | |||||
| // headerName: "Project Name", | |||||
| // flex: 1, | |||||
| // }, | |||||
| // { | |||||
| // id: 'team', | |||||
| // field: 'team', | |||||
| // headerName: "Team", | |||||
| // flex: 1, | |||||
| // }, | |||||
| // { | |||||
| // id: 'teamLeader', | |||||
| // field: 'teamLeader', | |||||
| // headerName: "Team Leader", | |||||
| // flex: 1, | |||||
| // }, | |||||
| // { | |||||
| // id: 'startDate', | |||||
| // field: 'startDate', | |||||
| // headerName: "Start Date", | |||||
| // flex: 1, | |||||
| // }, | |||||
| // { | |||||
| // id: 'targetEndDate', | |||||
| // field: 'targetEndDate', | |||||
| // headerName: "Target End Date", | |||||
| // flex: 1, | |||||
| // }, | |||||
| // { | |||||
| // id: 'client', | |||||
| // field: 'client', | |||||
| // headerName: "Client", | |||||
| // flex: 1, | |||||
| // }, | |||||
| // { | |||||
| // id: 'subsidiary', | |||||
| // field: 'subsidiary', | |||||
| // headerName: "Subsidiary", | |||||
| // flex: 1, | |||||
| // }, | |||||
| ]; | |||||
| const columns2 = [ | |||||
| { | |||||
| id: 'projectCode', | |||||
| field: 'projectCode', | |||||
| headerName: "Project Code", | |||||
| flex: 0.7, | |||||
| }, | |||||
| { | |||||
| id: 'projectName', | |||||
| field: 'projectName', | |||||
| headerName: "Project Name", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: 'clientName', | |||||
| field: 'clientName', | |||||
| headerName: "Client Name", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| id: 'cashFlowStatus', | |||||
| field: 'cashFlowStatus', | |||||
| headerName: "Cash Flow Status", | |||||
| flex: 1, | |||||
| renderCell: (params:any) => { | |||||
| if (params.row.cashFlowStatus === "Positive") { | |||||
| return ( | |||||
| <span className="text-lime-500">{params.row.cashFlowStatus}</span> | |||||
| ) | |||||
| } else if (params.row.cashFlowStatus === "Negative") { | |||||
| return ( | |||||
| <span className="text-red-500">{params.row.cashFlowStatus}</span> | |||||
| ) | |||||
| } | |||||
| }, | |||||
| }, | |||||
| { | |||||
| id: "cpi", | |||||
| field: "cpi", | |||||
| headerName: "CPI", | |||||
| flex: 0.7, | |||||
| renderCell: (params: any) => { | |||||
| if (params.row.cpi >= 1) { | |||||
| return <span className="text-lime-500">{params.row.cpi}</span>; | |||||
| } else if (params.row.cpi < 1) { | |||||
| return <span className="text-red-500">{params.row.cpi}</span>; | |||||
| } | |||||
| }, | |||||
| }, | |||||
| { | |||||
| id: 'totalFees', | |||||
| field: 'totalFees', | |||||
| headerName: "Total Fees (HKD)", | |||||
| flex: 1, | |||||
| renderCell: (params:any) => { | |||||
| return ( | |||||
| <span>${params.row.totalFees}</span> | |||||
| ) | |||||
| }, | |||||
| }, | |||||
| { | |||||
| id: 'totalBudget', | |||||
| field: 'totalBudget', | |||||
| headerName: "Total Budget (HKD)", | |||||
| flex: 1, | |||||
| renderCell: (params:any) => { | |||||
| return ( | |||||
| <span>${params.row.totalBudget}</span> | |||||
| ) | |||||
| }, | |||||
| }, | |||||
| { | |||||
| id: 'totalCumulativeExpenditure', | |||||
| field: 'totalCumulativeExpenditure', | |||||
| headerName: "Total Cumulative Expenditure (HKD)", | |||||
| flex: 1, | |||||
| renderCell: (params:any) => { | |||||
| return ( | |||||
| <span>${params.row.totalCumulativeExpenditure}</span> | |||||
| ) | |||||
| }, | |||||
| }, | |||||
| { | |||||
| id: 'totalInvoicedAmount', | |||||
| field: 'totalInvoicedAmount', | |||||
| headerName: "Total Invoiced Amount (HKD)", | |||||
| flex: 1, | |||||
| renderCell: (params:any) => { | |||||
| return ( | |||||
| <span>${params.row.totalInvoicedAmount}</span> | |||||
| ) | |||||
| }, | |||||
| }, | |||||
| { | |||||
| id: 'totalUnInvoicedAmount', | |||||
| field: 'totalUnInvoicedAmount', | |||||
| headerName: "Total Un-invoiced Amount (HKD)", | |||||
| flex: 1, | |||||
| renderCell: (params:any) => { | |||||
| return ( | |||||
| <span>${params.row.totalUnInvoicedAmount}</span> | |||||
| ) | |||||
| }, | |||||
| }, | |||||
| { | |||||
| id: 'totalReceivedAmount', | |||||
| field: 'totalReceivedAmount', | |||||
| headerName: "Total Received Amount (HKD)", | |||||
| flex: 1, | |||||
| renderCell: (params:any) => { | |||||
| return ( | |||||
| <span>${params.row.totalReceivedAmount}</span> | |||||
| ) | |||||
| }, | |||||
| }, | |||||
| ]; | |||||
| const handleSelectionChange = (newSelectionModel: GridRowSelectionModel) => { | |||||
| const selectedRowsData = selectedTeamData.filter((row: any) => | |||||
| newSelectionModel.includes(row.id), | |||||
| ); | |||||
| console.log(selectedRowsData); | |||||
| }; | |||||
| return ( | |||||
| <Grid item sm> | |||||
| <Card> | |||||
| <CardHeader className="text-slate-500" title="Active Project Financial Status"/> | |||||
| <div className="ml-10 mr-10" style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'start'}}> | |||||
| {projectFinancialData.map((record, index) => ( | |||||
| <div className="hover:cursor-pointer ml-4 mt-5 mb-4 inline-block" key={index} onClick={(r) => handleCardClick(index)}> | |||||
| <ProjectFinancialCard Title={record.title} TotalActiveProjectNumber={record.activeProject} TotalFees={record.fees} TotalBudget={record.budget} TotalCumulative={record.cumulativeExpenditure} TotalInvoicedAmount={record.invoicedAmount} TotalReceivedAmount={record.receivedAmount} CashFlowStatus={record.cashFlowStatus} CostPerformanceIndex={record.CPI} ClickedIndex={isCardClickedIndex} Index={index}/> | |||||
| </div> | |||||
| ))} | |||||
| </div> | |||||
| </Card> | |||||
| <Card className="mt-5"> | |||||
| <CardHeader className="text-slate-500" title="Selected Team's Project"/> | |||||
| <div style={{display:"inline-block",width:"99%",marginLeft:10}}> | |||||
| {/* <CustomDatagrid rows={clientFinancialRows} columns={columns} columnWidth={200} dataGridHeight={300} checkboxSelection={true} onRowSelectionModelChange={handleSelectionChange} selectionModel={selectionModel}/> */} | |||||
| <CustomDatagrid rows={clientFinancialRows} columns={columns} columnWidth={200} dataGridHeight={300}/> | |||||
| </div> | |||||
| </Card> | |||||
| <Card className="mt-5"> | |||||
| <CardHeader className="text-slate-500" title="Financial Status (by Project)"/> | |||||
| <div style={{display:"inline-block",width:"99%",marginLeft:10}}> | |||||
| <CustomDatagrid rows={projectFinancialRows} columns={columns2} columnWidth={200} dataGridHeight={300}/> | |||||
| </div> | |||||
| </Card> | |||||
| </Grid> | |||||
| ); | |||||
| }; | |||||
| export default ProjectFinancialSummary; | |||||
| @@ -1 +0,0 @@ | |||||
| export { default } from "./ProjectFinancialSummary"; | |||||
| @@ -1 +0,0 @@ | |||||
| export { default } from "./StaffUtilization"; | |||||
| @@ -1,100 +0,0 @@ | |||||
| "use client"; | |||||
| import * as React from "react"; | |||||
| import Grid from "@mui/material/Grid"; | |||||
| import { useEffect } from "react"; | |||||
| import { Card, CardContent, CardHeader } from "@mui/material"; | |||||
| import CustomCardGrid from "../CustomCardGrid/CustomCardGrid"; | |||||
| import "../../app/global.css"; | |||||
| import { PROJECT_CARD_STYLE } from "@/theme/colorConst"; | |||||
| interface ProjectGridProps { | |||||
| tab: number; | |||||
| } | |||||
| const cards = [ | |||||
| { | |||||
| code: "M1001 (C)", | |||||
| name: "Consultancy Project A", | |||||
| hr_spent: 12.75, | |||||
| hr_spent_normal: 0.0, | |||||
| hr_alloc: 150.0, | |||||
| hr_alloc_normal: 30.0, | |||||
| }, | |||||
| { | |||||
| code: "M1301 (C)", | |||||
| name: "Consultancy Project AAA", | |||||
| hr_spent: 4.25, | |||||
| hr_spent_normal: 0.25, | |||||
| hr_alloc: 30.0, | |||||
| hr_alloc_normal: 0.0, | |||||
| }, | |||||
| { | |||||
| code: "M1354 (C)", | |||||
| name: "Consultancy Project BBB", | |||||
| hr_spent: 57.0, | |||||
| hr_spent_normal: 6.5, | |||||
| hr_alloc: 100.0, | |||||
| hr_alloc_normal: 20.0, | |||||
| }, | |||||
| { | |||||
| code: "M1973 (C)", | |||||
| name: "Construction Project CCC", | |||||
| hr_spent: 12.75, | |||||
| hr_spent_normal: 0.0, | |||||
| hr_alloc: 150.0, | |||||
| hr_alloc_normal: 30.0, | |||||
| }, | |||||
| { | |||||
| code: "M2014 (T)", | |||||
| name: "Consultancy Project DDD", | |||||
| hr_spent: 1.0, | |||||
| hr_spent_normal: 0.0, | |||||
| hr_alloc: 10.0, | |||||
| hr_alloc_normal: 0.0, | |||||
| }, | |||||
| ]; | |||||
| const ProjectGrid: React.FC<ProjectGridProps> = (props) => { | |||||
| const [items, setItems] = React.useState<typeof cards>([]); | |||||
| useEffect(() => { | |||||
| if (props.tab == 0) { | |||||
| setItems(cards); | |||||
| } else { | |||||
| const filteredItems = cards; //cards.filter(item => (item.track == props.tab)); | |||||
| setItems(filteredItems); | |||||
| } | |||||
| }, [props.tab]); | |||||
| const cardLayout = (item: Record<string, string>) => { | |||||
| return ( | |||||
| <Card style={PROJECT_CARD_STYLE}> | |||||
| <CardHeader | |||||
| style={{ backgroundColor: "pink" }} | |||||
| title={item.code + "\u000A" + item.name} | |||||
| /> | |||||
| <CardContent> | |||||
| <p>Hours Spent: {item.hr_spent}</p> | |||||
| <p>Normal (Others): {item.hr_spent_normal}</p> | |||||
| <p>Hours Allocated: {item.hr_alloc}</p> | |||||
| <p>Normal (Others): {item.hr_alloc_normal}</p> | |||||
| </CardContent> | |||||
| </Card> | |||||
| ); | |||||
| }; | |||||
| // Apply the preset style to the cards in child, if not specified // | |||||
| return ( | |||||
| <Grid container md={12}> | |||||
| {/* <CustomSearchForm applySearch={applySearch} fields={InputFields}/> */} | |||||
| {/* item count = {items?.length??"idk"} , track/tab = {props.tab} */} | |||||
| <CustomCardGrid | |||||
| Title={props.tab.toString()} | |||||
| items={items} | |||||
| cardStyle={cardLayout} | |||||
| /> | |||||
| {/* <CustomCardGrid Title={props.tab.toString()} rows={rows} columns={columns} columnWidth={200} items={items}/> */} | |||||
| </Grid> | |||||
| ); | |||||
| }; | |||||
| export default ProjectGrid; | |||||
| @@ -3,7 +3,6 @@ import Grid from "@mui/material/Grid"; | |||||
| import Paper from "@mui/material/Paper"; | import Paper from "@mui/material/Paper"; | ||||
| import { useState } from "react"; | import { useState } from "react"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import AssignedProjectGrid from "../AssignedProjectGrid/AssignedProjectGrid"; | |||||
| import PageTitle from "../PageTitle/PageTitle"; | import PageTitle from "../PageTitle/PageTitle"; | ||||
| import { Suspense } from "react"; | import { Suspense } from "react"; | ||||
| import Button from "@mui/material/Button"; | import Button from "@mui/material/Button"; | ||||
| @@ -12,7 +11,6 @@ import { Add } from "@mui/icons-material"; | |||||
| import Link from "next/link"; | import Link from "next/link"; | ||||
| import { t } from "i18next"; | import { t } from "i18next"; | ||||
| import { Modal } from "@mui/material"; | import { Modal } from "@mui/material"; | ||||
| import CustomModal from "../CustomModal/CustomModal"; | |||||
| import EnterTimesheetModal from "../EnterTimesheet/EnterTimesheetModal"; | import EnterTimesheetModal from "../EnterTimesheet/EnterTimesheetModal"; | ||||
| import EnterLeaveModal from "../EnterLeave/EnterLeaveModal"; | import EnterLeaveModal from "../EnterLeave/EnterLeaveModal"; | ||||
| @@ -72,7 +70,6 @@ const UserWorkspacePage: React.FC = () => { | |||||
| isOpen={isLeaveModalVisible} | isOpen={isLeaveModalVisible} | ||||
| onClose={handleCloseLeaveModal} | onClose={handleCloseLeaveModal} | ||||
| /> | /> | ||||
| <AssignedProjectGrid Title="Assigned Project" /> | |||||
| </Grid> | </Grid> | ||||
| </Grid> | </Grid> | ||||
| ); | ); | ||||
| @@ -1,7 +1,7 @@ | |||||
| export const PRIVATE_ROUTES = [ | export const PRIVATE_ROUTES = [ | ||||
| "/analytics", | "/analytics", | ||||
| "/dashboard", | "/dashboard", | ||||
| "/home", | |||||
| "/dashboard", | |||||
| "/invoice", | "/invoice", | ||||
| "/projects", | "/projects", | ||||
| "/tasks", | "/tasks", | ||||
| @@ -1,16 +1,32 @@ | |||||
| "use client"; | "use client"; | ||||
| import * as React from "react"; | import * as React from "react"; | ||||
| import { ThemeProvider } from "@mui/material/styles"; | |||||
| import { ThemeProvider, createTheme } from "@mui/material/styles"; | |||||
| import CssBaseline from "@mui/material/CssBaseline"; | import CssBaseline from "@mui/material/CssBaseline"; | ||||
| import NextAppDirEmotionCacheProvider from "./EmotionCache"; | import NextAppDirEmotionCacheProvider from "./EmotionCache"; | ||||
| import theme from "./devias-material-kit"; | import theme from "./devias-material-kit"; | ||||
| import { zhHK, enUS } from "@mui/material/locale"; | |||||
| const getLocalizationFromLang = (lang: string) => { | |||||
| switch (lang) { | |||||
| case "zh": | |||||
| return zhHK; | |||||
| default: | |||||
| return enUS; | |||||
| } | |||||
| }; | |||||
| // Copied from https://github.com/mui/material-ui/blob/master/examples/material-ui-nextjs-ts/src/components/ThemeRegistry/ThemeRegistry.tsx | // Copied from https://github.com/mui/material-ui/blob/master/examples/material-ui-nextjs-ts/src/components/ThemeRegistry/ThemeRegistry.tsx | ||||
| export default function ThemeRegistry({ | export default function ThemeRegistry({ | ||||
| children, | children, | ||||
| lang, | |||||
| }: { | }: { | ||||
| children: React.ReactNode; | children: React.ReactNode; | ||||
| lang: string; | |||||
| }) { | }) { | ||||
| const themeWithLocale = React.useMemo( | |||||
| () => createTheme(theme, getLocalizationFromLang(lang)), | |||||
| [lang], | |||||
| ); | |||||
| return ( | return ( | ||||
| <NextAppDirEmotionCacheProvider options={{ key: "mui" }}> | <NextAppDirEmotionCacheProvider options={{ key: "mui" }}> | ||||
| <ThemeProvider theme={theme}> | <ThemeProvider theme={theme}> | ||||
| @@ -11,7 +11,25 @@ export const neutral = { | |||||
| 900: "#111927", | 900: "#111927", | ||||
| }; | }; | ||||
| // export const primary = { | |||||
| // lightest: "#F5F7FF", | |||||
| // light: "#EBEEFE", | |||||
| // main: "#6366F1", | |||||
| // dark: "#4338CA", | |||||
| // darkest: "#312E81", | |||||
| // contrastText: "#FFFFFF", | |||||
| // }; | |||||
| export const primary = { | export const primary = { | ||||
| lightest: "#f9fff5", | |||||
| light: "#f9feeb", | |||||
| main: "#8dba00", | |||||
| dark: "#638a01", | |||||
| darkest: "#4a5f14", | |||||
| contrastText: "#FFFFFF", | |||||
| }; | |||||
| export const secondary = { | |||||
| lightest: "#F5F7FF", | lightest: "#F5F7FF", | ||||
| light: "#EBEEFE", | light: "#EBEEFE", | ||||
| main: "#6366F1", | main: "#6366F1", | ||||
| @@ -1,6 +1,14 @@ | |||||
| import { common } from "@mui/material/colors"; | import { common } from "@mui/material/colors"; | ||||
| import { PaletteOptions } from "@mui/material/styles"; | import { PaletteOptions } from "@mui/material/styles"; | ||||
| import { error, primary, info, neutral, success, warning } from "./colors"; | |||||
| import { | |||||
| error, | |||||
| primary, | |||||
| secondary, | |||||
| info, | |||||
| neutral, | |||||
| success, | |||||
| warning, | |||||
| } from "./colors"; | |||||
| const palette = { | const palette = { | ||||
| action: { | action: { | ||||
| @@ -20,6 +28,7 @@ const palette = { | |||||
| info, | info, | ||||
| mode: "light", | mode: "light", | ||||
| primary, | primary, | ||||
| secondary, | |||||
| success, | success, | ||||
| text: { | text: { | ||||
| primary: neutral[900], | primary: neutral[900], | ||||