Переглянути джерело

Adding sorting to seach result, Modifly Bulk add payment(quarterly, half year...), Customer related update

main
MSI\2Fi 3 тижднів тому
джерело
коміт
0dfc274e39
8 змінених файлів з 123 додано та 17 видалено
  1. +7
    -1
      src/app/api/customer/index.ts
  2. +2
    -0
      src/app/api/reports/index.ts
  3. +30
    -4
      src/components/CreateProject/BulkAddPaymentModal.tsx
  4. +3
    -1
      src/components/CustomerSave/CustomerSave.tsx
  5. +4
    -3
      src/components/CustomerSave/CustomerSaveWrapper.tsx
  6. +3
    -3
      src/components/CustomerSearch/CustomerSearch.tsx
  7. +13
    -1
      src/components/GenerateLastModifiedReport/GenerateLastModifiedReport.tsx
  8. +61
    -4
      src/components/SearchResults/SearchResults.tsx

+ 7
- 1
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<string>(`${BASE_API_URL}/customer/getMaxCode`, {
next: { tags: ["customerMaxCode"] },
});
});

+ 2
- 0
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 {


+ 30
- 4
src/components/CreateProject/BulkAddPaymentModal.tsx Переглянути файл

@@ -45,6 +45,24 @@ export interface Props extends Omit<ModalProps, "children"> {
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<Props> = ({
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<Props> = ({
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<Props> = ({
{...field}
error={Boolean(formState.errors.dateType)}
>
<MenuItem value="monthly">{t("Monthly from")}</MenuItem>
{datetypeOptions.map(option => (
<MenuItem key={option.value} value={option.value}>
{t(option.label)}
</MenuItem>
))}
{/* <MenuItem value="monthly">{t("Monthly from")}</MenuItem>
<MenuItem value="weekly">{t("Weekly from")}</MenuItem>
<MenuItem value="fixed">{t("Fixed")}</MenuItem>
<MenuItem value="fixed">{t("Fixed")}</MenuItem> */}
</Select>
)}
rules={{


+ 3
- 1
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<Props> = ({
subsidiaries,
customerTypes,
maxCustomerCode
}) => {
const [serverError, setServerError] = useState("");
const [tabIndex, setTabIndex] = useState(0);
@@ -58,7 +60,7 @@ const CustomerSave: React.FC<Props> = ({
try {
const defaultCustomer = {
id: null,
code: "",
code: maxCustomerCode ?? "",
name: "",
brNo: null,
address: null,


+ 4
- 3
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 (
<CustomerSave subsidiaries={subsidiaries} customerTypes={customerTypes} />
<CustomerSave subsidiaries={subsidiaries} customerTypes={customerTypes} maxCustomerCode={maxCustomerCode} />
);
};



+ 3
- 3
src/components/CustomerSearch/CustomerSearch.tsx Переглянути файл

@@ -65,8 +65,8 @@ const CustomerSearch: React.FC<Props> = ({ customers, abilities }) => {
buttonIcon: <EditNote />,
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<Props> = ({ customers, abilities }) => {
}}
onReset={onReset}
/>
<SearchResults items={filteredCustomers} columns={columns} />
<SearchResults items={filteredCustomers} columns={columns} autoRedirectToFirstPage={true}/>
</>
);
};


+ 13
- 1
src/components/GenerateLastModifiedReport/GenerateLastModifiedReport.tsx Переглянути файл

@@ -33,6 +33,17 @@ const GenerateLastModifiedReport: React.FC<Props> = ({ 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<Props> = ({ 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) {


+ 61
- 4
src/components/SearchResults/SearchResults.tsx Переглянути файл

@@ -32,6 +32,8 @@ interface BaseColumn<T extends ResultWithId> {
needTranslation?: boolean;
type?: string;
isHidden?: boolean;
sortable?: boolean; // NEW
sortFn?: (a: T, b: T) => number; // NEW (optional custom sort)
}

interface ColumnWithAction<T extends ResultWithId> extends BaseColumn<T> {
@@ -158,6 +160,45 @@ function SearchResults<T extends ResultWithId>({

return column.underline ?? "always";
};

const [orderByCol, setOrderByCol] = useState<keyof T | null>(null);
const [order, setOrder] = useState<'asc' | 'desc'>('asc');

const handleSort = (col: Column<T>) => {
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<keyof T, Boolean>
// const [sortedItems, setSortedItems] = useState(items)
// const [orderProps, setOrderProps] = useState<OrderProps>(() => {
@@ -209,15 +250,31 @@ function SearchResults<T extends ResultWithId>({
<TableHead>
<TableRow>
{columns.filter(item => item.isHidden !== true).map((column, idx) => (
<TableCell key={`${column.name.toString()}${idx}`}>
{column?.type === "money" ? <div style={{display: "flex", justifyContent: "flex-end"}}>{column.label}</div> : column.label}
<TableCell
key={`${column.name.toString()}${idx}`}
onClick={() => handleSort(column)}
style={{ cursor: column.sortable ? 'pointer' : undefined }}
>
<span style={{ display: "flex", alignItems: "center" }}>
{column?.type === "money" ? (
<div style={{display: "flex", justifyContent: "flex-end", width: "100%"}}>
{column.label}
</div>
) : (
column.label
)}
{column.sortable && (
orderByCol === column.name ?
(order === 'asc' ? <ArrowUp fontSize="small" /> : <ArrowDown fontSize="small" />)
: <ArrowUp style={{ opacity: 0.3 }} fontSize="small" />
)}
</span>
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{items
{sortedItems
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((item) => {
return (


Завантаження…
Відмінити
Зберегти