From 5e0e5afddcb4d6b74bdac8c5029733324d78cb9b Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Thu, 25 Apr 2024 17:43:28 +0800 Subject: [PATCH 01/20] update claim, project (comment the subsidiary validation) --- src/app/api/claims/actions.ts | 3 + src/app/api/claims/index.ts | 15 ++-- src/app/utils/formatUtil.ts | 9 ++ src/components/ClaimDetail/ClaimDetail.tsx | 83 +++++++++++++------ .../ClaimDetail/ClaimFormInputGrid.tsx | 64 +++++++++++++- src/components/ClaimSearch/ClaimSearch.tsx | 20 +++-- .../CreateProject/ProjectClientDetails.tsx | 22 ++--- .../SearchResults/SearchResults.tsx | 14 +++- src/i18n/en/claim.json | 5 ++ src/i18n/en/common.json | 6 ++ src/i18n/zh/claim.json | 5 ++ src/i18n/zh/common.json | 6 ++ 12 files changed, 193 insertions(+), 59 deletions(-) diff --git a/src/app/api/claims/actions.ts b/src/app/api/claims/actions.ts index b4d99ad..d607c48 100644 --- a/src/app/api/claims/actions.ts +++ b/src/app/api/claims/actions.ts @@ -12,6 +12,9 @@ export interface ClaimInputFormByStaff { status: string; addClaimDetails: ClaimDetailTable[] + + // is grid editing + isGridEditing: boolean | null; } export interface ClaimDetailTable { diff --git a/src/app/api/claims/index.ts b/src/app/api/claims/index.ts index ceb9887..eb095f1 100644 --- a/src/app/api/claims/index.ts +++ b/src/app/api/claims/index.ts @@ -5,9 +5,10 @@ import "server-only"; export interface Claim { id: number; - created: string; - name: string; - cost: number; + code: string; + created: number[]; + project: ProjectCombo; + amount: number; type: "Expense" | "Petty Cash"; status: "Not Submitted" | "Waiting for Approval" | "Approved" | "Rejected"; remarks: string; @@ -41,8 +42,8 @@ export const preloadClaims = () => { }; export const fetchClaims = cache(async () => { - return mockClaims; - // return serverFetchJson(`${BASE_API_URL}/claim`); + // return mockClaims; + return serverFetchJson(`${BASE_API_URL}/claim`); }); export const fetchProjectCombo = cache(async () => { @@ -54,7 +55,7 @@ export const fetchProjectCombo = cache(async () => { // export const fetchAllCustomers = cache(async () => { // return serverFetchJson(`${BASE_API_URL}/customer`); // }); - +/* const mockClaims: Claim[] = [ { id: 1, @@ -83,4 +84,4 @@ const mockClaims: Claim[] = [ status: "Rejected", remarks: "Duplicate Claim Form", }, -]; +];*/ diff --git a/src/app/utils/formatUtil.ts b/src/app/utils/formatUtil.ts index 5f3e3e6..918b0ca 100644 --- a/src/app/utils/formatUtil.ts +++ b/src/app/utils/formatUtil.ts @@ -23,6 +23,15 @@ export const convertDateToString = (date: Date, format: string = OUTPUT_DATE_FOR return dayjs(date).format(format) } +export const convertDateArrayToString = (dateArray: number[], format: string = OUTPUT_DATE_FORMAT, needTime: boolean = false) => { + if (dateArray.length === 6) { + if (!needTime) { + const dateString = `${dateArray[0]}-${dateArray[1]}-${dateArray[2]}` + return dayjs(dateString).format(format) + } + } +} + const shortDateFormatter_en = new Intl.DateTimeFormat("en-HK", { weekday: "short", year: "numeric", diff --git a/src/components/ClaimDetail/ClaimDetail.tsx b/src/components/ClaimDetail/ClaimDetail.tsx index ad25cfa..db74447 100644 --- a/src/components/ClaimDetail/ClaimDetail.tsx +++ b/src/components/ClaimDetail/ClaimDetail.tsx @@ -15,6 +15,7 @@ import { ClaimInputFormByStaff, saveClaim } from "@/app/api/claims/actions"; import { DoneAll } from "@mui/icons-material"; import { expenseTypeCombo } from "@/app/utils/comboUtil"; import { convertDateToString } from "@/app/utils/formatUtil"; +import { errorDialog, submitDialog, successDialog, warningDialog } from "../Swal/CustomAlerts"; export interface Props { projectCombo: ProjectCombo[] @@ -40,17 +41,49 @@ const ClaimDetail: React.FC = ({ projectCombo }) => { const onSubmit = useCallback>( async (data, event) => { try { - console.log(data.addClaimDetails[0].newSupportingDocument); - console.log((event?.nativeEvent as any).submitter.name); + if (data.isGridEditing) { + warningDialog(t("Please save all the rows before submitting"), t) + return false + } + + let haveError = false + if (data.addClaimDetails.length === 0 || data.addClaimDetails.filter(row => String(row.description).trim().length === 0 || String(row.amount).trim().length === 0 || row.project === null || row.project === undefined || ((row.oldSupportingDocument === null || row.oldSupportingDocument === undefined) && (row.newSupportingDocument === null || row.newSupportingDocument === undefined))).length > 0) { + haveError = true + formProps.setError("addClaimDetails", { message: "Claim details include empty fields", type: "required" }) + } + + if (data.addClaimDetails.length > 0 && data.addClaimDetails.filter(row => row.invoiceDate.getTime() > new Date().getTime()).length > 0) { + haveError = true + formProps.setError("addClaimDetails", { message: "Claim details include invalid invoice date", type: "invalid_date" }) + } + + if (data.addClaimDetails.length > 0 && data.addClaimDetails.filter(row => row.project === null || row.project === undefined).length > 0) { + haveError = true + formProps.setError("addClaimDetails", { message: "Claim details include empty project", type: "invalid_project" }) + } + + if (data.addClaimDetails.length > 0 && data.addClaimDetails.filter(row => row.amount <= 0).length > 0) { + haveError = true + formProps.setError("addClaimDetails", { message: "Claim details include invalid amount", type: "invalid_amount" }) + } + + if (haveError) { + return false + } + const buttonName = (event?.nativeEvent as any).submitter.name - console.log(JSON.stringify(data)) const formData = new FormData() - // formData.append("expenseType", data.expenseType) + formData.append("expenseType", data.expenseType) + data.addClaimDetails.forEach((claimDetail) => { + formData.append("addClaimDetailIds", JSON.stringify(claimDetail.id)) + formData.append("addClaimDetailInvoiceDates", convertDateToString(claimDetail.invoiceDate, "YYYY-MM-DD")) + formData.append("addClaimDetailProjectIds", JSON.stringify(claimDetail.project.id)) + formData.append("addClaimDetailDescriptions", claimDetail.description) + formData.append("addClaimDetailAmounts", JSON.stringify(claimDetail.amount)) + formData.append("addClaimDetailNewSupportingDocuments", claimDetail.newSupportingDocument) + formData.append("addClaimDetailOldSupportingDocumentIds", JSON.stringify(claimDetail?.oldSupportingDocument?.id ?? -1)) + }) // for (let i = 0; i < data.addClaimDetails.length; i++) { - // // formData.append("addClaimDetails[]", JSON.stringify(data.addClaimDetails[i])) - // // formData.append("addClaimDetailsFiles[]", data.addClaimDetails[i].newSupportingDocument) - // console.log(data.addClaimDetails[i].invoiceDate) - // // data.addClaimDetails[i].invoiceDate = convertDateToString(data.addClaimDetails[i].invoiceDate) // const updatedData = { // id: data.addClaimDetails[i].id, // // project: data.addClaimDetails[i].project, @@ -63,22 +96,24 @@ const ClaimDetail: React.FC = ({ projectCombo }) => { // formData.append("addClaimDetailsFiles", data.addClaimDetails[i].newSupportingDocument) // formData.append("testFiles", data.addClaimDetails[i].newSupportingDocument) // } - // if (buttonName === "submit") { - // formData.append("status", "Not Submitted") - // } else if (buttonName === "save") { - // formData.append("status", "Waiting for Approval") - // } - // formData.append("id", "-1") + if (buttonName === "submit") { + formData.append("status", "Waiting for Approval") + } else if (buttonName === "save") { + formData.append("status", "Not Submitted") + } + formData.append("id", "-1") - // for (let i = 0; i < data.addClaimDetails.length; i++) { - // // const formData = new FormData(); - // // formData.append("newSupportingDocument", data.addClaimDetails[i].oldSupportingDocument); - // data.addClaimDetails[i].oldSupportingDocument = new Blob([data.addClaimDetails[i].oldSupportingDocument], {type: data.addClaimDetails[i].oldSupportingDocument.type}) - // } - await saveClaim(formData) setServerError(""); - // await saveProject(data); - // router.replace("/projects"); + + submitDialog(async () => { + const response = await saveClaim(formData); + + if (response.message === "Success") { + successDialog(t("Submit Success"), t).then(() => { + router.replace("/staffReimbursement"); + }) + } + }, t) } catch (e) { setServerError(t("An error has occurred. Please try again later.")); } @@ -107,10 +142,10 @@ const ClaimDetail: React.FC = ({ projectCombo }) => { - - diff --git a/src/components/ClaimDetail/ClaimFormInputGrid.tsx b/src/components/ClaimDetail/ClaimFormInputGrid.tsx index b6c2fde..9a98179 100644 --- a/src/components/ClaimDetail/ClaimFormInputGrid.tsx +++ b/src/components/ClaimDetail/ClaimFormInputGrid.tsx @@ -157,7 +157,7 @@ const ClaimFormInputGrid: React.FC = ({ projectCombo, }) => { const { t } = useTranslation() - const { control, setValue, getValues, formState: { errors } } = useFormContext(); + const { control, setValue, getValues, formState: { errors }, clearErrors, setError } = useFormContext(); const { fields } = useFieldArray({ control, name: "addClaimDetails" @@ -459,6 +459,56 @@ const ClaimFormInputGrid: React.FC = ({ }, ], [rows, rowModesModel, t],); + // check error + useEffect(() => { + if (getValues("addClaimDetails") === undefined || getValues("addClaimDetails") === null) { + return; + } + + if (getValues("addClaimDetails").length === 0) { + clearErrors("addClaimDetails") + } else { + + console.log(rows) + if (rows.filter(row => String(row.description).trim().length === 0 || String(row.amount).trim().length === 0 || row.project === null || row.project === undefined || ((row.oldSupportingDocument === null || row.oldSupportingDocument === undefined) && (row.newSupportingDocument === null || row.newSupportingDocument === undefined))).length > 0) { + setError("addClaimDetails", { message: "Claim details include empty fields", type: "required" }) + } else { + let haveError = false + + if (rows.filter(row => row.invoiceDate.getTime() > new Date().getTime()).length > 0) { + haveError = true + setError("addClaimDetails", { message: "Claim details include invalid invoice date", type: "invalid_date" }) + } + + if (rows.filter(row => row.project === null || row.project === undefined).length > 0) { + haveError = true + setError("addClaimDetails", { message: "Claim details include empty project", type: "invalid_project" }) + } + + if (rows.filter(row => row.amount <= 0).length > 0) { + haveError = true + setError("addClaimDetails", { message: "Claim details include invalid amount", type: "invalid_amount" }) + } + + if (!haveError) { + clearErrors("addClaimDetails") + } + } + } + }, [rows, rowModesModel]) + + // check editing + useEffect(() => { + const filteredByKey = Object.fromEntries( + Object.entries(rowModesModel).filter(([key, value]) => rowModesModel[key].mode === 'edit')) + + if (Object.keys(filteredByKey).length > 0) { + setValue("isGridEditing", true) + } else { + setValue("isGridEditing", false) + } + }, [rowModesModel]) + return ( = ({ }, }} > - {Boolean(errors.addClaimDetails?.type === "required") && ({ color: theme.palette.error.main })} variant="overline" display='inline-block' noWrap> + {Boolean(errors.addClaimDetails?.type === "required") && ({ color: theme.palette.error.main, ml: 3, mt: 1 })} variant="overline" display='inline-block' noWrap> {t("Please ensure at least one row is created, and all the fields are inputted and saved")} } - {Boolean(errors.addClaimDetails?.type === "format") && ({ color: theme.palette.error.main })} variant="overline" display='inline-block' noWrap> - {t("Please ensure the date formats are correct")} + {Boolean(errors.addClaimDetails?.type === "invalid_date") && ({ color: theme.palette.error.main, ml: 3, mt: 1 })} variant="overline" display='inline-block' noWrap> + {t("Please ensure the date are correct")} + } + {Boolean(errors.addClaimDetails?.type === "invalid_project") && ({ color: theme.palette.error.main, ml: 3, mt: 1 })} variant="overline" display='inline-block' noWrap> + {t("Please ensure the projects are selected")} + } + {Boolean(errors.addClaimDetails?.type === "invalid_amount") && ({ color: theme.palette.error.main, ml: 3, mt: 1 })} variant="overline" display='inline-block' noWrap> + {t("Please ensure the amount are correct")} }
= ({ claims }) => { // onClick: onClaimClick, // buttonIcon: , // }, - { name: "created", label: t("Creation Date") }, - { name: "name", label: t("Related Project Name") }, - { name: "cost", label: t("Amount (HKD)") }, + { name: "created", label: t("Creation Date"), type: "date" }, + { name: "code", label: t("Claim Code") }, + { name: "project", label: t("Related Project Name") }, + { name: "amount", label: t("Amount (HKD)") }, { name: "type", label: t("Expense Type"), needTranslation: true }, { name: "status", label: t("Status"), needTranslation: true }, { name: "remarks", label: t("Remarks") }, @@ -71,13 +73,13 @@ const ClaimSearch: React.FC = ({ claims }) => { onSearch={(query) => { setFilteredClaims( claims.filter( - (claim) => - dateInRange(claim.created, query.created, query.createdTo ?? undefined) && - claim.name.toLowerCase().includes(query.name.toLowerCase()) && - (claim.type.toLowerCase().includes(query.type.toLowerCase()) || query.type.toLowerCase() === "all") && - (claim.status.toLowerCase().includes(query.status.toLowerCase()) || query.status.toLowerCase() === "all") + (claim) => + dateInRange(convertDateArrayToString(claim.created, "YYYY-MM-DD")!!, query.created, query.createdTo ?? undefined) && + // claim.project.name.toLowerCase().includes(query.name.toLowerCase()) && + (claim.type.toLowerCase().includes(query.type.toLowerCase()) || query.type.toLowerCase() === "all") && + (claim.status.toLowerCase().includes(query.status.toLowerCase()) || query.status.toLowerCase() === "all") ), - ); + ); }} /> items={filteredClaims} columns={columns} /> diff --git a/src/components/CreateProject/ProjectClientDetails.tsx b/src/components/CreateProject/ProjectClientDetails.tsx index ef5a89f..09ff66a 100644 --- a/src/components/CreateProject/ProjectClientDetails.tsx +++ b/src/components/CreateProject/ProjectClientDetails.tsx @@ -475,17 +475,17 @@ const ProjectClientDetails: React.FC = ({ > {t("Client Subsidiary")} { - if ( - !customerSubsidiaryIds.find( - (subsidiaryId) => subsidiaryId === value, - ) - ) { - return t("Please choose a valid subsidiary"); - } else return true; - }, - }} + // rules={{ + // validate: (value) => { + // if ( + // !customerSubsidiaryIds.find( + // (subsidiaryId) => subsidiaryId === value, + // ) + // ) { + // return t("Please choose a valid subsidiary"); + // } else return true; + // }, + // }} defaultValue={customerSubsidiaryIds[0]} control={control} name="clientSubsidiaryId" diff --git a/src/components/SearchResults/SearchResults.tsx b/src/components/SearchResults/SearchResults.tsx index ea0744c..9dfb6e4 100644 --- a/src/components/SearchResults/SearchResults.tsx +++ b/src/components/SearchResults/SearchResults.tsx @@ -14,6 +14,7 @@ import TableRow from "@mui/material/TableRow"; import IconButton, { IconButtonOwnProps, IconButtonPropsColorOverrides } from "@mui/material/IconButton"; import { t } from "i18next"; import { useTranslation } from "react-i18next"; +import { convertDateArrayToString } from "@/app/utils/formatUtil"; export interface ResultWithId { id: string | number; @@ -23,7 +24,8 @@ interface BaseColumn { name: keyof T; label: string; color?: IconButtonOwnProps["color"]; - needTranslation?: boolean + needTranslation?: boolean; + type?: string; } interface ColumnWithAction extends BaseColumn { @@ -101,9 +103,13 @@ function SearchResults({ > {column.buttonIcon} - ) : ( - <>{column?.needTranslation ? t(item[columnName] as string) : item[columnName]} - )} + ) : + column?.type === "date" ? ( + <>{convertDateArrayToString(item[columnName] as number[])} + ) : + ( + <>{column?.needTranslation ? t(item[columnName] as string) : item[columnName]} + )} ); })} diff --git a/src/i18n/en/claim.json b/src/i18n/en/claim.json index bfd0f84..16b28f6 100644 --- a/src/i18n/en/claim.json +++ b/src/i18n/en/claim.json @@ -26,6 +26,11 @@ "Approved": "Approved", "Rejected": "Rejected", + "Please ensure at least one row is created, and all the fields are inputted and saved": "Please ensure at least one row is created, and all the fields are inputted and saved", + "Please ensure the date are correct": "Please ensure the date are correct", + "Please ensure the projects are selected": "Please ensure the projects are selected", + "Please ensure the amount are correct": "Please ensure the amount are correct", + "Description": "Description", "Actions": "Actions" } \ No newline at end of file diff --git a/src/i18n/en/common.json b/src/i18n/en/common.json index 51ea204..5f1d289 100644 --- a/src/i18n/en/common.json +++ b/src/i18n/en/common.json @@ -11,6 +11,12 @@ "Approved": "Approved", "Rejected": "Rejected", + "Do you want to submit?": "Do you want to submit?", + "Submit Success": "Submit Success", + "Submit Fail": "Submit Fail", + "Do you want to delete?": "Do you want to delete", + "Delete Success": "Delete Success", + "Search": "Search", "Search Criteria": "Search Criteria", "Cancel": "Cancel", diff --git a/src/i18n/zh/claim.json b/src/i18n/zh/claim.json index a22acca..22c11cb 100644 --- a/src/i18n/zh/claim.json +++ b/src/i18n/zh/claim.json @@ -26,6 +26,11 @@ "Approved": "已批核", "Rejected": "已拒絕", + "Please ensure at least one row is created, and all the fields are inputted and saved": "請確保已建立至少一行, 及已輸入和儲存所有欄位", + "Please ensure the date are correct": "請確保所有日期輸入正確", + "Please ensure the projects are selected": "請確保所有項目欄位已選擇", + "Please ensure the amount are correct": "請確保所有金額輸入正確", + "Description": "描述", "Actions": "行動" } \ No newline at end of file diff --git a/src/i18n/zh/common.json b/src/i18n/zh/common.json index f857985..e4642ea 100644 --- a/src/i18n/zh/common.json +++ b/src/i18n/zh/common.json @@ -9,6 +9,12 @@ "Approved": "已批核", "Rejected": "已拒絕", + "Do you want to submit?": "你是否確認要提交?", + "Submit Success": "提交成功", + "Submit Fail": "提交失敗", + "Do you want to delete?": "你是否確認要刪除?", + "Delete Success": "刪除成功", + "Search": "搜尋", "Search Criteria": "搜尋條件", "Cancel": "取消", From 03fd6e3bc7fe5c5658660580a5f469d076b2369d Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Thu, 25 Apr 2024 17:56:56 +0800 Subject: [PATCH 02/20] update claim --- src/components/ClaimSearch/ClaimSearch.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ClaimSearch/ClaimSearch.tsx b/src/components/ClaimSearch/ClaimSearch.tsx index e8fab2d..cafdec5 100644 --- a/src/components/ClaimSearch/ClaimSearch.tsx +++ b/src/components/ClaimSearch/ClaimSearch.tsx @@ -26,7 +26,7 @@ const ClaimSearch: React.FC = ({ claims }) => { const searchCriteria: Criterion[] = useMemo( () => [ { label: t("Creation Date From"), label2: t("Creation Date To"), paramName: "created", type: "dateRange" }, - { label: t("Related Project Name"), paramName: "name", type: "text" }, + // { label: t("Related Project Name"), paramName: "name", type: "text" }, { label: t("Expense Type"), paramName: "type", @@ -57,7 +57,7 @@ const ClaimSearch: React.FC = ({ claims }) => { // }, { name: "created", label: t("Creation Date"), type: "date" }, { name: "code", label: t("Claim Code") }, - { name: "project", label: t("Related Project Name") }, + // { name: "project", label: t("Related Project Name") }, { name: "amount", label: t("Amount (HKD)") }, { name: "type", label: t("Expense Type"), needTranslation: true }, { name: "status", label: t("Status"), needTranslation: true }, From cbda48860b8b72e7e8714b9ed80235b7ea2ee5cf Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Thu, 25 Apr 2024 17:57:28 +0800 Subject: [PATCH 03/20] update team delete --- src/app/api/staff/index.ts | 6 + src/app/api/team/actions.ts | 9 ++ .../CreateStaffForm/CreateStaffForm.tsx | 13 ++- .../CreateTeam/CreateTeamWrapper.tsx | 4 +- src/components/EditTeam/Allocation.tsx | 6 +- src/components/EditTeam/EditTeam.tsx | 21 +--- .../TeamSearch/ConfirmDeleteModal.tsx | 105 ++++++++++++++++++ src/components/TeamSearch/TeamSearch.tsx | 46 ++++++-- 8 files changed, 180 insertions(+), 30 deletions(-) create mode 100644 src/components/TeamSearch/ConfirmDeleteModal.tsx diff --git a/src/app/api/staff/index.ts b/src/app/api/staff/index.ts index 2c3f66d..f24f187 100644 --- a/src/app/api/staff/index.ts +++ b/src/app/api/staff/index.ts @@ -67,6 +67,12 @@ export const fetchStaff = cache(async () => { }); }); +export const fetchStaffWithoutTeam = cache(async () => { + return serverFetchJson(`${BASE_API_URL}/staffs/noteam`, { + next: { tags: ["staffs"] }, + }); +}); + // export const fetchStaffCombo = cache(async () => { // return serverFetchJson(`${BASE_API_URL}/staffs/combo`, { // next: { tags: ["staffs"] }, diff --git a/src/app/api/team/actions.ts b/src/app/api/team/actions.ts index a86ccf6..28496d0 100644 --- a/src/app/api/team/actions.ts +++ b/src/app/api/team/actions.ts @@ -51,3 +51,12 @@ export const saveTeam = async (data: CreateTeamInputs) => { headers: { "Content-Type": "application/json" }, }); }; + + +export const deleteTeam = async (data: TeamResult) => { + return serverFetchJson(`${BASE_API_URL}/team/delete/${data.id}`, { + method: "DELETE", + // body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }); +}; diff --git a/src/components/CreateStaffForm/CreateStaffForm.tsx b/src/components/CreateStaffForm/CreateStaffForm.tsx index bc55a01..31eb1fc 100644 --- a/src/components/CreateStaffForm/CreateStaffForm.tsx +++ b/src/components/CreateStaffForm/CreateStaffForm.tsx @@ -47,7 +47,7 @@ const CreateStaffForm: React.FC = ({ Title, fieldLists }) => { return haveError; } //check if joinDate > departDate - if (data.departDate != null && data.departDate != "Invalid Date") { + if (data.departDate != null && data.departDate != "Invalid Date" && data.departDate.length != 0) { if (data.joinDate != null) { const joinDate = new Date(data.joinDate); const departDate = new Date(data.departDate); @@ -56,6 +56,10 @@ const CreateStaffForm: React.FC = ({ Title, fieldLists }) => { return haveError; } } + if (data.departReason == null || data.departReason.length == 0) { + haveError = true; + return haveError; + } } if (haveError) { @@ -68,6 +72,13 @@ const CreateStaffForm: React.FC = ({ Title, fieldLists }) => { phone2: data.phone2.toString(), hourlyRate: typeof data.hourlyRate === 'string' ? parseInt(data.hourlyRate.replace("$", "").replace(",", "")) : 0 }; + if (postData.departDate?.length === 0 && postData.departReason?.length === 0) { + delete postData.departDate; + delete postData.departReason; + } + if (postData.remark?.length === 0) { + delete postData.remark; + } console.log(postData); setServerError(""); await saveStaff(postData); diff --git a/src/components/CreateTeam/CreateTeamWrapper.tsx b/src/components/CreateTeam/CreateTeamWrapper.tsx index 605ff83..3829a3a 100644 --- a/src/components/CreateTeam/CreateTeamWrapper.tsx +++ b/src/components/CreateTeam/CreateTeamWrapper.tsx @@ -3,7 +3,7 @@ import CreateTeam from "./CreateTeam"; import CreateTeamLoading from "./CreateTeamLoading"; // import { fetchTeam, fetchTeamLeads } from "@/app/api/team"; import { useSearchParams } from "next/navigation"; -import { fetchStaff } from "@/app/api/staff"; +import { fetchStaff, fetchStaffWithoutTeam } from "@/app/api/staff"; interface SubComponents { Loading: typeof CreateTeamLoading; @@ -14,7 +14,7 @@ const CreateTeamWrapper: React.FC & SubComponents = async () => { const [ staff, ] = await Promise.all([ - fetchStaff(), + fetchStaffWithoutTeam(), ]); return ; diff --git a/src/components/EditTeam/Allocation.tsx b/src/components/EditTeam/Allocation.tsx index 96b4b46..44c89b1 100644 --- a/src/components/EditTeam/Allocation.tsx +++ b/src/components/EditTeam/Allocation.tsx @@ -67,8 +67,8 @@ const Allocation: React.FC = ({ allStaffs: staff }) => { const removeStaff = useCallback((staff: StaffResult) => { setSelectedStaff((s) => s.filter((s) => s.id !== staff.id)); - setDeletedStaffIds((s) => s) - // setValue("deleteStaffIds", [...staff.id]) + // setDeletedStaffIds((s) => s) + setDeletedStaffIds((prevIds) => [...prevIds, staff.id]); }, []); const setTeamLead = useCallback( @@ -118,7 +118,7 @@ const Allocation: React.FC = ({ allStaffs: staff }) => { useEffect(() => { setValue("deleteStaffIds", deletedStaffIds) console.log(deletedStaffIds) - }, [deletedStaffIds, setValue]); + }, [deletedStaffIds]); const StaffPoolColumns = useMemo[]>( () => [ diff --git a/src/components/EditTeam/EditTeam.tsx b/src/components/EditTeam/EditTeam.tsx index 4b93754..095559b 100644 --- a/src/components/EditTeam/EditTeam.tsx +++ b/src/components/EditTeam/EditTeam.tsx @@ -70,7 +70,7 @@ const EditTeam: React.FC = async ({ staff, desc }) => { const tempDesc = desc.filter( (item) => item.id === parseInt(idString) ) - // console.log(filteredTeam); + if (filteredTeam.length > 0) { const filteredIds: number[] = filteredTeam.map((i) => ( i.id @@ -82,21 +82,7 @@ const EditTeam: React.FC = async ({ staff, desc }) => { formProps.reset({description: tempDesc[0].description, addStaffIds: idList}) setFilteredDesc(tempDesc[0].description) } - // console.log(staff); - // const desc = staff[0]?.description - // setDesc(desc) - // const staff = staff.map((item) => { - // return { - // id: item.id, - // name: item.name, - // staffId: item.staffId, - // teamId: item.teamId, - // staffName: item.staffName, - // currentPosition: item.currentPosition - // } as StaffResult - // }) - console.log(staff) setAllStaffs(staff) }, [searchParams]); @@ -124,11 +110,12 @@ const EditTeam: React.FC = async ({ staff, desc }) => { const tempData = { description: data.description, addStaffIds: data.addStaffIds, + deleteStaffIds: data.deleteStaffIds, id: parseInt(idString!!) } console.log(tempData) - // await saveTeam(tempData); - // router.replace("/settings/staff"); + await saveTeam(tempData); + router.replace("/settings/team"); } catch (e) { console.log(e); setServerError(t("An error has occurred. Please try again later.")); diff --git a/src/components/TeamSearch/ConfirmDeleteModal.tsx b/src/components/TeamSearch/ConfirmDeleteModal.tsx new file mode 100644 index 0000000..a5e7ed0 --- /dev/null +++ b/src/components/TeamSearch/ConfirmDeleteModal.tsx @@ -0,0 +1,105 @@ +"use client"; +import React, { useCallback, useMemo, useState } from "react"; +import Button from "@mui/material/Button"; +import { Card, Modal, Stack, Typography } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { Add } from "@mui/icons-material"; +import Check from "@mui/icons-material/Check"; +import Close from "@mui/icons-material/Close"; +import { TSMS_BUTTON_THEME } from "@/theme/colorConst"; +import { ThemeProvider } from "@emotion/react"; + +interface Props { + isOpen: boolean; + onConfirm: (data: any) => void; + onCancel: (data: any | null) => void; +} + +const ConfirmModal: React.FC = ({ ...props }) => { + const { t } = useTranslation(); + return ( + <> + + + <> + + {t("Confirm")} + + <> + + {t("Are You Sure")} + + + {/* */} + + + + + {/* */} + + + + + ); +}; + +export default ConfirmModal; diff --git a/src/components/TeamSearch/TeamSearch.tsx b/src/components/TeamSearch/TeamSearch.tsx index 9aae6f1..b2cc9e8 100644 --- a/src/components/TeamSearch/TeamSearch.tsx +++ b/src/components/TeamSearch/TeamSearch.tsx @@ -9,6 +9,9 @@ import EditNote from "@mui/icons-material/EditNote"; import DeleteIcon from '@mui/icons-material/Delete'; import { deleteStaff } from "@/app/api/staff/actions"; import { useRouter } from "next/navigation"; +import ConfirmModal from "./ConfirmDeleteModal"; +import { deleteTeam } from "@/app/api/team/actions"; + interface Props { team: TeamResult[]; @@ -50,23 +53,47 @@ const TeamSearch: React.FC = ({ team }) => { router.push(`/settings/team/edit?id=${id}`); }, [router, t]); + // const onDeleteClick = useCallback((team: TeamResult) => { + // console.log(team); + // deleteTeam + + // }, [router, t]); + + const onDeleteClick = (team: TeamResult) => { + console.log(team); + setData(team) + setIsOpen(!isOpen) + }; + + const onConfirm = useCallback(async (team: TeamResult) => { + console.log(team); + if (data) + await deleteTeam(data) + setIsOpen(false) + window.location.reload; + }, [deleteTeam, data]); + + const onCancel = useCallback(() => { + setIsOpen(false) + }, []); + const columns = useMemo[]>( () => [ { name: "action", - label: t("Actions"), + label: t("Edit"), onClick: onTeamClick, buttonIcon: , }, { name: "name", label: t("Name") }, { name: "code", label: t("Code") }, { name: "description", label: t("description") }, - // { - // name: "action", - // label: t("Actions"), - // onClick: deleteClick, - // buttonIcon: , - // }, + { + name: "action", + label: t("Delete"), + onClick: onDeleteClick, + buttonIcon: , + }, ], [t], ); @@ -89,6 +116,11 @@ const TeamSearch: React.FC = ({ team }) => { }} /> items={filteredTeam} columns={columns} /> + ); From 571f0cdba1949583612f1df45dc0c025d1265491 Mon Sep 17 00:00:00 2001 From: "MSI\\2Fi" Date: Thu, 25 Apr 2024 18:12:06 +0800 Subject: [PATCH 04/20] Download Invoice --- src/app/api/invoices/index.ts | 2 +- .../CreateInvoice/CreateInvoice.tsx | 84 +++++++++++++++++-- .../InvoiceSearch/InvoiceSearchWrapper.tsx | 9 +- 3 files changed, 87 insertions(+), 8 deletions(-) diff --git a/src/app/api/invoices/index.ts b/src/app/api/invoices/index.ts index 6036051..d20e0cf 100644 --- a/src/app/api/invoices/index.ts +++ b/src/app/api/invoices/index.ts @@ -7,7 +7,7 @@ export interface InvoiceResult { id: number; projectCode: string; projectName: string; - stage: String; + stage: string; comingPaymentMileStone: string; paymentMilestoneDate: string; resourceUsage: number; diff --git a/src/components/CreateInvoice/CreateInvoice.tsx b/src/components/CreateInvoice/CreateInvoice.tsx index f42dd74..df8ce7c 100644 --- a/src/components/CreateInvoice/CreateInvoice.tsx +++ b/src/components/CreateInvoice/CreateInvoice.tsx @@ -6,7 +6,7 @@ import Stack from "@mui/material/Stack"; import Print from '@mui/icons-material/Print'; // import { CreateInvoiceInputs, saveInvoice } from "@/app/api/companys/actions"; import { useRouter } from "next/navigation"; -import React, { useCallback, useState, useLayoutEffect } from "react"; +import React, { useCallback, useState, useLayoutEffect, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { FieldErrors, @@ -17,12 +17,13 @@ import { } from "react-hook-form"; import { useSearchParams } from 'next/navigation' import { InvoiceResult } from "@/app/api/invoices"; -import { InvoiceInformation, fetchInvoiceInfoById, fetchProjectInvoiceById } from "@/app/api/invoices/actions"; +import { InvoiceInformation, fetchInvoiceInfoById, fetchProjectInvoiceById, genInvoice } from "@/app/api/invoices/actions"; import InvoiceDetails from "./InvoiceDetails"; import ProjectDetails from "./ProjectDetails"; import ProjectTotalFee from "./ProjectTotalFee"; import { timestampToDateString } from "@/app/utils/formatUtil"; import dayjs from "dayjs"; +import { getSession } from "next-auth/react" const CreateInvoice: React.FC = ({ }) => { @@ -34,7 +35,7 @@ const CreateInvoice: React.FC = ({ const [invoiceDetail, setInvoiceDetail] = useState() const [serverError, setServerError] = useState(""); - // const { getValues } = useForm(); + const [accessToken, setAccessToken] = useState(''); const fetchProjectDetails = async () =>{ const projectId = searchParams.get("id") @@ -78,13 +79,84 @@ const CreateInvoice: React.FC = ({ fetchInvoiceDetails() }, []) + useEffect(() => { + const fetchData = async () => { + try { + const session = await getSession(); + + if (session?.accessToken) { + const accessToken = session.accessToken; + // Use the access token as needed + setAccessToken(accessToken) + console.log(accessToken); + } else { + throw new Error('Access token not found in the session.'); + } + } catch (error) { + console.error(error); + } + }; + + fetchData(); + }, []); + + const handleCancel = () => { router.back(); }; - const handlePrintout = () => { - // const formData = getValues(); - console.log("Printing in Progress") + + const handlePrintout = async () => { + const formData = formProps.getValues() + const projectId = searchParams.get("id") + // console.log(formData, projectId) + const tempData = { + ...formData, + id: projectId + } + try { + // Make an API request to generate the JasperReport + + const response = await fetch(`http://localhost:8090/api/invoices/pdf`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${accessToken}` + }, + + body: JSON.stringify(tempData), + }); + + // Check if the request was successful + if (response.ok) { + // Extract the filename from the response headers + const contentDisposition = response.headers.get('content-disposition'); + console.log(contentDisposition) + const fileName = contentDisposition + ? contentDisposition.split('filename=')[1] + : 'invoice.pdf'; + + // Convert the response data to a Blob object + const blob = await response.blob(); + + // Create a temporary URL for the Blob object + const url = URL.createObjectURL(blob); + + // Create a link element to trigger the file download + const link = document.createElement('a'); + link.href = url; + link.download = fileName; + link.click(); + + // Clean up the temporary URL + URL.revokeObjectURL(url); + } else { + throw new Error('Failed to generate the JasperReport.'); + } + } catch (error) { + console.error(error); + // Handle any errors that occurred during the process + } } const onSubmit = useCallback>( diff --git a/src/components/InvoiceSearch/InvoiceSearchWrapper.tsx b/src/components/InvoiceSearch/InvoiceSearchWrapper.tsx index 420736f..ba05baf 100644 --- a/src/components/InvoiceSearch/InvoiceSearchWrapper.tsx +++ b/src/components/InvoiceSearch/InvoiceSearchWrapper.tsx @@ -3,6 +3,8 @@ import React from "react"; import InvoiceSearch from "./InvoiceSearch"; import InvoiceSearchLoading from "./InvoiceSearchLoading"; import { fetchInvoices } from "@/app/api/invoices"; +import { timestampToDateString } from "@/app/utils/formatUtil"; + interface SubComponents { Loading: typeof InvoiceSearchLoading; @@ -11,7 +13,12 @@ interface SubComponents { const InvoiceSearchWrapper: React.FC & SubComponents = async () => { const Invoices = await fetchInvoices(); - return ; + const temp = Invoices.map((invoice) => ({ + ...invoice, + paymentMilestoneDate: timestampToDateString(invoice.paymentMilestoneDate) + })) + + return ; }; InvoiceSearchWrapper.Loading = InvoiceSearchLoading; From 11f83b98ef95b820a912f4cd9c9d014ded5560b8 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Thu, 25 Apr 2024 18:44:13 +0800 Subject: [PATCH 05/20] update --- src/components/CreateInvoice/CreateInvoice.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CreateInvoice/CreateInvoice.tsx b/src/components/CreateInvoice/CreateInvoice.tsx index df8ce7c..84f0350 100644 --- a/src/components/CreateInvoice/CreateInvoice.tsx +++ b/src/components/CreateInvoice/CreateInvoice.tsx @@ -17,7 +17,7 @@ import { } from "react-hook-form"; import { useSearchParams } from 'next/navigation' import { InvoiceResult } from "@/app/api/invoices"; -import { InvoiceInformation, fetchInvoiceInfoById, fetchProjectInvoiceById, genInvoice } from "@/app/api/invoices/actions"; +import { InvoiceInformation, fetchInvoiceInfoById, fetchProjectInvoiceById } from "@/app/api/invoices/actions"; import InvoiceDetails from "./InvoiceDetails"; import ProjectDetails from "./ProjectDetails"; import ProjectTotalFee from "./ProjectTotalFee"; From c861debf296ed2d2a788ada3026150fbf5fada75 Mon Sep 17 00:00:00 2001 From: "MSI\\2Fi" Date: Thu, 25 Apr 2024 18:50:18 +0800 Subject: [PATCH 06/20] Hide access token --- .../CreateInvoice/CreateInvoice.tsx | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/components/CreateInvoice/CreateInvoice.tsx b/src/components/CreateInvoice/CreateInvoice.tsx index 84f0350..e85d249 100644 --- a/src/components/CreateInvoice/CreateInvoice.tsx +++ b/src/components/CreateInvoice/CreateInvoice.tsx @@ -79,26 +79,26 @@ const CreateInvoice: React.FC = ({ fetchInvoiceDetails() }, []) - useEffect(() => { - const fetchData = async () => { - try { - const session = await getSession(); + // useEffect(() => { + // const fetchData = async () => { + // try { + // const session = await getSession(); - if (session?.accessToken) { - const accessToken = session.accessToken; - // Use the access token as needed - setAccessToken(accessToken) - console.log(accessToken); - } else { - throw new Error('Access token not found in the session.'); - } - } catch (error) { - console.error(error); - } - }; + // if (session?.accessToken) { + // const accessToken = session.accessToken; + // // Use the access token as needed + // setAccessToken(accessToken) + // console.log(accessToken); + // } else { + // throw new Error('Access token not found in the session.'); + // } + // } catch (error) { + // console.error(error); + // } + // }; - fetchData(); - }, []); + // fetchData(); + // }, []); const handleCancel = () => { From 7eff0285e78a90d84ca572f9393021ba0ea84ed2 Mon Sep 17 00:00:00 2001 From: "MSI\\2Fi" Date: Thu, 25 Apr 2024 19:19:43 +0800 Subject: [PATCH 07/20] Invoice --- src/components/CreateInvoice/CreateInvoice.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/CreateInvoice/CreateInvoice.tsx b/src/components/CreateInvoice/CreateInvoice.tsx index e85d249..a616f2f 100644 --- a/src/components/CreateInvoice/CreateInvoice.tsx +++ b/src/components/CreateInvoice/CreateInvoice.tsx @@ -130,8 +130,8 @@ const CreateInvoice: React.FC = ({ // Check if the request was successful if (response.ok) { // Extract the filename from the response headers - const contentDisposition = response.headers.get('content-disposition'); - console.log(contentDisposition) + const contentDisposition = response.headers.get("Content-Disposition"); + // response.headers.forEach(e => console.log(e)) const fileName = contentDisposition ? contentDisposition.split('filename=')[1] : 'invoice.pdf'; @@ -145,7 +145,7 @@ const CreateInvoice: React.FC = ({ // Create a link element to trigger the file download const link = document.createElement('a'); link.href = url; - link.download = fileName; + link.download = fileName.replace(/"/g, ''); link.click(); // Clean up the temporary URL From e89668fa5526e156c3376b675e335e892f77e96f Mon Sep 17 00:00:00 2001 From: "MSI\\2Fi" Date: Thu, 25 Apr 2024 19:37:57 +0800 Subject: [PATCH 08/20] fetchUtil, accept application/json --- src/app/utils/fetchUtil.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/utils/fetchUtil.ts b/src/app/utils/fetchUtil.ts index ca12311..0aaa798 100644 --- a/src/app/utils/fetchUtil.ts +++ b/src/app/utils/fetchUtil.ts @@ -15,6 +15,7 @@ export const serverFetch: typeof fetch = async (input, init) => { ...(accessToken ? { Authorization: `Bearer ${accessToken}`, + Accept: "application/json" } : {}), }, From 689893be78047ec082bee8a0187b80e1565e8f32 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Thu, 25 Apr 2024 19:41:17 +0800 Subject: [PATCH 09/20] update --- src/components/CreateStaff/CreateStaff.tsx | 45 ++++++++-------------- 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/src/components/CreateStaff/CreateStaff.tsx b/src/components/CreateStaff/CreateStaff.tsx index 35b8ded..a76da6b 100644 --- a/src/components/CreateStaff/CreateStaff.tsx +++ b/src/components/CreateStaff/CreateStaff.tsx @@ -20,12 +20,6 @@ import { fetchPositionCombo } from "@/app/api/positions/actions"; import { fetchGradeCombo } from "@/app/api/grades/actions"; import { fetchSkillCombo } from "@/app/api/skill/actions"; import { fetchSalaryCombo } from "@/app/api/salarys/actions"; -// import { fetchTeamCombo } from "@/app/api/team/actions"; -// import { fetchDepartmentCombo } from "@/app/api/departments/actions"; -// import { fetchPositionCombo } from "@/app/api/positions/actions"; -// import { fetchGradeCombo } from "@/app/api/grades/actions"; -// import { fetchSkillCombo } from "@/app/api/skill/actions"; -// import { fetchSalaryCombo } from "@/app/api/salarys/actions"; interface Field { // subtitle: string; @@ -157,63 +151,61 @@ const CreateStaff: React.FC = ({ Title }) => { id: "staffId", label: t("Staff ID"), type: "text", - value: "", required: true, }, { id: "name", label: t("Staff Name"), type: "text", - value: "", required: true, }, { id: "companyId", label: t("Company"), type: "combo-Obj", - options: companyCombo, + options: companyCombo || [], required: true, }, { id: "teamId", label: t("Team"), type: "combo-Obj", - options: teamCombo, + options: teamCombo || [], required: false, }, { id: "departmentId", label: t("Department"), type: "combo-Obj", - options: departmentCombo, + options: departmentCombo || [], required: true, }, { id: "gradeId", label: t("Grade"), type: "combo-Obj", - options: gradeCombo, + options: gradeCombo || [], required: false, }, { id: "skillSetId", label: t("Skillset"), type: "combo-Obj", - options: skillCombo, + options: skillCombo || [], required: false, }, { id: "currentPositionId", label: t("Current Position"), type: "combo-Obj", - options: positionCombo, + options: positionCombo || [], required: true, }, { id: "salaryId", label: t("Salary Point"), type: "combo-Obj", - options: salaryCombo, + options: salaryCombo || [], required: true, }, // { @@ -223,6 +215,12 @@ const CreateStaff: React.FC = ({ Title }) => { // value: "", // required: false, // }, + { + id: "hourlyRate", + label: t("Hourly Rate"), + type: "numeric-testing", + required: true, + }, { id: "employType", label: t("Employ Type"), @@ -235,7 +233,6 @@ const CreateStaff: React.FC = ({ Title }) => { id: "email", label: t("Email"), type: "text", - value: "", pattern: "^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$", message: t("input matching format"), required: true, @@ -244,8 +241,7 @@ const CreateStaff: React.FC = ({ Title }) => { id: "phone1", label: t("Phone1"), type: "text", - value: "", - // pattern: "^\\d{8}$", + pattern: "^\\d{8}$", message: t("input correct phone no."), required: true, }, @@ -253,8 +249,7 @@ const CreateStaff: React.FC = ({ Title }) => { id: "phone2", label: t("Phone2"), type: "text", - value: "", - // pattern: "^\\d{8}$", + pattern: "^\\d{8}$", message: t("input correct phone no."), required: false, }, @@ -264,15 +259,13 @@ const CreateStaff: React.FC = ({ Title }) => { id: "emergContactName", label: t("Emergency Contact Name"), type: "text", - value: "", required: true, }, { id: "emergContactPhone", label: t("Emergency Contact Phone"), type: "text", - value: "", - // pattern: "^\\d{8}$", + pattern: "^\\d{8}$", message: t("input correct phone no."), required: true, }, @@ -280,33 +273,29 @@ const CreateStaff: React.FC = ({ Title }) => { id: "joinDate", label: t("Join Date"), type: "multiDate", - value: "", required: true, }, { id: "joinPositionId", label: t("Join Position"), type: "combo-Obj", - options: positionCombo, + options: positionCombo || [], required: true, }, { id: "departDate", label: t("Depart Date"), type: "multiDate", - value: "", }, { id: "departReason", label: t("Depart Reason"), type: "text", - value: "", }, { id: "remark", label: t("Remark"), type: "remarks", - value: "", }, ] ]; From 36e36921bf4c4a5e9d3715dceaef6b08bfd20cf0 Mon Sep 17 00:00:00 2001 From: "MSI\\2Fi" Date: Thu, 25 Apr 2024 19:50:14 +0800 Subject: [PATCH 10/20] minor fix --- src/components/CreateInvoice/CreateInvoice.tsx | 3 ++- src/components/CreateInvoice/ProjectTotalFee.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/CreateInvoice/CreateInvoice.tsx b/src/components/CreateInvoice/CreateInvoice.tsx index a616f2f..aa400e5 100644 --- a/src/components/CreateInvoice/CreateInvoice.tsx +++ b/src/components/CreateInvoice/CreateInvoice.tsx @@ -121,7 +121,8 @@ const CreateInvoice: React.FC = ({ method: 'POST', headers: { 'Content-Type': 'application/json', - Authorization: `Bearer ${accessToken}` + Authorization: `Bearer ${accessToken}`, + Accept: "application/json" }, body: JSON.stringify(tempData), diff --git a/src/components/CreateInvoice/ProjectTotalFee.tsx b/src/components/CreateInvoice/ProjectTotalFee.tsx index 253332a..c4ac75a 100644 --- a/src/components/CreateInvoice/ProjectTotalFee.tsx +++ b/src/components/CreateInvoice/ProjectTotalFee.tsx @@ -37,7 +37,7 @@ const ProjectTotalFee: React.FC= ({}) => { {t("Project Total Fee")} - {moneyFormatter.format(projectTotal += amount)} + {moneyFormatter.format(amount ? projectTotal += amount : projectTotal)} ); From cfd24a260f2b2c87d9255f03cfb94abb51764978 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Thu, 25 Apr 2024 19:55:33 +0800 Subject: [PATCH 11/20] update --- src/components/CreateStaff/CreateStaff.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/CreateStaff/CreateStaff.tsx b/src/components/CreateStaff/CreateStaff.tsx index a76da6b..312f18e 100644 --- a/src/components/CreateStaff/CreateStaff.tsx +++ b/src/components/CreateStaff/CreateStaff.tsx @@ -215,12 +215,12 @@ const CreateStaff: React.FC = ({ Title }) => { // value: "", // required: false, // }, - { - id: "hourlyRate", - label: t("Hourly Rate"), - type: "numeric-testing", - required: true, - }, + // { + // id: "hourlyRate", + // label: t("Hourly Rate"), + // type: "numeric-testing", + // required: true, + // }, { id: "employType", label: t("Employ Type"), From a04c18d13b2a9987144ad5b1983a7452670a8439 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Thu, 25 Apr 2024 20:02:10 +0800 Subject: [PATCH 12/20] update --- src/components/EditStaff/EditStaff.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/EditStaff/EditStaff.tsx b/src/components/EditStaff/EditStaff.tsx index 5a75dc3..82966e7 100644 --- a/src/components/EditStaff/EditStaff.tsx +++ b/src/components/EditStaff/EditStaff.tsx @@ -122,9 +122,10 @@ const EditStaff: React.FC = async () => { }, [searchParams]); useEffect(() => { - let id = 0; + // let id = 0; if (idString) { - id = parseInt(idString); + const id = parseInt(idString); + console.log(id) setId(id); } fetchStaffEdit(id).then((staff) => { @@ -334,7 +335,6 @@ const EditStaff: React.FC = async () => { return ( <> - {/* {console.log(fieldLists)} */} ); From 70ea82e2ca054d8489c2058923ed28cd2e799027 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Thu, 25 Apr 2024 20:06:01 +0800 Subject: [PATCH 13/20] update claim --- src/components/ClaimDetail/ClaimFormInputGrid.tsx | 9 +++++---- src/components/ClaimSearch/ClaimSearch.tsx | 3 ++- src/components/SearchResults/SearchResults.tsx | 7 +++++-- src/i18n/en/claim.json | 2 +- src/i18n/zh/claim.json | 2 +- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/components/ClaimDetail/ClaimFormInputGrid.tsx b/src/components/ClaimDetail/ClaimFormInputGrid.tsx index 9a98179..24807bd 100644 --- a/src/components/ClaimDetail/ClaimFormInputGrid.tsx +++ b/src/components/ClaimDetail/ClaimFormInputGrid.tsx @@ -41,7 +41,7 @@ import { ProjectCombo } from "@/app/api/claims"; import { ClaimDetailTable, ClaimInputFormByStaff } from "@/app/api/claims/actions"; import { useFieldArray, useFormContext } from "react-hook-form"; import { GridRenderEditCellParams } from "@mui/x-data-grid"; -import { convertDateToString } from "@/app/utils/formatUtil"; +import { convertDateToString, moneyFormatter } from "@/app/utils/formatUtil"; interface BottomBarProps { getCostTotal: () => number; @@ -397,11 +397,12 @@ const ClaimFormInputGrid: React.FC = ({ }, { field: "amount", - headerName: t("Amount (HKD)"), + headerName: t("Amount"), editable: true, type: "number", + align: "right", valueFormatter: (params) => { - return `$ ${params.value ?? 0}`; + return moneyFormatter.format(params.value ?? 0); }, }, { @@ -423,7 +424,7 @@ const ClaimFormInputGrid: React.FC = ({ ); }, renderEditCell: (params) => { - const currentRow = rows.find(row => row.id === params.row.id); + // const currentRow = rows.find(row => row.id === params.row.id); return params.formattedValue ? ( handleLinkClick(params)} href="#">{params.formattedValue} diff --git a/src/components/ClaimSearch/ClaimSearch.tsx b/src/components/ClaimSearch/ClaimSearch.tsx index cafdec5..c0ab01f 100644 --- a/src/components/ClaimSearch/ClaimSearch.tsx +++ b/src/components/ClaimSearch/ClaimSearch.tsx @@ -20,6 +20,7 @@ type SearchParamNames = keyof SearchQuery; const ClaimSearch: React.FC = ({ claims }) => { const { t } = useTranslation(); + console.log(claims) // If claim searching is done on the server-side, then no need for this. const [filteredClaims, setFilteredClaims] = useState(claims); @@ -58,7 +59,7 @@ const ClaimSearch: React.FC = ({ claims }) => { { name: "created", label: t("Creation Date"), type: "date" }, { name: "code", label: t("Claim Code") }, // { name: "project", label: t("Related Project Name") }, - { name: "amount", label: t("Amount (HKD)") }, + { name: "amount", label: t("Amount"), type: "money" }, { name: "type", label: t("Expense Type"), needTranslation: true }, { name: "status", label: t("Status"), needTranslation: true }, { name: "remarks", label: t("Remarks") }, diff --git a/src/components/SearchResults/SearchResults.tsx b/src/components/SearchResults/SearchResults.tsx index 9dfb6e4..d057792 100644 --- a/src/components/SearchResults/SearchResults.tsx +++ b/src/components/SearchResults/SearchResults.tsx @@ -14,7 +14,7 @@ import TableRow from "@mui/material/TableRow"; import IconButton, { IconButtonOwnProps, IconButtonPropsColorOverrides } from "@mui/material/IconButton"; import { t } from "i18next"; import { useTranslation } from "react-i18next"; -import { convertDateArrayToString } from "@/app/utils/formatUtil"; +import { convertDateArrayToString, moneyFormatter } from "@/app/utils/formatUtil"; export interface ResultWithId { id: string | number; @@ -80,7 +80,7 @@ function SearchResults({ {columns.map((column, idx) => ( - {column.label} + {column?.type === "money" ?
{column.label}
: column.label}
))}
@@ -106,6 +106,9 @@ function SearchResults({ ) : column?.type === "date" ? ( <>{convertDateArrayToString(item[columnName] as number[])} + ) : + column?.type === "money" ? ( +
{moneyFormatter.format(item[columnName] as number)}
) : ( <>{column?.needTranslation ? t(item[columnName] as string) : item[columnName]} diff --git a/src/i18n/en/claim.json b/src/i18n/en/claim.json index 16b28f6..51f657c 100644 --- a/src/i18n/en/claim.json +++ b/src/i18n/en/claim.json @@ -8,7 +8,7 @@ "Related Project Name": "Related Project Name", "Expense Type": "Expense Type", "Status": "Status", - "Amount (HKD)": "Amount (HKD)", + "Amount": "Amount", "Remarks": "Remarks", "Invoice Date": "Invoice Date", "Supporting Document": "Supporting Document", diff --git a/src/i18n/zh/claim.json b/src/i18n/zh/claim.json index 22c11cb..92e5b7e 100644 --- a/src/i18n/zh/claim.json +++ b/src/i18n/zh/claim.json @@ -8,7 +8,7 @@ "Related Project Name": "相關項目名稱", "Expense Type": "費用類別", "Status": "狀態", - "Amount (HKD)": "金額 (HKD)", + "Amount": "金額", "Remarks": "備註", "Invoice Date": "收據日期", "Supporting Document": "支援文件", From fe1a052762ab2cb6fd8c5739d8b0901e4f182768 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Thu, 25 Apr 2024 20:13:31 +0800 Subject: [PATCH 14/20] update --- src/components/EditStaff/EditStaff.tsx | 400 ++++++++++++------------- 1 file changed, 200 insertions(+), 200 deletions(-) diff --git a/src/components/EditStaff/EditStaff.tsx b/src/components/EditStaff/EditStaff.tsx index 82966e7..44e7510 100644 --- a/src/components/EditStaff/EditStaff.tsx +++ b/src/components/EditStaff/EditStaff.tsx @@ -122,216 +122,216 @@ const EditStaff: React.FC = async () => { }, [searchParams]); useEffect(() => { - // let id = 0; + let id = 0; if (idString) { - const id = parseInt(idString); + id = parseInt(idString); console.log(id) - setId(id); - } - fetchStaffEdit(id).then((staff) => { - console.log(staff.data); - const data = staff.data; - ///////////////////// list 1 ///////////////////// - const list1 = keyOrder1 - .map((key) => { - switch (key) { - case "staffId": - return { - id: `${key}`, - label: t(`Staff ID`), - type: "text", - value: data[key] ?? "", - required: true, - }; - case "name": - return { - id: `${key}`, - label: t(`Staff Name`), - type: "text", - value: data[key] ?? "", - required: true, - }; - case "company": - return { - id: `${key}Id`, - label: t(`Company`), - type: "combo-Obj", - options: companyCombo, - value: data[key].id ?? "", - required: true, - }; - case "team": - return { - id: `${key}Id`, - label: t(`Team`), - type: "combo-Obj", - options: teamCombo, - value: data[key]?.id ?? "", - }; - case "department": - return { - id: `${key}Id`, - label: t(`Department`), - type: "combo-Obj", - options: departmentCombo, - value: data[key]?.id ?? "", - required: true, - // later check - }; - case "grade": - return { - id: `${key}Id`, - label: t(`Grade`), - type: "combo-Obj", - options: gradeCombo, - value: data[key] !== null ? data[key].id ?? "" : "", - }; - case "skill": - return { - id: `${key}SetId`, - label: t(`Skillset`), - type: "combo-Obj", - options: skillCombo, - value: data[key] !== null ? data[key].id ?? "" : "", - }; - case "currentPosition": - return { - id: `${key}Id`, - label: t(`Current Position`), - type: "combo-Obj", - options: positionCombo, - value: data[key].id ?? "", - required: true, - }; - case "salary": - return { - id: `salaryId`, - label: t(`Salary Point`), - type: "combo-Obj", - options: salaryCombo, - value: data[key] !== null ? data[key].id ?? "" : "", - required: true, - }; - // case "hourlyRate": - // return { - // id: `${key}`, - // label: t(`hourlyRate`), - // type: "text", - // value: "", - // // value: data[key], - // readOnly: true, - // }; - case "employType": - return { - id: `${key}`, - label: t(`Employ Type`), - type: "combo-Obj", - options: employTypeCombo, - value: data[key] ?? "", - required: true, - }; - case "email": - return { - id: `${key}`, - label: t(`Email`), - type: "text", - value: data[key] ?? "", - pattern: "^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$", - message: t("input matching format"), - required: true, - }; - case "phone1": - return { - id: `${key}`, - label: t(`Phone1`), - type: "text", - // pattern: "^\\d{8}$", - message: t("input correct phone no."), - value: data[key] ?? "", - required: true, - }; - case "phone2": - return { - id: `${key}`, - label: t(`Phone2`), - type: "text", - // pattern: "^\\d{8}$", - message: t("input correct phone no."), - value: data[key] ?? "", - } as Field; - default: - return null; - } - }).filter((item): item is Field => item !== null); - ///////////////////// list 2 ///////////////////// - const list2 = keyOrder2 - .map((key) => { - switch (key) { - case "emergContactName": - return { - id: `${key}`, - label: t(`Emergency Contact Name`), - type: "text", - value: data[key] ?? "", - required: true, - } as Field; - case "emergContactPhone": + fetchStaffEdit(id).then((staff) => { + console.log(staff.data); + const data = staff.data; + ///////////////////// list 1 ///////////////////// + const list1 = keyOrder1 + .map((key) => { + switch (key) { + case "staffId": return { id: `${key}`, - label: t(`Emergency Contact Phone`), + label: t(`Staff ID`), + type: "text", + value: data[key] ?? "", + required: true, + }; + case "name": + return { + id: `${key}`, + label: t(`Staff Name`), + type: "text", + value: data[key] ?? "", + required: true, + }; + case "company": + return { + id: `${key}Id`, + label: t(`Company`), + type: "combo-Obj", + options: companyCombo, + value: data[key].id ?? "", + required: true, + }; + case "team": + return { + id: `${key}Id`, + label: t(`Team`), + type: "combo-Obj", + options: teamCombo, + value: data[key]?.id ?? "", + }; + case "department": + return { + id: `${key}Id`, + label: t(`Department`), + type: "combo-Obj", + options: departmentCombo, + value: data[key]?.id ?? "", + required: true, + // later check + }; + case "grade": + return { + id: `${key}Id`, + label: t(`Grade`), + type: "combo-Obj", + options: gradeCombo, + value: data[key] !== null ? data[key].id ?? "" : "", + }; + case "skill": + return { + id: `${key}SetId`, + label: t(`Skillset`), + type: "combo-Obj", + options: skillCombo, + value: data[key] !== null ? data[key].id ?? "" : "", + }; + case "currentPosition": + return { + id: `${key}Id`, + label: t(`Current Position`), + type: "combo-Obj", + options: positionCombo, + value: data[key].id ?? "", + required: true, + }; + case "salary": + return { + id: `salaryId`, + label: t(`Salary Point`), + type: "combo-Obj", + options: salaryCombo, + value: data[key] !== null ? data[key].id ?? "" : "", + required: true, + }; + // case "hourlyRate": + // return { + // id: `${key}`, + // label: t(`hourlyRate`), + // type: "text", + // value: "", + // // value: data[key], + // readOnly: true, + // }; + case "employType": + return { + id: `${key}`, + label: t(`Employ Type`), + type: "combo-Obj", + options: employTypeCombo, + value: data[key] ?? "", + required: true, + }; + case "email": + return { + id: `${key}`, + label: t(`Email`), + type: "text", + value: data[key] ?? "", + pattern: "^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$", + message: t("input matching format"), + required: true, + }; + case "phone1": + return { + id: `${key}`, + label: t(`Phone1`), type: "text", // pattern: "^\\d{8}$", message: t("input correct phone no."), value: data[key] ?? "", required: true, + }; + case "phone2": + return { + id: `${key}`, + label: t(`Phone2`), + type: "text", + // pattern: "^\\d{8}$", + message: t("input correct phone no."), + value: data[key] ?? "", } as Field; - case "joinDate": - return { - id: `${key}`, - label: t(`Join Date`), - type: "multiDate", - value: data[key] ?? "", - required: true, - } as Field; - case "joinPosition": - return { - id: `${key}Id`, - label: t(`Join Position`), - type: "combo-Obj", - options: positionCombo, - value: data[key].id ?? "", - required: true, - } as Field; - case "departDate": - return { - id: `${key}`, - label: t(`Depart Date`), - type: "multiDate", - value: data[key] ?? "", - } as Field; - case "departReason": - return { - id: `${key}`, - label: t(`Depart Reason`), - type: "text", - value: data[key] ?? "", - } as Field; - case "remark": - return { - id: `remark`, - label: t(`Remark`), - type: "remarks", - value: data[key] ?? "", - } as Field; - default: - return null; - } - }).filter((item): item is Field => item !== null); - console.log(list2); - console.log([list1]); - setFieldLists([list1,list2]); - }); - }, [companyCombo]); + default: + return null; + } + }).filter((item): item is Field => item !== null); + ///////////////////// list 2 ///////////////////// + const list2 = keyOrder2 + .map((key) => { + switch (key) { + case "emergContactName": + return { + id: `${key}`, + label: t(`Emergency Contact Name`), + type: "text", + value: data[key] ?? "", + required: true, + } as Field; + case "emergContactPhone": + return { + id: `${key}`, + label: t(`Emergency Contact Phone`), + type: "text", + // pattern: "^\\d{8}$", + message: t("input correct phone no."), + value: data[key] ?? "", + required: true, + } as Field; + case "joinDate": + return { + id: `${key}`, + label: t(`Join Date`), + type: "multiDate", + value: data[key] ?? "", + required: true, + } as Field; + case "joinPosition": + return { + id: `${key}Id`, + label: t(`Join Position`), + type: "combo-Obj", + options: positionCombo, + value: data[key].id ?? "", + required: true, + } as Field; + case "departDate": + return { + id: `${key}`, + label: t(`Depart Date`), + type: "multiDate", + value: data[key] ?? "", + } as Field; + case "departReason": + return { + id: `${key}`, + label: t(`Depart Reason`), + type: "text", + value: data[key] ?? "", + } as Field; + case "remark": + return { + id: `remark`, + label: t(`Remark`), + type: "remarks", + value: data[key] ?? "", + } as Field; + default: + return null; + } + }).filter((item): item is Field => item !== null); + console.log(list2); + console.log([list1]); + setFieldLists([list1,list2]); + }); + } + + }, [companyCombo, idString]); return ( <> From 4f7e68e481862371b9075f6199512e7831f7ed29 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Thu, 25 Apr 2024 20:28:36 +0800 Subject: [PATCH 15/20] update --- src/components/EditStaff/EditStaff.tsx | 5 ++- .../EditStaffForm/EditStaffForm.tsx | 32 ++++++++++--------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/components/EditStaff/EditStaff.tsx b/src/components/EditStaff/EditStaff.tsx index 44e7510..bcc7346 100644 --- a/src/components/EditStaff/EditStaff.tsx +++ b/src/components/EditStaff/EditStaff.tsx @@ -53,7 +53,6 @@ const EditStaff: React.FC = async () => { const searchParams = useSearchParams(); const { t } = useTranslation(); const idString = searchParams.get("id"); - const [id, setId] = useState(0); const [fieldLists, setFieldLists] = useState(); const [companyCombo, setCompanyCombo] = useState(); const [teamCombo, setTeamCombo] = useState(); @@ -331,11 +330,11 @@ const EditStaff: React.FC = async () => { }); } - }, [companyCombo, idString]); + }, [companyCombo, teamCombo, departmentCombo, positionCombo, gradeCombo, skillCombo, salaryCombo, idString]); return ( <> - + ); }; diff --git a/src/components/EditStaffForm/EditStaffForm.tsx b/src/components/EditStaffForm/EditStaffForm.tsx index a02ca88..f15be32 100644 --- a/src/components/EditStaffForm/EditStaffForm.tsx +++ b/src/components/EditStaffForm/EditStaffForm.tsx @@ -26,14 +26,15 @@ interface Field { } interface formProps { - id: number; Title?: string[]; fieldLists: Field[][]; } -const EditStaffForm: React.FC = ({ id, Title, fieldLists }) => { +const EditStaffForm: React.FC = ({ Title, fieldLists }) => { const router = useRouter(); const { t } = useTranslation(); + const searchParams = useSearchParams(); + const idString = searchParams.get("id") const [serverError, setServerError] = useState(""); // make new inputs const onSubmit = useCallback>( @@ -49,20 +50,21 @@ const EditStaffForm: React.FC = ({ id, Title, fieldLists }) => { const formattedDate = dayjs(data.departDate, 'MM/DD/YYYY').format('YYYY-MM-DD'); formatDepartDate = formattedDate; } - // console.log(data); - const temp = { - id: id, - ...data, - emergContactPhone: data.emergContactPhone.toString(), - phone1: data.phone1.toString(), - phone2: data.phone1.toString(), - joinDate: formatJoinDate, - departDate: formatDepartDate, + if (idString) { + const temp = { + id: parseInt(idString), + ...data, + emergContactPhone: data.emergContactPhone.toString(), + phone1: data.phone1.toString(), + phone2: data.phone1.toString(), + joinDate: formatJoinDate, + departDate: formatDepartDate, + } + console.log(temp) + setServerError(""); + await saveStaff(temp); + router.replace("/settings/staff"); } - console.log(temp) - setServerError(""); - await saveStaff(temp); - router.replace("/settings/staff"); } catch (e) { setServerError(t("An error has occurred. Please try again later.")); } From 9e071674b2a85ca914c47208b912797bd9307a76 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Thu, 25 Apr 2024 20:38:38 +0800 Subject: [PATCH 16/20] URL --- src/components/CreateInvoice/CreateInvoice.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/CreateInvoice/CreateInvoice.tsx b/src/components/CreateInvoice/CreateInvoice.tsx index aa400e5..676a708 100644 --- a/src/components/CreateInvoice/CreateInvoice.tsx +++ b/src/components/CreateInvoice/CreateInvoice.tsx @@ -24,6 +24,7 @@ import ProjectTotalFee from "./ProjectTotalFee"; import { timestampToDateString } from "@/app/utils/formatUtil"; import dayjs from "dayjs"; import { getSession } from "next-auth/react" +import { BASE_API_URL } from "@/config/api"; const CreateInvoice: React.FC = ({ }) => { @@ -117,7 +118,7 @@ const CreateInvoice: React.FC = ({ try { // Make an API request to generate the JasperReport - const response = await fetch(`http://localhost:8090/api/invoices/pdf`, { + const response = await fetch(`${BASE_API_URL}/invoices/pdf`, { method: 'POST', headers: { 'Content-Type': 'application/json', From 1fcb9c2105022e4c71f01dda1a0f77feb6eaa937 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Thu, 25 Apr 2024 20:45:03 +0800 Subject: [PATCH 17/20] update team --- src/components/NavigationContent/NavigationContent.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx index 53dbb5c..405f454 100644 --- a/src/components/NavigationContent/NavigationContent.tsx +++ b/src/components/NavigationContent/NavigationContent.tsx @@ -22,6 +22,7 @@ import Company from '@mui/icons-material/Store'; import Department from '@mui/icons-material/Diversity3'; import Position from '@mui/icons-material/Paragliding'; import Salary from '@mui/icons-material/AttachMoney'; +import Team from '@mui/icons-material/Paragliding'; import { useTranslation } from "react-i18next"; import Typography from "@mui/material/Typography"; import { usePathname } from "next/navigation"; @@ -117,6 +118,7 @@ const navigationItems: NavigationItem[] = [ { icon: , label: "Department", path: "/settings/department" }, { icon: , label: "Position", path: "/settings/position" }, { icon: , label: "Salary", path: "/settings/salary" }, + { icon: , label: "Team", path: "/settings/team" }, ], }, ]; From 8b1cf493ac33fe50366184aa37aa05fb4ce1a1d8 Mon Sep 17 00:00:00 2001 From: "MSI\\2Fi" Date: Thu, 25 Apr 2024 20:51:01 +0800 Subject: [PATCH 18/20] commit --- src/components/CreateInvoice/CreateInvoice.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CreateInvoice/CreateInvoice.tsx b/src/components/CreateInvoice/CreateInvoice.tsx index 676a708..dfbcf84 100644 --- a/src/components/CreateInvoice/CreateInvoice.tsx +++ b/src/components/CreateInvoice/CreateInvoice.tsx @@ -118,7 +118,7 @@ const CreateInvoice: React.FC = ({ try { // Make an API request to generate the JasperReport - const response = await fetch(`${BASE_API_URL}/invoices/pdf`, { + const response = await fetch(`https://tsms-uat.2fi-solutions.com/api/invoices/pdf`, { method: 'POST', headers: { 'Content-Type': 'application/json', From 51c9834c763b1cbd3f443db1c980eda54c106e87 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Thu, 25 Apr 2024 21:00:23 +0800 Subject: [PATCH 19/20] URL --- src/components/CreateInvoice/CreateInvoice.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CreateInvoice/CreateInvoice.tsx b/src/components/CreateInvoice/CreateInvoice.tsx index dfbcf84..94f176b 100644 --- a/src/components/CreateInvoice/CreateInvoice.tsx +++ b/src/components/CreateInvoice/CreateInvoice.tsx @@ -118,7 +118,7 @@ const CreateInvoice: React.FC = ({ try { // Make an API request to generate the JasperReport - const response = await fetch(`https://tsms-uat.2fi-solutions.com/api/invoices/pdf`, { + const response = await fetch(`https://tsms-uat.2fi-solutions.com/invoices/pdf`, { method: 'POST', headers: { 'Content-Type': 'application/json', From 74ee9e2c879cc670398cfd4ac8f820d64320d28d Mon Sep 17 00:00:00 2001 From: "MSI\\2Fi" Date: Thu, 25 Apr 2024 21:28:48 +0800 Subject: [PATCH 20/20] URL --- src/components/CreateInvoice/CreateInvoice.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CreateInvoice/CreateInvoice.tsx b/src/components/CreateInvoice/CreateInvoice.tsx index 94f176b..0ed2dd8 100644 --- a/src/components/CreateInvoice/CreateInvoice.tsx +++ b/src/components/CreateInvoice/CreateInvoice.tsx @@ -118,7 +118,7 @@ const CreateInvoice: React.FC = ({ try { // Make an API request to generate the JasperReport - const response = await fetch(`https://tsms-uat.2fi-solutions.com/invoices/pdf`, { + const response = await fetch(`https://tsms-uat.2fi-solutions.com/back-api/invoices/pdf`, { method: 'POST', headers: { 'Content-Type': 'application/json',