MSI\2Fi 1 місяць тому
джерело
коміт
d337eb1e82
29 змінених файлів з 2835 додано та 2106 видалено
  1. +0
    -57
      src/app/(main)/scheduling/detail/edit/page.tsx
  2. +0
    -0
      src/app/(main)/scheduling/detailed/edit/not-found.tsx
  3. +57
    -0
      src/app/(main)/scheduling/detailed/edit/page.tsx
  4. +4
    -4
      src/app/(main)/scheduling/detailed/page.tsx
  5. +2
    -2
      src/app/(main)/scheduling/rough/edit/page.tsx
  6. +21
    -2
      src/app/api/scheduling/actions.ts
  7. +111
    -67
      src/app/api/scheduling/index.ts
  8. +30
    -0
      src/app/utils/formatUtil.ts
  9. +0
    -1
      src/components/DetailSchedule/index.ts
  10. +0
    -37
      src/components/DetailScheduleDetail/DetailScheduleDetailWrapper.tsx
  11. +0
    -1496
      src/components/DetailScheduleDetail/ViewByBomDetails.tsx
  12. +0
    -1
      src/components/DetailScheduleDetail/index.ts
  13. +2
    -2
      src/components/DetailedSchedule/DetailedScheduleLoading.tsx
  14. +1
    -9
      src/components/DetailedSchedule/DetailedScheduleSearchView.tsx
  15. +6
    -6
      src/components/DetailedSchedule/DetailedScheduleWrapper.tsx
  16. +1
    -0
      src/components/DetailedSchedule/index.ts
  17. +29
    -14
      src/components/DetailedScheduleDetail/DetailInfoCard.tsx
  18. +32
    -68
      src/components/DetailedScheduleDetail/DetailedScheduleDetailView.tsx
  19. +46
    -0
      src/components/DetailedScheduleDetail/DetailedScheduleDetailWrapper.tsx
  20. +50
    -0
      src/components/DetailedScheduleDetail/ProdTimeColumn.tsx
  21. +1878
    -0
      src/components/DetailedScheduleDetail/TempRecords.tsx
  22. +226
    -0
      src/components/DetailedScheduleDetail/ViewByFGDetails.tsx
  23. +1
    -0
      src/components/DetailedScheduleDetail/index.ts
  24. +1
    -1
      src/components/NavigationContent/NavigationContent.tsx
  25. +2
    -2
      src/components/RoughScheduleDetail/RoughScheduleDetailWrapper.tsx
  26. +1
    -7
      src/components/RoughScheduleDetail/RoughScheudleDetailView.tsx
  27. +2
    -1
      src/components/ScheduleTable/BomMaterialTable.tsx
  28. +330
    -328
      src/components/ScheduleTable/ScheduleTable.tsx
  29. +2
    -1
      src/i18n/zh/schedule.json

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

@@ -1,57 +0,0 @@
import { Metadata } from "next";
// import { getServerI18n, I18nProvider } from "@/i18n";
import { getServerI18n, I18nProvider } from "../../../../../i18n";
import Typography from "@mui/material/Typography";
// import { fetchQcItemDetails, preloadQcItem } from "@/app/api/settings/qcItem";
// import QcItemSave from "@/components/QcItemSave";
import {
fetchQcItemDetails,
preloadQcItem,
} from "../../../../../app/api/settings/qcItem";
import QcItemSave from "../../../../../components/QcItemSave";
import { isArray } from "lodash";
import { notFound } from "next/navigation";
// import { ServerFetchError } from "@/app/utils/fetchUtil";
// import DetailScheduleDetail from "@/components/DetailScheduleDetail";
import { ServerFetchError } from "../../../../../app/utils/fetchUtil";
import DetailScheduleDetail from "../../../../../components/DetailScheduleDetail";

export const metadata: Metadata = {
title: "Qc Item",
};

interface Props {
searchParams: { [key: string]: string | string[] | undefined };
}

const DetailScheduling: React.FC<Props> = async ({ searchParams }) => {
const { t } = await getServerI18n("schedule");

const id = searchParams["id"];

if (!id || isArray(id)) {
notFound();
}

// try {
// await fetchQcItemDetails(id)
// } catch (e) {
// if (e instanceof ServerFetchError && (e.response?.status === 404 || e.response?.status === 400)) {
// console.log(e)
// notFound();
// }
// }

return (
<>
<Typography variant="h4" marginInlineEnd={2}>
{t("FG Production Schedule")}
</Typography>
<I18nProvider namespaces={["schedule", "common", "project"]}>
<DetailScheduleDetail id={id} />
</I18nProvider>
</>
);
};

export default DetailScheduling;

src/app/(main)/scheduling/detail/edit/not-found.tsx → src/app/(main)/scheduling/detailed/edit/not-found.tsx Переглянути файл


+ 57
- 0
src/app/(main)/scheduling/detailed/edit/page.tsx Переглянути файл

@@ -0,0 +1,57 @@
import { Metadata } from "next";
import { getServerI18n, I18nProvider } from "@/i18n";
// import { getServerI18n, I18nProvider } from "../../../../../i18n";
import Typography from "@mui/material/Typography";
import { isArray, parseInt } from "lodash";
import { notFound } from "next/navigation";
import { SearchParams, ServerFetchError } from "@/app/utils/fetchUtil";
import DetailedScheduleDetail from "@/components/DetailedScheduleDetail";
import { type } from "os";
import { fetchDetailedProdScheduleDetail } from "@/app/api/scheduling";
import { Suspense } from "react";
// import { ServerFetchError } from "../../../../../app/utils/fetchUtil";
// import DetailedScheduleDetail from "../../../../../components/DetailedScheduleDetail";

export const metadata: Metadata = {
title: "FG Production Schedule",
};

// interface Props {
// searchParams: { [key: string]: string | string[] | undefined };
// }

type Props = SearchParams;

const DetailScheduling: React.FC<Props> = async ({ searchParams }) => {
const { t } = await getServerI18n("schedule");
const id = searchParams["id"];
const type = "detailed"

if (!id || isArray(id) || !isFinite(parseInt(id))) {
notFound();
}

try {
await fetchDetailedProdScheduleDetail(parseInt(id))
} catch (e) {
if (e instanceof ServerFetchError && (e.response?.status === 404 || e.response?.status === 400)) {
console.log(e)
notFound();
}
}

return (
<>
<Typography variant="h4" marginInlineEnd={2}>
{t("FG Production Schedule")}
</Typography>
<I18nProvider namespaces={["schedule", "common"]}>
<Suspense fallback={<DetailedScheduleDetail.Loading />}>
<DetailedScheduleDetail type={type} id={parseInt(id)} />
</Suspense>
</I18nProvider>
</>
);
};

export default DetailScheduling;

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

@@ -1,8 +1,8 @@
// import { TypeEnum } from "@/app/utils/typeEnum";
// import DetailSchedule from "@/components/DetailSchedule";
// import DetailedSchedule from "@/components/DetailedSchedule";
// import { getServerI18n } from "@/i18n";

