@@ -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 { I18nProvider } from "@/i18n"; | |||
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 { getServerI18n } from "@/i18n"; | |||
import DashboardPage from "@/components/DashboardPage"; | |||
import { SearchParams } from "@/app/utils/fetchUtil"; | |||
export const metadata: Metadata = { | |||
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"); | |||
return ( | |||
<I18nProvider namespaces={["dashboard", "common"]}> | |||
<DashboardPage/> | |||
<Suspense fallback={<DashboardPage.Loading />}> | |||
<DashboardPage | |||
searchParams={searchParams} | |||
/> | |||
</Suspense> | |||
</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 | |||
</Typography> | |||
<></> | |||
{/* <CompanyTeamCashFlowComponent /> */} | |||
</I18nProvider> | |||
); | |||
}; | |||
@@ -17,7 +17,7 @@ export default async function RootLayout({ | |||
return ( | |||
<html lang={lang}> | |||
<body> | |||
<ThemeRegistry>{children}</ThemeRegistry> | |||
<ThemeRegistry lang={lang}>{children}</ThemeRegistry> | |||
</body> | |||
</html> | |||
); | |||
@@ -1,7 +1,7 @@ | |||
import { permanentRedirect } from "next/navigation"; | |||
const Home: React.FC = async () => { | |||
permanentRedirect("/home"); | |||
permanentRedirect("/dashboard"); | |||
}; | |||
export default Home; |
@@ -3,6 +3,10 @@ import { getServerSession } from "next-auth"; | |||
import { headers } from "next/headers"; | |||
import { redirect } from "next/navigation"; | |||
export type SearchParams = { | |||
searchParams: { [key: string]: string | string[] | undefined }; | |||
} | |||
export const serverFetch: typeof fetch = async (input, init) => { | |||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | |||
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"; | |||
// Can make this nicer | |||
export const ProgressByClientSearchLoading: React.FC = () => { | |||
export const DashboardLoading: React.FC = () => { | |||
return ( | |||
<> | |||
<Card> | |||
@@ -16,14 +16,14 @@ export const ProgressByClientSearchLoading: React.FC = () => { | |||
<Skeleton variant="rounded" height={60} /> | |||
<Skeleton | |||
variant="rounded" | |||
height={50} | |||
height={50} | |||
width={100} | |||
sx={{ alignSelf: "flex-end" }} | |||
/> | |||
</Stack> | |||
</CardContent> | |||
</Card> | |||
<Card> | |||
<Card>EditUser | |||
<CardContent> | |||
<Stack spacing={2}> | |||
<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"; | |||
import Grid from "@mui/material/Grid"; | |||
import Paper from "@mui/material/Paper"; | |||
import { TFunction } from "i18next"; | |||
import { useTranslation } from "react-i18next"; | |||
import PageTitle from "../PageTitle/PageTitle"; | |||
import DashboardTabButton from "./DashboardTabButton"; | |||
import { ThemeProvider } from "@mui/material/styles"; | |||
import theme from "../../theme"; | |||
import Tabs, { TabsProps } from "@mui/material/Tabs"; | |||
import Tab from "@mui/material/Tab"; | |||
import React, { useCallback, useState } from "react"; | |||
import { TabsProps } from "@mui/material/Tabs"; | |||
import React, { useCallback, useEffect, useState } from "react"; | |||
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 = { | |||
abilities: string[] | |||
@@ -22,36 +12,18 @@ type Props = { | |||
const DashboardPage: React.FC<Props> = ({ | |||
abilities | |||
}) => { | |||
const [tabIndex, setTabIndex] = useState(0); | |||
const { t } = useTranslation("dashboard"); | |||
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 ( | |||
<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> | |||
); | |||
}; | |||
@@ -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 { getServerSession, Session } from "next-auth" | |||
import DashboardPage from "./DashboardPage" | |||
import { Typography } from "@mui/material" | |||
import { I18nProvider, getServerI18n } from "@/i18n"; | |||
import DashboardLoading from "./DashboardLoading"; | |||
export type SessionWithAbilities = { | |||
abilities: string[] | |||
} & 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 ( | |||
<DashboardPage | |||
abilities={session ? session?.abilities : []} | |||
/> | |||
<> | |||
<Typography variant="h4">{t("Dashboard")}</Typography> | |||
<DashboardPage | |||
abilities={session ? session?.abilities : []} | |||
/> | |||
</> | |||
) | |||
} | |||
DashboardWrapper.Loading = DashboardLoading; | |||
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"; | |||
// import { testing } from "@/app/api/timesheets"; | |||
import Grid from "@mui/material/Grid"; | |||
import Paper from "@mui/material/Paper"; | |||
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 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 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 { BASE_API_URL } from "@/config/api"; | |||
// import { fetchLeaves } from "@/app/api/leave"; | |||
interface EnterTimesheetModalProps { | |||
isOpen: boolean; | |||
onClose: () => void; | |||
@@ -1,23 +1,9 @@ | |||
"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 { 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 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 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 { BASE_API_URL } from "@/config/api"; | |||
@@ -33,8 +33,6 @@ interface NavigationItem { | |||
} | |||
const NavigationContent: React.FC = () => { | |||
// const abilities = window.localStorage.getItem("abilites") | |||
// console.log(abilities) | |||
const navigationItems: NavigationItem[] = [ | |||
{ | |||
icon: <Dashboard />, | |||
@@ -245,16 +243,16 @@ const NavigationContent: React.FC = () => { | |||
const pathname = usePathname(); | |||
const [openItems, setOpenItems] = React.useState<string[]>([]); | |||
const toggleItem = (path: string) => { | |||
const toggleItem = (label: string) => { | |||
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 isOpen = openItems.includes(item.path); | |||
const isOpen = openItems.includes(item.label); | |||
return ( | |||
<Box | |||
@@ -264,8 +262,8 @@ const NavigationContent: React.FC = () => { | |||
sx={{ textDecoration: "none", color: "inherit" }} | |||
> | |||
<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> | |||
<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 { 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"; | |||
@@ -12,7 +11,6 @@ import { Add } from "@mui/icons-material"; | |||
import Link from "next/link"; | |||
import { t } from "i18next"; | |||
import { Modal } from "@mui/material"; | |||
import CustomModal from "../CustomModal/CustomModal"; | |||
import EnterTimesheetModal from "../EnterTimesheet/EnterTimesheetModal"; | |||
import EnterLeaveModal from "../EnterLeave/EnterLeaveModal"; | |||
@@ -72,7 +70,6 @@ const UserWorkspacePage: React.FC = () => { | |||
isOpen={isLeaveModalVisible} | |||
onClose={handleCloseLeaveModal} | |||
/> | |||
<AssignedProjectGrid Title="Assigned Project" /> | |||
</Grid> | |||
</Grid> | |||
); | |||
@@ -1,7 +1,7 @@ | |||
export const PRIVATE_ROUTES = [ | |||
"/analytics", | |||
"/dashboard", | |||
"/home", | |||
"/dashboard", | |||
"/invoice", | |||
"/projects", | |||
"/tasks", | |||
@@ -1,16 +1,32 @@ | |||
"use client"; | |||
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 NextAppDirEmotionCacheProvider from "./EmotionCache"; | |||
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 | |||
export default function ThemeRegistry({ | |||
children, | |||
lang, | |||
}: { | |||
children: React.ReactNode; | |||
lang: string; | |||
}) { | |||
const themeWithLocale = React.useMemo( | |||
() => createTheme(theme, getLocalizationFromLang(lang)), | |||
[lang], | |||
); | |||
return ( | |||
<NextAppDirEmotionCacheProvider options={{ key: "mui" }}> | |||
<ThemeProvider theme={theme}> | |||
@@ -11,7 +11,25 @@ export const neutral = { | |||
900: "#111927", | |||
}; | |||
// export const primary = { | |||
// lightest: "#F5F7FF", | |||
// light: "#EBEEFE", | |||
// main: "#6366F1", | |||
// dark: "#4338CA", | |||
// darkest: "#312E81", | |||
// contrastText: "#FFFFFF", | |||
// }; | |||
export const primary = { | |||
lightest: "#f9fff5", | |||
light: "#f9feeb", | |||
main: "#8dba00", | |||
dark: "#638a01", | |||
darkest: "#4a5f14", | |||
contrastText: "#FFFFFF", | |||
}; | |||
export const secondary = { | |||
lightest: "#F5F7FF", | |||
light: "#EBEEFE", | |||
main: "#6366F1", | |||
@@ -1,6 +1,14 @@ | |||
import { common } from "@mui/material/colors"; | |||
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 = { | |||
action: { | |||
@@ -20,6 +28,7 @@ const palette = { | |||
info, | |||
mode: "light", | |||
primary, | |||
secondary, | |||
success, | |||
text: { | |||
primary: neutral[900], | |||