diff --git a/src/app/api/customer/index.ts b/src/app/api/customer/index.ts index a2c405a..cbdc0b2 100644 --- a/src/app/api/customer/index.ts +++ b/src/app/api/customer/index.ts @@ -1,4 +1,4 @@ -import { serverFetchJson } from "@/app/utils/fetchUtil"; +import { serverFetchJson, serverFetchString } from "@/app/utils/fetchUtil"; import { BASE_API_URL } from "@/config/api"; import { cache } from "react"; import "server-only"; @@ -88,3 +88,9 @@ export const fetchCustomerTypes = cache(async () => { next: { tags: ["customerTypes"] }, }); }); + +export const getMaxCustomerCode = cache(async () => { + return serverFetchString(`${BASE_API_URL}/customer/getMaxCode`, { + next: { tags: ["customerMaxCode"] }, + }); +}); diff --git a/src/app/api/reports/index.ts b/src/app/api/reports/index.ts index d7079d6..c55a6c7 100644 --- a/src/app/api/reports/index.ts +++ b/src/app/api/reports/index.ts @@ -151,10 +151,12 @@ export interface ProjectMonthlyReportFilter { export interface LastModifiedReportFilter { date: string; + dayRange: number; } export interface LastModifiedReportRequest { dateString: string; + dayRange: number; } export interface ExportCurrentStaffInfoRequest { diff --git a/src/components/CreateProject/BulkAddPaymentModal.tsx b/src/components/CreateProject/BulkAddPaymentModal.tsx index 0d61bb6..166e432 100644 --- a/src/components/CreateProject/BulkAddPaymentModal.tsx +++ b/src/components/CreateProject/BulkAddPaymentModal.tsx @@ -45,6 +45,24 @@ export interface Props extends Omit { modalSx?: SxProps; } +type DatetypeOption = { + value: string; + label: string; + unit: 'week' | 'month' | 'year'; + step: number; +}; + +const datetypeOptions: DatetypeOption[] = [ + { value: "weekly", label: "Weekly", unit: "week", step: 1 }, + { value: "biweekly", label: "Bi-Weekly", unit: "week", step: 2 }, + { value: "monthly", label: "Monthly", unit: "month", step: 1 }, + // { value: "bimonthly", label: "Every two months from", unit: "month", step: 2 }, + { value: "quarterly", label: "Quarterly", unit: "month", step: 3 }, + { value: "half-year", label: "Half Year", unit: "month", step: 6 }, + // { value: "yearly", label: "Yearly from", unit: "year", step: 1 }, + { value: "fixed", label: "Fixed", unit: "month", step: 0 }, // unit/step not used for fixed +]; + const modalSx: SxProps = { position: "absolute", top: "50%", @@ -99,6 +117,9 @@ const BulkAddPaymentModal: React.FC = ({ const amountForLastItem = truncateMoney( amountToDivide - dividedAmount * (numberOfEntries - 1), )!; + + const datetypeOption = datetypeOptions.find(r => r.value === dateType); + return Array(numberOfEntries) .fill(undefined) .map((_, index) => { @@ -106,8 +127,8 @@ const BulkAddPaymentModal: React.FC = ({ dateType === "fixed" ? dateReference : dateReference.add( - index, - dateType === "monthly" ? "month" : "week", + index * (datetypeOption?.step ?? 0), + datetypeOption?.unit ?? "month", ); return { @@ -194,9 +215,14 @@ const BulkAddPaymentModal: React.FC = ({ {...field} error={Boolean(formState.errors.dateType)} > - {t("Monthly from")} + {datetypeOptions.map(option => ( + + {t(option.label)} + + ))} + {/* {t("Monthly from")} {t("Weekly from")} - {t("Fixed")} + {t("Fixed")} */} )} rules={{ diff --git a/src/components/CustomerSave/CustomerSave.tsx b/src/components/CustomerSave/CustomerSave.tsx index dc5d6b6..f17b427 100644 --- a/src/components/CustomerSave/CustomerSave.tsx +++ b/src/components/CustomerSave/CustomerSave.tsx @@ -28,6 +28,7 @@ import { differenceBy } from "lodash"; export interface Props { subsidiaries: Subsidiary[], customerTypes: CustomerType[], + maxCustomerCode: string } const hasErrorsInTab = ( @@ -45,6 +46,7 @@ const hasErrorsInTab = ( const CustomerSave: React.FC = ({ subsidiaries, customerTypes, + maxCustomerCode }) => { const [serverError, setServerError] = useState(""); const [tabIndex, setTabIndex] = useState(0); @@ -58,7 +60,7 @@ const CustomerSave: React.FC = ({ try { const defaultCustomer = { id: null, - code: "", + code: maxCustomerCode ?? "", name: "", brNo: null, address: null, diff --git a/src/components/CustomerSave/CustomerSaveWrapper.tsx b/src/components/CustomerSave/CustomerSaveWrapper.tsx index 078f50a..74959c4 100644 --- a/src/components/CustomerSave/CustomerSaveWrapper.tsx +++ b/src/components/CustomerSave/CustomerSaveWrapper.tsx @@ -2,7 +2,7 @@ // import CreateProject from "./CreateProject"; // import { fetchProjectCategories } from "@/app/api/projects"; // import { fetchTeamLeads } from "@/app/api/staff"; -import { fetchCustomerTypes, fetchAllSubsidiaries } from "@/app/api/customer"; +import { fetchCustomerTypes, fetchAllSubsidiaries, getMaxCustomerCode } from "@/app/api/customer"; import CustomerSave from "./CustomerSave"; // type Props = { @@ -14,14 +14,15 @@ import CustomerSave from "./CustomerSave"; const CustomerSaveWrapper: React.FC = async () => { // const { params } = props // console.log(params) - const [subsidiaries, customerTypes] = + const [subsidiaries, customerTypes, maxCustomerCode] = await Promise.all([ fetchAllSubsidiaries(), fetchCustomerTypes(), + getMaxCustomerCode() ]); return ( - + ); }; diff --git a/src/components/CustomerSearch/CustomerSearch.tsx b/src/components/CustomerSearch/CustomerSearch.tsx index 7b39ade..c69c9c4 100644 --- a/src/components/CustomerSearch/CustomerSearch.tsx +++ b/src/components/CustomerSearch/CustomerSearch.tsx @@ -65,8 +65,8 @@ const CustomerSearch: React.FC = ({ customers, abilities }) => { buttonIcon: , isHidden: !maintainClient }, - { name: "code", label: t("Customer Code") }, - { name: "name", label: t("Customer Name") }, + { name: "code", label: t("Customer Code"), sortable: true }, + { name: "name", label: t("Customer Name"), sortable: true }, { name: "id", label: t("Delete"), @@ -94,7 +94,7 @@ const CustomerSearch: React.FC = ({ customers, abilities }) => { }} onReset={onReset} /> - + ); }; diff --git a/src/components/GenerateLastModifiedReport/GenerateLastModifiedReport.tsx b/src/components/GenerateLastModifiedReport/GenerateLastModifiedReport.tsx index 6181b3b..1c899ed 100644 --- a/src/components/GenerateLastModifiedReport/GenerateLastModifiedReport.tsx +++ b/src/components/GenerateLastModifiedReport/GenerateLastModifiedReport.tsx @@ -33,6 +33,17 @@ const GenerateLastModifiedReport: React.FC = ({ projects, companyHolidays paramName: "date", type: "date", }, + { + label: t("Range"), + paramName: "dayRange", + type: "autocomplete", + options: [ + {label: t("30 Days"), value: 30}, + {label: t("60 Days"), value: 60}, + {label: t("90 Days"), value: 90}, + ], + needAll: false + } ], [t] ); @@ -51,7 +62,8 @@ const GenerateLastModifiedReport: React.FC = ({ projects, companyHolidays let postData = { dateString: dayjs().format("YYYY-MM-DD").toString(), - holidays: uniqueHoliday + holidays: uniqueHoliday, + dayRange: query.dayRange }; console.log(query.date.length > 0) if (query.date.length > 0) { diff --git a/src/components/SearchResults/SearchResults.tsx b/src/components/SearchResults/SearchResults.tsx index adc7e12..b08b2b3 100644 --- a/src/components/SearchResults/SearchResults.tsx +++ b/src/components/SearchResults/SearchResults.tsx @@ -32,6 +32,8 @@ interface BaseColumn { needTranslation?: boolean; type?: string; isHidden?: boolean; + sortable?: boolean; // NEW + sortFn?: (a: T, b: T) => number; // NEW (optional custom sort) } interface ColumnWithAction extends BaseColumn { @@ -158,6 +160,45 @@ function SearchResults({ return column.underline ?? "always"; }; + + const [orderByCol, setOrderByCol] = useState(null); + const [order, setOrder] = useState<'asc' | 'desc'>('asc'); + + const handleSort = (col: Column) => { + if (!('sortable' in col) || !col.sortable) return; + + if (orderByCol === col.name) { + setOrder(prev => (prev === 'asc' ? 'desc' : 'asc')); + } else { + setOrderByCol(col.name); + setOrder('asc'); + } + }; + + const sortedItems = React.useMemo(() => { + if (!orderByCol) return items; + const column = columns.find(col => col.name === orderByCol); + if (!column) return items; + + let sorted = [...items]; + if (column.sortFn) { + sorted.sort((a, b) => + order === 'asc' ? column.sortFn!(a, b) : column.sortFn!(b, a) + ); + } else { + sorted.sort((a, b) => { + const aValue = a[orderByCol]; + const bValue = b[orderByCol]; + if (aValue == null) return 1; + if (bValue == null) return -1; + if (aValue < bValue) return order === 'asc' ? -1 : 1; + if (aValue > bValue) return order === 'asc' ? 1 : -1; + return 0; + }); + } + return sorted; + }, [items, orderByCol, order, columns]); + // type OrderProps = Record // const [sortedItems, setSortedItems] = useState(items) // const [orderProps, setOrderProps] = useState(() => { @@ -209,15 +250,31 @@ function SearchResults({ {columns.filter(item => item.isHidden !== true).map((column, idx) => ( - - {column?.type === "money" ?
{column.label}
: column.label} - + handleSort(column)} + style={{ cursor: column.sortable ? 'pointer' : undefined }} + > + + {column?.type === "money" ? ( +
+ {column.label} +
+ ) : ( + column.label + )} + {column.sortable && ( + orderByCol === column.name ? + (order === 'asc' ? : ) + : + )} +
))}
- {items + {sortedItems .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) .map((item) => { return (