浏览代码

update layout and auth related

feature/axios_provider
MSI\derek 5 个月前
父节点
当前提交
2073d43ccd
共有 53 个文件被更改,包括 110 次插入5914 次删除
  1. +0
    -17
      src/app/(main)/NotInUse/page.tsx
  2. +13
    -9
      src/app/(main)/dashboard/page.tsx
  3. +0
    -26
      src/app/(main)/home/CompanyTeamCashFlow/page.tsx
  4. +0
    -29
      src/app/(main)/home/ProjectCashFlow/page.tsx
  5. +0
    -26
      src/app/(main)/home/ProjectFinancialSummary/page.tsx
  6. +0
    -29
      src/app/(main)/home/ProjectStatusByClient/page.tsx
  7. +0
    -26
      src/app/(main)/home/StaffUtilization/page.tsx
  8. +0
    -1
      src/app/(main)/settings/user/page.tsx
  9. +1
    -1
      src/app/layout.tsx
  10. +1
    -1
      src/app/page.tsx
  11. +4
    -0
      src/app/utils/fetchUtil.ts
  12. +0
    -247
      src/components/AssignedProjectGrid/AssignedProjectGrid.tsx
  13. +0
    -1
      src/components/AssignedProjectGrid/index.ts
  14. +0
    -306
      src/components/CompanyTeamCashFlow/CompanyTeamCashFlow.tsx
  15. +0
    -1
      src/components/CompanyTeamCashFlow/index.ts
  16. +0
    -240
      src/components/CustomCardGrid/CustomCardGrid.tsx
  17. +0
    -1
      src/components/CustomCardGrid/index.ts
  18. +0
    -309
      src/components/CustomDatagrid/CustomDatagrid.tsx
  19. +0
    -1
      src/components/CustomDatagrid/index.ts
  20. +0
    -81
      src/components/CustomModal/CustomModal.tsx
  21. +0
    -1
      src/components/CustomModal/index.ts
  22. +0
    -379
      src/components/CustomSearchForm/CustomSearchForm.tsx
  23. +0
    -1
      src/components/CustomSearchForm/index.ts
  24. +4
    -4
      src/components/DashboardPage/DashboardLoading.tsx
  25. +9
    -37
      src/components/DashboardPage/DashboardPage.tsx
  26. +0
    -77
      src/components/DashboardPage/DashboardTabButton.tsx
  27. +25
    -5
      src/components/DashboardPage/DashboardWrapper.tsx
  28. +0
    -493
      src/components/DashboardPage/ProgressByClient.tsx
  29. +0
    -19
      src/components/EnterLeave/EnterLeaveModal.tsx
  30. +0
    -14
      src/components/EnterTimesheet/EnterTimesheetModal.tsx
  31. +7
    -9
      src/components/NavigationContent/NavigationContent.tsx
  32. +0
    -643
      src/components/ProgressByClient/ProgressByClient.tsx
  33. +0
    -1
      src/components/ProgressByClient/index.ts
  34. +0
    -57
      src/components/ProgressByClientSearch/ProgressByClientSearch.tsx
  35. +0
    -18
      src/components/ProgressByClientSearch/ProgressByClientSearchWrapper.tsx
  36. +0
    -1
      src/components/ProgressByClientSearch/index.ts
  37. +0
    -156
      src/components/ProgressCashFlowSearch/ProgressCashFlowSearch.tsx
  38. +0
    -40
      src/components/ProgressCashFlowSearch/ProgressCashFlowSearchLoading.tsx
  39. +0
    -18
      src/components/ProgressCashFlowSearch/ProgressCashFlowSearchWrapper.tsx
  40. +0
    -1
      src/components/ProgressCashFlowSearch/index.ts
  41. +0
    -638
      src/components/ProjectCashFlow/ProjectCashFlow.tsx
  42. +0
    -1
      src/components/ProjectCashFlow/index.ts
  43. +0
    -173
      src/components/ProjectFinancialSummary/ProjectFinancialCard.tsx
  44. +0
    -465
      src/components/ProjectFinancialSummary/ProjectFinancialSummary.tsx
  45. +0
    -1
      src/components/ProjectFinancialSummary/index.ts
  46. +0
    -1203
      src/components/StaffUtilization/StaffUtilization.tsx
  47. +0
    -1
      src/components/StaffUtilization/index.ts
  48. +0
    -100
      src/components/UserWorkspacePage/ProjectGrid.tsx
  49. +0
    -3
      src/components/UserWorkspacePage/UserWorkspacePage.tsx
  50. +1
    -1
      src/routes.ts
  51. +17
    -1
      src/theme/ThemeRegistry.tsx
  52. +18
    -0
      src/theme/devias-material-kit/colors.ts
  53. +10
    -1
      src/theme/devias-material-kit/palette.ts

