@@ -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; |
@@ -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; |
@@ -1,8 +1,8 @@ | |||||
// import { TypeEnum } from "@/app/utils/typeEnum"; | // import { TypeEnum } from "@/app/utils/typeEnum"; | ||||
// import DetailSchedule from "@/components/DetailSchedule"; | |||||
// import DetailedSchedule from "@/components/DetailedSchedule"; | |||||
// import { getServerI18n } from "@/i18n"; | // import { getServerI18n } from "@/i18n"; | ||||
import DetailSchedule from "../../../../components/DetailSchedule"; | |||||
import DetailedSchedule from "../../../../components/DetailedSchedule"; | |||||
import { getServerI18n } from "../../../../i18n"; | import { getServerI18n } from "../../../../i18n"; | ||||
import { I18nProvider } from "@/i18n"; | import { I18nProvider } from "@/i18n"; | ||||
import Stack from "@mui/material/Stack"; | import Stack from "@mui/material/Stack"; | ||||
@@ -32,8 +32,8 @@ const DetailScheduling: React.FC = async () => { | |||||
</Typography> | </Typography> | ||||
</Stack> | </Stack> | ||||
<I18nProvider namespaces={["schedule", "common"]}> | <I18nProvider namespaces={["schedule", "common"]}> | ||||
<Suspense fallback={<DetailSchedule.Loading />}> | |||||
<DetailSchedule type={type} /> | |||||
<Suspense fallback={<DetailedSchedule.Loading />}> | |||||
<DetailedSchedule type={type} /> | |||||
</Suspense> | </Suspense> | ||||
</I18nProvider> | </I18nProvider> | ||||
</> | </> |
@@ -12,7 +12,7 @@ import RoughScheduleDetailView from "@/components/RoughScheduleDetail"; | |||||
import { SearchParams, ServerFetchError } from "@/app/utils/fetchUtil"; | import { SearchParams, ServerFetchError } from "@/app/utils/fetchUtil"; | ||||
import { isArray, parseInt } from "lodash"; | import { isArray, parseInt } from "lodash"; | ||||
import { notFound } from "next/navigation"; | import { notFound } from "next/navigation"; | ||||
import { fetchProdScheduleDetail } from "@/app/api/scheduling"; | |||||
import { fetchRoughProdScheduleDetail } from "@/app/api/scheduling"; | |||||
export const metadata: Metadata = { | export const metadata: Metadata = { | ||||
title: "Demand Forecast Detail", | title: "Demand Forecast Detail", | ||||
@@ -30,7 +30,7 @@ const roughSchedulingDetail: React.FC<Props> = async ({ searchParams }) => { | |||||
} | } | ||||
try { | try { | ||||
await fetchProdScheduleDetail(parseInt(id)); | |||||
await fetchRoughProdScheduleDetail(parseInt(id)); | |||||
} catch (e) { | } catch (e) { | ||||
if ( | if ( | ||||
e instanceof ServerFetchError && | e instanceof ServerFetchError && | ||||
@@ -31,6 +31,11 @@ export interface ProdScheduleResultByPage { | |||||
records: ProdScheduleResult[]; | records: ProdScheduleResult[]; | ||||
} | } | ||||
export interface ReleaseDetailProdScheduleInputs { | |||||
id: number; | |||||
demandQty: number; | |||||
} | |||||
export const fetchProdSchedules = cache( | export const fetchProdSchedules = cache( | ||||
async (data: SearchProdSchedule | null) => { | async (data: SearchProdSchedule | null) => { | ||||
const params = convertObjToURLSearchParams<SearchProdSchedule>(data); | 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( | return serverFetchJson( | ||||
`${BASE_API_URL}/productionSchedule/testDetailSchedule`, | |||||
`${BASE_API_URL}/productionSchedule/testDetailedSchedule`, | |||||
{ | { | ||||
method: "GET", | method: "GET", | ||||
headers: { "Content-Type": "application/json" }, | 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"], | |||||
}, | |||||
} | |||||
) | |||||
}) |
@@ -5,85 +5,129 @@ import "server-only"; | |||||
export type ScheduleType = "all" | "rough" | "detailed" | "manual"; | export type ScheduleType = "all" | "rough" | "detailed" | "manual"; | ||||
// Rough | |||||
export interface RoughProdScheduleResult { | 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 { | 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 { | 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 { | 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 { | 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"] | |||||
} | |||||
}) | |||||
}) |
@@ -66,6 +66,36 @@ export const dayjsToDateString = (date: Dayjs) => { | |||||
return date.format(OUTPUT_DATE_FORMAT); | 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 } = { | export const stockInLineStatusMap: { [status: string]: number } = { | ||||
draft: 0, | draft: 0, | ||||
pending: 1, | pending: 1, | ||||
@@ -1 +0,0 @@ | |||||
export { default } from "./DetailScheduleWrapper"; |
@@ -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; |
@@ -1 +0,0 @@ | |||||
export { default } from "./DetailScheduleDetailWrapper"; |
@@ -5,7 +5,7 @@ import Stack from "@mui/material/Stack"; | |||||
import React from "react"; | import React from "react"; | ||||
// Can make this nicer | // Can make this nicer | ||||
export const DetailScheduleLoading: React.FC = () => { | |||||
export const DetailedScheduleLoading: React.FC = () => { | |||||
return ( | return ( | ||||
<> | <> | ||||
<Card> | <Card> | ||||
@@ -37,4 +37,4 @@ export const DetailScheduleLoading: React.FC = () => { | |||||
); | ); | ||||
}; | }; | ||||
export default DetailScheduleLoading; | |||||
export default DetailedScheduleLoading; |
@@ -2,18 +2,10 @@ | |||||
import React, { useCallback, useEffect, useMemo, useState } from "react"; | import React, { useCallback, useEffect, useMemo, useState } from "react"; | ||||
import SearchBox, { Criterion } from "../SearchBox"; | import SearchBox, { Criterion } from "../SearchBox"; | ||||
import { ItemsResult } from "@/app/api/settings/item"; | |||||
import SearchResults, { Column } from "../SearchResults"; | import SearchResults, { Column } from "../SearchResults"; | ||||
import { EditNote } from "@mui/icons-material"; | import { EditNote } from "@mui/icons-material"; | ||||
import { useRouter, useSearchParams } from "next/navigation"; | 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 { 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 { ScheduleType } from "@/app/api/scheduling"; | ||||
import { | import { | ||||
ProdScheduleResult, | ProdScheduleResult, | ||||
@@ -107,7 +99,7 @@ const DSOverview: React.FC<Props> = ({ type, defaultInputs }) => { | |||||
const onDetailClick = (record: ProdScheduleResult) => { | const onDetailClick = (record: ProdScheduleResult) => { | ||||
console.log("[debug] record", record); | 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>[]>( | const columns = useMemo<Column<ProdScheduleResult>[]>( |
@@ -1,18 +1,18 @@ | |||||
import React from "react"; | 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 { ScheduleType } from "@/app/api/scheduling"; | ||||
import { SearchProdSchedule } from "@/app/api/scheduling/actions"; | import { SearchProdSchedule } from "@/app/api/scheduling/actions"; | ||||
interface SubComponents { | interface SubComponents { | ||||
Loading: typeof DetailScheduleLoading; | |||||
Loading: typeof DetailedScheduleLoading; | |||||
} | } | ||||
type Props = { | type Props = { | ||||
type: ScheduleType; | type: ScheduleType; | ||||
}; | }; | ||||
const DetailScheduleWrapper: React.FC<Props> & SubComponents = async ({ | |||||
const DetailedScheduleWrapper: React.FC<Props> & SubComponents = async ({ | |||||
type, | type, | ||||
}) => { | }) => { | ||||
const defaultInputs: SearchProdSchedule = { | const defaultInputs: SearchProdSchedule = { | ||||
@@ -22,6 +22,6 @@ const DetailScheduleWrapper: React.FC<Props> & SubComponents = async ({ | |||||
return <DSOverview type={type} defaultInputs={defaultInputs} />; | return <DSOverview type={type} defaultInputs={defaultInputs} />; | ||||
}; | }; | ||||
DetailScheduleWrapper.Loading = DetailScheduleLoading; | |||||
DetailedScheduleWrapper.Loading = DetailedScheduleLoading; | |||||
export default DetailScheduleWrapper; | |||||
export default DetailedScheduleWrapper; |
@@ -0,0 +1 @@ | |||||
export { default } from "./DetailedScheduleWrapper"; |
@@ -17,13 +17,14 @@ import { InputDataGridProps, TableRow } from "../InputDataGrid/InputDataGrid"; | |||||
import { TypeEnum } from "@/app/utils/typeEnum"; | import { TypeEnum } from "@/app/utils/typeEnum"; | ||||
import { CreateItemInputs } from "@/app/api/settings/item/actions"; | import { CreateItemInputs } from "@/app/api/settings/item/actions"; | ||||
import { NumberInputProps } from "@/components/CreateItem/NumberInputProps"; | 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 | // temp interface input | ||||
type Props = { | type Props = { | ||||
// recordDetails: SaveDetailSchedule; | |||||
// recordDetails: SaveDetailedSchedule; | |||||
isEditing: boolean; | isEditing: boolean; | ||||
}; | }; | ||||
@@ -39,14 +40,15 @@ const DetailInfoCard: React.FC<Props> = ({ | |||||
const { | const { | ||||
control, | control, | ||||
register, | register, | ||||
getValues, | |||||
formState: { errors, defaultValues, touchedFields }, | formState: { errors, defaultValues, touchedFields }, | ||||
} = useFormContext<SaveDetailSchedule>(); | |||||
} = useFormContext<DetailedProdScheduleResult>(); | |||||
const [details, setDetails] = useState<SaveDetailSchedule | undefined>(undefined); | |||||
// const [details, setDetails] = useState<DetailedProdScheduleResult | undefined>(undefined); | |||||
useEffect(() => { | useEffect(() => { | ||||
console.log("[debug] record details", defaultValues) | console.log("[debug] record details", defaultValues) | ||||
setDetails(defaultValues as SaveDetailSchedule); | |||||
// setDetails(defaultValues as DetailedProdScheduleResult); | |||||
}, [defaultValues]) | }, [defaultValues]) | ||||
useEffect(() => { | useEffect(() => { | ||||
@@ -65,9 +67,10 @@ const DetailInfoCard: React.FC<Props> = ({ | |||||
<TextField | <TextField | ||||
label={t("Production Date")} | label={t("Production Date")} | ||||
fullWidth | fullWidth | ||||
{...register("productionDate", { | |||||
required: "name required!", | |||||
})} | |||||
// {...register("scheduleAt", { | |||||
// required: "Schedule At required!", | |||||
// })} | |||||
defaultValue={`${arrayToDateString(getValues("scheduleAt"))}`} | |||||
// defaultValue={details?.scheduledPeriod} | // defaultValue={details?.scheduledPeriod} | ||||
disabled={!isEditing} | disabled={!isEditing} | ||||
// error={Boolean(errors.name)} | // error={Boolean(errors.name)} | ||||
@@ -78,9 +81,15 @@ const DetailInfoCard: React.FC<Props> = ({ | |||||
<TextField | <TextField | ||||
label={t("Total Job Orders")} | label={t("Total Job Orders")} | ||||
fullWidth | 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} | // defaultValue={details?.productCount} | ||||
disabled={!isEditing} | disabled={!isEditing} | ||||
// error={Boolean(errors.code)} | // error={Boolean(errors.code)} | ||||
@@ -89,7 +98,7 @@ const DetailInfoCard: React.FC<Props> = ({ | |||||
</Grid> | </Grid> | ||||
<Grid item xs={6}> | <Grid item xs={6}> | ||||
<Controller | <Controller | ||||
name="totalProductionQty" | |||||
name="totalEstProdCount" | |||||
control={control} | control={control} | ||||
render={({ field }) => ( | render={({ field }) => ( | ||||
<TextField | <TextField | ||||
@@ -97,11 +106,17 @@ const DetailInfoCard: React.FC<Props> = ({ | |||||
label={t("Total Production Qty")} | label={t("Total Production Qty")} | ||||
fullWidth | fullWidth | ||||
disabled={!isEditing} | disabled={!isEditing} | ||||
value={ | |||||
// TODO: May update by table demand qty | |||||
defaultValue={ | |||||
typeof field.value == "number" | typeof field.value == "number" | ||||
? integerFormatter.format(field.value) | ? integerFormatter.format(field.value) | ||||
: 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} | // defaultValue={typeof (details?.productionCount) == "number" ? integerFormatter.format(details?.productionCount) : details?.productionCount} | ||||
// error={Boolean(errors.type)} | // error={Boolean(errors.type)} | ||||
// helperText={errors.type?.message} | // helperText={errors.type?.message} |
@@ -3,17 +3,12 @@ | |||||
import { useCallback, useEffect, useMemo, useState } from "react"; | import { useCallback, useEffect, useMemo, useState } from "react"; | ||||
import { useRouter, useSearchParams } from "next/navigation"; | import { useRouter, useSearchParams } from "next/navigation"; | ||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import { | |||||
//SaveDetailSchedule, | |||||
saveItem, | |||||
} from "@/app/api/settings/item/actions"; | |||||
import { | import { | ||||
FormProvider, | FormProvider, | ||||
SubmitErrorHandler, | SubmitErrorHandler, | ||||
SubmitHandler, | SubmitHandler, | ||||
useForm, | useForm, | ||||
} from "react-hook-form"; | } from "react-hook-form"; | ||||
import { deleteDialog } from "../Swal/CustomAlerts"; | |||||
import { | import { | ||||
Box, | Box, | ||||
Button, | Button, | ||||
@@ -26,38 +21,35 @@ import { | |||||
Typography, | Typography, | ||||
} from "@mui/material"; | } from "@mui/material"; | ||||
import { Add, Check, Close, EditNote } from "@mui/icons-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 { 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, { | 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 | // 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 = { | type Props = { | ||||
isEditMode: boolean; | isEditMode: boolean; | ||||
// type: TypeEnum; | // type: TypeEnum; | ||||
defaultValues: Partial<SaveDetailSchedule> | undefined; | |||||
defaultValues: Partial<DetailedProdScheduleResult> | undefined; | |||||
// qcChecks: ItemQc[] | // qcChecks: ItemQc[] | ||||
type: ScheduleType; | |||||
}; | }; | ||||
const DetailScheduleDetailView: React.FC<Props> = ({ | |||||
const DetailedScheduleDetailView: React.FC<Props> = ({ | |||||
isEditMode, | isEditMode, | ||||
// type, | // type, | ||||
defaultValues, | defaultValues, | ||||
// qcChecks | |||||
// qcChecks, | |||||
type | |||||
}) => { | }) => { | ||||
// console.log(type) | // console.log(type) | ||||
const apiRef = useGridApiRef(); | const apiRef = useGridApiRef(); | ||||
@@ -67,43 +59,11 @@ const DetailScheduleDetailView: React.FC<Props> = ({ | |||||
const [tabIndex, setTabIndex] = useState(0); | const [tabIndex, setTabIndex] = useState(0); | ||||
const { t } = useTranslation("schedule"); | const { t } = useTranslation("schedule"); | ||||
const router = useRouter(); | 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) | // 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; | 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 = () => { | const handleCancel = () => { | ||||
router.replace(`/scheduling/Detail`); | router.replace(`/scheduling/Detail`); | ||||
}; | }; | ||||
const onSubmit = useCallback<SubmitHandler<SaveDetailSchedule>>( | |||||
const onSubmit = useCallback<SubmitHandler<DetailedProdScheduleResult>>( | |||||
async (data, event) => { | async (data, event) => { | ||||
const hasErrors = false; | const hasErrors = false; | ||||
console.log(errors); | console.log(errors); | ||||
@@ -145,7 +105,7 @@ const DetailScheduleDetailView: React.FC<Props> = ({ | |||||
); | ); | ||||
// multiple tabs | // multiple tabs | ||||
const onSubmitError = useCallback<SubmitErrorHandler<SaveDetailSchedule>>( | |||||
const onSubmitError = useCallback<SubmitErrorHandler<DetailedProdScheduleResult>>( | |||||
(errors) => {}, | (errors) => {}, | ||||
[], | [], | ||||
); | ); | ||||
@@ -154,6 +114,10 @@ const DetailScheduleDetailView: React.FC<Props> = ({ | |||||
setIsEdit(!isEdit); | setIsEdit(!isEdit); | ||||
}; | }; | ||||
const onReleaseClick = useCallback(() => { | |||||
}, []) | |||||
return ( | return ( | ||||
<> | <> | ||||
<FormProvider {...formProps}> | <FormProvider {...formProps}> | ||||
@@ -198,7 +162,7 @@ const DetailScheduleDetailView: React.FC<Props> = ({ | |||||
</Typography> | </Typography> | ||||
)} | )} | ||||
{/* {tabIndex === 0 && <ViewByFGDetails isEdit={isEdit} apiRef={apiRef} />} */} | {/* {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} />} */} | {/* {tabIndex === 1 && <ViewByBomDetails isEdit={isEdit} apiRef={apiRef} isHideButton={true} />} */} | ||||
<Stack direction="row" justifyContent="flex-end" gap={1}> | <Stack direction="row" justifyContent="flex-end" gap={1}> | ||||
<Button | <Button | ||||
@@ -223,4 +187,4 @@ const DetailScheduleDetailView: React.FC<Props> = ({ | |||||
</> | </> | ||||
); | ); | ||||
}; | }; | ||||
export default DetailScheduleDetailView; | |||||
export default DetailedScheduleDetailView; |
@@ -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; |
@@ -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 |
@@ -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; |
@@ -0,0 +1 @@ | |||||
export { default } from "./DetailedScheduleDetailWrapper"; |
@@ -179,7 +179,7 @@ const NavigationContent: React.FC = () => { | |||||
{ | { | ||||
icon: <RequestQuote />, | icon: <RequestQuote />, | ||||
label: "Detail Scheduling", | label: "Detail Scheduling", | ||||
path: "/scheduling/detail", | |||||
path: "/scheduling/detailed", | |||||
}, | }, | ||||
{ | { | ||||
icon: <RequestQuote />, | icon: <RequestQuote />, | ||||
@@ -3,7 +3,7 @@ import { fetchItem } from "@/app/api/settings/item"; | |||||
import GeneralLoading from "@/components/General/GeneralLoading"; | import GeneralLoading from "@/components/General/GeneralLoading"; | ||||
import RoughScheduleDetailView from "@/components/RoughScheduleDetail/RoughScheudleDetailView"; | import RoughScheduleDetailView from "@/components/RoughScheduleDetail/RoughScheudleDetailView"; | ||||
import React from "react"; | import React from "react"; | ||||
import { ScheduleType, fetchProdScheduleDetail } from "@/app/api/scheduling"; | |||||
import { ScheduleType, fetchRoughProdScheduleDetail } from "@/app/api/scheduling"; | |||||
interface SubComponents { | interface SubComponents { | ||||
Loading: typeof GeneralLoading; | Loading: typeof GeneralLoading; | ||||
} | } | ||||
@@ -17,7 +17,7 @@ const RoughScheduleDetailWrapper: React.FC<Props> & SubComponents = async ({ | |||||
id, | id, | ||||
type, | type, | ||||
}) => { | }) => { | ||||
const prodSchedule = id ? await fetchProdScheduleDetail(id) : undefined; | |||||
const prodSchedule = id ? await fetchRoughProdScheduleDetail(id) : undefined; | |||||
return ( | return ( | ||||
<RoughScheduleDetailView | <RoughScheduleDetailView | ||||
@@ -3,14 +3,12 @@ | |||||
import { useCallback, useEffect, useMemo, useState } from "react"; | import { useCallback, useEffect, useMemo, useState } from "react"; | ||||
import { useRouter, useSearchParams } from "next/navigation"; | import { useRouter, useSearchParams } from "next/navigation"; | ||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import { CreateItemInputs, saveItem } from "@/app/api/settings/item/actions"; | |||||
import { | import { | ||||
FormProvider, | FormProvider, | ||||
SubmitErrorHandler, | SubmitErrorHandler, | ||||
SubmitHandler, | SubmitHandler, | ||||
useForm, | useForm, | ||||
} from "react-hook-form"; | } from "react-hook-form"; | ||||
import { deleteDialog } from "../Swal/CustomAlerts"; | |||||
import { | import { | ||||
Box, | Box, | ||||
Button, | Button, | ||||
@@ -23,16 +21,12 @@ import { | |||||
Typography, | Typography, | ||||
} from "@mui/material"; | } from "@mui/material"; | ||||
import { Add, Check, Close, EditNote } from "@mui/icons-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 DetailInfoCard from "@/components/RoughScheduleDetail/DetailInfoCard"; | ||||
import ViewByFGDetails from "@/components/RoughScheduleDetail/ViewByFGDetails"; | import ViewByFGDetails from "@/components/RoughScheduleDetail/ViewByFGDetails"; | ||||
import ViewByBomDetails from "@/components/RoughScheduleDetail/ViewByBomDetails"; | 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 { RoughProdScheduleResult, ScheduleType } from "@/app/api/scheduling"; | ||||
import { arrayToDayjs, dayjsToDateString } from "@/app/utils/formatUtil"; | import { arrayToDayjs, dayjsToDateString } from "@/app/utils/formatUtil"; | ||||
import { useGridApiRef } from "@mui/x-data-grid"; | |||||
type Props = { | type Props = { | ||||
isEditMode: boolean; | isEditMode: boolean; | ||||
@@ -37,6 +37,7 @@ import { decimalFormatter } from "@/app/utils/formatUtil"; | |||||
import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline"; | import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline"; | ||||
import HighlightOffIcon from "@mui/icons-material/HighlightOff"; | import HighlightOffIcon from "@mui/icons-material/HighlightOff"; | ||||
import { | import { | ||||
DetailedProdScheduleLineBomMaterialResult, | |||||
RoughProdScheduleLineBomMaterialResult, | RoughProdScheduleLineBomMaterialResult, | ||||
ScheduleType, | ScheduleType, | ||||
} from "@/app/api/scheduling"; | } from "@/app/api/scheduling"; | ||||
@@ -46,7 +47,7 @@ interface ResultWithId { | |||||
} | } | ||||
interface Props { | interface Props { | ||||
bomMaterial: RoughProdScheduleLineBomMaterialResult[]; | |||||
bomMaterial: RoughProdScheduleLineBomMaterialResult[] | DetailedProdScheduleLineBomMaterialResult[]; | |||||
type: ScheduleType; | type: ScheduleType; | ||||
} | } | ||||
@@ -1,11 +1,11 @@ | |||||
"use client"; | "use client"; | ||||
import React, { | import React, { | ||||
CSSProperties, | |||||
DetailedHTMLProps, | |||||
HTMLAttributes, | |||||
useEffect, | |||||
useState, | |||||
CSSProperties, | |||||
DetailedHTMLProps, | |||||
HTMLAttributes, | |||||
useEffect, | |||||
useState, | |||||
} from "react"; | } from "react"; | ||||
import Paper from "@mui/material/Paper"; | import Paper from "@mui/material/Paper"; | ||||
import Table from "@mui/material/Table"; | 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 PlayCircleOutlineIcon from "@mui/icons-material/PlayCircleOutline"; | ||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import { | import { | ||||
RoughProdScheduleLineBomMaterialResult, | |||||
RoughProdScheduleLineResultByFg, | |||||
RoughProdScheduleResult, | |||||
ScheduleType, | |||||
DetailedProdScheduleLineResult, | |||||
RoughProdScheduleLineBomMaterialResult, | |||||
RoughProdScheduleLineResultByFg, | |||||
RoughProdScheduleResult, | |||||
ScheduleType, | |||||
} from "@/app/api/scheduling"; | } from "@/app/api/scheduling"; | ||||
import { defaultPagingController } from "../SearchResults/SearchResults"; | |||||
export interface ResultWithId { | export interface ResultWithId { | ||||
id: string | number; | |||||
// id: number; | |||||
id: string | number; | |||||
// id: number; | |||||
} | } | ||||
interface BaseColumn<T extends ResultWithId> { | 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> { | 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> = | export type Column<T extends ResultWithId> = | ||||
| BaseColumn<T> | |||||
| ColumnWithAction<T>; | |||||
| BaseColumn<T> | |||||
| ColumnWithAction<T>; | |||||
interface Props<T extends ResultWithId> { | interface Props<T extends ResultWithId> { | ||||
index?: number; | |||||
items: T[]; | |||||
columns: Column<T>[]; | |||||
noWrapper?: boolean; | |||||
setPagingController: (value: { | |||||
pageNum: number; | |||||
pageSize: number; | |||||
totalCount: number; | |||||
index?: 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>({ | 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>) { | }: 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 ( | 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> | </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; | export default ScheduleTable; |
@@ -81,5 +81,6 @@ | |||||
"Job Qty": "工單數量", | "Job Qty": "工單數量", | ||||
"mat": "物料", | "mat": "物料", | ||||
"Product Count(s)": "產品數量", | "Product Count(s)": "產品數量", | ||||
"Schedule Period To": "排程期間至" | |||||
"Schedule Period To": "排程期間至", | |||||
"Overall": "總計" | |||||
} | } |