import DetailSchedule from "../../../../components/DetailSchedule";
import DetailedSchedule from "../../../../components/DetailedSchedule";
import { getServerI18n } from "../../../../i18n";
import { I18nProvider } from "@/i18n";
import Stack from "@mui/material/Stack";
@@ -32,8 +32,8 @@ const DetailScheduling: React.FC = async () => {
</Typography>
</Stack>
<I18nProvider namespaces={["schedule", "common"]}>
<Suspense fallback={<DetailSchedule.Loading />}>
<DetailSchedule type={type} />
<Suspense fallback={<DetailedSchedule.Loading />}>
<DetailedSchedule type={type} />
</Suspense>
</I18nProvider>
</>

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

@@ -12,7 +12,7 @@ import RoughScheduleDetailView from "@/components/RoughScheduleDetail";
import { SearchParams, ServerFetchError } from "@/app/utils/fetchUtil";
import { isArray, parseInt } from "lodash";
import { notFound } from "next/navigation";
import { fetchProdScheduleDetail } from "@/app/api/scheduling";
import { fetchRoughProdScheduleDetail } from "@/app/api/scheduling";

export const metadata: Metadata = {
title: "Demand Forecast Detail",
@@ -30,7 +30,7 @@ const roughSchedulingDetail: React.FC<Props> = async ({ searchParams }) => {
}

try {
await fetchProdScheduleDetail(parseInt(id));
await fetchRoughProdScheduleDetail(parseInt(id));
} catch (e) {
if (
e instanceof ServerFetchError &&


+ 21
- 2
src/app/api/scheduling/actions.ts Переглянути файл

@@ -31,6 +31,11 @@ export interface ProdScheduleResultByPage {
records: ProdScheduleResult[];
}

export interface ReleaseDetailProdScheduleInputs {
id: number;
demandQty: number;
}

export const fetchProdSchedules = cache(
async (data: SearchProdSchedule | null) => {
const params = convertObjToURLSearchParams<SearchProdSchedule>(data);
@@ -61,9 +66,9 @@ export const testRoughSchedule = cache(async () => {
);
});

export const testDetailSchedule = cache(async () => {
export const testDetailedSchedule = cache(async () => {
return serverFetchJson(
`${BASE_API_URL}/productionSchedule/testDetailSchedule`,
`${BASE_API_URL}/productionSchedule/testDetailedSchedule`,
{
method: "GET",
headers: { "Content-Type": "application/json" },
@@ -73,3 +78,17 @@ export const testDetailSchedule = cache(async () => {
},
);
});

export const releaseProdScheduleLine = cache(async (data: ReleaseDetailProdScheduleInputs) => {
return serverFetchJson(
`${BASE_API_URL}/productionSchedule/releaseLine`,
{
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
next: {
tags: ["prodSchedules"],
},
}
)
})

+ 111
- 67
src/app/api/scheduling/index.ts Переглянути файл

@@ -5,85 +5,129 @@ import "server-only";

export type ScheduleType = "all" | "rough" | "detailed" | "manual";

// Rough
export interface RoughProdScheduleResult {
id: number;
scheduleAt: number[];
schedulePeriod: number[];
schedulePeriodTo: number[];
totalEstProdCount: number;
totalFGType: number;
type: string;
prodScheduleLinesByFg: RoughProdScheduleLineResultByFg[];
prodScheduleLinesByFgByDate: {
[assignDate: number]: RoughProdScheduleLineResultByFg[];
};
prodScheduleLinesByBom: RoughProdScheduleLineResultByBom[];
prodScheduleLinesByBomByDate: {
[assignDate: number]: RoughProdScheduleLineResultByBomByDate[];
};
id: number;
scheduleAt: number[];
schedulePeriod: number[];
schedulePeriodTo: number[];
totalEstProdCount: number;
totalFGType: number;
type: string;
prodScheduleLinesByFg: RoughProdScheduleLineResultByFg[];
prodScheduleLinesByFgByDate: {
[assignDate: number]: RoughProdScheduleLineResultByFg[];
};
prodScheduleLinesByBom: RoughProdScheduleLineResultByBom[];
prodScheduleLinesByBomByDate: {
[assignDate: number]: RoughProdScheduleLineResultByBomByDate[];
};
}

export interface RoughProdScheduleLineResultByFg {
id: number;
code: string;
name: string;
type: string;
availableQty: number;
prodQty: number;
lastMonthAvgSales: number;
estCloseBal: number;
priority: number;
assignDate: number;
bomMaterials: RoughProdScheduleLineBomMaterialResult[];
id: number;
code: string;
name: string;
type: string;
availableQty: number;
prodQty: number;
lastMonthAvgSales: number;
estCloseBal: number;
priority: number;
assignDate: number;
bomMaterials: RoughProdScheduleLineBomMaterialResult[];
}

export interface RoughProdScheduleLineBomMaterialResult {
id: number;
code: string;
name: string;
type: string;
availableQty: number;
demandQty: number;
uomName: string;
id: number;
code: string;
name: string;
type: string;
availableQty: number;
demandQty: number;
uomName: string;
}

export interface RoughProdScheduleLineResultByBom {
id: number;
code: string;
name: string;
type: string;
availableQty: number;
totalDemandQty: number;
demandQty1: number;
demandQty2: number;
demandQty3: number;
demandQty4: number;
demandQty5: number;
demandQty6: number;
demandQty7: number;
uomName: string;
id: number;
code: string;
name: string;
type: string;
availableQty: number;
totalDemandQty: number;
demandQty1: number;
demandQty2: number;
demandQty3: number;
demandQty4: number;
demandQty5: number;
demandQty6: number;
demandQty7: number;
uomName: string;
}

export interface RoughProdScheduleLineResultByBomByDate {
id: number;
code: string;
name: string;
type: string;
availableQty: number;
demandQty: number;
assignDate: number;
uomName: string;
id: number;
code: string;
name: string;
type: string;
availableQty: number;
demandQty: number;
assignDate: number;
uomName: string;
}

export const fetchProdScheduleDetail = cache(async (id: number) => {
return serverFetchJson<RoughProdScheduleResult>(
`${BASE_API_URL}/productionSchedule/detail/${id}`,
{
method: "GET",
headers: { "Content-Type": "application/json" },
next: {
tags: ["prodSchedule"],
},
},
);
});
// Detailed
export interface DetailedProdScheduleResult {
id: number;
scheduleAt: number[];
totalEstProdCount: number;
totalFGType: number;
prodScheduleLines: DetailedProdScheduleLineResult[];
}

export interface DetailedProdScheduleLineResult {
id: number;
bomMaterials: DetailedProdScheduleLineBomMaterialResult[];
jobNo: string;
code: string;
name: string;
type: string;
demandQty: number;
prodTimeInMinute: DetailedProdScheduleLineProdTimeResult[];
priority: number;
}

export interface DetailedProdScheduleLineBomMaterialResult {
id: number;
code: string;
name: string;
type: string;
availableQty: number;
demandQty: number;
}

export interface DetailedProdScheduleLineProdTimeResult {
equipName: string;
totalMinutes: number;
}

// API
export const fetchRoughProdScheduleDetail = cache(async (id: number) => {
return serverFetchJson<RoughProdScheduleResult>(`${BASE_API_URL}/productionSchedule/detail/rough/${id}`, {
method: "GET",
headers: { "Content-Type": "application/json" },
next: {
tags: ["prodSchedule"]
}
})
})

export const fetchDetailedProdScheduleDetail = cache(async (id: number) => {
return serverFetchJson<DetailedProdScheduleResult>(`${BASE_API_URL}/productionSchedule/detail/detailed/${id}`, {
method: "GET",
headers: { "Content-Type": "application/json" },
next: {
tags: ["prodSchedule"]
}
})
})

+ 30
- 0
src/app/utils/formatUtil.ts Переглянути файл

@@ -66,6 +66,36 @@ export const dayjsToDateString = (date: Dayjs) => {
return date.format(OUTPUT_DATE_FORMAT);
};

export const minutesToHoursMinutes = (minutes: number): string => {
const defaultHrStr = "hr"
const defaultMinStr = "min"

if (minutes == 0) {
return `0 ${defaultMinStr}`
}

const hrs = Math.floor(minutes / 60)
const mins = minutes % 60

let finalHrStr: string = ""
if (hrs > 1) {
finalHrStr = `${hrs} ${defaultHrStr}s`
} else if (hrs == 1) {
finalHrStr = `1 ${defaultHrStr}`
}

let finalMinStr: string = ""
if (mins > 1) {
finalMinStr = `${mins} ${defaultMinStr}s`
} else if (mins == 1) {
finalMinStr = `1 ${defaultMinStr}`
}

let colon = finalHrStr.length > 0 && finalMinStr.length > 0 ? ":" : ""

return `${finalHrStr} ${colon} ${finalMinStr}`.trim()
}

export const stockInLineStatusMap: { [status: string]: number } = {
draft: 0,
pending: 1,


+ 0
- 1
src/components/DetailSchedule/index.ts Переглянути файл

@@ -1 +0,0 @@
export { default } from "./DetailScheduleWrapper";

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

@@ -1,37 +0,0 @@
import { CreateItemInputs } from "@/app/api/settings/item/actions";
import { fetchItem } from "@/app/api/settings/item";
import GeneralLoading from "@/components/General/GeneralLoading";
import DetailScheduleDetailView from "@/components/DetailScheduleDetail/DetailScheudleDetailView";

interface SubComponents {
Loading: typeof GeneralLoading;
}

type EditDetailScheduleDetailProps = {
id?: string | number;
};

type Props = EditDetailScheduleDetailProps;

const DetailScheduleDetailWrapper: React.FC<Props> & SubComponents = async ({
id,
}) => {
const defaultValues = {
id: 1,
productionDate: "2025-05-07",
totalJobOrders: 13,
totalProductionQty: 21000,
};

return (
<DetailScheduleDetailView
isEditMode={Boolean(id)}
defaultValues={defaultValues}
// qcChecks={qcChecks || []}
/>
);
};

DetailScheduleDetailWrapper.Loading = GeneralLoading;

export default DetailScheduleDetailWrapper;

+ 0
- 1496
src/components/DetailScheduleDetail/ViewByBomDetails.tsx
Різницю між файлами не показано, бо вона завелика
Переглянути файл


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

@@ -1 +0,0 @@
export { default } from "./DetailScheduleDetailWrapper";

src/components/DetailSchedule/DetailScheduleLoading.tsx → src/components/DetailedSchedule/DetailedScheduleLoading.tsx Переглянути файл

@@ -5,7 +5,7 @@ import Stack from "@mui/material/Stack";
import React from "react";

// Can make this nicer
export const DetailScheduleLoading: React.FC = () => {
export const DetailedScheduleLoading: React.FC = () => {
return (
<>
<Card>
@@ -37,4 +37,4 @@ export const DetailScheduleLoading: React.FC = () => {
);
};

export default DetailScheduleLoading;
export default DetailedScheduleLoading;

src/components/DetailSchedule/DetailScheduleSearchView.tsx → src/components/DetailedSchedule/DetailedScheduleSearchView.tsx Переглянути файл

@@ -2,18 +2,10 @@

import React, { useCallback, useEffect, useMemo, useState } from "react";
import SearchBox, { Criterion } from "../SearchBox";
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 { 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,
@@ -107,7 +99,7 @@ const DSOverview: React.FC<Props> = ({ type, defaultInputs }) => {

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

const columns = useMemo<Column<ProdScheduleResult>[]>(

src/components/DetailSchedule/DetailScheduleWrapper.tsx → src/components/DetailedSchedule/DetailedScheduleWrapper.tsx Переглянути файл

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

interface SubComponents {
Loading: typeof DetailScheduleLoading;
Loading: typeof DetailedScheduleLoading;
}

type Props = {
type: ScheduleType;
};

const DetailScheduleWrapper: React.FC<Props> & SubComponents = async ({
const DetailedScheduleWrapper: React.FC<Props> & SubComponents = async ({
type,
}) => {
const defaultInputs: SearchProdSchedule = {
@@ -22,6 +22,6 @@ const DetailScheduleWrapper: React.FC<Props> & SubComponents = async ({
return <DSOverview type={type} defaultInputs={defaultInputs} />;
};

DetailScheduleWrapper.Loading = DetailScheduleLoading;
DetailedScheduleWrapper.Loading = DetailedScheduleLoading;

export default DetailScheduleWrapper;
export default DetailedScheduleWrapper;

+ 1
- 0
src/components/DetailedSchedule/index.ts Переглянути файл

@@ -0,0 +1 @@
export { default } from "./DetailedScheduleWrapper";

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

@@ -17,13 +17,14 @@ import { InputDataGridProps, TableRow } from "../InputDataGrid/InputDataGrid";
import { TypeEnum } from "@/app/utils/typeEnum";
import { CreateItemInputs } from "@/app/api/settings/item/actions";
import { NumberInputProps } from "@/components/CreateItem/NumberInputProps";
import { integerFormatter } from "@/app/utils/formatUtil";
import { SaveDetailSchedule } from "./DetailScheudleDetailView";
import { arrayToDateString, integerFormatter } from "@/app/utils/formatUtil";
import { DetailedProdScheduleResult } from "@/app/api/scheduling";
// import { SaveDetailedSchedule } from "./DetailedScheduleDetailView";

// temp interface input

type Props = {
// recordDetails: SaveDetailSchedule;
// recordDetails: SaveDetailedSchedule;
isEditing: boolean;
};

@@ -39,14 +40,15 @@ const DetailInfoCard: React.FC<Props> = ({
const {
control,
register,
getValues,
formState: { errors, defaultValues, touchedFields },
} = useFormContext<SaveDetailSchedule>();
} = useFormContext<DetailedProdScheduleResult>();

const [details, setDetails] = useState<SaveDetailSchedule | undefined>(undefined);
// const [details, setDetails] = useState<DetailedProdScheduleResult | undefined>(undefined);

useEffect(() => {
console.log("[debug] record details", defaultValues)
setDetails(defaultValues as SaveDetailSchedule);
// setDetails(defaultValues as DetailedProdScheduleResult);
}, [defaultValues])

useEffect(() => {
@@ -65,9 +67,10 @@ const DetailInfoCard: React.FC<Props> = ({
<TextField
label={t("Production Date")}
fullWidth
{...register("productionDate", {
required: "name required!",
})}
// {...register("scheduleAt", {
// required: "Schedule At required!",
// })}
defaultValue={`${arrayToDateString(getValues("scheduleAt"))}`}
// defaultValue={details?.scheduledPeriod}
disabled={!isEditing}
// error={Boolean(errors.name)}
@@ -78,9 +81,15 @@ const DetailInfoCard: React.FC<Props> = ({
<TextField
label={t("Total Job Orders")}
fullWidth
{...register("totalJobOrders", {
required: "code required!",
})}
// {...register("totalFGType", {
// required: "Total FG Type required!",
// })}
// TODO: May update by table row qty
defaultValue={
typeof getValues("totalFGType") == "number"
? integerFormatter.format(getValues("totalFGType"))
: getValues("totalFGType")
}
// defaultValue={details?.productCount}
disabled={!isEditing}
// error={Boolean(errors.code)}
@@ -89,7 +98,7 @@ const DetailInfoCard: React.FC<Props> = ({
</Grid>
<Grid item xs={6}>
<Controller
name="totalProductionQty"
name="totalEstProdCount"
control={control}
render={({ field }) => (
<TextField
@@ -97,11 +106,17 @@ const DetailInfoCard: React.FC<Props> = ({
label={t("Total Production Qty")}
fullWidth
disabled={!isEditing}
value={
// TODO: May update by table demand qty
defaultValue={
typeof field.value == "number"
? integerFormatter.format(field.value)
: field.value
}
// value={
// typeof field.value == "number"
// ? integerFormatter.format(field.value)
// : field.value
// }
// defaultValue={typeof (details?.productionCount) == "number" ? integerFormatter.format(details?.productionCount) : details?.productionCount}
// error={Boolean(errors.type)}
// helperText={errors.type?.message}

src/components/DetailScheduleDetail/DetailScheudleDetailView.tsx → src/components/DetailedScheduleDetail/DetailedScheduleDetailView.tsx Переглянути файл

@@ -3,17 +3,12 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { useTranslation } from "react-i18next";
import {
//SaveDetailSchedule,
saveItem,
} from "@/app/api/settings/item/actions";
import {
FormProvider,
SubmitErrorHandler,
SubmitHandler,
useForm,
} from "react-hook-form";
import { deleteDialog } from "../Swal/CustomAlerts";
import {
Box,
Button,
@@ -26,38 +21,35 @@ import {
Typography,
} from "@mui/material";
import { Add, Check, Close, EditNote } from "@mui/icons-material";
import { ItemQc, ItemsResult } from "@/app/api/settings/item";
import { useGridApiRef } from "@mui/x-data-grid";
import ProductDetails from "@/components/CreateItem/ProductDetails";
import DetailInfoCard from "@/components/DetailScheduleDetail/DetailInfoCard";
import DetailInfoCard from "@/components/DetailedScheduleDetail/DetailInfoCard";
import ViewByFGDetails, {
FGRecord,
} from "@/components/DetailScheduleDetail/ViewByFGDetails";
import ViewByBomDetails from "@/components/DetailScheduleDetail/ViewByBomDetails";
import EditableSearchResults, {
Column,
} from "@/components/SearchResults/EditableSearchResults";
// FGRecord,
} from "@/components/DetailedScheduleDetail/ViewByFGDetails";
import { DetailedProdScheduleResult, ScheduleType } from "@/app/api/scheduling";

// temp interface input
export interface SaveDetailSchedule {
id: number;
productionDate: string;
totalJobOrders: number;
totalProductionQty: number;
}
// export interface SaveDetailedSchedule {
// id: number;
// productionDate: string;
// totalJobOrders: number;
// totalProductionQty: number;
// }

type Props = {
isEditMode: boolean;
// type: TypeEnum;
defaultValues: Partial<SaveDetailSchedule> | undefined;
defaultValues: Partial<DetailedProdScheduleResult> | undefined;
// qcChecks: ItemQc[]
type: ScheduleType;
};

const DetailScheduleDetailView: React.FC<Props> = ({
const DetailedScheduleDetailView: React.FC<Props> = ({
isEditMode,
// type,
defaultValues,
// qcChecks
// qcChecks,
type
}) => {
// console.log(type)
const apiRef = useGridApiRef();
@@ -67,43 +59,11 @@ const DetailScheduleDetailView: React.FC<Props> = ({
const [tabIndex, setTabIndex] = useState(0);
const { t } = useTranslation("schedule");
const router = useRouter();
const [isEdit, setIsEdit] = useState(false);
//const title = "Demand Forecast Detail"
const [mode, redirPath] = useMemo(() => {
// var typeId = TypeEnum.CONSUMABLE_ID
let title = "";
let mode = "";
let redirPath = "";
// if (type === TypeEnum.MATERIAL) {
// typeId = TypeEnum.MATERIAL_ID
// title = "Material";
// redirPath = "/settings/material";
// }
// if (type === TypeEnum.PRODUCT) {
// typeId = TypeEnum.PRODUCT_ID
title = "Product";
redirPath = "scheduling/detail/edit";
// }
// if (type === TypeEnum.BYPRODUCT) {
// typeId = TypeEnum.BYPRODUCT_ID
// title = "By-Product";
// redirPath = "/settings/byProduct";
// }
if (isEditMode) {
mode = "Edit";
} else {
mode = "Create";
}
return [mode, redirPath];
}, [isEditMode]);
const [isEdit, setIsEdit] = useState(false);
// console.log(typeId)
const formProps = useForm<SaveDetailSchedule>({
defaultValues: defaultValues ? defaultValues : {
id: 1,
productionDate: "2025-05-07",
totalJobOrders: 13,
totalProductionQty: 21000,
} as SaveDetailSchedule,
const formProps = useForm<DetailedProdScheduleResult>({
defaultValues: defaultValues
});
const errors = formProps.formState.errors;

@@ -114,17 +74,17 @@ const DetailScheduleDetailView: React.FC<Props> = ({
[],
);

const [pagingController, setPagingController] = useState({
pageNum: 1,
pageSize: 10,
totalCount: 0,
});
// const [pagingController, setPagingController] = useState({
// pageNum: 1,
// pageSize: 10,
// totalCount: 0,
// });

const handleCancel = () => {
router.replace(`/scheduling/Detail`);
};

const onSubmit = useCallback<SubmitHandler<SaveDetailSchedule>>(
const onSubmit = useCallback<SubmitHandler<DetailedProdScheduleResult>>(
async (data, event) => {
const hasErrors = false;
console.log(errors);
@@ -145,7 +105,7 @@ const DetailScheduleDetailView: React.FC<Props> = ({
);

// multiple tabs
const onSubmitError = useCallback<SubmitErrorHandler<SaveDetailSchedule>>(
const onSubmitError = useCallback<SubmitErrorHandler<DetailedProdScheduleResult>>(
(errors) => {},
[],
);
@@ -154,6 +114,10 @@ const DetailScheduleDetailView: React.FC<Props> = ({
setIsEdit(!isEdit);
};

const onReleaseClick = useCallback(() => {

}, [])

return (
<>
<FormProvider {...formProps}>
@@ -198,7 +162,7 @@ const DetailScheduleDetailView: React.FC<Props> = ({
</Typography>
)}
{/* {tabIndex === 0 && <ViewByFGDetails isEdit={isEdit} apiRef={apiRef} />} */}
<ViewByFGDetails isEdit={isEdit} apiRef={apiRef} />
<ViewByFGDetails isEdit={isEdit} apiRef={apiRef} onReleaseClick={onReleaseClick} type={type}/>
{/* {tabIndex === 1 && <ViewByBomDetails isEdit={isEdit} apiRef={apiRef} isHideButton={true} />} */}
<Stack direction="row" justifyContent="flex-end" gap={1}>
<Button
@@ -223,4 +187,4 @@ const DetailScheduleDetailView: React.FC<Props> = ({
</>
);
};
export default DetailScheduleDetailView;
export default DetailedScheduleDetailView;

+ 46
- 0
src/components/DetailedScheduleDetail/DetailedScheduleDetailWrapper.tsx Переглянути файл

@@ -0,0 +1,46 @@
import { CreateItemInputs } from "@/app/api/settings/item/actions";
import { fetchItem } from "@/app/api/settings/item";
import GeneralLoading from "@/components/General/GeneralLoading";
import DetailedScheduleDetailView from "@/components/DetailedScheduleDetail/DetailedScheduleDetailView";
import { ScheduleType, fetchDetailedProdScheduleDetail } from "@/app/api/scheduling";

interface SubComponents {
Loading: typeof GeneralLoading;
}

type EditDetailedScheduleDetailProps = {
id?: number;
type: ScheduleType;
};

type Props = EditDetailedScheduleDetailProps;

const DetailedScheduleDetailWrapper: React.FC<Props> & SubComponents = async ({
id,
type,
}) => {
// const defaultValues = {
// id: 1,
// productionDate: "2025-05-07",
// totalJobOrders: 13,
// totalProductionQty: 21000,
// };

const prodSchedule = id ? await fetchDetailedProdScheduleDetail(id) : undefined

if (prodSchedule) {
prodSchedule.prodScheduleLines = prodSchedule.prodScheduleLines.sort((a, b) => b.priority - a.priority)
}
return (
<DetailedScheduleDetailView
isEditMode={Boolean(id)}
defaultValues={prodSchedule}
type={type}
// qcChecks={qcChecks || []}
/>
);
};

DetailedScheduleDetailWrapper.Loading = GeneralLoading;

export default DetailedScheduleDetailWrapper;

+ 50
- 0
src/components/DetailedScheduleDetail/ProdTimeColumn.tsx Переглянути файл

@@ -0,0 +1,50 @@
import { DetailedProdScheduleLineProdTimeResult } from "@/app/api/scheduling"
import { minutesToHoursMinutes } from "@/app/utils/formatUtil";
import { Box, Divider, Grid, Typography } from "@mui/material";
import React, { useMemo } from "react"
import { useTranslation } from "react-i18next";

interface Props {
prodTimeInMinute: DetailedProdScheduleLineProdTimeResult[];
}

const ProdTimeColumn: React.FC<Props> = ({ prodTimeInMinute }) => {

const { t } = useTranslation("schedule")
const overallMinutes = useMemo(() =>
prodTimeInMinute
.map((ele) => ele.totalMinutes)
.reduce((acc, cur) => acc + cur, 0)
, [])

return (
<Box>
{
prodTimeInMinute.map(({ equipName, totalMinutes }, index) => {
return (
<Grid container key={`${equipName}-${index}`}>
<Grid item key={`${equipName}-${index}-1`} xs={4} sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<Typography>{equipName}:</Typography>
</Grid>
<Grid item key={`${equipName}-${index}-2`} xs={8} sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<Typography>{minutesToHoursMinutes(totalMinutes)}</Typography>
</Grid>
</Grid>
)
})
}
<Divider sx={{ border: 1, borderColor: "darkgray" }} />
<Grid container>
<Grid item xs={4} sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<Typography>{t("Overall")}:</Typography>
</Grid>
<Grid item xs={8} sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<Typography>{minutesToHoursMinutes(overallMinutes)}</Typography>
</Grid>
</Grid>
</Box>
)
}

export default ProdTimeColumn

+ 1878
- 0
src/components/DetailedScheduleDetail/TempRecords.tsx
Різницю між файлами не показано, бо вона завелика
Переглянути файл


+ 226
- 0
src/components/DetailedScheduleDetail/ViewByFGDetails.tsx Переглянути файл

@@ -0,0 +1,226 @@
"use client";

import { CreateItemInputs } from "@/app/api/settings/item/actions";
import {
GridColDef,
GridRowModel,
GridRenderEditCellParams,
GridEditInputCell,
GridRowSelectionModel,
useGridApiRef,
} from "@mui/x-data-grid";
import { MutableRefObject, useCallback, useMemo, useState } from "react";
import { useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Box, Grid, Tooltip, Typography } from "@mui/material";
import { GridApiCommunity } from "@mui/x-data-grid/internals";
import { decimalFormatter, integerFormatter, minutesToHoursMinutes } from "@/app/utils/formatUtil";
import { DetailedProdScheduleLineResult, DetailedProdScheduleResult, ScheduleType } from "@/app/api/scheduling";
import ProdTimeColumn from "./ProdTimeColumn";
import ScheduleTable, { Column } from "../ScheduleTable/ScheduleTable";

type Props = {
apiRef: MutableRefObject<GridApiCommunity>;
isEdit: boolean;
type: ScheduleType;
onReleaseClick: () => void;
};

// export type FGRecord = {
// id: string | number;
// code: string;
// name: string;
// inStockQty: number;
// productionQty?: number;
// purchaseQty?: number;
// };

const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit, type, onReleaseClick }) => {
const {
t,
i18n: { language },
} = useTranslation("schedule");

const {
getValues,
formState: { errors, defaultValues, touchedFields },
} = useFormContext<DetailedProdScheduleResult>();
// const apiRef = useGridApiRef();

// const [pagingController, setPagingController] = useState([
// {
// pageNum: 1,
// pageSize: 10,
// totalCount: 0,
// },
// {
// pageNum: 1,
// pageSize: 10,
// totalCount: 0,
// },
// {
// pageNum: 1,
// pageSize: 10,
// totalCount: 0,
// },
// {
// pageNum: 1,
// pageSize: 10,
// totalCount: 0,
// },
// {
// pageNum: 1,
// pageSize: 10,
// totalCount: 0,
// },
// {
// pageNum: 1,
// pageSize: 10,
// totalCount: 0,
// },
// {
// pageNum: 1,
// pageSize: 10,
// totalCount: 0,
// },
// {
// pageNum: 1,
// pageSize: 10,
// totalCount: 0,
// },
// ]);

// const updatePagingController = (updatedObj) => {
// setPagingController((prevState) => {
// return prevState.map((item, index) => {
// if (index === updatedObj?.index) {
// return {
// ...item,
// pageNum: item.pageNum,
// pageSize: item.pageSize,
// totalCount: item.totalCount,
// };
// } else return item;
// });
// });
// };

const columns = useMemo<Column<DetailedProdScheduleLineResult>[]>(
() => [
{
field: "jobNo",
label: t("Job No."),
type: "read-only",
// editable: true,
},
{
field: "code",
label: t("code"),
type: "read-only",
// editable: true,
},
{
field: "name",
label: t("name"),
type: "read-only",
},
{
field: "type",
label: t("type"),
type: "read-only",
// editable: true,
},
// {
// field: "inStockQty",
// label: "Available Qty",
// type: 'read-only',
// style: {
// textAlign: "right",
// },
// // editable: true,
// renderCell: (row: FGRecord) => {
// if (typeof (row.inStockQty) == "number") {
// return decimalFormatter.format(row.inStockQty)
// }
// return row.inStockQty
// }
// },
{
field: "demandQty",
label: t("Demand Qty"),
type: "input",
style: {
textAlign: "right",
},
renderCell: (row) => {
if (typeof row.demandQty == "number") {
return decimalFormatter.format(row.demandQty ?? 0);
}
return row.demandQty;
},
},
{
field: "prodTimeInMinute",
label: t("Estimated Production Time"),
type: "read-only",
style: {
textAlign: "right",
},
renderCell: (row) => {
return <ProdTimeColumn prodTimeInMinute={row.prodTimeInMinute} />
}
},
{
field: "priority",
label: t("Production Priority"),
type: "read-only",
style: {
textAlign: "right",
},
// editable: true,
},
],
[],
);

return (
<Grid container spacing={2}>
{/* <Grid item xs={12} key={"all"}>
<Typography variant="overline" display="block" marginBlockEnd={1}>
{t("FG Demand List (7 Days)")}
</Typography>
<EditableSearchResults<FGRecord>
index={7}
items={fakeOverallRecords}
columns={overallColumns}
setPagingController={updatePagingController}
pagingController={pagingController[7]}
isAutoPaging={false}
isEditable={false}
isEdit={isEdit}
hasCollapse={true}
/>
</Grid> */}
{/* {dayPeriod.map((date, index) => ( */}
<Grid item xs={12}>
{/* <Typography variant="overline" display="block" marginBlockEnd={1}>
{`${t("FG Demand Date")}: ${date}`}
</Typography> */}
<ScheduleTable<DetailedProdScheduleLineResult>
type={type}
// items={fakeRecords[index]} // Use the corresponding records for the day
items={getValues("prodScheduleLines")} // Use the corresponding records for the day
columns={columns}
// setPagingController={updatePagingController}
// pagingController={pagingController[index]}
isAutoPaging={false}
isEditable={true}
isEdit={isEdit}
hasCollapse={true}
/>
</Grid>
{/* ))} */}
</Grid>
);
};
export default ViewByFGDetails;

+ 1
- 0
src/components/DetailedScheduleDetail/index.ts Переглянути файл

@@ -0,0 +1 @@
export { default } from "./DetailedScheduleDetailWrapper";

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

@@ -179,7 +179,7 @@ const NavigationContent: React.FC = () => {
{
icon: <RequestQuote />,
label: "Detail Scheduling",
path: "/scheduling/detail",
path: "/scheduling/detailed",
},
{
icon: <RequestQuote />,


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

@@ -3,7 +3,7 @@ import { fetchItem } from "@/app/api/settings/item";
import GeneralLoading from "@/components/General/GeneralLoading";
import RoughScheduleDetailView from "@/components/RoughScheduleDetail/RoughScheudleDetailView";
import React from "react";
import { ScheduleType, fetchProdScheduleDetail } from "@/app/api/scheduling";
import { ScheduleType, fetchRoughProdScheduleDetail } from "@/app/api/scheduling";
interface SubComponents {
Loading: typeof GeneralLoading;
}
@@ -17,7 +17,7 @@ const RoughScheduleDetailWrapper: React.FC<Props> & SubComponents = async ({
id,
type,
}) => {
const prodSchedule = id ? await fetchProdScheduleDetail(id) : undefined;
const prodSchedule = id ? await fetchRoughProdScheduleDetail(id) : undefined;

return (
<RoughScheduleDetailView


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

@@ -3,14 +3,12 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { useTranslation } from "react-i18next";
import { CreateItemInputs, saveItem } from "@/app/api/settings/item/actions";
import {
FormProvider,
SubmitErrorHandler,
SubmitHandler,
useForm,
} from "react-hook-form";
import { deleteDialog } from "../Swal/CustomAlerts";
import {
Box,
Button,
@@ -23,16 +21,12 @@ import {
Typography,
} from "@mui/material";
import { Add, Check, Close, EditNote } from "@mui/icons-material";
import { ItemQc, ItemsResult } from "@/app/api/settings/item";
import { useGridApiRef } from "@mui/x-data-grid";
import ProductDetails from "@/components/CreateItem/ProductDetails";
import DetailInfoCard from "@/components/RoughScheduleDetail/DetailInfoCard";
import ViewByFGDetails from "@/components/RoughScheduleDetail/ViewByFGDetails";
import ViewByBomDetails from "@/components/RoughScheduleDetail/ViewByBomDetails";
import ScheduleTable from "@/components/ScheduleTable";
import { Column } from "@/components/ScheduleTable/ScheduleTable";
import { RoughProdScheduleResult, ScheduleType } from "@/app/api/scheduling";
import { arrayToDayjs, dayjsToDateString } from "@/app/utils/formatUtil";
import { useGridApiRef } from "@mui/x-data-grid";

type Props = {
isEditMode: boolean;


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

@@ -37,6 +37,7 @@ import { decimalFormatter } from "@/app/utils/formatUtil";
import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline";
import HighlightOffIcon from "@mui/icons-material/HighlightOff";
import {
DetailedProdScheduleLineBomMaterialResult,
RoughProdScheduleLineBomMaterialResult,
ScheduleType,
} from "@/app/api/scheduling";
@@ -46,7 +47,7 @@ interface ResultWithId {
}

interface Props {
bomMaterial: RoughProdScheduleLineBomMaterialResult[];
bomMaterial: RoughProdScheduleLineBomMaterialResult[] | DetailedProdScheduleLineBomMaterialResult[];
type: ScheduleType;
}



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

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

import React, {
CSSProperties,
DetailedHTMLProps,
HTMLAttributes,
useEffect,
useState,
CSSProperties,
DetailedHTMLProps,
HTMLAttributes,
useEffect,
useState,
} from "react";
import Paper from "@mui/material/Paper";
import Table from "@mui/material/Table";
@@ -30,363 +30,365 @@ import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil";
import PlayCircleOutlineIcon from "@mui/icons-material/PlayCircleOutline";
import { useTranslation } from "react-i18next";
import {
RoughProdScheduleLineBomMaterialResult,
RoughProdScheduleLineResultByFg,
RoughProdScheduleResult,
ScheduleType,
DetailedProdScheduleLineResult,
RoughProdScheduleLineBomMaterialResult,
RoughProdScheduleLineResultByFg,
RoughProdScheduleResult,
ScheduleType,
} from "@/app/api/scheduling";
import { defaultPagingController } from "../SearchResults/SearchResults";

export interface ResultWithId {
id: string | number;
// id: number;
id: string | number;
// id: number;
}

interface BaseColumn<T extends ResultWithId> {
field: keyof T;
label: string;
type: string;
options?: T[];
renderCell?: (params: T) => React.ReactNode;
style?: Partial<HTMLElement["style"]> & {
[propName: string]: string;
} & CSSProperties;
field: keyof T;
label: string;
type: string;
options?: T[];
renderCell?: (params: T) => React.ReactNode;
style?: Partial<HTMLElement["style"]> & {
[propName: string]: string;
} & CSSProperties;
}

interface ColumnWithAction<T extends ResultWithId> extends BaseColumn<T> {
onClick: (item: T) => void;
buttonIcon: React.ReactNode;
buttonColor?: "inherit" | "default" | "primary" | "secondary";
onClick: (item: T) => void;
buttonIcon: React.ReactNode;
buttonColor?: "inherit" | "default" | "primary" | "secondary";
}

export type Column<T extends ResultWithId> =
| BaseColumn<T>
| ColumnWithAction<T>;
| BaseColumn<T>
| ColumnWithAction<T>;

interface Props<T extends ResultWithId> {
index?: number;
items: T[];
columns: Column<T>[];
noWrapper?: boolean;
setPagingController: (value: {
pageNum: number;
pageSize: number;
totalCount: number;
index?: number;
}) => void;
pagingController: { pageNum: number; pageSize: number; totalCount: number };
isAutoPaging: boolean;
isEdit: boolean;
isEditable: boolean;
hasCollapse: boolean;
type: ScheduleType;
items: T[];
columns: Column<T>[];
noWrapper?: boolean;
setPagingController?: (value: {
pageNum: number;
pageSize: number;
totalCount: number;
index?: number;
}) => void;
pagingController?: { pageNum: number; pageSize: number; totalCount: number };
isAutoPaging: boolean;
isEdit: boolean;
isEditable: boolean;
hasCollapse: boolean;
type: ScheduleType;
}

function ScheduleTable<T extends ResultWithId>({
type,
index = 7,
items,
columns,
noWrapper,
pagingController,
setPagingController,
isAutoPaging = true,
isEdit = false,
isEditable = true,
hasCollapse = false,
type,
index = 7,
items,
columns,
noWrapper,
pagingController = undefined,
setPagingController = undefined,
isAutoPaging = true,
isEdit = false,
isEditable = true,
hasCollapse = false,
}: Props<T>) {
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10);
const [editingRowId, setEditingRowId] = useState<number | null>(null);
const [editedItems, setEditedItems] = useState<T[]>(items);
const { t } = useTranslation("schedule");
useEffect(() => {
setEditedItems(items);
}, [items]);
const handleChangePage = (_event: unknown, newPage: number) => {
setPage(newPage);
if (setPagingController) {
setPagingController({
...pagingController,
pageNum: newPage + 1,
index: index ?? -1,
});
}
};
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10);
const [editingRowId, setEditingRowId] = useState<number | null>(null);
const [editedItems, setEditedItems] = useState<T[]>(items);
const { t } = useTranslation("schedule");
useEffect(() => {
setEditedItems(items);
}, [items]);
const handleChangePage = (_event: unknown, newPage: number) => {
setPage(newPage);
if (setPagingController && pagingController) {
setPagingController({
...pagingController,
pageNum: newPage + 1,
index: index ?? -1,
});
}
};

const handleChangeRowsPerPage = (
event: React.ChangeEvent<HTMLInputElement>,
) => {
setRowsPerPage(+event.target.value);
setPage(0);
if (setPagingController) {
setPagingController({
...pagingController,
pageSize: +event.target.value,
pageNum: 1,
index: index,
});
}
};
const handleChangeRowsPerPage = (
event: React.ChangeEvent<HTMLInputElement>,
) => {
setRowsPerPage(+event.target.value);
setPage(0);
if (setPagingController && pagingController) {
setPagingController({
...pagingController,
pageSize: +event.target.value,
pageNum: 1,
index: index,
});
}
};

const handleEditClick = (id: number) => {
setEditingRowId(id);
};
const handleEditClick = (id: number) => {
setEditingRowId(id);
};

const handleSaveClick = (item: T) => {
setEditingRowId(null);
// Call API or any save logic here
setEditedItems((prev) =>
prev.map((row) => (row.id === item.id ? { ...row } : row)),
);
};
const handleSaveClick = (item: T) => {
setEditingRowId(null);
// Call API or any save logic here
setEditedItems((prev) =>
prev.map((row) => (row.id === item.id ? { ...row } : row)),
);
};

const handleInputChange = (
id: number,
field: keyof T,
value: string | number[],
) => {
setEditedItems((prev) =>
prev.map((item) => (item.id === id ? { ...item, [field]: value } : item)),
);
};
const handleInputChange = (
id: number,
field: keyof T,
value: string | number[],
) => {
setEditedItems((prev) =>
prev.map((item) => (item.id === id ? { ...item, [field]: value } : item)),
);
};

const handleDeleteClick = (id: number) => {
// Implement delete logic here
setEditedItems((prev) => prev.filter((item) => item.id !== id));
};
const handleDeleteClick = (id: number) => {
// Implement delete logic here
setEditedItems((prev) => prev.filter((item) => item.id !== id));
};

useEffect(() => {
console.log("[debug] isEdit in table", isEdit);
//TODO: switch all record to not in edit mode and save the changes
if (!isEdit) {
editedItems?.forEach((item) => {
// Call save logic here
// console.log("Saving item:", item);
// Reset editing state if needed
});
useEffect(() => {
console.log("[debug] isEdit in table", isEdit);
//TODO: switch all record to not in edit mode and save the changes
if (!isEdit) {
editedItems?.forEach((item) => {
// Call save logic here
// console.log("Saving item:", item);
// Reset editing state if needed
});

setEditingRowId(null);
}
}, [isEdit]);
setEditingRowId(null);
}
}, [isEdit]);

function isRoughType(type: ScheduleType): type is "rough" {
return type === "rough";
}
function isRoughType(type: ScheduleType): type is "rough" {
return type === "rough";
}

function isDetailedType(type: ScheduleType): type is "detailed" {
return type === "detailed";
}
function isDetailedType(type: ScheduleType): type is "detailed" {
return type === "detailed";
}

function Row(props: { row: T }) {
const { row } = props;
const [open, setOpen] = useState(false);
// console.log(row)
return (
<>
<TableRow hover tabIndex={-1} key={row.id}>
{isDetailedType(type) && (
<TableCell>
<IconButton disabled={!isEdit}>
<PlayCircleOutlineIcon />
</IconButton>
</TableCell>
)}
{(isEditable || hasCollapse) && (
<TableCell>
{editingRowId === row.id ? (
<>
{isDetailedType(type) && isEditable && (
<IconButton
disabled={!isEdit}
onClick={() => handleSaveClick(row)}
>
<SaveIcon />
</IconButton>
)}
{isDetailedType(type) && isEditable && (
<IconButton
disabled={!isEdit}
onClick={() => setEditingRowId(null)}
>
<CancelIcon />
</IconButton>
)}
{hasCollapse && (
<IconButton
aria-label="expand row"
size="small"
onClick={() => setOpen(!open)}
>
{open ? (
<KeyboardArrowUpIcon />
) : (
<KeyboardArrowDownIcon />
)}
<Typography>{t("View BoM")}</Typography>
</IconButton>
)}
</>
) : (
<>
{isDetailedType(type) && isEditable && (
<IconButton
disabled={!isEdit}
onClick={() => handleEditClick(row.id as number)}
>
<EditIcon />
</IconButton>
)}
{isDetailedType(type) && isEditable && (
<IconButton
disabled={!isEdit}
onClick={() => handleDeleteClick(row.id as number)}
>
<DeleteIcon />
</IconButton>
)}
{hasCollapse && (
<IconButton
aria-label="expand row"
size="small"
onClick={() => setOpen(!open)}
>
{open ? (
<KeyboardArrowUpIcon />
) : (
<KeyboardArrowDownIcon />
)}
<Typography>{t("View BoM")}</Typography>
</IconButton>
)}
</>
)}
</TableCell>
)}
{columns.map((column, idx) => {
const columnName = column.field;
return (
<TableCell key={`${columnName.toString()}-${idx}`}>
{editingRowId === row.id ? (
(() => {
switch (column.type) {
case "input":
function Row(props: { row: T }) {
const { row } = props;
const [open, setOpen] = useState(false);
// console.log(row)
return (
<>
<TableRow hover tabIndex={-1} key={row.id}>
{isDetailedType(type) && (
<TableCell>
<IconButton disabled={!isEdit}>
<PlayCircleOutlineIcon />
</IconButton>
</TableCell>
)}
{(isEditable || hasCollapse) && (
<TableCell>
{editingRowId === row.id ? (
<>
{isDetailedType(type) && isEditable && (
<IconButton
disabled={!isEdit}
onClick={() => handleSaveClick(row)}
>
<SaveIcon />
</IconButton>
)}
{isDetailedType(type) && isEditable && (
<IconButton
disabled={!isEdit}
onClick={() => setEditingRowId(null)}
>
<CancelIcon />
</IconButton>
)}
{hasCollapse && (
<IconButton
aria-label="expand row"
size="small"
onClick={() => setOpen(!open)}
>
{open ? (
<KeyboardArrowUpIcon />
) : (
<KeyboardArrowDownIcon />
)}
<Typography>{t("View BoM")}</Typography>
</IconButton>
)}
</>
) : (
<>
{isDetailedType(type) && isEditable && (
<IconButton
disabled={!isEdit}
onClick={() => handleEditClick(row.id as number)}
>
<EditIcon />
</IconButton>
)}
{isDetailedType(type) && isEditable && (
<IconButton
disabled={!isEdit}
onClick={() => handleDeleteClick(row.id as number)}
>
<DeleteIcon />
</IconButton>
)}
{hasCollapse && (
<IconButton
aria-label="expand row"
size="small"
onClick={() => setOpen(!open)}
>
{open ? (
<KeyboardArrowUpIcon />
) : (
<KeyboardArrowDownIcon />
)}
<Typography>{t("View BoM")}</Typography>
</IconButton>
)}
</>
)}
</TableCell>
)}
{columns.map((column, idx) => {
const columnName = column.field;
return (
<TextField
hiddenLabel={true}
fullWidth
defaultValue={row[columnName] as string}
onChange={(e) =>
handleInputChange(
row.id as number,
columnName,
e.target.value,
)
}
/>
<TableCell key={`${columnName.toString()}-${idx}`}>
{editingRowId === row.id ? (
(() => {
switch (column.type) {
case "input":
return (
<TextField
hiddenLabel={true}
fullWidth
defaultValue={row[columnName] as string}
onChange={(e) =>
handleInputChange(
row.id as number,
columnName,
e.target.value,
)
}
/>
);
// case 'multi-select':
// //TODO: May need update if use
// return (
// <MultiSelect
// //label={column.label}
// options={column.options ?? []}
// selectedValues={[]}
// onChange={(selectedValues) => handleInputChange(row.id as number, columnName, selectedValues)}
// />
// );
case "read-only":
return <span>{row[columnName] as string}</span>;
default:
return null; // Handle any default case if needed
}
})()
) : column.renderCell ? (
<div style={column.style}>{column.renderCell(row)}</div>
) : (
<div style={column.style}>
<span
onDoubleClick={() =>
isEdit && handleEditClick(row.id as number)
}
>
{row[columnName] as string}
</span>
</div>
)}
</TableCell>
);
// case 'multi-select':
// //TODO: May need update if use
// return (
// <MultiSelect
// //label={column.label}
// options={column.options ?? []}
// selectedValues={[]}
// onChange={(selectedValues) => handleInputChange(row.id as number, columnName, selectedValues)}
// />
// );
case "read-only":
return <span>{row[columnName] as string}</span>;
default:
return null; // Handle any default case if needed
}
})()
) : column.renderCell ? (
<div style={column.style}>{column.renderCell(row)}</div>
) : (
<div style={column.style}>
<span
onDoubleClick={() =>
isEdit && handleEditClick(row.id as number)
}
>
{row[columnName] as string}
</span>
</div>
)}
</TableCell>
);
})}
</TableRow>
<TableRow>
{hasCollapse && (
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
<Collapse in={open} timeout="auto" unmountOnExit>
<Table>
<TableBody>
<TableRow>
<TableCell>
<BomMaterialTable
type={type}
bomMaterial={
(row as unknown as RoughProdScheduleLineResultByFg)
.bomMaterials
}
/>
</TableCell>
</TableRow>
</TableBody>
})}
</TableRow>
<TableRow>
{hasCollapse && (
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
<Collapse in={open} timeout="auto" unmountOnExit>
<Table>
<TableBody>
<TableRow>
<TableCell>
<BomMaterialTable
type={type}
bomMaterial={
isDetailedType(type) ? (row as unknown as RoughProdScheduleLineResultByFg).bomMaterials
: (row as unknown as DetailedProdScheduleLineResult).bomMaterials
}
/>
</TableCell>
</TableRow>
</TableBody>
</Table>
</Collapse>
</TableCell>
)}
</TableRow>
</>
);
}

const table = (
<>
<TableContainer sx={{ maxHeight: 440 }}>
<Table stickyHeader>
<TableHead>
<TableRow>
{isDetailedType(type) && <TableCell>{t("Release")}</TableCell>}
{(isEditable || hasCollapse) && (
<TableCell>{t("Actions")}</TableCell>
)}{" "}
{/* Action Column Header */}
{columns.map((column, idx) => (
<TableCell
style={column.style}
key={`${column.field.toString()}${idx}`}
>
{column.label}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{/* {(isAutoPaging ? editedItems.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) : editedItems).map((item) => ( */}
{editedItems
?.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
?.map((item) => <Row key={item.id} row={item} />)}
</TableBody>
</Table>
</Collapse>
</TableCell>
)}
</TableRow>
</>
</TableContainer>
<TablePagination
rowsPerPageOptions={[10, 25, 100]}
component="div"
// count={pagingController.totalCount === 0 ? editedItems.length : pagingController.totalCount}
count={editedItems?.length ?? 0}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</>
);
}

const table = (
<>
<TableContainer sx={{ maxHeight: 440 }}>
<Table stickyHeader>
<TableHead>
<TableRow>
{isDetailedType(type) && <TableCell>{t("Release")}</TableCell>}
{(isEditable || hasCollapse) && (
<TableCell>{t("Actions")}</TableCell>
)}{" "}
{/* Action Column Header */}
{columns.map((column, idx) => (
<TableCell
style={column.style}
key={`${column.field.toString()}${idx}`}
>
{column.label}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{/* {(isAutoPaging ? editedItems.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) : editedItems).map((item) => ( */}
{editedItems
?.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
?.map((item) => <Row key={item.id} row={item} />)}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[10, 25, 100]}
component="div"
// count={pagingController.totalCount === 0 ? editedItems.length : pagingController.totalCount}
count={editedItems?.length ?? 0}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</>
);

return noWrapper ? table : <Paper sx={{ overflow: "hidden" }}>{table}</Paper>;
return noWrapper ? table : <Paper sx={{ overflow: "hidden" }}>{table}</Paper>;
}

export default ScheduleTable;

+ 2
- 1
src/i18n/zh/schedule.json Переглянути файл

@@ -81,5 +81,6 @@
"Job Qty": "工單數量",
"mat": "物料",
"Product Count(s)": "產品數量",
"Schedule Period To": "排程期間至"
"Schedule Period To": "排程期間至",
"Overall": "總計"
}

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