+ 0
- 17
src/app/(main)/NotInUse/page.tsx 查看文件

@@ -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;

src/app/(main)/home/page.tsx → src/app/(main)/dashboard/page.tsx 查看文件

@@ -1,26 +1,30 @@
import { Metadata } from "next";
import { I18nProvider } from "@/i18n";
import DashboardPageButton from "@/components/DashboardPage/DashboardTabButton";
import ProgressByClientSearch from "@/components/ProgressByClientSearch";
import { Suspense } from "react";
import Tabs, { TabsProps } from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
import { getServerI18n } from "@/i18n";
import DashboardPage from "@/components/DashboardPage";
import { SearchParams } from "@/app/utils/fetchUtil";

export const metadata: Metadata = {
title: "Dashboard",
};

// type Props = {
// test: string
// }
const Dashboard: React.FC = async () => {
type Props = {
} & SearchParams

const Dashboard: React.FC<Props> = async ({
searchParams
}) => {
const { t } = await getServerI18n("dashboard");

return (
<I18nProvider namespaces={["dashboard", "common"]}>
<DashboardPage/>
<Suspense fallback={<DashboardPage.Loading />}>
<DashboardPage
searchParams={searchParams}
/>
</Suspense>
</I18nProvider>
)
};

+ 0
- 26
src/app/(main)/home/CompanyTeamCashFlow/page.tsx 查看文件

@@ -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;

+ 0
- 29
src/app/(main)/home/ProjectCashFlow/page.tsx 查看文件

@@ -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;

+ 0
- 26
src/app/(main)/home/ProjectFinancialSummary/page.tsx 查看文件

@@ -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;

+ 0
- 29
src/app/(main)/home/ProjectStatusByClient/page.tsx 查看文件

@@ -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;

+ 0
- 26
src/app/(main)/home/StaffUtilization/page.tsx 查看文件

@@ -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;

+ 0
- 1
src/app/(main)/settings/user/page.tsx 查看文件

@@ -13,7 +13,6 @@ const User: React.FC = () => {
User
</Typography>
<></>
{/* <CompanyTeamCashFlowComponent /> */}
</I18nProvider>
);
};


+ 1
- 1
src/app/layout.tsx 查看文件

