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

[Prod Schedule] clear error

master
cyril.tsui 1 місяць тому
джерело
коміт
b0d255cfb5
6 змінених файлів з 227 додано та 138 видалено
  1. +4
    -5
      src/app/(main)/scheduling/detail/page.tsx
  2. +185
    -113
      src/components/DetailSchedule/DetailScheduleSearchView.tsx
  3. +14
    -2
      src/components/DetailSchedule/DetailScheduleWrapper.tsx
  4. +1
    -1
      src/components/DetailScheduleDetail/DetailInfoCard.tsx
  5. +1
    -1
      src/components/DetailScheduleDetail/DetailScheudleDetailView.tsx
  6. +22
    -16
      src/components/SearchResults/EditableSearchResults.tsx

+ 4
- 5
src/app/(main)/scheduling/detail/page.tsx Переглянути файл

@@ -2,7 +2,6 @@
// import DetailSchedule from "@/components/DetailSchedule";
// import { getServerI18n } from "@/i18n";

import { TypeEnum } from "../../../../app/utils/typeEnum";
import DetailSchedule from "../../../../components/DetailSchedule";
import { getServerI18n } from "../../../../i18n";
import { I18nProvider } from "@/i18n";
@@ -16,8 +15,8 @@ export const metadata: Metadata = {
};

