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