@@ -17,7 +17,7 @@ export default async function RootLayout({
return (
<html lang={lang}>
<body>
<ThemeRegistry>{children}</ThemeRegistry>
<ThemeRegistry lang={lang}>{children}</ThemeRegistry>
</body>
</html>
);


+ 1
- 1
src/app/page.tsx 查看文件

@@ -1,7 +1,7 @@
import { permanentRedirect } from "next/navigation";

const Home: React.FC = async () => {
permanentRedirect("/home");
permanentRedirect("/dashboard");
};

export default Home;

+ 4
- 0
src/app/utils/fetchUtil.ts 查看文件

@@ -3,6 +3,10 @@ import { getServerSession } from "next-auth";
import { headers } from "next/headers";
import { redirect } from "next/navigation";

export type SearchParams = {
searchParams: { [key: string]: string | string[] | undefined };
}

export const serverFetch: typeof fetch = async (input, init) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const session = await getServerSession<any, SessionWithTokens>(authOptions);


+ 0
- 247
src/components/AssignedProjectGrid/AssignedProjectGrid.tsx 查看文件

@@ -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;

+ 0
- 1
src/components/AssignedProjectGrid/index.ts 查看文件

@@ -1 +0,0 @@
export { default } from "./AssignedProjectGrid";

+ 0
- 306
src/components/CompanyTeamCashFlow/CompanyTeamCashFlow.tsx 查看文件

@@ -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:&nbsp;
</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"
>
&lt;
</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"
>
&gt;
</button>
</div>
<div className="inline-block ml-2">
<Label className="text-slate-500 font-medium">
Team:&nbsp;
</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;

+ 0
- 1
src/components/CompanyTeamCashFlow/index.ts 查看文件

@@ -1 +0,0 @@
export { default } from "./CompanyTeamCashFlow";

+ 0
- 240
src/components/CustomCardGrid/CustomCardGrid.tsx 查看文件

@@ -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;

+ 0
- 1
src/components/CustomCardGrid/index.ts 查看文件

@@ -1 +0,0 @@
export { default } from "./CustomCardGrid";

+ 0
- 309
src/components/CustomDatagrid/CustomDatagrid.tsx 查看文件

@@ -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;

+ 0
- 1
src/components/CustomDatagrid/index.ts 查看文件

@@ -1 +0,0 @@
export { default } from "./CustomDatagrid";

+ 0
- 81
src/components/CustomModal/CustomModal.tsx 查看文件

@@ -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;

+ 0
- 1
src/components/CustomModal/index.ts 查看文件

@@ -1 +0,0 @@
export { default } from "./CustomModal";

+ 0
- 379
src/components/CustomSearchForm/CustomSearchForm.tsx 查看文件

@@ -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 />
&nbsp;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 />
&nbsp;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;

+ 0
- 1
src/components/CustomSearchForm/index.ts 查看文件

@@ -1 +0,0 @@
export { default } from "./CustomSearchForm";

src/components/ProgressByClientSearch/ProgressByClientSearchLoading.tsx → src/components/DashboardPage/DashboardLoading.tsx 查看文件

@@ -5,7 +5,7 @@ import Stack from "@mui/material/Stack";
import React from "react";

// Can make this nicer
export const ProgressByClientSearchLoading: React.FC = () => {
export const DashboardLoading: React.FC = () => {
return (
<>
<Card>
@@ -16,14 +16,14 @@ export const ProgressByClientSearchLoading: React.FC = () => {
<Skeleton variant="rounded" height={60} />
<Skeleton
variant="rounded"
height={50}
height={50}
width={100}
sx={{ alignSelf: "flex-end" }}
/>
</Stack>
</CardContent>
</Card>
<Card>
<Card>EditUser
<CardContent>
<Stack spacing={2}>
<Skeleton variant="rounded" height={40} />
@@ -37,4 +37,4 @@ export const ProgressByClientSearchLoading: React.FC = () => {
);
};

export default ProgressByClientSearchLoading;
export default DashboardLoading;

+ 9
- 37
src/components/DashboardPage/DashboardPage.tsx 查看文件

@@ -1,20 +1,10 @@
"use client";
import Grid from "@mui/material/Grid";
import Paper from "@mui/material/Paper";
import { TFunction } from "i18next";
import { useTranslation } from "react-i18next";
import PageTitle from "../PageTitle/PageTitle";
import DashboardTabButton from "./DashboardTabButton";
import { ThemeProvider } from "@mui/material/styles";
import theme from "../../theme";
import Tabs, { TabsProps } from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
import React, { useCallback, useState } from "react";
import { TabsProps } from "@mui/material/Tabs";
import React, { useCallback, useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import ProgressByClient from "./ProgressByClient";
import ProgressByClientSearch from "@/components/ProgressByClientSearch";
import { Suspense } from "react";
import { getSession } from "next-auth/react";

type Props = {
abilities: string[]
@@ -22,36 +12,18 @@ type Props = {
const DashboardPage: React.FC<Props> = ({
abilities
}) => {
const [tabIndex, setTabIndex] = useState(0);
const { t } = useTranslation("dashboard");
const router = useRouter();
window.localStorage.setItem("abilities", JSON.stringify(abilities))

const handleCancel = () => {
router.back();
};
const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
(_e, newValue) => {
setTabIndex(newValue);
},
[],
);
useEffect(()=> {
window.localStorage.setItem("abilities", JSON.stringify(abilities))
})

return (
<ThemeProvider theme={theme}>
<Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
<Tab label="Project Financial Summary" />
{/* <Tab label="Project Cash Flow" />
<Tab label="Project Progress by Client" />
<Tab label="Project Resource Utilization" />
<Tab label="Staff Utilization" /> */}
</Tabs>
{tabIndex === 2 && <ProgressByClient />}
{/* <Grid container height="100vh" style={{ backgroundColor: theme.palette.background.default}}>
<Grid item sm>
<PageTitle BigTitle={"Dashboards"}/>
<DashboardTabButton/>
</Grid>
</Grid> */}
<>

</>
</ThemeProvider>
);
};


+ 0
- 77
src/components/DashboardPage/DashboardTabButton.tsx 查看文件

@@ -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;

+ 25
- 5
src/components/DashboardPage/DashboardWrapper.tsx 查看文件

@@ -1,18 +1,38 @@
import { authOptions } from "@/config/authConfig"
import { getServerSession, Session } from "next-auth"
import DashboardPage from "./DashboardPage"
import { Typography } from "@mui/material"
import { I18nProvider, getServerI18n } from "@/i18n";
import DashboardLoading from "./DashboardLoading";

export type SessionWithAbilities = {
abilities: string[]
} & Session | null

const DashboardWrapper: React.FC = async () => {
const session: SessionWithAbilities = await getServerSession(authOptions)
interface SubComponents {
Loading: typeof DashboardLoading;
}

type Props = {
searchParams: { [key: string]: string | string[] | undefined };
}
const DashboardWrapper: React.FC<Props> & SubComponents = async ({
searchParams
}) => {

const { t } = await getServerI18n("dashboard");
const session: SessionWithAbilities = await getServerSession(authOptions)

return (
<DashboardPage
abilities={session ? session?.abilities : []}
/>
<>
<Typography variant="h4">{t("Dashboard")}</Typography>
<DashboardPage
abilities={session ? session?.abilities : []}
/>
</>
)
}
DashboardWrapper.Loading = DashboardLoading;

export default DashboardWrapper

+ 0
- 493
src/components/DashboardPage/ProgressByClient.tsx 查看文件

@@ -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;

+ 0
- 19
src/components/EnterLeave/EnterLeaveModal.tsx 查看文件

@@ -1,28 +1,9 @@
"use client";

// import { testing } from "@/app/api/timesheets";
import Grid from "@mui/material/Grid";
import Paper from "@mui/material/Paper";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import AssignedProjectGrid from "../AssignedProjectGrid/AssignedProjectGrid";
import PageTitle from "../PageTitle/PageTitle";
import { Suspense } from "react";
import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import { Add } from "@mui/icons-material";
import Link from "next/link";
import { t } from "i18next";
import { Card, Modal, Typography } from "@mui/material";
import CustomModal from "../CustomModal/CustomModal";
import { PROJECT_MODAL_STYLE } from "@/theme/colorConst";
import CustomDatagrid from "../CustomDatagrid/CustomDatagrid";
import { DataGrid } from "@mui/x-data-grid";
import TimesheetInputGrid from "./LeaveInputGrid";
import { BASE_API_URL } from "@/config/api";

// import { fetchLeaves } from "@/app/api/leave";

interface EnterTimesheetModalProps {
isOpen: boolean;
onClose: () => void;


+ 0
- 14
src/components/EnterTimesheet/EnterTimesheetModal.tsx 查看文件

@@ -1,23 +1,9 @@
"use client";

// import { testing } from "@/app/api/timesheets";
import Grid from "@mui/material/Grid";
import Paper from "@mui/material/Paper";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import AssignedProjectGrid from "../AssignedProjectGrid/AssignedProjectGrid";
import PageTitle from "../PageTitle/PageTitle";
import { Suspense } from "react";
import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import { Add } from "@mui/icons-material";
import Link from "next/link";
import { t } from "i18next";
import { Card, Modal, Typography } from "@mui/material";
import CustomModal from "../CustomModal/CustomModal";
import { PROJECT_MODAL_STYLE } from "@/theme/colorConst";
import CustomDatagrid from "../CustomDatagrid/CustomDatagrid";
import { DataGrid } from "@mui/x-data-grid";
import TimesheetInputGrid from "./TimesheetInputGrid";
import { BASE_API_URL } from "@/config/api";



+ 7
- 9
src/components/NavigationContent/NavigationContent.tsx 查看文件

@@ -33,8 +33,6 @@ interface NavigationItem {
}

const NavigationContent: React.FC = () => {
// const abilities = window.localStorage.getItem("abilites")
// console.log(abilities)
const navigationItems: NavigationItem[] = [
{
icon: <Dashboard />,
@@ -245,16 +243,16 @@ const NavigationContent: React.FC = () => {
const pathname = usePathname();

const [openItems, setOpenItems] = React.useState<string[]>([]);
const toggleItem = (path: string) => {
const toggleItem = (label: string) => {
setOpenItems((prevOpenItems) =>
prevOpenItems.includes(path)
? prevOpenItems.filter((item) => item !== path)
: [...prevOpenItems, path],
prevOpenItems.includes(label)
? prevOpenItems.filter((item) => item !== label)
: [...prevOpenItems, label],
);
};

const renderNavigationItem = (item: NavigationItem) => {
const isOpen = openItems.includes(item.path);
const isOpen = openItems.includes(item.label);

return (
<Box
@@ -264,8 +262,8 @@ const NavigationContent: React.FC = () => {
sx={{ textDecoration: "none", color: "inherit" }}
>
<ListItemButton
selected={pathname.includes(item.path)}
onClick={() => item.children && toggleItem(item.path)}
selected={pathname.includes(item.label)}
onClick={() => item.children && toggleItem(item.label)}
>
<ListItemIcon>{item.icon}</ListItemIcon>
<ListItemText primary={t(item.label)} />


+ 0
- 643
src/components/ProgressByClient/ProgressByClient.tsx 查看文件

@@ -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;

+ 0
- 1
src/components/ProgressByClient/index.ts 查看文件

@@ -1 +0,0 @@
export { default } from "./ProgressByClient";

+ 0
- 57
src/components/ProgressByClientSearch/ProgressByClientSearch.tsx 查看文件

@@ -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;

+ 0
- 18
src/components/ProgressByClientSearch/ProgressByClientSearchWrapper.tsx 查看文件

@@ -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;

+ 0
- 1
src/components/ProgressByClientSearch/index.ts 查看文件

@@ -1 +0,0 @@
export { default } from "./ProgressByClientSearchWrapper";

+ 0
- 156
src/components/ProgressCashFlowSearch/ProgressCashFlowSearch.tsx 查看文件

@@ -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;

+ 0
- 40
src/components/ProgressCashFlowSearch/ProgressCashFlowSearchLoading.tsx 查看文件

@@ -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;

+ 0
- 18
src/components/ProgressCashFlowSearch/ProgressCashFlowSearchWrapper.tsx 查看文件

@@ -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;

+ 0
- 1
src/components/ProgressCashFlowSearch/index.ts 查看文件

@@ -1 +0,0 @@
export { default } from "./ProgressCashFlowSearchWrapper";

+ 0
- 638
src/components/ProjectCashFlow/ProjectCashFlow.tsx 查看文件

@@ -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:&nbsp;
</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"
>
&lt;
</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"
>
&gt;
</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;

+ 0
- 1
src/components/ProjectCashFlow/index.ts 查看文件

@@ -1 +0,0 @@
export { default } from "./ProjectCashFlow";

+ 0
- 173
src/components/ProjectFinancialSummary/ProjectFinancialCard.tsx 查看文件

@@ -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;

+ 0
- 465
src/components/ProjectFinancialSummary/ProjectFinancialSummary.tsx 查看文件

@@ -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;

+ 0
- 1
src/components/ProjectFinancialSummary/index.ts 查看文件

@@ -1 +0,0 @@
export { default } from "./ProjectFinancialSummary";

+ 0
- 1203
src/components/StaffUtilization/StaffUtilization.tsx
文件差异内容过多而无法显示
查看文件


+ 0
- 1
src/components/StaffUtilization/index.ts 查看文件

@@ -1 +0,0 @@
export { default } from "./StaffUtilization";

+ 0
- 100
src/components/UserWorkspacePage/ProjectGrid.tsx 查看文件

@@ -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;

+ 0
- 3
src/components/UserWorkspacePage/UserWorkspacePage.tsx 查看文件

@@ -3,7 +3,6 @@ import Grid from "@mui/material/Grid";
import Paper from "@mui/material/Paper";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import AssignedProjectGrid from "../AssignedProjectGrid/AssignedProjectGrid";
import PageTitle from "../PageTitle/PageTitle";
import { Suspense } from "react";
import Button from "@mui/material/Button";
@@ -12,7 +11,6 @@ import { Add } from "@mui/icons-material";
import Link from "next/link";
import { t } from "i18next";
import { Modal } from "@mui/material";
import CustomModal from "../CustomModal/CustomModal";
import EnterTimesheetModal from "../EnterTimesheet/EnterTimesheetModal";
import EnterLeaveModal from "../EnterLeave/EnterLeaveModal";

@@ -72,7 +70,6 @@ const UserWorkspacePage: React.FC = () => {
isOpen={isLeaveModalVisible}
onClose={handleCloseLeaveModal}
/>
<AssignedProjectGrid Title="Assigned Project" />
</Grid>
</Grid>
);


+ 1
- 1
src/routes.ts 查看文件

@@ -1,7 +1,7 @@
export const PRIVATE_ROUTES = [
"/analytics",
"/dashboard",
"/home",
"/dashboard",
"/invoice",
"/projects",
"/tasks",


+ 17
- 1
src/theme/ThemeRegistry.tsx 查看文件

@@ -1,16 +1,32 @@
"use client";
import * as React from "react";
import { ThemeProvider } from "@mui/material/styles";
import { ThemeProvider, createTheme } from "@mui/material/styles";
import CssBaseline from "@mui/material/CssBaseline";
import NextAppDirEmotionCacheProvider from "./EmotionCache";
import theme from "./devias-material-kit";
import { zhHK, enUS } from "@mui/material/locale";


const getLocalizationFromLang = (lang: string) => {
switch (lang) {
case "zh":
return zhHK;
default:
return enUS;
}
};
// Copied from https://github.com/mui/material-ui/blob/master/examples/material-ui-nextjs-ts/src/components/ThemeRegistry/ThemeRegistry.tsx
export default function ThemeRegistry({
children,
lang,
}: {
children: React.ReactNode;
lang: string;
}) {
const themeWithLocale = React.useMemo(
() => createTheme(theme, getLocalizationFromLang(lang)),
[lang],
);
return (
<NextAppDirEmotionCacheProvider options={{ key: "mui" }}>
<ThemeProvider theme={theme}>


+ 18
- 0
src/theme/devias-material-kit/colors.ts 查看文件

@@ -11,7 +11,25 @@ export const neutral = {
900: "#111927",
};

// export const primary = {
// lightest: "#F5F7FF",
// light: "#EBEEFE",
// main: "#6366F1",
// dark: "#4338CA",
// darkest: "#312E81",
// contrastText: "#FFFFFF",
// };

export const primary = {
lightest: "#f9fff5",
light: "#f9feeb",
main: "#8dba00",
dark: "#638a01",
darkest: "#4a5f14",
contrastText: "#FFFFFF",
};

export const secondary = {
lightest: "#F5F7FF",
light: "#EBEEFE",
main: "#6366F1",


+ 10
- 1
src/theme/devias-material-kit/palette.ts 查看文件

@@ -1,6 +1,14 @@
import { common } from "@mui/material/colors";
import { PaletteOptions } from "@mui/material/styles";
import { error, primary, info, neutral, success, warning } from "./colors";
import {
error,
primary,
secondary,
info,
neutral,
success,
warning,
} from "./colors";

const palette = {
action: {
@@ -20,6 +28,7 @@ const palette = {
info,
mode: "light",
primary,
secondary,
success,
text: {
primary: neutral[900],


正在加载...
取消
保存