From 00515e2445ec50b64a71214653e231f96d347142 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Wed, 16 Apr 2025 11:05:07 +0800 Subject: [PATCH 1/3] update --- src/app/api/invoices/actions.ts | 4 +- .../InvoiceSearch/CreateInvoiceModal.tsx | 2 +- .../InvoiceSearch/InvoiceSearch.tsx | 16 +++-- .../SearchResults/SearchResults.tsx | 67 +++++++++++++++++-- 4 files changed, 75 insertions(+), 14 deletions(-) diff --git a/src/app/api/invoices/actions.ts b/src/app/api/invoices/actions.ts index d98c5e8..d40bb41 100644 --- a/src/app/api/invoices/actions.ts +++ b/src/app/api/invoices/actions.ts @@ -2,7 +2,7 @@ import { serverFetchJson, serverFetchString, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; import { BASE_API_URL } from "@/config/api"; -import { revalidateTag } from "next/cache"; +import { revalidatePath, revalidateTag } from "next/cache"; import { cache } from "react"; export interface InvoiceResult { @@ -145,8 +145,8 @@ export const updateInvoice = async (data: any) => { body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }); - revalidateTag("invoices") + revalidatePath("/(main)/invoice") return updateInvoice; } diff --git a/src/components/InvoiceSearch/CreateInvoiceModal.tsx b/src/components/InvoiceSearch/CreateInvoiceModal.tsx index 727ac98..7720673 100644 --- a/src/components/InvoiceSearch/CreateInvoiceModal.tsx +++ b/src/components/InvoiceSearch/CreateInvoiceModal.tsx @@ -69,7 +69,7 @@ const CreateInvoiceModal: React.FC = ({isOpen, onClose, projects, invoice if (response === "OK") { onClose() successDialog(t("Submit Success"), t).then(() => { - router.replace("/invoice"); + // router.replace("/invoice"); }) } }, t) diff --git a/src/components/InvoiceSearch/InvoiceSearch.tsx b/src/components/InvoiceSearch/InvoiceSearch.tsx index 1e2a517..cb098a9 100644 --- a/src/components/InvoiceSearch/InvoiceSearch.tsx +++ b/src/components/InvoiceSearch/InvoiceSearch.tsx @@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next"; import SearchResults, { Column } from "../SearchResults"; import EditNote from "@mui/icons-material/EditNote"; import { moneyFormatter } from "@/app/utils/formatUtil" -import { Button, ButtonGroup, Stack, Tab, Tabs, TabsProps, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, TextField, CardContent, Typography, Divider, Card } from "@mui/material"; +import { Button, ButtonGroup, Stack, Tab, Tabs, TabsProps, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, TextField, CardContent, Typography, Divider, Card, ThemeProvider } from "@mui/material"; import FileUploadIcon from '@mui/icons-material/FileUpload'; import AddIcon from '@mui/icons-material/Add'; import { deleteInvoice, importIssuedInovice, importReceivedInovice, updateInvoice } from "@/app/api/invoices/actions"; @@ -23,6 +23,8 @@ import { ProjectResult } from "@/app/api/projects"; import { IMPORT_INVOICE, IMPORT_RECEIPT } from "@/middleware"; import InvoiceSearchLoading from "./InvoiceSearchLoading"; import { NumberFormatValues, NumericFormat } from "react-number-format"; +import theme from "@/theme"; +import { SMALL_ROW_THEME } from "@/theme/colorConst"; interface CustomMoneyComponentProps { params: GridRenderEditCellParams; @@ -614,11 +616,13 @@ const InvoiceSearch: React.FC & SubComponents = ({ invoices, projects, ab { !loading && // tabIndex == 0 && - - items={filteredIvoices} - columns={combinedColumns} - autoRedirectToFirstPage - /> + + + items={filteredIvoices} + columns={combinedColumns} + autoRedirectToFirstPage + /> + } {t("Edit Invoice")} diff --git a/src/components/SearchResults/SearchResults.tsx b/src/components/SearchResults/SearchResults.tsx index 031bf44..0aee1ab 100644 --- a/src/components/SearchResults/SearchResults.tsx +++ b/src/components/SearchResults/SearchResults.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useEffect } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import Paper from "@mui/material/Paper"; import Table from "@mui/material/Table"; import TableBody from "@mui/material/TableBody"; @@ -18,6 +18,9 @@ import { convertDateArrayToString, moneyFormatter } from "@/app/utils/formatUtil import DoneIcon from '@mui/icons-material/Done'; import CloseIcon from '@mui/icons-material/Close'; import { Button, Link, LinkOwnProps } from "@mui/material"; +import { orderBy, sortBy } from "lodash"; +import ArrowUp from '@mui/icons-material/KeyboardArrowUp'; +import ArrowDown from '@mui/icons-material/KeyboardArrowDown'; export interface ResultWithId { id: string | number; @@ -155,7 +158,50 @@ function SearchResults({ return column.underline ?? "always"; }; - + type OrderProps = Record + const [sortedItems, setSortedItems] = useState(items) + const [orderProps, setOrderProps] = useState(() => { + if (items.length === 0) { + return {} as OrderProps + } + return Object.keys(sortedItems[0]).reduce((acc, key) => { + if (key === "deleted" || key === "id") return acc + acc[key as keyof T] = false; + return acc; + }, {} as OrderProps); + }); + const changeOrder = useCallback((key: keyof T) => { + // preserve all column sorting + // setOrderProps( + // (prev) => ({ + // [...prev]: false, + // [key]: !prev[key] + // }) + // ) + // only sort 1 column + setOrderProps( + (prev) => { + const newOrderProps = Object.keys(prev).reduce((acc, currKey) => { + acc[currKey as keyof T] = currKey === key ? !prev[currKey as keyof T] : false; + return acc; + }, {} as OrderProps); + return newOrderProps; + } + ) + }, []) + const sortingItems = useCallback( + (key: keyof T) => { + // true === asc + // false === desc + console.log(orderProps) + if (orderProps[key]) { + return orderBy(sortedItems, key, 'asc') + } else { + return orderBy(sortedItems, key, 'desc') + } + } + , [sortedItems, orderProps] + ) const table = ( <> @@ -163,14 +209,25 @@ function SearchResults({ {columns.filter(item => item.isHidden !== true).map((column, idx) => ( - + { + changeOrder(column.name) + setSortedItems(sortingItems(column.name)) + }} + > {column?.type === "money" ?
{column.label}
: column.label} + {(() => { + const isAscending = orderProps[column.name]; + if (isAscending === undefined) return undefined; + return isAscending ? : ; + })()}
))}
- {items + {sortedItems .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) .map((item) => { return ( @@ -223,7 +280,7 @@ function SearchResults({ Date: Wed, 16 Apr 2025 11:13:16 +0800 Subject: [PATCH 2/3] merge main to develop --- src/app/utils/formatUtil.ts | 2 +- .../CreateProject/CreateProject.tsx | 54 ++++++++++++------- .../CreateProject/CreateProjectWrapper.tsx | 2 +- src/components/CreateProject/Milestone.tsx | 12 ++++- .../CreateProject/ProjectClientDetails.tsx | 8 +-- src/components/CreateStaff/StaffInfo.tsx | 2 +- src/components/LeaveTable/LeaveTable.tsx | 3 ++ .../NavigationContent/NavigationContent.tsx | 21 ++++---- 8 files changed, 64 insertions(+), 40 deletions(-) diff --git a/src/app/utils/formatUtil.ts b/src/app/utils/formatUtil.ts index 15c7d82..bc72e4d 100644 --- a/src/app/utils/formatUtil.ts +++ b/src/app/utils/formatUtil.ts @@ -32,7 +32,7 @@ export const truncateMoney = (amount: number | undefined) => { const fractionDigits = maximumFractionDigits ?? minimumFractionDigits ?? 0; const factor = Math.pow(10, fractionDigits); - const truncatedAmount = Math.floor(amount * factor) / factor; + const truncatedAmount = Math.round(amount * factor) / factor; return truncatedAmount; }; diff --git a/src/components/CreateProject/CreateProject.tsx b/src/components/CreateProject/CreateProject.tsx index dbde1a1..1842f9a 100644 --- a/src/components/CreateProject/CreateProject.tsx +++ b/src/components/CreateProject/CreateProject.tsx @@ -59,7 +59,7 @@ import { } from "../Swal/CustomAlerts"; import dayjs from "dayjs"; import { DELETE_PROJECT } from "@/middleware"; -import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; +import { OUTPUT_DATE_FORMAT, sumMoney } from "@/app/utils/formatUtil"; import { deleteDraft, loadDraft, saveToLocalStorage } from "@/app/utils/draftUtils"; export interface Props { @@ -273,7 +273,7 @@ const CreateProject: React.FC = ({ const onSubmit = useCallback>( async (data, event) => { try { - console.log(data); + // console.log(data); // detect errors let hasErrors = false; @@ -374,30 +374,43 @@ const CreateProject: React.FC = ({ data.milestones[parseFloat(key)].startDate = null data.milestones[parseFloat(key)].endDate = null } - // if ( - // !Boolean(startDate) || - // startDate === "Invalid Date" || - // !Boolean(endDate) || - // endDate === "Invalid Date" || - // new Date(startDate) > new Date(endDate) - // ) { - // formProps.setError("milestones", { - // message: "milestones is not valid", - // type: "invalid", - // }); - // setTabIndex(3); - // hasErrors = true; - // } - + /* + unused code for checking the stage start date and end date + if ( + !Boolean(startDate) || + startDate === "Invalid Date" || + !Boolean(endDate) || + endDate === "Invalid Date" || + new Date(startDate) > new Date(endDate) + ) { + formProps.setError("milestones", { + message: "milestones is not valid", + type: "invalid", + }); + setTabIndex(3); + hasErrors = true; + } + + unused code for bulk add milestone payment projectTotal += payments.reduce( (acc, payment) => acc + payment.amount, 0, ); + **/ + + projectTotal += payments.reduce( + (acc, p) => sumMoney(acc, p.amount), + 0, + ); + + }); + // console.log(projectTotal) + console.log(milestonesKeys) if ( - projectTotal !== data.expectedProjectFee || - milestonesKeys.length !== taskGroupKeys.length + projectTotal !== data.expectedProjectFee + // || milestonesKeys.length !== taskGroupKeys.length ) { formProps.setError("milestones", { message: "milestones is not valid", @@ -484,7 +497,7 @@ const CreateProject: React.FC = ({ setServerError(t("An error has occurred. Please try again later.")); } }, - [router, t], + [router, t, draftId], ); const onSubmitError = useCallback>( @@ -565,6 +578,7 @@ const CreateProject: React.FC = ({ const saveDraft = useCallback(async () => { const currentTimestamp = Date.now() + console.log(currentTimestamp) saveToLocalStorage(draftId || currentTimestamp, formProps.getValues()); diff --git a/src/components/CreateProject/CreateProjectWrapper.tsx b/src/components/CreateProject/CreateProjectWrapper.tsx index 7595fbe..6dcb46a 100644 --- a/src/components/CreateProject/CreateProjectWrapper.tsx +++ b/src/components/CreateProject/CreateProjectWrapper.tsx @@ -83,7 +83,7 @@ const CreateProjectWrapper: React.FC = async (props) => { var filteredTeamLeads = teamId ? teamLeads.filter( (teamLead) => teamLead.teamId === teamId, ) : teamLeads - if (userStaff?.id !== null && userStaff?.id == 1) { + if (userStaff?.id != null && userStaff?.id == 1) { filteredTeamLeads = teamLeads.filter( (teamLead) => teamLead.teamId === teamId || teamLead.team == "ST", ) diff --git a/src/components/CreateProject/Milestone.tsx b/src/components/CreateProject/Milestone.tsx index 5b4c3a6..69f41bc 100644 --- a/src/components/CreateProject/Milestone.tsx +++ b/src/components/CreateProject/Milestone.tsx @@ -23,6 +23,7 @@ import { useFormContext } from "react-hook-form"; import { CreateProjectInputs } from "@/app/api/projects/actions"; import MilestoneSection from "./MilestoneSection"; import ProjectTotalFee from "./ProjectTotalFee"; +import { sumMoney } from "@/app/utils/formatUtil"; export interface Props { allTasks: Task[]; @@ -89,10 +90,17 @@ const Milestone: React.FC = ({ allTasks, isActive }) => { // hasError = true // } - projectTotal += payments.reduce((acc, payment) => acc + payment.amount, 0) + // projectTotal += payments.reduce((acc, payment) => acc + payment.amount, 0) + projectTotal += payments.reduce( + (acc, p) => sumMoney(acc, p.amount), + 0, + ); + }) + console.log(milestonesKeys) - if (projectTotal !== expectedTotalFee || milestonesKeys.length !== taskGroupsIds.length) { + // if (projectTotal !== expectedTotalFee || milestonesKeys.length !== taskGroupsIds.length) { + if (projectTotal !== expectedTotalFee) { hasError = true } // console.log(Object.keys(milestones).reduce((acc, key) => acc + milestones[parseFloat(key)].payments.reduce((acc2, value) => acc2 + value.amount, 0), 0)) diff --git a/src/components/CreateProject/ProjectClientDetails.tsx b/src/components/CreateProject/ProjectClientDetails.tsx index 4414949..5e86694 100644 --- a/src/components/CreateProject/ProjectClientDetails.tsx +++ b/src/components/CreateProject/ProjectClientDetails.tsx @@ -242,18 +242,18 @@ const ProjectClientDetails: React.FC = ({ // (acc, wn) => ({ ...acc, [wn.id]: wn.name }), // {}, // ); - const planStart = getValues("projectPlanStart") - const planEnd = getValues("projectPlanEnd") + const planStart = watch("projectPlanStart") + const planEnd = watch("projectPlanEnd") useEffect(() => { let hasErrors = false if( - !planStart || planStart > planEnd + !planStart || new Date(planStart) > new Date(planEnd) ){ hasErrors = true; } if( - !planEnd || planStart > planEnd + !planEnd || new Date(planStart) > new Date(planEnd) ){ hasErrors = true; } diff --git a/src/components/CreateStaff/StaffInfo.tsx b/src/components/CreateStaff/StaffInfo.tsx index 212295c..cee90c4 100644 --- a/src/components/CreateStaff/StaffInfo.tsx +++ b/src/components/CreateStaff/StaffInfo.tsx @@ -26,7 +26,7 @@ import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { DemoItem } from "@mui/x-date-pickers/internals/demo"; import dayjs from "dayjs"; import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; -import TableModal from "./TableModal"; +// import TableModal from "./TableModal"; import { Preview } from "@mui/icons-material"; import SalaryEffectiveModel from "../EditStaff/SalaryEffectiveModel"; import TeamHistoryModal from "../EditStaff/TeamHistoryModal"; diff --git a/src/components/LeaveTable/LeaveTable.tsx b/src/components/LeaveTable/LeaveTable.tsx index 4e36f44..c95f4b8 100644 --- a/src/components/LeaveTable/LeaveTable.tsx +++ b/src/components/LeaveTable/LeaveTable.tsx @@ -13,12 +13,14 @@ interface Props { leaveTypes: LeaveType[]; timesheetRecords: RecordTimesheetInput; companyHolidays: HolidaysResult[]; + isSaturdayWorker: boolean; } const LeaveTable: React.FC = ({ leaveTypes, timesheetRecords, companyHolidays, + isSaturdayWorker }) => { const { watch } = useFormContext(); const currentInput = watch(); @@ -32,6 +34,7 @@ const LeaveTable: React.FC = ({ timesheetEntries={timesheetRecords} EntryTableComponent={LeaveEntryTable} entryTableProps={{ leaveTypes }} + isSaturdayWorker={isSaturdayWorker} /> ); }; diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx index f533e20..cf72724 100644 --- a/src/components/NavigationContent/NavigationContent.tsx +++ b/src/components/NavigationContent/NavigationContent.tsx @@ -110,10 +110,19 @@ const NavigationContent: React.FC = ({ abilities, username }) => { path: "/home", showOnMobile: true, }, + // { + // icon: , + // label: "Financial Summary", + // path: "/dashboard/ProjectFinancialSummary", + // isHidden: ![VIEW_DASHBOARD_ALL, VIEW_DASHBOARD_SELF].some((ability) => + // abilities!.includes(ability), + // ), + // showOnMobile: false, + // }, { icon: , label: "Financial Summary", - path: "/dashboard/ProjectFinancialSummary", + path: "/dashboard/ProjectFinancialSummaryV2", isHidden: ![VIEW_DASHBOARD_ALL, VIEW_DASHBOARD_SELF].some((ability) => abilities!.includes(ability), ), @@ -172,16 +181,6 @@ const NavigationContent: React.FC = ({ abilities, username }) => { }, ], }, - - // { - // icon: , - // label: "Financial Summary", - // path: "/dashboard/ProjectFinancialSummaryV2", - // isHidden: ![VIEW_DASHBOARD_ALL, VIEW_DASHBOARD_SELF].some((ability) => - // abilities!.includes(ability), - // ), - // showOnMobile: true, - // }, // No Claim function in Breaur, will be implement later // { From 47e66dee1ce24f4e202a6bb160122d4033f0368f Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Wed, 16 Apr 2025 11:14:15 +0800 Subject: [PATCH 3/3] update --- src/theme/colorConst.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/theme/colorConst.js b/src/theme/colorConst.js index d523dbc..5ca4965 100644 --- a/src/theme/colorConst.js +++ b/src/theme/colorConst.js @@ -176,6 +176,19 @@ export const PW_RULE_THEME = createTheme({ }, }); +export const SMALL_ROW_THEME = createTheme({ + components: { + MuiTableRow: { + styleOverrides: { + root: { + // padding: "0px", + height: "0px" + } + } + } + } +}) + export const TSMS_BUTTON_THEME = createTheme({ palette: { primary: {