From f1c206b373efd15341f2bbbc9290015114a8a62e Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Thu, 18 Apr 2024 12:08:23 +0800 Subject: [PATCH 1/4] update wording & fix bugs --- .../dashboard/ProjectFinancialSummary/page.tsx | 2 +- src/components/CustomerDetail/CustomerDetail.tsx | 4 +++- src/components/DashboardPage/DashboardPage.tsx | 4 ++-- .../DashboardPage/DashboardTabButton.tsx | 16 ++++++++-------- .../DashboardPage/ProgressByClient.tsx | 6 +++--- .../NavigationContent/NavigationContent.tsx | 6 +++--- .../ProgressByClient/ProgressByClient.tsx | 6 +++--- src/components/ProgressByTeam/ProgressByTeam.tsx | 6 +++--- .../ProjectCashFlow/ProjectCashFlow.tsx | 12 ++++++------ .../SubsidiaryDetail/SubsidiaryDetail.tsx | 3 +-- 10 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/app/(main)/dashboard/ProjectFinancialSummary/page.tsx b/src/app/(main)/dashboard/ProjectFinancialSummary/page.tsx index 3549f77..fcfd256 100644 --- a/src/app/(main)/dashboard/ProjectFinancialSummary/page.tsx +++ b/src/app/(main)/dashboard/ProjectFinancialSummary/page.tsx @@ -17,7 +17,7 @@ const ProjectFinancialSummary: React.FC = () => { return ( - Project Financial Summary + Financial Summary diff --git a/src/components/CustomerDetail/CustomerDetail.tsx b/src/components/CustomerDetail/CustomerDetail.tsx index 47b47c0..2c81e17 100644 --- a/src/components/CustomerDetail/CustomerDetail.tsx +++ b/src/components/CustomerDetail/CustomerDetail.tsx @@ -75,7 +75,9 @@ const CustomerDetail: React.FC = ({ const customer = await fetchCustomer(parseInt(id)) - if (customer !== null && Object.keys(customer).length > 0) { + console.log(customer) + + if (customer !== null && Object.keys(customer).length > 0 && !Object.values(customer.customer).every(x => x === null)) { const tempCustomerInput = { id: customer.customer.id, code: customer.customer.code ?? "", diff --git a/src/components/DashboardPage/DashboardPage.tsx b/src/components/DashboardPage/DashboardPage.tsx index d3d70c7..7fb107c 100644 --- a/src/components/DashboardPage/DashboardPage.tsx +++ b/src/components/DashboardPage/DashboardPage.tsx @@ -31,9 +31,9 @@ const DashboardPage: React.FC = () => { return ( - + - + diff --git a/src/components/DashboardPage/DashboardTabButton.tsx b/src/components/DashboardPage/DashboardTabButton.tsx index 1f95098..b822605 100644 --- a/src/components/DashboardPage/DashboardTabButton.tsx +++ b/src/components/DashboardPage/DashboardTabButton.tsx @@ -16,7 +16,7 @@ const DashboardTabButton: React.FC = () => { const renderContent = () => { switch (activeTab) { case "financialSummary": - return
Project Financial Summary
; + return
Financial Summary
; case "cashFlow": return
Project Cash Flow
; case "progressByClient": @@ -26,7 +26,7 @@ const DashboardTabButton: React.FC = () => { case "staffUtilization": return
Staff Utilization
; default: - return
Project Financial Summary
; + return
Financial Summary
; } }; const [tabIndex, setTabIndex] = useState(0); @@ -40,16 +40,16 @@ const DashboardTabButton: React.FC = () => { // //
// {activeTab !== 'financialSummary' ? - // : - // + // : + // // } // {activeTab !== 'cashFlow' ? // : // // } // {activeTab !== 'progressByClient' ? - // : - // + // : + // // } // {activeTab !== 'resourceUtilization' ? // : @@ -65,9 +65,9 @@ const DashboardTabButton: React.FC = () => { //
//
- + - + diff --git a/src/components/DashboardPage/ProgressByClient.tsx b/src/components/DashboardPage/ProgressByClient.tsx index 6475c03..2392c97 100644 --- a/src/components/DashboardPage/ProgressByClient.tsx +++ b/src/components/DashboardPage/ProgressByClient.tsx @@ -298,7 +298,7 @@ const ProgressByClient: React.FC = () => { const series: ApexAxisChartSeries | ApexNonAxisChartSeries = [ { - name: "Current Stage Completion Percentage", + name: "Project Resource Consumption Percentage", data: [80, 55, 40, 65, 70], }, ]; @@ -372,7 +372,7 @@ const ProgressByClient: React.FC = () => { }, }, title: { - text: "Current Stage Completion Percentage", + text: "Project Resource Consumption Percentage", align: "center", }, grid: { @@ -426,7 +426,7 @@ const ProgressByClient: React.FC = () => {
- +
, - label: "Project Financial Summary", + label: "Financial Summary", path: "/dashboard/ProjectFinancialSummary", }, { @@ -84,12 +84,12 @@ const navigationItems: NavigationItem[] = [ children: [ { icon: , - label: "ClaimApproval", + label: "Claim Approval", path: "/staffReimbursement/ClaimApproval", }, { icon: , - label: "ClaimSummary", + label: "Claim Summary", path: "/staffReimbursement/ClaimSummary", }, ], diff --git a/src/components/ProgressByClient/ProgressByClient.tsx b/src/components/ProgressByClient/ProgressByClient.tsx index 939f5d3..6815f73 100644 --- a/src/components/ProgressByClient/ProgressByClient.tsx +++ b/src/components/ProgressByClient/ProgressByClient.tsx @@ -333,7 +333,7 @@ const ProgressByClient: React.FC = () => { const series: ApexAxisChartSeries | ApexNonAxisChartSeries = [ { - name: "Current Stage Completion Percentage", + name: "Project Resource Consumption Percentage", data: [80, 55, 40, 65, 70], }, ]; @@ -426,7 +426,7 @@ const ProgressByClient: React.FC = () => { }, }, title: { - text: "Current Stage Completion Percentage", + text: "Project Resource Consumption Percentage", align: "center", }, grid: { @@ -503,7 +503,7 @@ const ProgressByClient: React.FC = () => {
- +
{ const series: ApexAxisChartSeries | ApexNonAxisChartSeries = [ { - name: "Current Stage Completion Percentage", + name: "Project Resource Consumption Percentage", data: [80, 55, 40, 65, 70], }, ]; @@ -403,7 +403,7 @@ const ProgressByTeam: React.FC = () => { }, }, title: { - text: "Current Stage Completion Percentage", + text: "Project Resource Consumption Percentage", align: "center", }, grid: { @@ -480,7 +480,7 @@ const ProgressByTeam: React.FC = () => {
- +
{ className="text-sm font-medium ml-5 mt-2" style={{ color: "#898d8d" }} > - Total A. Receivable + Total Invoiced Amount
{ className="text-sm font-medium ml-5" style={{ color: "#898d8d" }} > - Amount Received + Total Received Amount
{ className="text-sm font-medium ml-5" style={{ color: "#898d8d" }} > - Remaining Balance + Accounts Receivable
{ className="text-sm font-medium ml-5 mt-2" style={{ color: "#898d8d" }} > - Budgeted Expenditure + Total Budget
{ className="text-sm font-medium ml-5" style={{ color: "#898d8d" }} > - Actual Expenditure + Total Cumulative Expenditure
{ className="text-sm font-medium ml-5" style={{ color: "#898d8d" }} > - Remaining Balance + Accounts Receivable
= ({ const subsidiary = await fetchSubsidiary(parseInt(id)) - if (subsidiary !== null && Object.keys(subsidiary).length > 0) { - console.log(subsidiary) + if (subsidiary !== null && Object.keys(subsidiary).length > 0 && !Object.values(subsidiary.subsidiary).every(x => x === null)) { const tempSubsidiaryInput = { id: subsidiary.subsidiary.id, code: subsidiary.subsidiary.code ?? "", From b612d7fbd11d185e485cd5b328a050b8a8583d11 Mon Sep 17 00:00:00 2001 From: leoho2fi Date: Thu, 18 Apr 2024 17:10:38 +0800 Subject: [PATCH 2/4] add report page --- package.json | 4 +- public/temp/AR01_Late Start Report.xlsx | Bin 0 -> 12581 bytes .../analytics/CostandExpenseReport/page.tsx | 24 +++ src/app/(main)/analytics/DelayReport/page.tsx | 24 +++ .../(main)/analytics/LateStartReport/page.tsx | 24 +++ .../ProjectCompletionReport/page.tsx | 24 +++ .../ProjectCompletionReportWO/page.tsx | 24 +++ .../ResourceOvercomsumptionReport/page.tsx | 24 +++ src/app/(main)/analytics/page.tsx | 4 +- src/app/api/report/index.ts | 44 ++++ .../LateStartReport/LateStartReport.tsx | 17 ++ src/components/LateStartReport/index.ts | 2 + .../DownloadReportButton.tsx | 40 ++++ .../LateStartReportGen/LateStartReportGen.tsx | 44 ++++ .../LateStartReportGenLoading.tsx | 41 ++++ .../LateStartReportGenWrapper.tsx | 19 ++ src/components/LateStartReportGen/index.ts | 2 + src/components/ReportSearchBox/SearchBox.tsx | 201 ++++++++++++++++++ src/components/ReportSearchBox/index.ts | 3 + src/components/utils/downloadExcel.ts | 9 + src/components/utils/generateFakeData.ts | 40 ++++ 21 files changed, 612 insertions(+), 2 deletions(-) create mode 100644 public/temp/AR01_Late Start Report.xlsx create mode 100644 src/app/(main)/analytics/CostandExpenseReport/page.tsx create mode 100644 src/app/(main)/analytics/DelayReport/page.tsx create mode 100644 src/app/(main)/analytics/LateStartReport/page.tsx create mode 100644 src/app/(main)/analytics/ProjectCompletionReport/page.tsx create mode 100644 src/app/(main)/analytics/ProjectCompletionReportWO/page.tsx create mode 100644 src/app/(main)/analytics/ResourceOvercomsumptionReport/page.tsx create mode 100644 src/app/api/report/index.ts create mode 100644 src/components/LateStartReport/LateStartReport.tsx create mode 100644 src/components/LateStartReport/index.ts create mode 100644 src/components/LateStartReportGen/DownloadReportButton.tsx create mode 100644 src/components/LateStartReportGen/LateStartReportGen.tsx create mode 100644 src/components/LateStartReportGen/LateStartReportGenLoading.tsx create mode 100644 src/components/LateStartReportGen/LateStartReportGenWrapper.tsx create mode 100644 src/components/LateStartReportGen/index.ts create mode 100644 src/components/ReportSearchBox/SearchBox.tsx create mode 100644 src/components/ReportSearchBox/index.ts create mode 100644 src/components/utils/downloadExcel.ts create mode 100644 src/components/utils/generateFakeData.ts diff --git a/package.json b/package.json index 5db5867..2578a2f 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@emotion/cache": "^11.11.0", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", + "@faker-js/faker": "^8.4.1", "@fontsource/inter": "^5.0.16", "@fontsource/plus-jakarta-sans": "^5.0.18", "@mui/icons-material": "^5.15.0", @@ -38,7 +39,8 @@ "react-select": "^5.8.0", "reactstrap": "^9.2.2", "styled-components": "^6.1.8", - "sweetalert2": "^11.10.3" + "sweetalert2": "^11.10.3", + "xlsx-js-style": "^1.2.0" }, "devDependencies": { "@types/lodash": "^4.14.202", diff --git a/public/temp/AR01_Late Start Report.xlsx b/public/temp/AR01_Late Start Report.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..03d6b6b9e3c5bfedd5dfc213cb61cad67522123d GIT binary patch literal 12581 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�nPt}o2aA~u>h2kp?vM0+ zmm`mStD^LS8LaIX=U&;--k{EmLe_3g1eId#c7bdznnU*|YzbIvNRV?xbePmP(U7WACl)pwjXhJbf+3$d>I?(86k^n`fs7tY=l z)w2ycS$+<|ZI@7*oGh)EW1Bqqey6)hJiN5Ic>T2+SI(MvNo9$Bx$BM*fd^WoNz59o z**5-wjCALp&bhHHfGZEN7P&78bfLfeDBaIjY!=*rcmmwm_UZROUZ zBE7ZN6Fy%wgcuSaLgBN#v~(ix)&7v4cISynNX>Uo$_{Rz{@vY|w>OqTqN!<@paS6S zt-#;9!aT8hPxQK9|It$B@8KXWIt@P@=QTCC#Vkq^rw56W`UWvk%-4;aR)Q4CRr7sRn%PQ}uDS zXjGto?v9viJ}m2Y50Y3-+Q8n^={xn=ZB?PWYskHE_`(A$RqIAlr5Mb_v?sPECvJ+PASH@dd z!(R4-$d8O>vL^~rP;^{m`x>p^T_88b;Fg`1@&Mzutl*gYZ9So|C%C~PftnGQ2O z!qGOftYJBbO`>S3WEh9-VXtNcL@2(lfhrw4FT4ZmR}cc9#KwgndO6n`PrR?qQ5G95 zsS@9I;p)Sy1wZZU@?z{Wuudg6C)Wig^Y@+}&PX?w=nvee;VL9S$u&cmS!Eo>9W5@qEBiKao6!`t^%sJ@s^J#Hu51dp(Zd`EWFn6uM$1J)k*%IynoM~yZ`#8 zgoJNvNyPtFLPLA||LNeH^8I^DkL$9XW<-2zHfs&o{xl~kh^EC^Q9@S~OsuwSbNW5y zNQ~SwduN)FHE@Wo1?%3BD@{wMIUEAC_GxkW*LdcR$ zDUF?&HImI9Ys3o{zt2K39eSSV4*MY9jd3e)v$f}ytvq1={$kmK09s=(95aIrd!J$E zlakHZ0m@d;fH&zp`7m z^*k%#NkglIY1tW+2`elte|h^x|I+MtAark>$3M4v|L05o=llVE`( zoeSU0<-b}|e;53{G4D^&kvA*+txfND;lK9#{3!|m)WG~9{QuMa^E=M(O(1_FZ3F+m zAMv-Akl#^$FR}iK@&oA)l-~=kzXSZvZ~qAp`_`KB7qyVH2)?3Z;!4Z4gRKs008i9Cx27peY#(F{|}HUU@`yz literal 0 HcmV?d00001 diff --git a/src/app/(main)/analytics/CostandExpenseReport/page.tsx b/src/app/(main)/analytics/CostandExpenseReport/page.tsx new file mode 100644 index 0000000..2f046c3 --- /dev/null +++ b/src/app/(main)/analytics/CostandExpenseReport/page.tsx @@ -0,0 +1,24 @@ +//src\app\(main)\analytics\LateStartReport\page.tsx +import { Metadata } from "next"; +import { I18nProvider } from "@/i18n"; +import Typography from "@mui/material/Typography"; +import LateStartReportComponent from "@/components/LateStartReport"; + +export const metadata: Metadata = { + title: "Project Status by Client", +}; + +const ProjectLateReport: React.FC = () => { + return ( + + + Cost and Expense Report + + {/* }> + + */} + + + ); +}; +export default ProjectLateReport; diff --git a/src/app/(main)/analytics/DelayReport/page.tsx b/src/app/(main)/analytics/DelayReport/page.tsx new file mode 100644 index 0000000..154fd58 --- /dev/null +++ b/src/app/(main)/analytics/DelayReport/page.tsx @@ -0,0 +1,24 @@ +//src\app\(main)\analytics\LateStartReport\page.tsx +import { Metadata } from "next"; +import { I18nProvider } from "@/i18n"; +import Typography from "@mui/material/Typography"; +import LateStartReportComponent from "@/components/LateStartReport"; + +export const metadata: Metadata = { + title: "Project Status by Client", +}; + +const ProjectLateReport: React.FC = () => { + return ( + + + Delay Report + + {/* }> + + */} + + + ); +}; +export default ProjectLateReport; diff --git a/src/app/(main)/analytics/LateStartReport/page.tsx b/src/app/(main)/analytics/LateStartReport/page.tsx new file mode 100644 index 0000000..7f4ade5 --- /dev/null +++ b/src/app/(main)/analytics/LateStartReport/page.tsx @@ -0,0 +1,24 @@ +//src\app\(main)\analytics\LateStartReport\page.tsx +import { Metadata } from "next"; +import { I18nProvider } from "@/i18n"; +import Typography from "@mui/material/Typography"; +import LateStartReportComponent from "@/components/LateStartReport"; + +export const metadata: Metadata = { + title: "Project Status by Client", +}; + +const ProjectLateReport: React.FC = () => { + return ( + + + Late Start Report + + {/* }> + + */} + + + ); +}; +export default ProjectLateReport; diff --git a/src/app/(main)/analytics/ProjectCompletionReport/page.tsx b/src/app/(main)/analytics/ProjectCompletionReport/page.tsx new file mode 100644 index 0000000..c938efc --- /dev/null +++ b/src/app/(main)/analytics/ProjectCompletionReport/page.tsx @@ -0,0 +1,24 @@ +//src\app\(main)\analytics\LateStartReport\page.tsx +import { Metadata } from "next"; +import { I18nProvider } from "@/i18n"; +import Typography from "@mui/material/Typography"; +import LateStartReportComponent from "@/components/LateStartReport"; + +export const metadata: Metadata = { + title: "Project Status by Client", +}; + +const ProjectLateReport: React.FC = () => { + return ( + + + Project Completion Report + + {/* }> + + */} + + + ); +}; +export default ProjectLateReport; diff --git a/src/app/(main)/analytics/ProjectCompletionReportWO/page.tsx b/src/app/(main)/analytics/ProjectCompletionReportWO/page.tsx new file mode 100644 index 0000000..5b914ad --- /dev/null +++ b/src/app/(main)/analytics/ProjectCompletionReportWO/page.tsx @@ -0,0 +1,24 @@ +//src\app\(main)\analytics\LateStartReport\page.tsx +import { Metadata } from "next"; +import { I18nProvider } from "@/i18n"; +import Typography from "@mui/material/Typography"; +import LateStartReportComponent from "@/components/LateStartReport"; + +export const metadata: Metadata = { + title: "Project Status by Client", +}; + +const ProjectLateReport: React.FC = () => { + return ( + + + Project Completion Report with Outstanding Un-billed Hours + + {/* }> + + */} + + + ); +}; +export default ProjectLateReport; diff --git a/src/app/(main)/analytics/ResourceOvercomsumptionReport/page.tsx b/src/app/(main)/analytics/ResourceOvercomsumptionReport/page.tsx new file mode 100644 index 0000000..4f5c75a --- /dev/null +++ b/src/app/(main)/analytics/ResourceOvercomsumptionReport/page.tsx @@ -0,0 +1,24 @@ +//src\app\(main)\analytics\LateStartReport\page.tsx +import { Metadata } from "next"; +import { I18nProvider } from "@/i18n"; +import Typography from "@mui/material/Typography"; +import LateStartReportComponent from "@/components/LateStartReport"; + +export const metadata: Metadata = { + title: "Project Status by Client", +}; + +const ProjectLateReport: React.FC = () => { + return ( + + + Resource Overconsumption Report + + {/* }> + + */} + + + ); +}; +export default ProjectLateReport; diff --git a/src/app/(main)/analytics/page.tsx b/src/app/(main)/analytics/page.tsx index 9ca9a24..d20fa34 100644 --- a/src/app/(main)/analytics/page.tsx +++ b/src/app/(main)/analytics/page.tsx @@ -1,3 +1,4 @@ +//src\app\(main)\analytics\page.tsx import { Metadata } from "next"; export const metadata: Metadata = { @@ -5,7 +6,8 @@ export const metadata: Metadata = { }; const Analytics: React.FC = async () => { - return "Analytics"; + //return "Analytics"; + return
Analytics
; }; export default Analytics; diff --git a/src/app/api/report/index.ts b/src/app/api/report/index.ts new file mode 100644 index 0000000..588692c --- /dev/null +++ b/src/app/api/report/index.ts @@ -0,0 +1,44 @@ +//src\app\api\report\index.ts +import { cache } from "react"; + +export interface LateStart { + id: number; + projectCode: string; + projectName: string; + team: string; + teamLeader: string; + startDate: string; + startDateFrom: string; + startDateTo: string; + targetEndDate: string; + client: string; + subsidiary: string; + nextstage: string; + nextstageenddate: string; +} + +export const preloadProjects = () => { + fetchProjectsCashFlow(); +}; + +export const fetchProjectsCashFlow = cache(async () => { + return mockProjects; +}); + +const mockProjects: LateStart[] = [ + { + id: 1, + projectCode: "CUST-001", + projectName: "Client A", + team: "N/A", + teamLeader: "N/A", + startDate: "1/2/2024", + startDateFrom: "1/2/2024", + startDateTo: "1/2/2024", + targetEndDate: "30/3/2024", + client: "ss", + subsidiary: "sus", + nextstage:"s1", + nextstageenddate:"30/2/2024", + }, +]; diff --git a/src/components/LateStartReport/LateStartReport.tsx b/src/components/LateStartReport/LateStartReport.tsx new file mode 100644 index 0000000..85df0b9 --- /dev/null +++ b/src/components/LateStartReport/LateStartReport.tsx @@ -0,0 +1,17 @@ +//src\components\LateStartReport\LateStartReport.tsx +"use client"; +import * as React from "react"; +import "../../app/global.css"; +import { Suspense } from "react"; +import LateStartReportGen from "@/components/LateStartReportGen"; + +const LateStartReport: React.FC = () => { + + return ( + }> + + + ); +}; + +export default LateStartReport; \ No newline at end of file diff --git a/src/components/LateStartReport/index.ts b/src/components/LateStartReport/index.ts new file mode 100644 index 0000000..bb8ef69 --- /dev/null +++ b/src/components/LateStartReport/index.ts @@ -0,0 +1,2 @@ +//src\components\LateStartReport\index.ts +export { default } from "./LateStartReport"; diff --git a/src/components/LateStartReportGen/DownloadReportButton.tsx b/src/components/LateStartReportGen/DownloadReportButton.tsx new file mode 100644 index 0000000..b838fba --- /dev/null +++ b/src/components/LateStartReportGen/DownloadReportButton.tsx @@ -0,0 +1,40 @@ +// DownloadReportButton.tsx +// import React, { useState } from 'react'; +// import { generateFakeData } from '../utils/generateFakeData'; +// import { downloadExcel } from '../utils/downloadExcel'; + +// export const DownloadReportButton: React.FC = () => { +// const [isLoading, setIsLoading] = useState(false); + +// const handleDownload = async () => { +// setIsLoading(true); +// const data = generateFakeData(10); +// downloadExcel(data); +// setIsLoading(false); +// }; + +// return ( +// +// ); +// }; + +import React from 'react'; + +export const DownloadReportButton: React.FC = () => { + const handleDownload = () => { + const link = document.createElement('a'); + link.href = '/temp/AR01_Late Start Report.xlsx'; // Adjust the path as necessary + link.download = 'AR01_Late Start Report.xlsx'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; + + return ( + + ); +}; \ No newline at end of file diff --git a/src/components/LateStartReportGen/LateStartReportGen.tsx b/src/components/LateStartReportGen/LateStartReportGen.tsx new file mode 100644 index 0000000..8ebfae2 --- /dev/null +++ b/src/components/LateStartReportGen/LateStartReportGen.tsx @@ -0,0 +1,44 @@ +//src\components\LateStartReportGen\LateStartReportGen.tsx +"use client"; +import React, { useMemo, useState } from "react"; +import SearchBox, { Criterion } from "../SearchBox"; +import { useTranslation } from "react-i18next"; +import { CashFlow } from "@/app/api/cashflow"; +import { DownloadReportButton } from './DownloadReportButton'; +interface Props { + projects: CashFlow[]; +} +type SearchQuery = Partial>; +type SearchParamNames = keyof SearchQuery; + +const ProgressByClientSearch: React.FC = ({ projects }) => { + const { t } = useTranslation("projects"); + + const searchCriteria: Criterion[] = useMemo( + () => [ + { label: "Team", paramName: "team", type: "text" }, + { label: "Client", paramName: "client", type: "text" }, + { + label: "Remained Date From", + label2: "Remained Date To", + paramName: "targetEndDate", + type: "dateRange", + }, + ], + [t], + ); + + return ( + <> + { + console.log(query); + }} + /> + + + ); +}; + +export default ProgressByClientSearch; diff --git a/src/components/LateStartReportGen/LateStartReportGenLoading.tsx b/src/components/LateStartReportGen/LateStartReportGenLoading.tsx new file mode 100644 index 0000000..93143ce --- /dev/null +++ b/src/components/LateStartReportGen/LateStartReportGenLoading.tsx @@ -0,0 +1,41 @@ +//src\components\LateStartReportGen\LateStartReportGenLoading.tsx +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 LateStartReportGenLoading: React.FC = () => { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default LateStartReportGenLoading; diff --git a/src/components/LateStartReportGen/LateStartReportGenWrapper.tsx b/src/components/LateStartReportGen/LateStartReportGenWrapper.tsx new file mode 100644 index 0000000..5ba8325 --- /dev/null +++ b/src/components/LateStartReportGen/LateStartReportGenWrapper.tsx @@ -0,0 +1,19 @@ +//src\components\LateStartReportGen\LateStartReportGenWrapper.tsx +import { fetchProjectsCashFlow } from "@/app/api/cashflow"; +import React from "react"; +import LateStartReportGen from "./LateStartReportGen"; +import LateStartReportGenLoading from "./LateStartReportGenLoading"; + +interface SubComponents { + Loading: typeof LateStartReportGenLoading; +} + +const LateStartReportGenWrapper: React.FC & SubComponents = async () => { + const clentprojects = await fetchProjectsCashFlow(); + + return ; +}; + +LateStartReportGenWrapper.Loading = LateStartReportGenLoading; + +export default LateStartReportGenWrapper; \ No newline at end of file diff --git a/src/components/LateStartReportGen/index.ts b/src/components/LateStartReportGen/index.ts new file mode 100644 index 0000000..55e0f5d --- /dev/null +++ b/src/components/LateStartReportGen/index.ts @@ -0,0 +1,2 @@ +//src\components\LateStartReportGen\index.ts +export { default } from "./LateStartReportGenWrapper"; diff --git a/src/components/ReportSearchBox/SearchBox.tsx b/src/components/ReportSearchBox/SearchBox.tsx new file mode 100644 index 0000000..fe058f7 --- /dev/null +++ b/src/components/ReportSearchBox/SearchBox.tsx @@ -0,0 +1,201 @@ +"use client"; + +import Grid from "@mui/material/Grid"; +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import Typography from "@mui/material/Typography"; +import React, { useCallback, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import TextField from "@mui/material/TextField"; +import FormControl from "@mui/material/FormControl"; +import InputLabel from "@mui/material/InputLabel"; +import Select, { SelectChangeEvent } from "@mui/material/Select"; +import MenuItem from "@mui/material/MenuItem"; +import CardActions from "@mui/material/CardActions"; +import Button from "@mui/material/Button"; +import RestartAlt from "@mui/icons-material/RestartAlt"; +import Search from "@mui/icons-material/Search"; +import dayjs from "dayjs"; +import "dayjs/locale/zh-hk"; +import { DatePicker } from "@mui/x-date-pickers/DatePicker"; +import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; +import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; +import { Box } from "@mui/material"; + +interface BaseCriterion { + label: string; + label2?: string; + paramName: T; + paramName2?: T; +} + +interface TextCriterion extends BaseCriterion { + type: "text"; +} + +interface SelectCriterion extends BaseCriterion { + type: "select"; + options: string[]; +} + +interface DateRangeCriterion extends BaseCriterion { + type: "dateRange"; +} + +export type Criterion = + | TextCriterion + | SelectCriterion + | DateRangeCriterion; + +interface Props { + criteria: Criterion[]; + onSearch: (inputs: Record) => void; + onReset?: () => void; +} + +function SearchBox({ + criteria, + onSearch, + onReset, +}: Props) { + const { t } = useTranslation("common"); + const defaultInputs = useMemo( + () => + criteria.reduce>( + (acc, c) => { + return { ...acc, [c.paramName]: c.type === "select" ? "All" : "" }; + }, + {} as Record, + ), + [criteria], + ); + const [inputs, setInputs] = useState(defaultInputs); + + const makeInputChangeHandler = useCallback( + (paramName: T): React.ChangeEventHandler => { + return (e) => { + setInputs((i) => ({ ...i, [paramName]: e.target.value })); + }; + }, + [], + ); + + const makeSelectChangeHandler = useCallback((paramName: T) => { + return (e: SelectChangeEvent) => { + setInputs((i) => ({ ...i, [paramName]: e.target.value })); + }; + }, []); + + const makeDateChangeHandler = useCallback((paramName: T) => { + return (e: any) => { + setInputs((i) => ({ ...i, [paramName]: dayjs(e).format("YYYY-MM-DD") })); + }; + }, []); + + const makeDateToChangeHandler = useCallback((paramName: T) => { + return (e: any) => { + setInputs((i) => ({ + ...i, + [paramName + "To"]: dayjs(e).format("YYYY-MM-DD"), + })); + }; + }, []); + + const handleReset = () => { + setInputs(defaultInputs); + onReset?.(); + }; + + const handleSearch = () => { + onSearch(inputs); + }; + + return ( + + + {t("Search Criteria")} + + {criteria.map((c) => { + return ( + + {c.type === "text" && ( + + )} + {c.type === "select" && ( + + {c.label} + + + )} + {c.type === "dateRange" && ( + + + + + + + {"-"} + + + + + + + )} + + ); + })} + + + + + + + + ); +} + +export default SearchBox; diff --git a/src/components/ReportSearchBox/index.ts b/src/components/ReportSearchBox/index.ts new file mode 100644 index 0000000..37a0659 --- /dev/null +++ b/src/components/ReportSearchBox/index.ts @@ -0,0 +1,3 @@ +//src\components\SearchBox\index.ts +export { default } from "./SearchBox"; +export type { Criterion } from "./SearchBox"; diff --git a/src/components/utils/downloadExcel.ts b/src/components/utils/downloadExcel.ts new file mode 100644 index 0000000..825ccb3 --- /dev/null +++ b/src/components/utils/downloadExcel.ts @@ -0,0 +1,9 @@ +// downloadExcel.ts +import * as XLSX from 'xlsx-js-style'; + +export const downloadExcel = (data: any[]) => { + const worksheet = XLSX.utils.json_to_sheet(data); + const workbook = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(workbook, worksheet, 'Report'); + XLSX.writeFile(workbook, 'Report.xlsx'); +}; diff --git a/src/components/utils/generateFakeData.ts b/src/components/utils/generateFakeData.ts new file mode 100644 index 0000000..2b2df45 --- /dev/null +++ b/src/components/utils/generateFakeData.ts @@ -0,0 +1,40 @@ +// generateFakeData.ts +import { faker } from '@faker-js/faker'; + +interface ProjectData { + id: number; + projectCode: string; + projectName: string; + team: string; + teamLeader: string; + startDate: string; + startDateFrom: string; + startDateTo: string; + targetEndDate: string; + client: string; + subsidiary: string; + nextstage: string; + nextstageenddate: string; +} + +export const generateFakeData = (numEntries: number): ProjectData[] => { + const data: ProjectData[] = []; + for (let i = 0; i < numEntries; i++) { + data.push({ + id: i + 1, + projectCode: faker.datatype.uuid(), + projectName: faker.commerce.productName(), + team: faker.commerce.department(), + teamLeader: faker.name.fullName(), // Corrected from findName to fullName + startDate: faker.date.recent(90).toISOString().split('T')[0], + startDateFrom: faker.date.past(1).toISOString().split('T')[0], + startDateTo: faker.date.future(1).toISOString().split('T')[0], + targetEndDate: faker.date.future(1).toISOString().split('T')[0], + client: faker.company.name(), // Corrected from companyName to name + subsidiary: faker.company.name(), // Corrected from companyName to name + nextstage: "Design", + nextstageenddate: faker.date.future(2).toISOString().split('T')[0], + }); + } + return data; +}; From f942c91a361d7d7a57c4097a32ffba18e32d6a76 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Thu, 18 Apr 2024 18:03:59 +0800 Subject: [PATCH 3/4] Update customer, subsidiary, staff --- .../(main)/settings/customer/create/page.tsx | 2 +- src/app/api/staff/actions.ts | 2 +- src/components/CreateStaff/CreateStaff.tsx | 30 +++++++-------- .../CustomInputForm/CustomInputForm.tsx | 2 +- src/components/CustomerDetail/ContactInfo.tsx | 2 +- .../CustomerDetail/CustomerDetail.tsx | 4 +- .../CustomerDetail/CustomerInfo.tsx | 2 +- src/components/EditStaff/EditStaff.tsx | 37 ++++++++++--------- .../SubsidiaryDetail/ContactInfo.tsx | 2 +- .../SubsidiaryDetail/SubsidiaryDetail.tsx | 2 +- src/i18n/en/breadcrumb.json | 4 +- src/i18n/en/subsidiary.json | 4 +- 12 files changed, 47 insertions(+), 46 deletions(-) diff --git a/src/app/(main)/settings/customer/create/page.tsx b/src/app/(main)/settings/customer/create/page.tsx index c4f13b4..e0dc0e0 100644 --- a/src/app/(main)/settings/customer/create/page.tsx +++ b/src/app/(main)/settings/customer/create/page.tsx @@ -6,7 +6,7 @@ import Typography from "@mui/material/Typography"; import { Metadata } from "next"; export const metadata: Metadata = { - title: "Create Customer", + title: "Create Client", }; const CreateCustomer: React.FC = async () => { diff --git a/src/app/api/staff/actions.ts b/src/app/api/staff/actions.ts index 8eb5ff3..1098508 100644 --- a/src/app/api/staff/actions.ts +++ b/src/app/api/staff/actions.ts @@ -19,7 +19,7 @@ export interface CreateStaffInputs { companyId: number; gradeId: number; teamId: number; - salaryEffId: number; + salaryId: number; email: string; phone1: string; phone2: string; diff --git a/src/components/CreateStaff/CreateStaff.tsx b/src/components/CreateStaff/CreateStaff.tsx index 0aed5ab..35b8ded 100644 --- a/src/components/CreateStaff/CreateStaff.tsx +++ b/src/components/CreateStaff/CreateStaff.tsx @@ -179,7 +179,7 @@ const CreateStaff: React.FC = ({ Title }) => { label: t("Team"), type: "combo-Obj", options: teamCombo, - required: true, + required: false, }, { id: "departmentId", @@ -193,14 +193,14 @@ const CreateStaff: React.FC = ({ Title }) => { label: t("Grade"), type: "combo-Obj", options: gradeCombo, - required: true, + required: false, }, { id: "skillSetId", label: t("Skillset"), type: "combo-Obj", options: skillCombo, - required: true, + required: false, }, { id: "currentPositionId", @@ -210,19 +210,19 @@ const CreateStaff: React.FC = ({ Title }) => { required: true, }, { - id: "salaryEffId", + id: "salaryId", label: t("Salary Point"), type: "combo-Obj", options: salaryCombo, required: true, }, - { - id: "hourlyRate", - label: t("Hourly Rate"), - type: "numeric-testing", - value: "", - required: true, - }, + // { + // id: "hourlyRate", + // label: t("Hourly Rate"), + // type: "numeric-testing", + // value: "", + // required: false, + // }, { id: "employType", label: t("Employ Type"), @@ -245,7 +245,7 @@ const CreateStaff: React.FC = ({ Title }) => { label: t("Phone1"), type: "text", value: "", - pattern: "^\\d{8}$", + // pattern: "^\\d{8}$", message: t("input correct phone no."), required: true, }, @@ -254,9 +254,9 @@ const CreateStaff: React.FC = ({ Title }) => { label: t("Phone2"), type: "text", value: "", - pattern: "^\\d{8}$", + // pattern: "^\\d{8}$", message: t("input correct phone no."), - required: true, + required: false, }, ], [ @@ -272,7 +272,7 @@ const CreateStaff: React.FC = ({ Title }) => { label: t("Emergency Contact Phone"), type: "text", value: "", - pattern: "^\\d{8}$", + // pattern: "^\\d{8}$", message: t("input correct phone no."), required: true, }, diff --git a/src/components/CustomInputForm/CustomInputForm.tsx b/src/components/CustomInputForm/CustomInputForm.tsx index ffcaa7a..dc7f269 100644 --- a/src/components/CustomInputForm/CustomInputForm.tsx +++ b/src/components/CustomInputForm/CustomInputForm.tsx @@ -274,7 +274,7 @@ const CustomInputForm: React.FC = ({ fullWidth {...register(field.id, { pattern: - /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/, + /^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$/, })} defaultValue={!field.value ? `${field.value}` : ""} required={field.required ?? false} diff --git a/src/components/CustomerDetail/ContactInfo.tsx b/src/components/CustomerDetail/ContactInfo.tsx index 9566264..f608b00 100644 --- a/src/components/CustomerDetail/ContactInfo.tsx +++ b/src/components/CustomerDetail/ContactInfo.tsx @@ -231,7 +231,7 @@ const ContactInfo: React.FC = ({ if (errorRows.length > 0) { setError("addContacts", { message: "Contact details include empty fields", type: "required" }) } else { - const errorRows_EmailFormat = rows.filter(row => !/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/.test(String(row.email))) + const errorRows_EmailFormat = rows.filter(row => !/^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$/.test(String(row.email))) if (errorRows_EmailFormat.length > 0) { setError("addContacts", { message: "Contact details include empty fields", type: "email_format" }) } else { diff --git a/src/components/CustomerDetail/CustomerDetail.tsx b/src/components/CustomerDetail/CustomerDetail.tsx index 2c81e17..88a99ad 100644 --- a/src/components/CustomerDetail/CustomerDetail.tsx +++ b/src/components/CustomerDetail/CustomerDetail.tsx @@ -167,7 +167,7 @@ const CustomerDetail: React.FC = ({ formProps.setError("code", { message: "Code is empty", type: "required" }) } - // if (data.email && data.email?.length > 0 && !/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/.test(data.email)) { + // if (data.email && data.email?.length > 0 && !/^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$/.test(data.email)) { // haveError = true // formProps.setError("email", { message: "Email format is not valid", type: "custom" }) // } @@ -182,7 +182,7 @@ const CustomerDetail: React.FC = ({ formProps.setError("addContacts", { message: "Contact info includes empty fields", type: "required" }) } - if (data.addContacts.length > 0 && data.addContacts.filter(row => !/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/.test(String(row.email))).length > 0) { + if (data.addContacts.length > 0 && data.addContacts.filter(row => !/^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$/.test(String(row.email))).length > 0) { haveError = true formProps.setError("addContacts", { message: "Contact info includes invalid email", type: "email_format" }) } diff --git a/src/components/CustomerDetail/CustomerInfo.tsx b/src/components/CustomerDetail/CustomerInfo.tsx index 695f6d5..3eaa327 100644 --- a/src/components/CustomerDetail/CustomerInfo.tsx +++ b/src/components/CustomerDetail/CustomerInfo.tsx @@ -107,7 +107,7 @@ const CustomerInfo: React.FC = ({ label={t("Customer Email")} fullWidth {...register("email", { - pattern: /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/, + pattern: /^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$/, })} error={Boolean(errors.email)} helperText={Boolean(errors.email) && t("Please input correct customer email")} diff --git a/src/components/EditStaff/EditStaff.tsx b/src/components/EditStaff/EditStaff.tsx index 02a3d04..76ebdc7 100644 --- a/src/components/EditStaff/EditStaff.tsx +++ b/src/components/EditStaff/EditStaff.tsx @@ -77,7 +77,7 @@ const EditStaff: React.FC = async () => { "grade", "skill", "currentPosition", - "salaryEffective", + "salary", "hourlyRate", "employType", "email", @@ -140,6 +140,7 @@ const EditStaff: React.FC = async () => { label: t(`Staff ID`), type: "text", value: data[key] ?? "", + required: true, }; case "name": return { @@ -179,7 +180,7 @@ const EditStaff: React.FC = async () => { label: t(`Grade`), type: "combo-Obj", options: gradeCombo, - value: data[key].id ?? "", + value: data[key] !== null ? data[key].id ?? "" : "", }; case "skill": return { @@ -187,7 +188,7 @@ const EditStaff: React.FC = async () => { label: t(`Skillset`), type: "combo-Obj", options: skillCombo, - value: data[key].id ?? "", + value: data[key] !== null ? data[key].id ?? "" : "", }; case "currentPosition": return { @@ -197,23 +198,23 @@ const EditStaff: React.FC = async () => { options: positionCombo, value: data[key].id ?? "", }; - case "salaryEffective": + case "salary": return { - id: `salaryEffId`, + id: `salaryId`, label: t(`Salary Point`), type: "combo-Obj", options: salaryCombo, - value: data[key].salary.id ?? "", - }; - case "hourlyRate": - return { - id: `${key}`, - label: t(`hourlyRate`), - type: "text", - value: "", - // value: data[key], - readOnly: true, + value: data[key] !== null ? data[key].id ?? "" : "", }; + // case "hourlyRate": + // return { + // id: `${key}`, + // label: t(`hourlyRate`), + // type: "text", + // value: "", + // // value: data[key], + // readOnly: true, + // }; case "employType": return { id: `${key}`, @@ -236,7 +237,7 @@ const EditStaff: React.FC = async () => { id: `${key}`, label: t(`Phone1`), type: "text", - pattern: "^\\d{8}$", + // pattern: "^\\d{8}$", message: t("input correct phone no."), value: data[key] ?? "", }; @@ -245,7 +246,7 @@ const EditStaff: React.FC = async () => { id: `${key}`, label: t(`Phone2`), type: "text", - pattern: "^\\d{8}$", + // pattern: "^\\d{8}$", message: t("input correct phone no."), value: data[key] ?? "", } as Field; @@ -269,7 +270,7 @@ const EditStaff: React.FC = async () => { id: `${key}`, label: t(`Emergency Contact Phonee`), type: "text", - pattern: "^\\d{8}$", + // pattern: "^\\d{8}$", message: t("input correct phone no."), value: data[key] ?? "", } as Field; diff --git a/src/components/SubsidiaryDetail/ContactInfo.tsx b/src/components/SubsidiaryDetail/ContactInfo.tsx index 9e2c16d..ef84ad7 100644 --- a/src/components/SubsidiaryDetail/ContactInfo.tsx +++ b/src/components/SubsidiaryDetail/ContactInfo.tsx @@ -231,7 +231,7 @@ const ContactInfo: React.FC = ({ if (errorRows.length > 0) { setError("addContacts", { message: "Contact details include empty fields", type: "required" }) } else { - const errorRows_EmailFormat = rows.filter(row => !/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/.test(String(row.email))) + const errorRows_EmailFormat = rows.filter(row => !/^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$/.test(String(row.email))) if (errorRows_EmailFormat.length > 0) { setError("addContacts", { message: "Contact details include empty fields", type: "email_format" }) diff --git a/src/components/SubsidiaryDetail/SubsidiaryDetail.tsx b/src/components/SubsidiaryDetail/SubsidiaryDetail.tsx index 339fd52..5f89570 100644 --- a/src/components/SubsidiaryDetail/SubsidiaryDetail.tsx +++ b/src/components/SubsidiaryDetail/SubsidiaryDetail.tsx @@ -157,7 +157,7 @@ const SubsidiaryDetail: React.FC = ({ formProps.setError("addContacts", { message: "Contact info includes empty fields", type: "required" }) } - if (data.addContacts.length > 0 && data.addContacts.filter(row => !/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/.test(String(row.email))).length > 0) { + if (data.addContacts.length > 0 && data.addContacts.filter(row => !/^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$/.test(String(row.email))).length > 0) { haveError = true formProps.setError("addContacts", { message: "Contact info includes invalid email", type: "email_format" }) } diff --git a/src/i18n/en/breadcrumb.json b/src/i18n/en/breadcrumb.json index 72bdcc8..5361966 100644 --- a/src/i18n/en/breadcrumb.json +++ b/src/i18n/en/breadcrumb.json @@ -1,6 +1,6 @@ { "Overview": "Overview", - "customer": "Customer", - "Create Customer": "Create Customer" + "customer": "Client", + "Create Customer": "Create Client" } \ No newline at end of file diff --git a/src/i18n/en/subsidiary.json b/src/i18n/en/subsidiary.json index d26d4e1..3c06c1c 100644 --- a/src/i18n/en/subsidiary.json +++ b/src/i18n/en/subsidiary.json @@ -15,8 +15,8 @@ "Customer Type": "Client Type", "Customer Allocation": "Client Allocation", "Search by customer code, name or br no.": "Search by client code, name or br no.", - "Client Pool": "Client Pool", - "Allocated Client": "Allocated Client", + "Customer Pool": "Client Pool", + "Allocated Customer": "Allocated Client", "Please input correct subsidiary code": "Please input correct client code", "Please input correct subsidiary name": "Please input correct client name", From 14ad911bb4082cb806dfc1c3d863576b93c916c1 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Thu, 18 Apr 2024 18:14:02 +0800 Subject: [PATCH 4/4] update staff --- src/components/EditStaff/EditStaff.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/components/EditStaff/EditStaff.tsx b/src/components/EditStaff/EditStaff.tsx index 76ebdc7..1fbf96b 100644 --- a/src/components/EditStaff/EditStaff.tsx +++ b/src/components/EditStaff/EditStaff.tsx @@ -148,6 +148,7 @@ const EditStaff: React.FC = async () => { label: t(`Staff Name`), type: "text", value: data[key] ?? "", + required: true, }; case "company": return { @@ -156,6 +157,7 @@ const EditStaff: React.FC = async () => { type: "combo-Obj", options: companyCombo, value: data[key].id ?? "", + required: true, }; case "team": return { @@ -172,6 +174,7 @@ const EditStaff: React.FC = async () => { type: "combo-Obj", options: departmentCombo, value: data[key]?.id ?? "", + required: true, // later check }; case "grade": @@ -197,6 +200,7 @@ const EditStaff: React.FC = async () => { type: "combo-Obj", options: positionCombo, value: data[key].id ?? "", + required: true, }; case "salary": return { @@ -205,6 +209,7 @@ const EditStaff: React.FC = async () => { type: "combo-Obj", options: salaryCombo, value: data[key] !== null ? data[key].id ?? "" : "", + required: true, }; // case "hourlyRate": // return { @@ -222,6 +227,7 @@ const EditStaff: React.FC = async () => { type: "combo-Obj", options: employTypeCombo, value: data[key] ?? "", + required: true, }; case "email": return { @@ -231,6 +237,7 @@ const EditStaff: React.FC = async () => { value: data[key] ?? "", pattern: "^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$", message: t("input matching format"), + required: true, }; case "phone1": return { @@ -240,6 +247,7 @@ const EditStaff: React.FC = async () => { // pattern: "^\\d{8}$", message: t("input correct phone no."), value: data[key] ?? "", + required: true, }; case "phone2": return { @@ -264,6 +272,7 @@ const EditStaff: React.FC = async () => { label: t(`Emergency Contact Name`), type: "text", value: data[key] ?? "", + required: true, } as Field; case "emergContactPhone": return { @@ -273,6 +282,7 @@ const EditStaff: React.FC = async () => { // pattern: "^\\d{8}$", message: t("input correct phone no."), value: data[key] ?? "", + required: true, } as Field; case "joinDate": return { @@ -280,6 +290,7 @@ const EditStaff: React.FC = async () => { label: t(`Join Date`), type: "multiDate", value: data[key] ?? "", + required: true, } as Field; case "joinPosition": return { @@ -288,6 +299,7 @@ const EditStaff: React.FC = async () => { type: "combo-Obj", options: positionCombo, value: data[key].id ?? "", + required: true, } as Field; case "departDate": return {