const DetailScheduling: React.FC = async () => {
const project = TypeEnum.PRODUCT
const { t } = await getServerI18n("detailScheduling");
const { t } = await getServerI18n("schedule");
const type = "detailed"
// preloadClaims();

return (
@@ -32,9 +31,9 @@ const DetailScheduling: React.FC = async () => {
{t("Detail Scheduling")}
</Typography>
</Stack>
<I18nProvider namespaces={["detailScheduling", "items", "common","schedule"]}>
<I18nProvider namespaces={["schedule", "common"]}>
<Suspense fallback={<DetailSchedule.Loading />}>
<DetailSchedule />
<DetailSchedule type={type}/>
</Suspense>
</I18nProvider>
</>


+ 185
- 113
src/components/DetailSchedule/DetailScheduleSearchView.tsx Переглянути файл

@@ -1,67 +1,75 @@
"use client";

import React, {useCallback, useEffect, useMemo, useState} from "react";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import SearchBox, { Criterion } from "../SearchBox";
import { ItemsResult} from "@/app/api/settings/item";
import { ItemsResult } from "@/app/api/settings/item";
import SearchResults, { Column } from "../SearchResults";
import { EditNote } from "@mui/icons-material";
import { useRouter, useSearchParams } from "next/navigation";
import { GridDeleteIcon } from "@mui/x-data-grid";
import { TypeEnum } from "@/app/utils/typeEnum";
import axios from "axios";
import {BASE_API_URL, NEXT_PUBLIC_API_URL} from "@/config/api";
import { BASE_API_URL, NEXT_PUBLIC_API_URL } from "@/config/api";
import { useTranslation } from "react-i18next";
import axiosInstance from "@/app/(main)/axios/axiosInstance";
import Qs from 'qs';
import EditableSearchResults from "@/components/SearchResults/EditableSearchResults"; // Make sure to import Qs
import { ScheduleType } from "@/app/api/scheduling";
import { ProdScheduleResult, SearchProdSchedule, fetchProdSchedules } from "@/app/api/scheduling/actions";
import { defaultPagingController } from "../SearchResults/SearchResults";
import { arrayToDateString, decimalFormatter } from "@/app/utils/formatUtil";
import dayjs from "dayjs";
import { uniqBy } from "lodash";

// may need move to "index" or "actions"
type RecordStructure = {
id: number,
scheduledPeriod: string,
scheduledAt: string,
productCount: number,
};
// type RecordStructure = {
// id: number,
// scheduledPeriod: string,
// scheduledAt: string,
// productCount: number,
// };

type Props = {
records: RecordStructure[];
type: ScheduleType;
// records: RecordStructure[];
defaultInputs: SearchProdSchedule;
};

type SearchQuery = Partial<Omit<RecordStructure, "id">>;
type SearchQuery = Partial<Omit<SearchProdSchedule, "id" | "pageSize" | "pageNum">>;
type SearchParamNames = keyof SearchQuery;

const DSOverview: React.FC<Props> = ({ records }) => {
const [filteredItems, setFilteredItems] = useState<RecordStructure[]>(records ?? []);
const { t } = useTranslation("detailScheduling");
const DSOverview: React.FC<Props> = ({ type, defaultInputs }) => {
const [filteredSchedules, setFilteredSchedules] = useState<ProdScheduleResult[]>([]);
const { t } = useTranslation("schedule");
const router = useRouter();
const [filterObj, setFilterObj] = useState({});
const [tempSelectedValue, setTempSelectedValue] = useState({});
const [pagingController, setPagingController] = useState({
pageNum: 1,
pageSize: 10,
totalCount: 0,
})

const [mode, redirPath] = useMemo(() => {
// var typeId = TypeEnum.CONSUMABLE_ID
let title = "";
const mode = "Search";
let redirPath = "";
title = "Product";
redirPath = "/scheduling/detail";
return [mode, redirPath];
}, []);
// const [filterObj, setFilterObj] = useState({});
// const [tempSelectedValue, setTempSelectedValue] = useState({});
const [pagingController, setPagingController] = useState(defaultPagingController)
const [totalCount, setTotalCount] = useState(0)
const [inputs, setInputs] = useState(defaultInputs)
const typeOptions = [
{
value: "detailed",
label: t("Detailed")
},
{
value: "manual",
label: t("Manual")
},
]

const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() =>
[
{ label: t("Schedule Period"), paramName: "scheduledPeriod", type: "dateRange" },
{ label: t("Scheduled At"), paramName: "scheduledAt", type: "dateRange" },
{ label: t("Product Count"), paramName: "productCount", type: "text" },
() => {
var searchCriteria: Criterion<SearchParamNames>[] = [
{ label: t("Schedule Period"), label2: t("Schedule Period To"), paramName: "schedulePeriod", type: "dateRange" },
{ label: t("Production Date"), paramName: "scheduleAt", type: "date" },
{ label: t("Product Count"), paramName: "totalEstProdCount", type: "text" },
{ label: t("Type"), paramName: "types", type: "autocomplete", options: typeOptions },
]
,
[t, records]
return searchCriteria
},
[t]
);

// const onDetailClick = useCallback(
@@ -71,36 +79,54 @@ const DSOverview: React.FC<Props> = ({ records }) => {
// [router]
// );

const onDeleteClick = useCallback(
(item: ItemsResult) => {},
[router]
);
// const onDeleteClick = useCallback(
// (item: ItemsResult) => {},
// [router]
// );

const onDetailClick = (record: any) => {
const onDetailClick = (record: ProdScheduleResult) => {
console.log("[debug] record", record);
router.push(`/scheduling/detail/edit?id=${record.id}`);

}

const columns = useMemo<Column<RecordStructure>[]>(
const columns = useMemo<Column<ProdScheduleResult>[]>(
() => [
{
name: "id",
label: t("Details"),
onClick: (record)=>onDetailClick(record),
onClick: (record) => onDetailClick(record),
buttonIcon: <EditNote />,
},
{
name: "scheduledPeriod",
name: "schedulePeriod",
label: t("Demand Forecast Period"),
renderCell: (params) => {
return `${arrayToDateString(params.schedulePeriod)} - ${arrayToDateString(params.schedulePeriodTo)}`
}
},
{
name: "scheduledAt",
label: t("Scheduled At"),
name: "scheduleAt",
label: t("Production Date"),
renderCell: (params) => {
return arrayToDateString(params.scheduleAt)
}
},
{
name: "productCount",
name: "totalEstProdCount",
label: t("Product Count(s)"),
headerAlign: "right",
align: "right",
renderCell: (params) => {
return decimalFormatter.format(params.totalEstProdCount)
}
},
{
name: "type",
label: t("Type"),
renderCell: (params) => {
return t(params.type)
}
},
// {
// name: "action",
@@ -109,92 +135,138 @@ const DSOverview: React.FC<Props> = ({ records }) => {
// onClick: onDeleteClick,
// },
],
[filteredItems]
[filteredSchedules]
);

const refetchData = useCallback(async (query: Record<SearchParamNames, string> | SearchProdSchedule, actionType: "reset" | "search" | "paging") => {
// console.log(query)
const defaultTypes = ["detailed", "manual"]
const convertedTypes = (query.types == undefined || typeof (query.types) == "string" ? query.types?.toLowerCase() == "all" ? defaultTypes : [query.types]
: query.types.some((ele) => ele.toLowerCase() === "all") ? defaultTypes : query.types) as ScheduleType[];

console.log(convertedTypes)
console.log(query.types)
const params: SearchProdSchedule = {
scheduleAt: dayjs(query?.scheduleAt).isValid() ? query?.scheduleAt : undefined,
schedulePeriod: dayjs(query?.schedulePeriod).isValid() ? query?.schedulePeriod : undefined,
schedulePeriodTo: dayjs(query?.schedulePeriodTo).isValid() ? query?.schedulePeriodTo : undefined,
totalEstProdCount: query?.totalEstProdCount ? Number(query?.totalEstProdCount) : undefined,
types: convertedTypes,
pageNum: pagingController.pageNum - 1,
pageSize: pagingController.pageSize
}
const response = await fetchProdSchedules(params)

// console.log(response)
if (response) {
setTotalCount(response.total)
switch (actionType) {
case "reset":
case "search":
setFilteredSchedules(() => response.records)
break;
case "paging":
setFilteredSchedules((fs) => uniqBy([...fs, ...response.records], "id"))
break;
}
}
}, [pagingController, setPagingController])

useEffect(() => {
refetchData(filterObj);
refetchData(inputs, "paging")
}, [pagingController])

}, [filterObj, pagingController.pageNum, pagingController.pageSize]);
// useEffect(() => {
// refetchData(filterObj);

const refetchData = async (filterObj: SearchQuery | null) => {
// }, [filterObj, pagingController.pageNum, pagingController.pageSize]);

const authHeader = axiosInstance.defaults.headers['Authorization'];
if (!authHeader) {
return; // Exit the function if the token is not set
}
// const refetchData = async (filterObj: SearchQuery | null) => {

const params ={
pageNum: pagingController.pageNum,
pageSize: pagingController.pageSize,
...filterObj,
...tempSelectedValue,
}
// const authHeader = axiosInstance.defaults.headers['Authorization'];
// if (!authHeader) {
// return; // Exit the function if the token is not set
// }

try {
const response = await axiosInstance.get<ItemsResult[]>(`${NEXT_PUBLIC_API_URL}/items/getRecordByPage`, {
params,
paramsSerializer: (params) => {
return Qs.stringify(params, { arrayFormat: 'repeat' });
},
});
//setFilteredItems(response.data.records);
setFilteredItems([
{
id: 1,
scheduledPeriod: "2025-05-11 to 2025-05-17",
scheduledAt: "2025-05-07",
productCount: 13,
},
{
id: 2,
scheduledPeriod: "2025-05-18 to 2025-05-24",
scheduledAt: "2025-05-14",
productCount: 15,
},
{
id: 3,
scheduledPeriod: "2025-05-25 to 2025-05-31",
scheduledAt: "2025-05-21",
productCount: 13,
},
])
setPagingController({
...pagingController,
totalCount: response.data.total
})
return response; // Return the data from the response
} catch (error) {
console.error('Error fetching items:', error);
throw error; // Rethrow the error for further handling
}
};
// const params = {
// pageNum: pagingController.pageNum,
// pageSize: pagingController.pageSize,
// ...filterObj,
// ...tempSelectedValue,
// }

// try {
// const response = await axiosInstance.get<ItemsResult[]>(`${NEXT_PUBLIC_API_URL}/items/getRecordByPage`, {
// params,
// paramsSerializer: (params) => {
// return Qs.stringify(params, { arrayFormat: 'repeat' });
// },
// });
// //setFilteredItems(response.data.records);
// setFilteredItems([
// {
// id: 1,
// scheduledPeriod: "2025-05-11 to 2025-05-17",
// scheduledAt: "2025-05-07",
// productCount: 13,
// },
// {
// id: 2,
// scheduledPeriod: "2025-05-18 to 2025-05-24",
// scheduledAt: "2025-05-14",
// productCount: 15,
// },
// {
// id: 3,
// scheduledPeriod: "2025-05-25 to 2025-05-31",
// scheduledAt: "2025-05-21",
// productCount: 13,
// },
// ])
// setPagingController({
// ...pagingController,
// totalCount: response.data.total
// })
// return response; // Return the data from the response
// } catch (error) {
// console.error('Error fetching items:', error);
// throw error; // Rethrow the error for further handling
// }
// };

const onReset = useCallback(() => {
//setFilteredItems(items ?? []);
setFilterObj({});
setTempSelectedValue({});
refetchData(null);
}, [records]);
// setFilterObj({});
// setTempSelectedValue({});
refetchData(inputs, "reset");
}, []);

return (
<>
<SearchBox
criteria={searchCriteria}
onSearch={(query) => {
setFilterObj({
...query
})
setInputs(() => (
{
scheduleAt: query?.scheduleAt,
schedulePeriod: query?.schedulePeriod,
schedulePeriodTo: query?.schedulePeriodTo,
totalEstProdCount: Number(query?.totalEstProdCount),
types: query.types as unknown as ScheduleType[]
}
))
refetchData(query, "search")
}}
onReset={onReset}
/>
<SearchResults<RecordStructure>
items={filteredItems}
<SearchResults<ProdScheduleResult>
items={filteredSchedules}
columns={columns}
setPagingController={setPagingController}
pagingController={pagingController}
isAutoPaging={false}
// hasCollapse={false}
totalCount={totalCount}
// isAutoPaging={false}
// hasCollapse={false}
/>
</>
);


+ 14
- 2
src/components/DetailSchedule/DetailScheduleWrapper.tsx Переглянути файл

@@ -1,14 +1,26 @@
import React from "react";
import {DetailScheduleLoading} from "./DetailScheduleLoading";
import DSOverview from "./DetailScheduleSearchView";
import { ScheduleType } from "@/app/api/scheduling";
import { SearchProdSchedule } from "@/app/api/scheduling/actions";

interface SubComponents {
Loading: typeof DetailScheduleLoading;
}

const DetailScheduleWrapper: React.FC & SubComponents = async () => {
type Props = {
type: ScheduleType
}

const DetailScheduleWrapper: React.FC<Props> & SubComponents = async ({
type
}) => {

const defaultInputs: SearchProdSchedule = {
types: ["detailed", "manual"]
}

return <DSOverview records={[]} />;
return <DSOverview type={type} defaultInputs={defaultInputs} />;
};

DetailScheduleWrapper.Loading = DetailScheduleLoading;


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

@@ -23,7 +23,7 @@ import { SaveDetailSchedule } from "./DetailScheudleDetailView";
// temp interface input

type Props = {
recordDetails: SaveDetailSchedule;
recordDetails: any;
isEditing: boolean;
};



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

@@ -4,7 +4,7 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { useTranslation } from "react-i18next";
import {
SaveDetailSchedule,
// SaveDetailSchedule,
saveItem,
} from "@/app/api/settings/item/actions";
import {


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

@@ -1,6 +1,6 @@
"use client";

import React, { useEffect, useState } from "react";
import React, { CSSProperties, useEffect, useState } from "react";
import Paper from "@mui/material/Paper";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
@@ -23,6 +23,7 @@ import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil";
import PlayCircleOutlineIcon from '@mui/icons-material/PlayCircleOutline';
import { useTranslation } from "react-i18next";
import { RoughProdScheduleLineResultByFg } from "@/app/api/scheduling";
export interface ResultWithId {
id: string | number;
}
@@ -32,8 +33,9 @@ interface BaseColumn<T extends ResultWithId> {
label: string;
type: string;
options?: T[];
renderCell?: (T) => void;
style?: Partial<HTMLElement["style"]> & { [propName: string]: string };
renderCell?: (params: T) => React.ReactNode;
style?: Partial<HTMLElement["style"]> & { [propName: string]: string } & CSSProperties;
// style?: Partial<HTMLElement["style"]> & { [propName: string]: string };
}

interface ColumnWithAction<T extends ResultWithId> extends BaseColumn<T> {
@@ -52,7 +54,11 @@ interface Props<T extends ResultWithId> {
noWrapper?: boolean,
setPagingController: (value: { pageNum: number; pageSize: number; totalCount: number, index?: number }) => void,
pagingController: { pageNum: number; pageSize: number; totalCount: number },
isAutoPaging: boolean
isAutoPaging: boolean,
index: any,
isEdit: any,
isEditable: any,
hasCollapse: any,
}

function EditableSearchResults<T extends ResultWithId>({
@@ -78,7 +84,7 @@ function EditableSearchResults<T extends ResultWithId>({
}, [items])
const handleChangePage = (_event: unknown, newPage: number) => {
setPage(newPage);
setPagingController({ ...pagingController, pageNum: newPage + 1 }, (index ?? -1));
// setPagingController({ ...pagingController, pageNum: newPage + 1 }, (index ?? -1));
};

const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
@@ -134,7 +140,7 @@ function EditableSearchResults<T extends ResultWithId>({
<>
<TableRow hover tabIndex={-1} key={row.id}>
<TableCell>
<IconButton disable={!isEdit}>
<IconButton disabled={!isEdit}>
<PlayCircleOutlineIcon />
</IconButton>
</TableCell>
@@ -208,15 +214,15 @@ function EditableSearchResults<T extends ResultWithId>({
onChange={(e) => handleInputChange(row.id as number, columnName, e.target.value)}
/>
);
case 'multi-select':
return (
<MultiSelect
//label={column.label}
options={column.options}
selectedValues={[]}
onChange={(selectedValues) => handleInputChange(row.id as number, columnName, selectedValues)}
/>
);
// case 'multi-select':
// return (
// <MultiSelect
// //label={column.label}
// options={column.options}
// selectedValues={[]}
// onChange={(selectedValues) => handleInputChange(row.id as number, columnName, selectedValues)}
// />
// );
case 'read-only':
return (
<span>
@@ -253,7 +259,7 @@ function EditableSearchResults<T extends ResultWithId>({
<TableRow>
<TableCell>
<TempInputGridForMockUp
stockInLine={row.lines as any[]}
stockInLine={(row as unknown as {lines: string[]}).lines as any[]}
/>
</TableCell>
</TableRow>


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