diff --git a/.env.production b/.env.production index 9fd4c12..fa578f2 100644 --- a/.env.production +++ b/.env.production @@ -1,4 +1,4 @@ -API_URL=http://localhost:8090/api +API_URL=http://10.100.0.81:8090/api NEXTAUTH_SECRET=secret -NEXTAUTH_URL=https://fpsms-uat.2fi-solutions.com -NEXT_PUBLIC_API_URL=http://localhost:8090/api \ No newline at end of file +NEXTAUTH_URL=http://10.100.0.81:3000 +NEXT_PUBLIC_API_URL=http://10.100.0.81:8090/api \ No newline at end of file diff --git a/next.config.js b/next.config.js index 883f839..061e19c 100644 --- a/next.config.js +++ b/next.config.js @@ -9,11 +9,11 @@ const withPWA = require("next-pwa")({ }); const nextConfig = { - // eslint: { - // // Warning: This allows production builds to successfully complete even if - // // your project has ESLint errors. - // ignoreDuringBuilds: true, - // }, + eslint: { + // Warning: This allows production builds to successfully complete even if + // your project has ESLint errors. + ignoreDuringBuilds: true, + }, }; module.exports = withPWA(nextConfig); diff --git a/src/app/(main)/jo/edit/not-found.tsx b/src/app/(main)/jo/edit/not-found.tsx new file mode 100644 index 0000000..6561158 --- /dev/null +++ b/src/app/(main)/jo/edit/not-found.tsx @@ -0,0 +1,19 @@ +import { getServerI18n } from "@/i18n"; +import { Stack, Typography, Link } from "@mui/material"; +import NextLink from "next/link"; + +export default async function NotFound() { + const { t } = await getServerI18n("schedule", "common"); + + return ( + + {t("Not Found")} + + {t("The job order page was not found!")} + + + {t("Return to all job orders")} + + + ); +} diff --git a/src/app/(main)/jo/edit/page.tsx b/src/app/(main)/jo/edit/page.tsx new file mode 100644 index 0000000..29b9c7f --- /dev/null +++ b/src/app/(main)/jo/edit/page.tsx @@ -0,0 +1,49 @@ +import { fetchJoDetail } from "@/app/api/jo"; +import { SearchParams, ServerFetchError } from "@/app/utils/fetchUtil"; +import JoSave from "@/components/JoSave"; +import { I18nProvider, getServerI18n } from "@/i18n"; +import { Typography } from "@mui/material"; +import { isArray } from "lodash"; +import { Metadata } from "next"; +import { notFound } from "next/navigation"; +import { Suspense } from "react"; + +export const metadata: Metadata = { + title: "Edit Job Order Detail" +} + +type Props = SearchParams; + +const JoEdit: React.FC = async ({ searchParams }) => { + const { t } = await getServerI18n("jo"); + const id = searchParams["id"]; + + if (!id || isArray(id) || !isFinite(parseInt(id))) { + notFound(); + } + + try { + await fetchJoDetail(parseInt(id)) + } catch (e) { + if (e instanceof ServerFetchError && (e.response?.status === 404 || e.response?.status === 400)) { + console.log(e) + notFound(); + } + } + + + return ( + <> + + {t("Edit Job Order Detail")} + + + }> + + + + + ); +} + +export default JoEdit; \ No newline at end of file diff --git a/src/app/(main)/jo/page.tsx b/src/app/(main)/jo/page.tsx new file mode 100644 index 0000000..8922424 --- /dev/null +++ b/src/app/(main)/jo/page.tsx @@ -0,0 +1,35 @@ +import JoSearch from "@/components/JoSearch"; +import { I18nProvider, getServerI18n } from "@/i18n"; +import { Stack, Typography } from "@mui/material"; +import { Metadata } from "next"; +import React, { Suspense } from "react"; + +export const metadata: Metadata = { + title: "Job Order" +} + +const jo: React.FC = async () => { + const { t } = await getServerI18n("jo"); + + return ( + <> + + + {t("Job Order")} + + + + }> + + + + + ) +} + +export default jo; \ No newline at end of file diff --git a/src/app/(main)/pickOrder/page.tsx b/src/app/(main)/pickOrder/page.tsx index 9d9e9ac..88f6790 100644 --- a/src/app/(main)/pickOrder/page.tsx +++ b/src/app/(main)/pickOrder/page.tsx @@ -17,16 +17,6 @@ const PickOrder: React.FC = async () => { return ( <> - - - {t("Pick Order")} - - }> diff --git a/src/app/(main)/scheduling/detail/edit/page.tsx b/src/app/(main)/scheduling/detail/edit/page.tsx deleted file mode 100644 index 1212996..0000000 --- a/src/app/(main)/scheduling/detail/edit/page.tsx +++ /dev/null @@ -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 = 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 ( - <> - - {t("FG Production Schedule")} - - - - - - ); -}; - -export default DetailScheduling; diff --git a/src/app/(main)/scheduling/detail/edit/not-found.tsx b/src/app/(main)/scheduling/detailed/edit/not-found.tsx similarity index 100% rename from src/app/(main)/scheduling/detail/edit/not-found.tsx rename to src/app/(main)/scheduling/detailed/edit/not-found.tsx diff --git a/src/app/(main)/scheduling/detailed/edit/page.tsx b/src/app/(main)/scheduling/detailed/edit/page.tsx new file mode 100644 index 0000000..35d18db --- /dev/null +++ b/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 = 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 ( + <> + + {t("FG Production Schedule")} + + + }> + + + + + ); +}; + +export default DetailScheduling; diff --git a/src/app/(main)/scheduling/detail/page.tsx b/src/app/(main)/scheduling/detailed/page.tsx similarity index 80% rename from src/app/(main)/scheduling/detail/page.tsx rename to src/app/(main)/scheduling/detailed/page.tsx index 52da5b9..b60e7ea 100644 --- a/src/app/(main)/scheduling/detail/page.tsx +++ b/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 () => { - }> - + }> + diff --git a/src/app/(main)/scheduling/rough/edit/page.tsx b/src/app/(main)/scheduling/rough/edit/page.tsx index 1096da1..9ac1a04 100644 --- a/src/app/(main)/scheduling/rough/edit/page.tsx +++ b/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 = async ({ searchParams }) => { } try { - await fetchProdScheduleDetail(parseInt(id)); + await fetchRoughProdScheduleDetail(parseInt(id)); } catch (e) { if ( e instanceof ServerFetchError && diff --git a/src/app/api/jo/actions.ts b/src/app/api/jo/actions.ts index c43fe41..e9ea350 100644 --- a/src/app/api/jo/actions.ts +++ b/src/app/api/jo/actions.ts @@ -1,8 +1,39 @@ "use server"; -import { serverFetchJson } from "@/app/utils/fetchUtil"; + +import { cache } from 'react'; +import { Pageable, serverFetchJson } from "@/app/utils/fetchUtil"; import { Machine, Operator } from "."; import { BASE_API_URL } from "@/config/api"; import { revalidateTag } from "next/cache"; +import { convertObjToURLSearchParams } from "@/app/utils/commonUtil"; + +export interface SearchJoResultRequest extends Pageable { + code: string; + name: string; +} + +export interface SearchJoResultResponse { + records: SearchJoResult[]; + total: number; +} + +export interface SearchJoResult { + id: number; + code: string; + name: string; + reqQty: number; + uom: string; + status: string; +} + +export interface ReleaseJoRequest { + id: number; +} + +export interface ReleaseJoResponse { + id: number; + entity: { status: string } +} export interface IsOperatorExistResponse { id: number | null; @@ -49,3 +80,29 @@ export const isCorrectMachineUsed = async (machineCode: string) => { revalidateTag("po"); return isExist; }; + + +export const fetchJos = cache(async (data?: SearchJoResultRequest) => { + const queryStr = convertObjToURLSearchParams(data) + const response = serverFetchJson( + `${BASE_API_URL}/jo/getRecordByPage?${queryStr}`, + { + method: "GET", + headers: { "Content-Type": "application/json" }, + next: { + tags: ["jos"] + } + } + ) + + return response +}) + +export const releaseJo = cache(async (data: ReleaseJoRequest) => { + return serverFetchJson(`${BASE_API_URL}/jo/release`, + { + method: "POST", + body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }) +}) \ No newline at end of file diff --git a/src/app/api/jo/index.ts b/src/app/api/jo/index.ts index f15ae8c..6b0af80 100644 --- a/src/app/api/jo/index.ts +++ b/src/app/api/jo/index.ts @@ -1,5 +1,9 @@ "server=only"; +import { serverFetchJson } from "@/app/utils/fetchUtil"; +import { BASE_API_URL } from "@/config/api"; +import { cache } from "react"; + export interface Operator { id: number; name: string; @@ -12,3 +16,34 @@ export interface Machine { code: string; qrCode: string; } + +export interface JoDetail { + id: number; + code: string; + name: string; + reqQty: number; + outputQtyUom: string; + pickLines: JoDetailPickLine[]; + status: string; +} + +export interface JoDetailPickLine { + id: number; + code: string; + name: string; + lotNo: string; + reqQty: number; + uom: string; + status: string; +} + +export const fetchJoDetail = cache(async (id: number) => { + return serverFetchJson(`${BASE_API_URL}/jo/detail/${id}`, + { + method: "GET", + headers: { "Content-Type": "application/json"}, + next: { + tags: ["jo"] + } + }) +}) \ No newline at end of file diff --git a/src/app/api/pickOrder/actions.ts b/src/app/api/pickOrder/actions.ts index 6991c39..9e5ca11 100644 --- a/src/app/api/pickOrder/actions.ts +++ b/src/app/api/pickOrder/actions.ts @@ -15,6 +15,27 @@ import { } from "."; import { PurchaseQcResult } from "../po/actions"; // import { BASE_API_URL } from "@/config/api"; + +export interface SavePickOrderLineRequest { + itemId: number + qty: number + uomId: number +} + +export interface SavePickOrderRequest { + type: string + targetDate: string + pickOrderLine: SavePickOrderLineRequest[] +} +export interface PostPickOrderResponse { + id: number | null; + name: string; + code: string; + type?: string; + message: string | null; + errorPosition: string + entity?: T | T[]; +} export interface PostStockOutLiineResponse { id: number | null; name: string; @@ -22,7 +43,7 @@ export interface PostStockOutLiineResponse { type?: string; message: string | null; errorPosition: string | keyof T; - entity: T | T[]; + entity: T | T[] | null; } export interface ReleasePickOrderInputs { @@ -60,6 +81,19 @@ export interface PickOrderApprovalInput { rejectQty: number; status: string; } +export const createPickOrder = async (data: SavePickOrderRequest) => { + console.log(data); + const po = await serverFetchJson( + `${BASE_API_URL}/pickOrder/create`, + { + method: "POST", + body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }, + ); + revalidateTag("pickorder"); + return po; +} export const consolidatePickOrder = async (ids: number[]) => { const pickOrder = await serverFetchJson( @@ -181,7 +215,7 @@ export const fetchConsoDetail = cache(async (consoCode: string) => { export const releasePickOrder = async (data: ReleasePickOrderInputs) => { console.log(data); console.log(JSON.stringify(data)); - const po = await serverFetchJson<{ body: any; status: number }>( + const po = await serverFetchJson<{ consoCode: string }>( `${BASE_API_URL}/pickOrder/releaseConso`, { method: "POST", @@ -232,3 +266,13 @@ export const completeConsoPickOrder = async (consoCode: string) => { revalidateTag("pickorder"); return po; }; + +export const fetchConsoStatus = cache(async (consoCode: string) => { + return serverFetchJson<{ status: string }>( + `${BASE_API_URL}/stockOut/get-status/${consoCode}`, + { + method: "GET", + next: { tags: ["pickorder"] }, + }, + ); +}); diff --git a/src/app/api/pickOrder/index.ts b/src/app/api/pickOrder/index.ts index 850614e..18c109f 100644 --- a/src/app/api/pickOrder/index.ts +++ b/src/app/api/pickOrder/index.ts @@ -13,7 +13,7 @@ export interface PickOrderResult { id: number; code: string; consoCode?: string; - targetDate: number[]; + targetDate: string; completeDate?: number[]; type: string; status: string; @@ -80,6 +80,7 @@ export interface PreReleasePickOrderSummary { } export interface PickOrderLineWithSuggestedLot { + poStatus: string; id: number; itemName: string; qty: number; diff --git a/src/app/api/scheduling/actions.ts b/src/app/api/scheduling/actions.ts index 4265533..030533e 100644 --- a/src/app/api/scheduling/actions.ts +++ b/src/app/api/scheduling/actions.ts @@ -4,7 +4,8 @@ import { convertObjToURLSearchParams } from "@/app/utils/commonUtil"; import { serverFetchJson } from "@/app/utils/fetchUtil"; import { BASE_API_URL } from "@/config/api"; import { cache } from "react"; -import { ScheduleType } from "."; +import { DetailedProdScheduleLineBomMaterialResult, DetailedProdScheduleLineResult, ScheduleType } from "."; +import { revalidateTag } from "next/cache"; export interface SearchProdSchedule { scheduleAt?: string; @@ -31,6 +32,29 @@ export interface ProdScheduleResultByPage { records: ProdScheduleResult[]; } +export interface ReleaseProdScheduleInputs { + id: number; + demandQty: number; +} + +export interface ReleaseProdScheduleResponse { + id: number; + code: string; + entity: { + prodScheduleLines: DetailedProdScheduleLineResult[]; + }; + message: string; +} + +export interface SaveProdScheduleResponse { + id: number; + code: string; + entity: { + bomMaterials: DetailedProdScheduleLineBomMaterialResult[] + }; + message: string; +} + export const fetchProdSchedules = cache( async (data: SearchProdSchedule | null) => { const params = convertObjToURLSearchParams(data); @@ -61,9 +85,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 +97,33 @@ export const testDetailSchedule = cache(async () => { }, ); }); + +export const releaseProdScheduleLine = cache(async (data: ReleaseProdScheduleInputs) => { + const response = serverFetchJson( + `${BASE_API_URL}/productionSchedule/detail/detailed/releaseLine`, + { + method: "POST", + body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + } + ); + + revalidateTag("prodSchedules"); + + return response; +}) + +export const saveProdScheduleLine = cache(async (data: ReleaseProdScheduleInputs) => { + const response = serverFetchJson( + `${BASE_API_URL}/productionSchedule/detail/detailed/save`, + { + method: "POST", + body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + } + ); + + revalidateTag("prodSchedules"); + + return response; +}) \ No newline at end of file diff --git a/src/app/api/scheduling/index.ts b/src/app/api/scheduling/index.ts index 6a19d35..9002573 100644 --- a/src/app/api/scheduling/index.ts +++ b/src/app/api/scheduling/index.ts @@ -5,85 +5,132 @@ 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( - `${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; + bomOutputQty: number; + prodTimeInMinute: DetailedProdScheduleLineProdTimeResult[]; + priority: number; + approved: boolean; + proportion: 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(`${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(`${BASE_API_URL}/productionSchedule/detail/detailed/${id}`, { + method: "GET", + headers: { "Content-Type": "application/json" }, + next: { + tags: ["prodSchedule"] + } + }) +}) \ No newline at end of file diff --git a/src/app/api/settings/item/actions.ts b/src/app/api/settings/item/actions.ts index 0ca7f4b..3a2b39f 100644 --- a/src/app/api/settings/item/actions.ts +++ b/src/app/api/settings/item/actions.ts @@ -7,8 +7,9 @@ import { import { revalidateTag } from "next/cache"; import { BASE_API_URL } from "@/config/api"; import { CreateItemResponse } from "../../utils"; -import { ItemQc } from "."; +import { ItemQc, ItemsResult } from "."; import { QcChecksInputs } from "../qcCheck/actions"; +import { cache } from "react"; // export type TypeInputs = { // id: number; @@ -37,6 +38,7 @@ export type CreateItemInputs = { }; export const saveItem = async (data: CreateItemInputs) => { +<<<<<<< HEAD // try { const item = await serverFetchJson>(`${BASE_API_URL}/items/new`, { method: "POST", @@ -46,3 +48,29 @@ export const saveItem = async (data: CreateItemInputs) => { revalidateTag("items"); return item }; +======= + const item = await serverFetchJson>( + `${BASE_API_URL}/items/new`, + { + method: "POST", + body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }, + ); + revalidateTag("items"); + return item; +}; + +export interface ItemCombo { + id: number, + label: string, + uomId: number, + uom: string, +} + +export const fetchAllItemsInClient = cache(async () => { + return serverFetchJson(`${BASE_API_URL}/items/consumables`, { + next: { tags: ["items"] }, + }); +}); +>>>>>>> da1fb872bf3b92e66cff3af4e1b8d1057c47effa diff --git a/src/app/api/settings/item/index.ts b/src/app/api/settings/item/index.ts index c0b0559..5d224c2 100644 --- a/src/app/api/settings/item/index.ts +++ b/src/app/api/settings/item/index.ts @@ -35,6 +35,8 @@ export type ItemsResult = { type: string; qcChecks: ItemQc[]; action?: any; + fgName?: string; + excludeDate?: string; }; export type Result = { diff --git a/src/app/utils/commonUtil.ts b/src/app/utils/commonUtil.ts index 195b85e..925ba83 100644 --- a/src/app/utils/commonUtil.ts +++ b/src/app/utils/commonUtil.ts @@ -10,8 +10,8 @@ export const downloadFile = (blobData: Uint8Array, filename: string) => { link.click(); }; -export const convertObjToURLSearchParams = ( - data: T | null, +export const convertObjToURLSearchParams = ( + data?: T | null, ): string => { if (isEmpty(data)) { return ""; diff --git a/src/app/utils/formatUtil.ts b/src/app/utils/formatUtil.ts index 040e5de..4c797f0 100644 --- a/src/app/utils/formatUtil.ts +++ b/src/app/utils/formatUtil.ts @@ -23,6 +23,7 @@ export const moneyFormatter = new Intl.NumberFormat("en-HK", { export const decimalFormatter = new Intl.NumberFormat("en-HK", { minimumFractionDigits: 2, + maximumFractionDigits: 2, }); export const integerFormatter = new Intl.NumberFormat("en-HK", {}); @@ -66,6 +67,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}` + } + + const colon = finalHrStr.length > 0 && finalMinStr.length > 0 ? ":" : "" + + return `${finalHrStr} ${colon} ${finalMinStr}`.trim() +} + export const stockInLineStatusMap: { [status: string]: number } = { draft: 0, pending: 1, diff --git a/src/components/Breadcrumb/Breadcrumb.tsx b/src/components/Breadcrumb/Breadcrumb.tsx index e1a09e6..4fc235e 100644 --- a/src/components/Breadcrumb/Breadcrumb.tsx +++ b/src/components/Breadcrumb/Breadcrumb.tsx @@ -25,6 +25,8 @@ const pathToLabelMap: { [path: string]: string } = { "/pickOrder": "Pick Order", "/po": "Purchase Order", "/dashboard": "dashboard", + "/jo": "Job Order", + "/jo/edit": "Edit Job Order", }; const Breadcrumb = () => { diff --git a/src/components/DetailSchedule/index.ts b/src/components/DetailSchedule/index.ts deleted file mode 100644 index e6a0aee..0000000 --- a/src/components/DetailSchedule/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./DetailScheduleWrapper"; diff --git a/src/components/DetailScheduleDetail/DetailScheduleDetailWrapper.tsx b/src/components/DetailScheduleDetail/DetailScheduleDetailWrapper.tsx deleted file mode 100644 index d359468..0000000 --- a/src/components/DetailScheduleDetail/DetailScheduleDetailWrapper.tsx +++ /dev/null @@ -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 & SubComponents = async ({ - id, -}) => { - const defaultValues = { - id: 1, - productionDate: "2025-05-07", - totalJobOrders: 13, - totalProductionQty: 21000, - }; - - return ( - - ); -}; - -DetailScheduleDetailWrapper.Loading = GeneralLoading; - -export default DetailScheduleDetailWrapper; diff --git a/src/components/DetailScheduleDetail/DetailScheudleDetailView.tsx b/src/components/DetailScheduleDetail/DetailScheudleDetailView.tsx deleted file mode 100644 index 124048e..0000000 --- a/src/components/DetailScheduleDetail/DetailScheudleDetailView.tsx +++ /dev/null @@ -1,226 +0,0 @@ -"use client"; - -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, - Grid, - Link, - Stack, - Tab, - Tabs, - TabsProps, - 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 ViewByFGDetails, { - FGRecord, -} from "@/components/DetailScheduleDetail/ViewByFGDetails"; -import ViewByBomDetails from "@/components/DetailScheduleDetail/ViewByBomDetails"; -import EditableSearchResults, { - Column, -} from "@/components/SearchResults/EditableSearchResults"; - -// temp interface input -export interface SaveDetailSchedule { - id: number; - productionDate: string; - totalJobOrders: number; - totalProductionQty: number; -} - -type Props = { - isEditMode: boolean; - // type: TypeEnum; - defaultValues: Partial | undefined; - // qcChecks: ItemQc[] -}; - -const DetailScheduleDetailView: React.FC = ({ - isEditMode, - // type, - defaultValues, - // qcChecks -}) => { - // console.log(type) - const apiRef = useGridApiRef(); - const params = useSearchParams(); - console.log(params.get("id")); - const [serverError, setServerError] = useState(""); - 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]); - // console.log(typeId) - const formProps = useForm({ - defaultValues: defaultValues ? defaultValues : { - id: 1, - productionDate: "2025-05-07", - totalJobOrders: 13, - totalProductionQty: 21000, - } as SaveDetailSchedule, - }); - const errors = formProps.formState.errors; - - const handleTabChange = useCallback>( - (_e, newValue) => { - setTabIndex(newValue); - }, - [], - ); - - const [pagingController, setPagingController] = useState({ - pageNum: 1, - pageSize: 10, - totalCount: 0, - }); - - const handleCancel = () => { - router.replace(`/scheduling/Detail`); - }; - - const onSubmit = useCallback>( - async (data, event) => { - const hasErrors = false; - console.log(errors); - // console.log(apiRef.current.getCellValue(2, "lowerLimit")) - // apiRef.current. - try { - if (hasErrors) { - setServerError(t("An error has occurred. Please try again later.")); - return false; - } - } catch (e) { - // backend error - setServerError(t("An error has occurred. Please try again later.")); - console.log(e); - } - }, - [apiRef, router, t], - ); - - // multiple tabs - const onSubmitError = useCallback>( - (errors) => {}, - [], - ); - - const onClickEdit = () => { - setIsEdit(!isEdit); - }; - - return ( - <> - - - {/**/} - {/* */} - {/* {t(`${mode} ${title}`)}*/} - {/* */} - {/**/} - - - - - - {/* - - - */} - {serverError && ( - - {serverError} - - )} - {/* {tabIndex === 0 && } */} - - {/* {tabIndex === 1 && } */} - - - - - - - - ); -}; -export default DetailScheduleDetailView; diff --git a/src/components/DetailScheduleDetail/ViewByBomDetails.tsx b/src/components/DetailScheduleDetail/ViewByBomDetails.tsx deleted file mode 100644 index 92c3263..0000000 --- a/src/components/DetailScheduleDetail/ViewByBomDetails.tsx +++ /dev/null @@ -1,1319 +0,0 @@ -"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, - useEffect, - useMemo, - useState, -} from "react"; -import { useFormContext } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import InputDataGrid, { TableRow } from "../InputDataGrid/InputDataGrid"; -import { Box, Grid, Tooltip, Typography } from "@mui/material"; -import { ItemQc } from "@/app/api/settings/item"; -import { QcChecksInputs } from "@/app/api/settings/qcCheck/actions"; -import { GridApiCommunity } from "@mui/x-data-grid/internals"; -import { RiceBowl } from "@mui/icons-material"; -import EditableSearchResults, { - Column, -} from "@/components/SearchResults/EditableSearchResults"; -import { decimalFormatter } from "@/app/utils/formatUtil"; -import { GridRenderCellParams } from "@mui/x-data-grid"; - -type Props = { - apiRef: MutableRefObject - isEdit: boolean -}; -type EntryError = - | { - [field in keyof QcChecksInputs]?: string; - } - | undefined; - -export type FGRecord = { - id: string | number; - code: string; - name: string; - inStockQty: number; - purchaseQty: number; -}; - -export type FGOverallRecord = { - id: string | number; - code: string; - name: string; - type: string; - inStockQty: number; - purchaseQty: number; - purchaseQty1: number; - purchaseQty2: number; - purchaseQty3: number; - purchaseQty4: number; - purchaseQty5: number; - purchaseQty6: number; - purchaseQty7: number; - overallPurchaseQty: number; -}; - -const ViewByBomDetails: React.FC = ({ apiRef, isEdit }) => { - const { - t, - i18n: { language }, - } = useTranslation("schedule"); - - const { - formState: { errors, defaultValues, touchedFields }, - } = useFormContext(); - // const apiRef = useGridApiRef(); - - const dayPeriod = [ - "2025-05-11", - "2025-05-12", - "2025-05-13", - "2025-05-14", - "2025-05-15", - "2025-05-16", - "2025-05-17", - ]; - - const fakeRecords = useMemo( - () => [ - [ - { - id: 1, - code: "MH0040", - type: "Material", - name: "大豆油(1噸/桶)", - inStockQty: 54.44, - purchaseQty: 972.12, - }, - { - id: 2, - code: "GI3236", - type: "Material", - name: "清水(煮過牛腩)", - inStockQty: 317.52, - purchaseQty: 3175.2, - }, - { - id: 3, - code: "MG1700", - type: "Material", - name: "STERILTOM 意大利茄粒", - inStockQty: 9.0, - purchaseQty: 90, - }, - { - id: 4, - code: "FA0533", - type: "Material", - name: "乾蔥茸", - inStockQty: 6.04, - purchaseQty: 60.4, - }, - { - id: 5, - code: "FA0210", - type: "Material", - name: "薑茸", - inStockQty: 6.04, - purchaseQty: 66.45, - }, - { - id: 6, - code: "FA0608", - type: "Material", - name: "粗蒜茸", - inStockQty: 6.04, - purchaseQty: 78.55, - }, - { - id: 7, - code: "FA0056", - type: "Material", - name: "洋蔥肉", - inStockQty: 241.98, - purchaseQty: 2419.8, - }, - { - id: 8, - code: "PP1188", - type: "Material", - name: "咖喱膽", - inStockQty: 36.0, - purchaseQty: 0, - }, - { - id: 9, - code: "PP8001", - type: "Material", - name: "咖哩汁箱料粉", - inStockQty: 77.42, - purchaseQty: 774.2, - }, - { - id: 10, - code: "PP1096", - type: "Material", - name: "白麵撈", - inStockQty: 60.0, - purchaseQty: 0, - }, - { - id: 11, - code: "NA0476", - type: "Material", - name: "2磅份量三邊覆合袋 (0.1x225x260mm)個計", - inStockQty: 600.0, - purchaseQty: 6000, - }, - { - id: 12, - code: "MH0040", - type: "Material", - name: "大豆油(1噸/桶)", - inStockQty: 0, - purchaseQty: 972.12, - }, - { - id: 13, - code: "FA0161", - type: "Material", - name: "洋蔥粒", - inStockQty: 0, - purchaseQty: 28.15, - }, - { - id: 14, - code: "MG1288", - type: "Material", - name: "炸紅蔥頭", - inStockQty: 0, - purchaseQty: 6.05, - }, - { - id: 15, - code: "MG0066", - type: "Material", - name: "咖哩料(5斤x16包+2斤/包)", - inStockQty: 0, - purchaseQty: 241.98, - }, - { - id: 16, - code: "MH0040", - type: "Material", - name: "星加坡綠富貴花牌幼白麵粉 (50磅/包)", - inStockQty: 0, - purchaseQty: 250.0, - }, - ], - [ - { - id: 1, - code: "MH0040", - type: "Material", - name: "大豆油(1噸/桶)", - inStockQty: 54.44, - purchaseQty: 972.12, - }, - { - id: 2, - code: "GI3236", - type: "Material", - name: "清水(煮過牛腩)", - inStockQty: 317.52, - purchaseQty: 3175.2, - }, - { - id: 3, - code: "MG1700", - type: "Material", - name: "STERILTOM 意大利茄粒", - inStockQty: 9.0, - purchaseQty: 90, - }, - { - id: 4, - code: "FA0533", - type: "Material", - name: "乾蔥茸", - inStockQty: 6.04, - purchaseQty: 60.4, - }, - { - id: 5, - code: "FA0210", - type: "Material", - name: "薑茸", - inStockQty: 6.04, - purchaseQty: 66.45, - }, - { - id: 6, - code: "FA0608", - type: "Material", - name: "粗蒜茸", - inStockQty: 6.04, - purchaseQty: 78.55, - }, - { - id: 7, - code: "FA0056", - type: "Material", - name: "洋蔥肉", - inStockQty: 241.98, - purchaseQty: 2419.8, - }, - { - id: 8, - code: "PP1188", - type: "Material", - name: "咖喱膽", - inStockQty: 36.0, - purchaseQty: 0, - }, - { - id: 9, - code: "PP8001", - type: "Material", - name: "咖哩汁箱料粉", - inStockQty: 77.42, - purchaseQty: 774.2, - }, - { - id: 10, - code: "PP1096", - type: "Material", - name: "白麵撈", - inStockQty: 60.0, - purchaseQty: 0, - }, - { - id: 11, - code: "NA0476", - type: "Material", - name: "2磅份量三邊覆合袋 (0.1x225x260mm)個計", - inStockQty: 600.0, - purchaseQty: 6000, - }, - { - id: 12, - code: "MH0040", - type: "Material", - name: "大豆油(1噸/桶)", - inStockQty: 0, - purchaseQty: 972.12, - }, - { - id: 13, - code: "FA0161", - type: "Material", - name: "洋蔥粒", - inStockQty: 0, - purchaseQty: 28.15, - }, - { - id: 14, - code: "MG1288", - type: "Material", - name: "炸紅蔥頭", - inStockQty: 0, - purchaseQty: 6.05, - }, - { - id: 15, - code: "MG0066", - type: "Material", - name: "咖哩料(5斤x16包+2斤/包)", - inStockQty: 0, - purchaseQty: 241.98, - }, - { - id: 16, - code: "MH0040", - type: "Material", - name: "星加坡綠富貴花牌幼白麵粉 (50磅/包)", - inStockQty: 0, - purchaseQty: 250.0, - }, - ], - [ - { - id: 1, - code: "MH0040", - type: "Material", - name: "大豆油(1噸/桶)", - inStockQty: 54.44, - purchaseQty: 972.12, - }, - { - id: 2, - code: "GI3236", - type: "Material", - name: "清水(煮過牛腩)", - inStockQty: 317.52, - purchaseQty: 3175.2, - }, - { - id: 3, - code: "MG1700", - type: "Material", - name: "STERILTOM 意大利茄粒", - inStockQty: 9.0, - purchaseQty: 90, - }, - { - id: 4, - code: "FA0533", - type: "Material", - name: "乾蔥茸", - inStockQty: 6.04, - purchaseQty: 60.4, - }, - { - id: 5, - code: "FA0210", - type: "Material", - name: "薑茸", - inStockQty: 6.04, - purchaseQty: 66.45, - }, - { - id: 6, - code: "FA0608", - type: "Material", - name: "粗蒜茸", - inStockQty: 6.04, - purchaseQty: 78.55, - }, - { - id: 7, - code: "FA0056", - type: "Material", - name: "洋蔥肉", - inStockQty: 241.98, - purchaseQty: 2419.8, - }, - { - id: 8, - code: "PP1188", - type: "Material", - name: "咖喱膽", - inStockQty: 36.0, - purchaseQty: 0, - }, - { - id: 9, - code: "PP8001", - type: "Material", - name: "咖哩汁箱料粉", - inStockQty: 77.42, - purchaseQty: 774.2, - }, - { - id: 10, - code: "PP1096", - type: "Material", - name: "白麵撈", - inStockQty: 60.0, - purchaseQty: 0, - }, - { - id: 11, - code: "NA0476", - type: "Material", - name: "2磅份量三邊覆合袋 (0.1x225x260mm)個計", - inStockQty: 600.0, - purchaseQty: 6000, - }, - { - id: 12, - code: "MH0040", - type: "Material", - name: "大豆油(1噸/桶)", - inStockQty: 0, - purchaseQty: 972.12, - }, - { - id: 13, - code: "FA0161", - type: "Material", - name: "洋蔥粒", - inStockQty: 0, - purchaseQty: 28.15, - }, - { - id: 14, - code: "MG1288", - type: "Material", - name: "炸紅蔥頭", - inStockQty: 0, - purchaseQty: 6.05, - }, - { - id: 15, - code: "MG0066", - type: "Material", - name: "咖哩料(5斤x16包+2斤/包)", - inStockQty: 0, - purchaseQty: 241.98, - }, - { - id: 16, - code: "MH0040", - type: "Material", - name: "星加坡綠富貴花牌幼白麵粉 (50磅/包)", - inStockQty: 0, - purchaseQty: 250.0, - }, - ], - [ - { - id: 1, - code: "MH0040", - type: "Material", - name: "大豆油(1噸/桶)", - inStockQty: 54.44, - purchaseQty: 972.12, - }, - { - id: 2, - code: "GI3236", - type: "Material", - name: "清水(煮過牛腩)", - inStockQty: 317.52, - purchaseQty: 3175.2, - }, - { - id: 3, - code: "MG1700", - type: "Material", - name: "STERILTOM 意大利茄粒", - inStockQty: 9.0, - purchaseQty: 90, - }, - { - id: 4, - code: "FA0533", - type: "Material", - name: "乾蔥茸", - inStockQty: 6.04, - purchaseQty: 60.4, - }, - { - id: 5, - code: "FA0210", - type: "Material", - name: "薑茸", - inStockQty: 6.04, - purchaseQty: 66.45, - }, - { - id: 6, - code: "FA0608", - type: "Material", - name: "粗蒜茸", - inStockQty: 6.04, - purchaseQty: 78.55, - }, - { - id: 7, - code: "FA0056", - type: "Material", - name: "洋蔥肉", - inStockQty: 241.98, - purchaseQty: 2419.8, - }, - { - id: 8, - code: "PP1188", - type: "Material", - name: "咖喱膽", - inStockQty: 36.0, - purchaseQty: 0, - }, - { - id: 9, - code: "PP8001", - type: "Material", - name: "咖哩汁箱料粉", - inStockQty: 77.42, - purchaseQty: 774.2, - }, - { - id: 10, - code: "PP1096", - type: "Material", - name: "白麵撈", - inStockQty: 60.0, - purchaseQty: 0, - }, - { - id: 11, - code: "NA0476", - type: "Material", - name: "2磅份量三邊覆合袋 (0.1x225x260mm)個計", - inStockQty: 600.0, - purchaseQty: 6000, - }, - { - id: 12, - code: "MH0040", - type: "Material", - name: "大豆油(1噸/桶)", - inStockQty: 0, - purchaseQty: 972.12, - }, - { - id: 13, - code: "FA0161", - type: "Material", - name: "洋蔥粒", - inStockQty: 0, - purchaseQty: 28.15, - }, - { - id: 14, - code: "MG1288", - type: "Material", - name: "炸紅蔥頭", - inStockQty: 0, - purchaseQty: 6.05, - }, - { - id: 15, - code: "MG0066", - type: "Material", - name: "咖哩料(5斤x16包+2斤/包)", - inStockQty: 0, - purchaseQty: 241.98, - }, - { - id: 16, - code: "MH0040", - type: "Material", - name: "星加坡綠富貴花牌幼白麵粉 (50磅/包)", - inStockQty: 0, - purchaseQty: 250.0, - }, - ], - [ - { - id: 1, - code: "MH0040", - type: "Material", - name: "大豆油(1噸/桶)", - inStockQty: 54.44, - purchaseQty: 972.12, - }, - { - id: 2, - code: "GI3236", - type: "Material", - name: "清水(煮過牛腩)", - inStockQty: 317.52, - purchaseQty: 3175.2, - }, - { - id: 3, - code: "MG1700", - type: "Material", - name: "STERILTOM 意大利茄粒", - inStockQty: 9.0, - purchaseQty: 90, - }, - { - id: 4, - code: "FA0533", - type: "Material", - name: "乾蔥茸", - inStockQty: 6.04, - purchaseQty: 60.4, - }, - { - id: 5, - code: "FA0210", - type: "Material", - name: "薑茸", - inStockQty: 6.04, - purchaseQty: 66.45, - }, - { - id: 6, - code: "FA0608", - type: "Material", - name: "粗蒜茸", - inStockQty: 6.04, - purchaseQty: 78.55, - }, - { - id: 7, - code: "FA0056", - type: "Material", - name: "洋蔥肉", - inStockQty: 241.98, - purchaseQty: 2419.8, - }, - { - id: 8, - code: "PP1188", - type: "Material", - name: "咖喱膽", - inStockQty: 36.0, - purchaseQty: 0, - }, - { - id: 9, - code: "PP8001", - type: "Material", - name: "咖哩汁箱料粉", - inStockQty: 77.42, - purchaseQty: 774.2, - }, - { - id: 10, - code: "PP1096", - type: "Material", - name: "白麵撈", - inStockQty: 60.0, - purchaseQty: 0, - }, - { - id: 11, - code: "NA0476", - type: "Material", - name: "2磅份量三邊覆合袋 (0.1x225x260mm)個計", - inStockQty: 600.0, - purchaseQty: 6000, - }, - { - id: 12, - code: "MH0040", - type: "Material", - name: "大豆油(1噸/桶)", - inStockQty: 0, - purchaseQty: 972.12, - }, - { - id: 13, - code: "FA0161", - type: "Material", - name: "洋蔥粒", - inStockQty: 0, - purchaseQty: 28.15, - }, - { - id: 14, - code: "MG1288", - type: "Material", - name: "炸紅蔥頭", - inStockQty: 0, - purchaseQty: 6.05, - }, - { - id: 15, - code: "MG0066", - type: "Material", - name: "咖哩料(5斤x16包+2斤/包)", - inStockQty: 0, - purchaseQty: 241.98, - }, - { - id: 16, - code: "MH0040", - type: "Material", - name: "星加坡綠富貴花牌幼白麵粉 (50磅/包)", - inStockQty: 0, - purchaseQty: 250.0, - }, - ], - [ - { - id: 1, - code: "MH0040", - type: "Material", - name: "大豆油(1噸/桶)", - inStockQty: 54.44, - purchaseQty: 972.12, - }, - { - id: 2, - code: "GI3236", - type: "Material", - name: "清水(煮過牛腩)", - inStockQty: 317.52, - purchaseQty: 3175.2, - }, - { - id: 3, - code: "MG1700", - type: "Material", - name: "STERILTOM 意大利茄粒", - inStockQty: 9.0, - purchaseQty: 90, - }, - { - id: 4, - code: "FA0533", - type: "Material", - name: "乾蔥茸", - inStockQty: 6.04, - purchaseQty: 60.4, - }, - { - id: 5, - code: "FA0210", - type: "Material", - name: "薑茸", - inStockQty: 6.04, - purchaseQty: 66.45, - }, - { - id: 6, - code: "FA0608", - type: "Material", - name: "粗蒜茸", - inStockQty: 6.04, - purchaseQty: 78.55, - }, - { - id: 7, - code: "FA0056", - type: "Material", - name: "洋蔥肉", - inStockQty: 241.98, - purchaseQty: 2419.8, - }, - { - id: 8, - code: "PP1188", - type: "Material", - name: "咖喱膽", - inStockQty: 36.0, - purchaseQty: 0, - }, - { - id: 9, - code: "PP8001", - type: "Material", - name: "咖哩汁箱料粉", - inStockQty: 77.42, - purchaseQty: 774.2, - }, - { - id: 10, - code: "PP1096", - type: "Material", - name: "白麵撈", - inStockQty: 60.0, - purchaseQty: 0, - }, - { - id: 11, - code: "NA0476", - type: "Material", - name: "2磅份量三邊覆合袋 (0.1x225x260mm)個計", - inStockQty: 600.0, - purchaseQty: 6000, - }, - { - id: 12, - code: "MH0040", - type: "Material", - name: "大豆油(1噸/桶)", - inStockQty: 0, - purchaseQty: 972.12, - }, - { - id: 13, - code: "FA0161", - type: "Material", - name: "洋蔥粒", - inStockQty: 0, - purchaseQty: 28.15, - }, - { - id: 14, - code: "MG1288", - type: "Material", - name: "炸紅蔥頭", - inStockQty: 0, - purchaseQty: 6.05, - }, - { - id: 15, - code: "MG0066", - type: "Material", - name: "咖哩料(5斤x16包+2斤/包)", - inStockQty: 0, - purchaseQty: 241.98, - }, - { - id: 16, - code: "MH0040", - type: "Material", - name: "星加坡綠富貴花牌幼白麵粉 (50磅/包)", - inStockQty: 0, - purchaseQty: 250.0, - }, - ], - [ - { - id: 1, - code: "MH0040", - type: "Material", - name: "大豆油(1噸/桶)", - inStockQty: 54.44, - purchaseQty: 972.12, - }, - { - id: 2, - code: "GI3236", - type: "Material", - name: "清水(煮過牛腩)", - inStockQty: 317.52, - purchaseQty: 3175.2, - }, - { - id: 3, - code: "MG1700", - type: "Material", - name: "STERILTOM 意大利茄粒", - inStockQty: 9.0, - purchaseQty: 90, - }, - { - id: 4, - code: "FA0533", - type: "Material", - name: "乾蔥茸", - inStockQty: 6.04, - purchaseQty: 60.4, - }, - { - id: 5, - code: "FA0210", - type: "Material", - name: "薑茸", - inStockQty: 6.04, - purchaseQty: 66.45, - }, - { - id: 6, - code: "FA0608", - type: "Material", - name: "粗蒜茸", - inStockQty: 6.04, - purchaseQty: 78.55, - }, - { - id: 7, - code: "FA0056", - type: "Material", - name: "洋蔥肉", - inStockQty: 241.98, - purchaseQty: 2419.8, - }, - { - id: 8, - code: "PP1188", - type: "Material", - name: "咖喱膽", - inStockQty: 36.0, - purchaseQty: 0, - }, - { - id: 9, - code: "PP8001", - type: "Material", - name: "咖哩汁箱料粉", - inStockQty: 77.42, - purchaseQty: 774.2, - }, - { - id: 10, - code: "PP1096", - type: "Material", - name: "白麵撈", - inStockQty: 60.0, - purchaseQty: 0, - }, - { - id: 11, - code: "NA0476", - type: "Material", - name: "2磅份量三邊覆合袋 (0.1x225x260mm)個計", - inStockQty: 600.0, - purchaseQty: 6000, - }, - { - id: 12, - code: "MH0040", - type: "Material", - name: "大豆油(1噸/桶)", - inStockQty: 0, - purchaseQty: 972.12, - }, - { - id: 13, - code: "FA0161", - type: "Material", - name: "洋蔥粒", - inStockQty: 0, - purchaseQty: 28.15, - }, - { - id: 14, - code: "MG1288", - type: "Material", - name: "炸紅蔥頭", - inStockQty: 0, - purchaseQty: 6.05, - }, - { - id: 15, - code: "MG0066", - type: "Material", - name: "咖哩料(5斤x16包+2斤/包)", - inStockQty: 0, - purchaseQty: 241.98, - }, - { - id: 16, - code: "MH0040", - type: "Material", - name: "星加坡綠富貴花牌幼白麵粉 (50磅/包)", - inStockQty: 0, - purchaseQty: 250.0, - }, - ], - ], - [], - ); - - const updatePagingController = (updatedObj: { index: number, pageNum: number, pageSize: number, totalCount: number }) => { - 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 [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 overallColumns = useMemo[]>( - () => [ - { - 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: t("Available Qty"), - type: "read-only", - style: { - textAlign: "right", - }, - renderCell: (row: FGOverallRecord) => { - if (typeof row.inStockQty == "number") { - return decimalFormatter.format(row.inStockQty); - } - return row.inStockQty; - }, - // editable: true, - }, - { - field: "overallPurchaseQty", - label: t("Total Demand Qty"), - type: "read-only", - style: { - textAlign: "right", - }, - renderCell: (row: FGOverallRecord) => { - if (typeof row.overallPurchaseQty == "number") { - return decimalFormatter.format(row.overallPurchaseQty); - } - return row.overallPurchaseQty; - }, - }, - { - field: "purchaseQty1", - label: t("Demand Qty (Day1)"), - type: "read-only", - style: { - textAlign: "right", - }, - renderCell: (row: FGOverallRecord) => { - if (typeof row.purchaseQty1 == "number") { - return decimalFormatter.format(row.purchaseQty1); - } - return row.purchaseQty1; - }, - }, - { - field: "purchaseQty2", - label: t("Demand Qty (Day2)"), - type: "read-only", - style: { - textAlign: "right", - }, - renderCell: (row: FGOverallRecord) => { - if (typeof row.purchaseQty2 == "number") { - return decimalFormatter.format(row.purchaseQty2); - } - return row.purchaseQty2; - }, - }, - { - field: "purchaseQty3", - label: t("Demand Qty (Day3)"), - type: "read-only", - style: { - textAlign: "right", - }, - renderCell: (row: FGOverallRecord) => { - if (typeof row.purchaseQty3 == "number") { - return decimalFormatter.format(row.purchaseQty3); - } - return row.purchaseQty3; - }, - }, - { - field: "purchaseQty4", - label: t("Demand Qty (Day4)"), - type: "read-only", - style: { - textAlign: "right", - }, - renderCell: (row: FGOverallRecord) => { - if (typeof row.purchaseQty4 == "number") { - return decimalFormatter.format(row.purchaseQty4); - } - return row.purchaseQty4; - }, - }, - { - field: "purchaseQty5", - label: t("Demand Qty (Day5)"), - type: "read-only", - style: { - textAlign: "right", - }, - renderCell: (row: FGOverallRecord) => { - if (typeof row.purchaseQty5 == "number") { - return decimalFormatter.format(row.purchaseQty5); - } - return row.purchaseQty5; - }, - }, - { - field: "purchaseQty6", - label: t("Demand Qty (Day6)"), - type: "read-only", - style: { - textAlign: "right", - }, - renderCell: (row: FGOverallRecord) => { - if (typeof row.purchaseQty6 == "number") { - return decimalFormatter.format(row.purchaseQty6); - } - return row.purchaseQty6; - }, - }, - { - field: "purchaseQty7", - label: t("Demand Qty (Day7)"), - type: "read-only", - style: { - textAlign: "right", - }, - renderCell: (row: FGOverallRecord) => { - if (typeof row.purchaseQty7 == "number") { - return decimalFormatter.format(row.purchaseQty7); - } - return row.purchaseQty7; - }, - }, - ], - [], - ); - - const columns = useMemo[]>( - () => [ - { - 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", - }, - { - field: "inStockQty", - label: t("Available Qty"), - type: "read-only", - // editable: true, - style: { - textAlign: "right", - }, - renderCell: (row: FGRecord) => { - if (typeof row.inStockQty == "number") { - return decimalFormatter.format(row.inStockQty); - } - return row.inStockQty; - }, - }, - { - field: "purchaseQty", - label: t("Demand Qty"), - type: "read-only", - style: { - textAlign: "right", - }, - renderCell: (row: FGRecord) => { - if (typeof row.purchaseQty == "number") { - return decimalFormatter.format(row.purchaseQty); - } - return row.purchaseQty; - }, - }, - ], - [], - ); - - return ( - - - - {t("Material Demand List (7 Days)")} - - - index={7} - items={fakeOverallRecords} - isMockUp={true} - columns={overallColumns} - setPagingController={updatePagingController} - pagingController={pagingController[7]} - isAutoPaging={true} - isEditable={false} - isEdit={false} - /> - - {dayPeriod.map((date, index) => ( - - - {`${t("Material Demand Date")}: ${date}`} - - - index={index} - items={fakeRecords[index]} // Use the corresponding records for the day - columns={columns} - setPagingController={updatePagingController} - pagingController={pagingController[index]} - isAutoPaging={true} - isEditable={false} - isEdit={isEdit} - /> - - ))} - - ); -}; -export default ViewByBomDetails; diff --git a/src/components/DetailScheduleDetail/ViewByFGDetails.tsx b/src/components/DetailScheduleDetail/ViewByFGDetails.tsx index 8ceb04c..2300447 100644 --- a/src/components/DetailScheduleDetail/ViewByFGDetails.tsx +++ b/src/components/DetailScheduleDetail/ViewByFGDetails.tsx @@ -40,6 +40,8 @@ export type FGRecord = { inStockQty: number; productionQty?: number; purchaseQty?: number; + safetyStock?: number; + lastMonthAvgStock?: number }; const ViewByFGDetails: React.FC = ({ apiRef, isEdit }) => { @@ -1742,7 +1744,7 @@ const ViewByFGDetails: React.FC = ({ apiRef, isEdit }) => { }, ]); - const updatePagingController = (updatedObj) => { + const updatePagingController = (updatedObj: any) => { setPagingController((prevState) => { return prevState.map((item, index) => { if (index === updatedObj?.index) { @@ -2176,6 +2178,7 @@ const ViewByFGDetails: React.FC = ({ apiRef, isEdit }) => { {`${t("FG Demand Date")}: ${date}`} */} + index={1} items={fakeRecords[index]} // Use the corresponding records for the day columns={columns} setPagingController={updatePagingController} diff --git a/src/components/DetailScheduleDetail/index.ts b/src/components/DetailScheduleDetail/index.ts deleted file mode 100644 index e8e5b2f..0000000 --- a/src/components/DetailScheduleDetail/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./DetailScheduleDetailWrapper"; diff --git a/src/components/DetailSchedule/DetailScheduleLoading.tsx b/src/components/DetailedSchedule/DetailedScheduleLoading.tsx similarity index 91% rename from src/components/DetailSchedule/DetailScheduleLoading.tsx rename to src/components/DetailedSchedule/DetailedScheduleLoading.tsx index eb5ae41..88954f2 100644 --- a/src/components/DetailSchedule/DetailScheduleLoading.tsx +++ b/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 ( <> @@ -37,4 +37,4 @@ export const DetailScheduleLoading: React.FC = () => { ); }; -export default DetailScheduleLoading; +export default DetailedScheduleLoading; diff --git a/src/components/DetailSchedule/DetailScheduleSearchView.tsx b/src/components/DetailedSchedule/DetailedScheduleSearchView.tsx similarity index 94% rename from src/components/DetailSchedule/DetailScheduleSearchView.tsx rename to src/components/DetailedSchedule/DetailedScheduleSearchView.tsx index b666d4c..492a13b 100644 --- a/src/components/DetailSchedule/DetailScheduleSearchView.tsx +++ b/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 = ({ 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[]>( diff --git a/src/components/DetailSchedule/DetailScheduleWrapper.tsx b/src/components/DetailedSchedule/DetailedScheduleWrapper.tsx similarity index 53% rename from src/components/DetailSchedule/DetailScheduleWrapper.tsx rename to src/components/DetailedSchedule/DetailedScheduleWrapper.tsx index 2d7037d..263fcbe 100644 --- a/src/components/DetailSchedule/DetailScheduleWrapper.tsx +++ b/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 & SubComponents = async ({ +const DetailedScheduleWrapper: React.FC & SubComponents = async ({ type, }) => { const defaultInputs: SearchProdSchedule = { @@ -22,6 +22,6 @@ const DetailScheduleWrapper: React.FC & SubComponents = async ({ return ; }; -DetailScheduleWrapper.Loading = DetailScheduleLoading; +DetailedScheduleWrapper.Loading = DetailedScheduleLoading; -export default DetailScheduleWrapper; +export default DetailedScheduleWrapper; diff --git a/src/components/DetailedSchedule/index.ts b/src/components/DetailedSchedule/index.ts new file mode 100644 index 0000000..3b109ab --- /dev/null +++ b/src/components/DetailedSchedule/index.ts @@ -0,0 +1 @@ +export { default } from "./DetailedScheduleWrapper"; diff --git a/src/components/DetailScheduleDetail/DetailInfoCard.tsx b/src/components/DetailedScheduleDetail/DetailInfoCard.tsx similarity index 68% rename from src/components/DetailScheduleDetail/DetailInfoCard.tsx rename to src/components/DetailedScheduleDetail/DetailInfoCard.tsx index 76e973d..8ee780b 100644 --- a/src/components/DetailScheduleDetail/DetailInfoCard.tsx +++ b/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 = ({ const { control, register, + getValues, formState: { errors, defaultValues, touchedFields }, - } = useFormContext(); + } = useFormContext(); - const [details, setDetails] = useState(undefined); + // const [details, setDetails] = useState(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 = ({ = ({ = ({ ( = ({ 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} diff --git a/src/components/DetailedScheduleDetail/DetailedScheduleDetailView.tsx b/src/components/DetailedScheduleDetail/DetailedScheduleDetailView.tsx new file mode 100644 index 0000000..61d2e86 --- /dev/null +++ b/src/components/DetailedScheduleDetail/DetailedScheduleDetailView.tsx @@ -0,0 +1,272 @@ +"use client"; + +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useTranslation } from "react-i18next"; +import { + FormProvider, + SubmitErrorHandler, + SubmitHandler, + useFieldArray, + useForm, +} from "react-hook-form"; +import { + Box, + Button, + Grid, + Link, + Stack, + Tab, + Tabs, + TabsProps, + Typography, +} from "@mui/material"; +import { Add, Check, Close, EditNote } from "@mui/icons-material"; +import { useGridApiRef } from "@mui/x-data-grid"; +import DetailInfoCard from "@/components/DetailedScheduleDetail/DetailInfoCard"; +import ViewByFGDetails, { + // FGRecord, +} from "@/components/DetailedScheduleDetail/ViewByFGDetails"; +import { DetailedProdScheduleLineResult, DetailedProdScheduleResult, ScheduleType } from "@/app/api/scheduling"; +import { releaseProdScheduleLine, saveProdScheduleLine } from "@/app/api/scheduling/actions"; +import useUploadContext from "../UploadProvider/useUploadContext"; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; + +// temp interface input +// export interface SaveDetailedSchedule { +// id: number; +// productionDate: string; +// totalJobOrders: number; +// totalProductionQty: number; +// } + +type Props = { + isEditMode: boolean; + // type: TypeEnum; + defaultValues: Partial | undefined; + // qcChecks: ItemQc[] + type: ScheduleType; +}; + +const DetailedScheduleDetailView: React.FC = ({ + isEditMode, + // type, + defaultValues, + // qcChecks, + type +}) => { + // console.log(type) + const apiRef = useGridApiRef(); + const params = useSearchParams(); + console.log(params.get("id")); + const [serverError, setServerError] = useState(""); + const [tabIndex, setTabIndex] = useState(0); + const { t } = useTranslation("schedule"); + const router = useRouter(); + const [isEdit, setIsEdit] = useState(false); + const { setIsUploading } = useUploadContext() + + // console.log(typeId) + const formProps = useForm({ + defaultValues: defaultValues + }); + const errors = formProps.formState.errors; + + const lineFormProps = useFieldArray({ + control: formProps.control, + name: "prodScheduleLines" + }) + + const handleTabChange = useCallback>( + (_e, newValue) => { + setTabIndex(newValue); + }, + [], + ); + + // const calNewProportion = useCallback((demandQty: number, bomOutputQty: number) => { + // return ((demandQty ?? 0) / (bomOutputQty ?? 1)).toFixed(2) + // }, []) + + // const [pagingController, setPagingController] = useState({ + // pageNum: 1, + // pageSize: 10, + // totalCount: 0, + // }); + + const handleCancel = () => { + router.replace(`/scheduling/detailed`); + }; + + const onSubmit = useCallback>( + async (data, event) => { + const hasErrors = false; + console.log(errors); + // console.log(apiRef.current.getCellValue(2, "lowerLimit")) + // apiRef.current. + try { + if (hasErrors) { + setServerError(t("An error has occurred. Please try again later.")); + return false; + } + } catch (e) { + // backend error + setServerError(t("An error has occurred. Please try again later.")); + console.log(e); + } + }, + [apiRef, router, t], + ); + + // multiple tabs + const onSubmitError = useCallback>( + (errors) => { }, + [], + ); + + const onClickEdit = () => { + setIsEdit(!isEdit); + }; + + const onReleaseClick = useCallback(async (row: DetailedProdScheduleLineResult) => { + setIsUploading(true) + + try { + const response = await releaseProdScheduleLine({ + id: row.id, + demandQty: row.demandQty + }) + + if (response) { + const index = formProps.getValues("prodScheduleLines").findIndex(ele => ele.id == row.id) + // console.log(index, formProps.getValues(`prodScheduleLines.${index}.approved`)) + // formProps.setValue(`prodScheduleLines.${index}.approved`, true) + // formProps.setValue(`prodScheduleLines.${index}.jobNo`, response.code) + formProps.setValue(`prodScheduleLines`, response.entity.prodScheduleLines.sort((a, b) => b.priority - a.priority)) + // console.log(index, formProps.getValues(`prodScheduleLines.${index}.approved`)) + } + setIsUploading(false) + } catch (e) { + console.log(e) + setIsUploading(false) + } + }, []) + + const [tempValue, setTempValue] = useState(null) + const onEditClick = useCallback((rowId: number) => { + const row = formProps.getValues("prodScheduleLines").find(ele => ele.id == rowId) + if (row) { + setTempValue(row.demandQty) + } + }, []) + + const handleEditChange = useCallback((rowId: number, fieldName: keyof DetailedProdScheduleLineResult, newValue: number | string) => { + const index = formProps.getValues("prodScheduleLines").findIndex(ele => ele.id == rowId) + formProps.setValue(`prodScheduleLines.${index}.demandQty`, Number(newValue)) + }, []) + + const onSaveClick = useCallback(async (row: DetailedProdScheduleLineResult) => { + setIsUploading(true) + try { + const response = await saveProdScheduleLine({ + id: row.id, + demandQty: row.demandQty + }) + + if (response) { + const index = formProps.getValues("prodScheduleLines").findIndex(ele => ele.id == row.id) + formProps.setValue(`prodScheduleLines.${index}.bomMaterials`, response.entity.bomMaterials) + } + setIsUploading(false) + } catch (e) { + console.log(e) + setIsUploading(false) + } + }, []) + + const onCancelClick = useCallback(async (rowId: number) => { + // if (tempValue) { + const index = formProps.getValues("prodScheduleLines").findIndex(ele => ele.id == rowId) + formProps.setValue(`prodScheduleLines.${index}.demandQty`, Number(tempValue)) + // } + }, [tempValue]) + + return ( + <> + + + {/**/} + {/* */} + {/* {t(`${mode} ${title}`)}*/} + {/* */} + {/**/} + + {/* + + */} + + {/* + + + */} + {serverError && ( + + {serverError} + + )} + {/* {tabIndex === 0 && } */} + + {/* {tabIndex === 1 && } */} + + {/* */} + + + + + + ); +}; +export default DetailedScheduleDetailView; diff --git a/src/components/DetailedScheduleDetail/DetailedScheduleDetailWrapper.tsx b/src/components/DetailedScheduleDetail/DetailedScheduleDetailWrapper.tsx new file mode 100644 index 0000000..349d90b --- /dev/null +++ b/src/components/DetailedScheduleDetail/DetailedScheduleDetailWrapper.tsx @@ -0,0 +1,44 @@ +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 & 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 ( + + ); +}; + +DetailedScheduleDetailWrapper.Loading = GeneralLoading; + +export default DetailedScheduleDetailWrapper; diff --git a/src/components/DetailedScheduleDetail/ProdTimeColumn.tsx b/src/components/DetailedScheduleDetail/ProdTimeColumn.tsx new file mode 100644 index 0000000..2bb967a --- /dev/null +++ b/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 = ({ prodTimeInMinute }) => { + + const { t } = useTranslation("schedule") + const overallMinutes = useMemo(() => + prodTimeInMinute + .map((ele) => ele.totalMinutes) + .reduce((acc, cur) => acc + cur, 0) + , []) + + return ( + + + { + prodTimeInMinute.map(({ equipName, totalMinutes }, index) => { + return ( + + + {equipName}: + + + {minutesToHoursMinutes(totalMinutes)} + + + ) + }) + } + + + + {t("Overall")}: + + + {minutesToHoursMinutes(overallMinutes)} + + + + ) +} + +export default ProdTimeColumn \ No newline at end of file diff --git a/src/components/DetailedScheduleDetail/TempRecords.tsx b/src/components/DetailedScheduleDetail/TempRecords.tsx new file mode 100644 index 0000000..6520cb3 --- /dev/null +++ b/src/components/DetailedScheduleDetail/TempRecords.tsx @@ -0,0 +1,1878 @@ +// ViewByFGDetails.tsx +const dayPeriod11 = [ + "2025-05-07", + // '2025-05-12', + // '2025-05-13', + // '2025-05-14', + // '2025-05-15', + // '2025-05-16', + // '2025-05-17', +]; + +// const fakeRecordLine = useMemo( +const fakeRecordLine11 = [ + [ + { + id: 1, + code: "mt1", + name: "material 1", + inStockQty: 10, + purchaseQty: 1, + }, + { + id: 2, + code: "mt2", + name: "material 2", + inStockQty: 20, + purchaseQty: 199, + }, + ], + [ + { + id: 3, + code: "mt3", + name: "material 3", + inStockQty: 30, + purchaseQty: 3, + }, + { + id: 4, + code: "mt4", + name: "material 4", + inStockQty: 40, + purchaseQty: 499, + }, + ], + [ + { + id: 5, + code: "mt5", + name: "material 5", + inStockQty: 50, + purchaseQty: 5, + }, + { + id: 6, + code: "mt6", + name: "material 6", + inStockQty: 60, + purchaseQty: 699, + }, + ], + [ + { + id: 7, + code: "mt7", + name: "material 7", + inStockQty: 70, + purchaseQty: 7, + }, + { + id: 8, + code: "mt8", + name: "material 8", + inStockQty: 80, + purchaseQty: 899, + }, + ], + [ + { + id: 9, + code: "mt9", + name: "material 9", + inStockQty: 90, + purchaseQty: 9, + }, + { + id: 10, + code: "mt10", + name: "material 10", + inStockQty: 100, + purchaseQty: 999, + }, + ], + [ + { + id: 11, + code: "mt11", + name: "material 11", + inStockQty: 110, + purchaseQty: 11, + }, + { + id: 12, + code: "mt12", + name: "material 12", + inStockQty: 120, + purchaseQty: 1299, + }, + ], + [ + { + id: 13, + code: "mt13", + name: "material 13", + inStockQty: 130, + purchaseQty: 13, + }, + { + id: 14, + code: "mt14", + name: "material 14", + inStockQty: 140, + purchaseQty: 1499, + }, + ], +]; + +// const fakeRecords = useMemo( +const fakeRecords11 = [ + [ + { + id: 1, + jobNo: "JO20250507001", + estimatedProductionTime: "1 hr", + priority: 85, + code: "PP1193", + type: "FG", + name: "蔥油(1磅) ", + inStockQty: 1322, + productionQty: 661, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 100, + purchaseQty: 20, + }, + { + id: 2, + code: "FA0161", + type: "Material", + name: "洋蔥粒", + inStockQty: 80, + purchaseQty: 10, + }, + ], + }, + { + id: 2, + jobNo: "JO20250507002", + estimatedProductionTime: "2 hrs", + priority: 80, + code: " PP1096", + type: "FG", + name: "白麵撈", + inStockQty: 1040, + productionQty: 520, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 1000, + purchaseQty: 190.0, + }, + { + id: 1, + code: "MH0040", + type: "Material", + name: "星加坡綠富貴花牌幼白麵粉 (50磅/包)", + inStockQty: 1000, + purchaseQty: 250.0, + }, + { + id: 2, + code: "FA0161", + type: "Material", + name: "蔥油", + inStockQty: 1322, + purchaseQty: 0, + }, + ], + }, + { + id: 3, + jobNo: "JO20250507003", + estimatedProductionTime: "5 hrs : 15 mins", + priority: 35, + code: "PP1080", + type: "FG", + name: "咖哩汁", + inStockQty: 2400, + productionQty: 1200.0, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 108.88, + }, + { + id: 2, + code: "GI3236", + type: "Material", + name: "清水(煮過牛腩)", + inStockQty: 317.52, + purchaseQty: 635.04, + }, + { + id: 3, + code: "MG1700", + type: "Material", + name: "STERILTOM 意大利茄粒", + inStockQty: 9.0, + purchaseQty: 18.0, + }, + { + id: 4, + code: "FA0533", + type: "Material", + name: "乾蔥茸", + inStockQty: 6.04, + purchaseQty: 12.08, + }, + { + id: 5, + code: "FA0210", + type: "Material", + name: "薑茸", + inStockQty: 0, + purchaseQty: 12.08, + }, + { + id: 6, + code: "FA0608", + type: "Material", + name: "粗蒜茸", + inStockQty: 0, + purchaseQty: 12.08, + }, + { + id: 7, + code: "FA0056", + type: "Material", + name: "洋蔥肉", + inStockQty: 241.98, + purchaseQty: 483.96, + }, + { + id: 8, + code: "PP1188", + type: "Material", + name: "咖喱膽", + inStockQty: 36.0, + purchaseQty: 72.0, + }, + { + id: 9, + code: "PP8001", + type: "Material", + name: "咖哩汁箱料粉", + inStockQty: 77.42, + purchaseQty: 154.84, + }, + { + id: 10, + code: "PP1096", + type: "Material", + name: "白麵撈", + inStockQty: 60.0, + purchaseQty: 120.0, + }, + { + id: 10, + code: "NA0476", + type: "Material", + name: "2磅份量三邊覆合袋 (0.1x225x260mm)個計", + inStockQty: 600.0, + purchaseQty: 200.0, + }, + ], + }, + { + id: 4, + jobNo: "JO20250507004", + estimatedProductionTime: "3 hrs", + priority: 20, + code: " PP1188", + type: "FG", + name: "咖喱膽", + inStockQty: 1016.2, + productionQty: 508.1, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 217.72, + }, + { + id: 2, + code: "FA0161", + type: "Material", + name: "洋蔥粒", + inStockQty: 0, + purchaseQty: 18.15, + }, + { + id: 3, + code: "FA0608", + type: "Material", + name: "粗蒜茸", + inStockQty: 0, + purchaseQty: 18.15, + }, + { + id: 4, + code: "MG1288", + type: "Material", + name: "炸紅蔥頭", + inStockQty: 0, + purchaseQty: 6.05, + }, + { + id: 5, + code: "FA0210", + type: "Material", + name: "薑茸", + inStockQty: 0, + purchaseQty: 6.05, + }, + { + id: 6, + code: "MG0066", + type: "Material", + name: "咖哩料(5斤x16包+2斤/包)", + inStockQty: 0, + purchaseQty: 241.98, + }, + ], + }, + ], + [ + { + id: 1, + code: "PP1080", + type: "FG", + name: "咖哩汁", + inStockQty: 2400, + productionQty: 1200.0, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 108.88, + }, + { + id: 2, + code: "GI3236", + type: "Material", + name: "清水(煮過牛腩)", + inStockQty: 317.52, + purchaseQty: 635.04, + }, + { + id: 3, + code: "MG1700", + type: "Material", + name: "STERILTOM 意大利茄粒", + inStockQty: 9.0, + purchaseQty: 18.0, + }, + { + id: 4, + code: "FA0533", + type: "Material", + name: "乾蔥茸", + inStockQty: 6.04, + purchaseQty: 12.08, + }, + { + id: 5, + code: "FA0210", + type: "Material", + name: "薑茸", + inStockQty: 0, + purchaseQty: 12.08, + }, + { + id: 6, + code: "FA0608", + type: "Material", + name: "粗蒜茸", + inStockQty: 0, + purchaseQty: 12.08, + }, + { + id: 7, + code: "FA0056", + type: "Material", + name: "洋蔥肉", + inStockQty: 241.98, + purchaseQty: 483.96, + }, + { + id: 8, + code: "PP1188", + type: "Material", + name: "咖喱膽", + inStockQty: 36.0, + purchaseQty: 72.0, + }, + { + id: 9, + code: "PP8001", + type: "Material", + name: "咖哩汁箱料粉", + inStockQty: 77.42, + purchaseQty: 154.84, + }, + { + id: 10, + code: "PP1096", + type: "Material", + name: "白麵撈", + inStockQty: 60.0, + purchaseQty: 120.0, + }, + { + id: 10, + code: "NA0476", + type: "Material", + name: "2磅份量三邊覆合袋 (0.1x225x260mm)個計", + inStockQty: 600.0, + purchaseQty: 200.0, + }, + ], + }, + { + id: 2, + code: "PP1193", + type: "FG", + name: "蔥油(1磅) ", + inStockQty: 1322, + productionQty: 661, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 20, + }, + { + id: 2, + code: "FA0161", + type: "Material", + name: "洋蔥粒", + inStockQty: 0, + purchaseQty: 10, + }, + ], + }, + { + id: 3, + code: " PP1188", + type: "FG", + name: "咖喱膽", + inStockQty: 1016.2, + productionQty: 508.1, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 217.72, + }, + { + id: 2, + code: "FA0161", + type: "Material", + name: "洋蔥粒", + inStockQty: 0, + purchaseQty: 18.15, + }, + { + id: 3, + code: "FA0608", + type: "Material", + name: "粗蒜茸", + inStockQty: 0, + purchaseQty: 18.15, + }, + { + id: 4, + code: "MG1288", + type: "Material", + name: "炸紅蔥頭", + inStockQty: 0, + purchaseQty: 6.05, + }, + { + id: 5, + code: "FA0210", + type: "Material", + name: "薑茸", + inStockQty: 0, + purchaseQty: 6.05, + }, + { + id: 6, + code: "MG0066", + type: "Material", + name: "咖哩料(5斤x16包+2斤/包)", + inStockQty: 0, + purchaseQty: 241.98, + }, + ], + }, + { + id: 4, + code: " PP1096", + type: "FG", + name: "白麵撈", + inStockQty: 1040, + productionQty: 520, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 190.0, + }, + { + id: 1, + code: "MH0040", + type: "Material", + name: "星加坡綠富貴花牌幼白麵粉 (50磅/包)", + inStockQty: 0, + purchaseQty: 250.0, + }, + { + id: 2, + code: "FA0161", + type: "Material", + name: "蔥油", + inStockQty: 1322, + purchaseQty: 0, + }, + ], + }, + ], + [ + { + id: 1, + code: "PP1080", + type: "FG", + name: "咖哩汁", + inStockQty: 2400, + productionQty: 1200.0, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 108.88, + }, + { + id: 2, + code: "GI3236", + type: "Material", + name: "清水(煮過牛腩)", + inStockQty: 317.52, + purchaseQty: 635.04, + }, + { + id: 3, + code: "MG1700", + type: "Material", + name: "STERILTOM 意大利茄粒", + inStockQty: 9.0, + purchaseQty: 18.0, + }, + { + id: 4, + code: "FA0533", + type: "Material", + name: "乾蔥茸", + inStockQty: 6.04, + purchaseQty: 12.08, + }, + { + id: 5, + code: "FA0210", + type: "Material", + name: "薑茸", + inStockQty: 0, + purchaseQty: 12.08, + }, + { + id: 6, + code: "FA0608", + type: "Material", + name: "粗蒜茸", + inStockQty: 0, + purchaseQty: 12.08, + }, + { + id: 7, + code: "FA0056", + type: "Material", + name: "洋蔥肉", + inStockQty: 241.98, + purchaseQty: 483.96, + }, + { + id: 8, + code: "PP1188", + type: "Material", + name: "咖喱膽", + inStockQty: 36.0, + purchaseQty: 72.0, + }, + { + id: 9, + code: "PP8001", + type: "Material", + name: "咖哩汁箱料粉", + inStockQty: 77.42, + purchaseQty: 154.84, + }, + { + id: 10, + code: "PP1096", + type: "Material", + name: "白麵撈", + inStockQty: 60.0, + purchaseQty: 120.0, + }, + { + id: 10, + code: "NA0476", + type: "Material", + name: "2磅份量三邊覆合袋 (0.1x225x260mm)個計", + inStockQty: 600.0, + purchaseQty: 200.0, + }, + ], + }, + { + id: 2, + code: "PP1193", + type: "FG", + name: "蔥油(1磅) ", + inStockQty: 1322, + productionQty: 661, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 20, + }, + { + id: 2, + code: "FA0161", + type: "Material", + name: "洋蔥粒", + inStockQty: 0, + purchaseQty: 10, + }, + ], + }, + { + id: 3, + code: " PP1188", + type: "FG", + name: "咖喱膽", + inStockQty: 1016.2, + productionQty: 508.1, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 217.72, + }, + { + id: 2, + code: "FA0161", + type: "Material", + name: "洋蔥粒", + inStockQty: 0, + purchaseQty: 18.15, + }, + { + id: 3, + code: "FA0608", + type: "Material", + name: "粗蒜茸", + inStockQty: 0, + purchaseQty: 18.15, + }, + { + id: 4, + code: "MG1288", + type: "Material", + name: "炸紅蔥頭", + inStockQty: 0, + purchaseQty: 6.05, + }, + { + id: 5, + code: "FA0210", + type: "Material", + name: "薑茸", + inStockQty: 0, + purchaseQty: 6.05, + }, + { + id: 6, + code: "MG0066", + type: "Material", + name: "咖哩料(5斤x16包+2斤/包)", + inStockQty: 0, + purchaseQty: 241.98, + }, + ], + }, + { + id: 4, + code: " PP1096", + type: "FG", + name: "白麵撈", + inStockQty: 1040, + productionQty: 520, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 190.0, + }, + { + id: 1, + code: "MH0040", + type: "Material", + name: "星加坡綠富貴花牌幼白麵粉 (50磅/包)", + inStockQty: 0, + purchaseQty: 250.0, + }, + { + id: 2, + code: "FA0161", + type: "Material", + name: "蔥油", + inStockQty: 1322, + purchaseQty: 0, + }, + ], + }, + ], + [ + { + id: 1, + code: "PP1080", + type: "FG", + name: "咖哩汁", + inStockQty: 2400, + productionQty: 1200.0, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 108.88, + }, + { + id: 2, + code: "GI3236", + type: "Material", + name: "清水(煮過牛腩)", + inStockQty: 317.52, + purchaseQty: 635.04, + }, + { + id: 3, + code: "MG1700", + type: "Material", + name: "STERILTOM 意大利茄粒", + inStockQty: 9.0, + purchaseQty: 18.0, + }, + { + id: 4, + code: "FA0533", + type: "Material", + name: "乾蔥茸", + inStockQty: 6.04, + purchaseQty: 12.08, + }, + { + id: 5, + code: "FA0210", + type: "Material", + name: "薑茸", + inStockQty: 0, + purchaseQty: 12.08, + }, + { + id: 6, + code: "FA0608", + type: "Material", + name: "粗蒜茸", + inStockQty: 0, + purchaseQty: 12.08, + }, + { + id: 7, + code: "FA0056", + type: "Material", + name: "洋蔥肉", + inStockQty: 241.98, + purchaseQty: 483.96, + }, + { + id: 8, + code: "PP1188", + type: "Material", + name: "咖喱膽", + inStockQty: 36.0, + purchaseQty: 72.0, + }, + { + id: 9, + code: "PP8001", + type: "Material", + name: "咖哩汁箱料粉", + inStockQty: 77.42, + purchaseQty: 154.84, + }, + { + id: 10, + code: "PP1096", + type: "Material", + name: "白麵撈", + inStockQty: 60.0, + purchaseQty: 120.0, + }, + { + id: 10, + code: "NA0476", + type: "Material", + name: "2磅份量三邊覆合袋 (0.1x225x260mm)個計", + inStockQty: 600.0, + purchaseQty: 200.0, + }, + ], + }, + { + id: 2, + code: "PP1193", + type: "FG", + name: "蔥油(1磅) ", + inStockQty: 1322, + productionQty: 661, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 20, + }, + { + id: 2, + code: "FA0161", + type: "Material", + name: "洋蔥粒", + inStockQty: 0, + purchaseQty: 10, + }, + ], + }, + { + id: 3, + code: " PP1188", + type: "FG", + name: "咖喱膽", + inStockQty: 1016.2, + productionQty: 508.1, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 217.72, + }, + { + id: 2, + code: "FA0161", + type: "Material", + name: "洋蔥粒", + inStockQty: 0, + purchaseQty: 18.15, + }, + { + id: 3, + code: "FA0608", + type: "Material", + name: "粗蒜茸", + inStockQty: 0, + purchaseQty: 18.15, + }, + { + id: 4, + code: "MG1288", + type: "Material", + name: "炸紅蔥頭", + inStockQty: 0, + purchaseQty: 6.05, + }, + { + id: 5, + code: "FA0210", + type: "Material", + name: "薑茸", + inStockQty: 0, + purchaseQty: 6.05, + }, + { + id: 6, + code: "MG0066", + type: "Material", + name: "咖哩料(5斤x16包+2斤/包)", + inStockQty: 0, + purchaseQty: 241.98, + }, + ], + }, + { + id: 4, + code: " PP1096", + type: "FG", + name: "白麵撈", + inStockQty: 1040, + productionQty: 520, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 190.0, + }, + { + id: 1, + code: "MH0040", + type: "Material", + name: "星加坡綠富貴花牌幼白麵粉 (50磅/包)", + inStockQty: 0, + purchaseQty: 250.0, + }, + { + id: 2, + code: "FA0161", + type: "Material", + name: "蔥油", + inStockQty: 1322, + purchaseQty: 0, + }, + ], + }, + ], + [ + { + id: 1, + code: "PP1080", + name: "咖哩汁", + inStockQty: 2400, + productionQty: 1200.0, + lines: [ + { + id: 1, + code: "MH0040", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 108.88, + }, + { + id: 2, + code: "GI3236", + name: "清水(煮過牛腩)", + inStockQty: 317.52, + purchaseQty: 635.04, + }, + { + id: 3, + code: "MG1700", + name: "STERILTOM 意大利茄粒", + inStockQty: 9.0, + purchaseQty: 18.0, + }, + { + id: 4, + code: "FA0533", + name: "乾蔥茸", + inStockQty: 6.04, + purchaseQty: 12.08, + }, + { + id: 5, + code: "FA0210", + name: "薑茸", + inStockQty: 0, + purchaseQty: 12.08, + }, + { + id: 6, + code: "FA0608", + name: "粗蒜茸", + inStockQty: 0, + purchaseQty: 12.08, + }, + { + id: 7, + code: "FA0056", + name: "洋蔥肉", + inStockQty: 241.98, + purchaseQty: 483.96, + }, + { + id: 8, + code: "PP1188", + name: "咖喱膽", + inStockQty: 36.0, + purchaseQty: 72.0, + }, + { + id: 9, + code: "PP8001", + name: "咖哩汁箱料粉", + inStockQty: 77.42, + purchaseQty: 154.84, + }, + { + id: 10, + code: "PP1096", + name: "白麵撈", + inStockQty: 60.0, + purchaseQty: 120.0, + }, + { + id: 10, + code: "NA0476", + name: "2磅份量三邊覆合袋 (0.1x225x260mm)個計", + inStockQty: 600.0, + purchaseQty: 200.0, + }, + ], + }, + { + id: 2, + code: "PP1193", + name: "蔥油(1磅) ", + inStockQty: 1322, + productionQty: 661, + lines: [ + { + id: 1, + code: "MH0040", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 20, + }, + { + id: 2, + code: "FA0161", + name: "洋蔥粒", + inStockQty: 0, + purchaseQty: 10, + }, + ], + }, + { + id: 3, + code: " PP1188", + name: "咖喱膽", + inStockQty: 1016.2, + productionQty: 508.1, + lines: [ + { + id: 1, + code: "MH0040", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 217.72, + }, + { + id: 2, + code: "FA0161", + name: "洋蔥粒", + inStockQty: 0, + purchaseQty: 18.15, + }, + { + id: 3, + code: "FA0608", + name: "粗蒜茸", + inStockQty: 0, + purchaseQty: 18.15, + }, + { + id: 4, + code: "MG1288", + name: "炸紅蔥頭", + inStockQty: 0, + purchaseQty: 6.05, + }, + { + id: 5, + code: "FA0210", + name: "薑茸", + inStockQty: 0, + purchaseQty: 6.05, + }, + { + id: 6, + code: "MG0066", + name: "咖哩料(5斤x16包+2斤/包)", + inStockQty: 0, + purchaseQty: 241.98, + }, + ], + }, + { + id: 4, + code: " PP1096", + name: "白麵撈", + inStockQty: 1040, + productionQty: 520, + lines: [ + { + id: 1, + code: "MH0040", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 190.0, + }, + { + id: 1, + code: "MH0040", + name: "星加坡綠富貴花牌幼白麵粉 (50磅/包)", + inStockQty: 0, + purchaseQty: 250.0, + }, + { + id: 2, + code: "FA0161", + name: "蔥油", + inStockQty: 1322, + purchaseQty: 0, + }, + ], + }, + ], + [ + { + id: 1, + code: "PP1080", + type: "FG", + name: "咖哩汁", + inStockQty: 2400, + productionQty: 1200.0, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 108.88, + }, + { + id: 2, + code: "GI3236", + type: "Material", + name: "清水(煮過牛腩)", + inStockQty: 317.52, + purchaseQty: 635.04, + }, + { + id: 3, + code: "MG1700", + type: "Material", + name: "STERILTOM 意大利茄粒", + inStockQty: 9.0, + purchaseQty: 18.0, + }, + { + id: 4, + code: "FA0533", + type: "Material", + name: "乾蔥茸", + inStockQty: 6.04, + purchaseQty: 12.08, + }, + { + id: 5, + code: "FA0210", + type: "Material", + name: "薑茸", + inStockQty: 0, + purchaseQty: 12.08, + }, + { + id: 6, + code: "FA0608", + type: "Material", + name: "粗蒜茸", + inStockQty: 0, + purchaseQty: 12.08, + }, + { + id: 7, + code: "FA0056", + type: "Material", + name: "洋蔥肉", + inStockQty: 241.98, + purchaseQty: 483.96, + }, + { + id: 8, + code: "PP1188", + type: "Material", + name: "咖喱膽", + inStockQty: 36.0, + purchaseQty: 72.0, + }, + { + id: 9, + code: "PP8001", + type: "Material", + name: "咖哩汁箱料粉", + inStockQty: 77.42, + purchaseQty: 154.84, + }, + { + id: 10, + code: "PP1096", + type: "Material", + name: "白麵撈", + inStockQty: 60.0, + purchaseQty: 120.0, + }, + { + id: 10, + code: "NA0476", + type: "Material", + name: "2磅份量三邊覆合袋 (0.1x225x260mm)個計", + inStockQty: 600.0, + purchaseQty: 200.0, + }, + ], + }, + { + id: 2, + code: "PP1193", + type: "FG", + name: "蔥油(1磅) ", + inStockQty: 1322, + productionQty: 661, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 20, + }, + { + id: 2, + code: "FA0161", + type: "Material", + name: "洋蔥粒", + inStockQty: 0, + purchaseQty: 10, + }, + ], + }, + { + id: 3, + code: " PP1188", + type: "FG", + name: "咖喱膽", + inStockQty: 1016.2, + productionQty: 508.1, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 217.72, + }, + { + id: 2, + code: "FA0161", + type: "Material", + name: "洋蔥粒", + inStockQty: 0, + purchaseQty: 18.15, + }, + { + id: 3, + code: "FA0608", + type: "Material", + name: "粗蒜茸", + inStockQty: 0, + purchaseQty: 18.15, + }, + { + id: 4, + code: "MG1288", + type: "Material", + name: "炸紅蔥頭", + inStockQty: 0, + purchaseQty: 6.05, + }, + { + id: 5, + code: "FA0210", + type: "Material", + name: "薑茸", + inStockQty: 0, + purchaseQty: 6.05, + }, + { + id: 6, + code: "MG0066", + type: "Material", + name: "咖哩料(5斤x16包+2斤/包)", + inStockQty: 0, + purchaseQty: 241.98, + }, + ], + }, + { + id: 4, + code: " PP1096", + type: "FG", + name: "白麵撈", + inStockQty: 1040, + productionQty: 520, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 190.0, + }, + { + id: 1, + code: "MH0040", + type: "Material", + name: "星加坡綠富貴花牌幼白麵粉 (50磅/包)", + inStockQty: 0, + purchaseQty: 250.0, + }, + { + id: 2, + code: "FA0161", + type: "Material", + name: "蔥油", + inStockQty: 1322, + purchaseQty: 0, + }, + ], + }, + ], + [ + { + id: 1, + code: "PP1080", + type: "FG", + name: "咖哩汁", + inStockQty: 2400, + productionQty: 1200.0, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 108.88, + }, + { + id: 2, + code: "GI3236", + type: "Material", + name: "清水(煮過牛腩)", + inStockQty: 317.52, + purchaseQty: 635.04, + }, + { + id: 3, + code: "MG1700", + type: "Material", + name: "STERILTOM 意大利茄粒", + inStockQty: 9.0, + purchaseQty: 18.0, + }, + { + id: 4, + code: "FA0533", + type: "Material", + name: "乾蔥茸", + inStockQty: 6.04, + purchaseQty: 12.08, + }, + { + id: 5, + code: "FA0210", + type: "Material", + name: "薑茸", + inStockQty: 0, + purchaseQty: 12.08, + }, + { + id: 6, + code: "FA0608", + type: "Material", + name: "粗蒜茸", + inStockQty: 0, + purchaseQty: 12.08, + }, + { + id: 7, + code: "FA0056", + type: "Material", + name: "洋蔥肉", + inStockQty: 241.98, + purchaseQty: 483.96, + }, + { + id: 8, + code: "PP1188", + type: "Material", + name: "咖喱膽", + inStockQty: 36.0, + purchaseQty: 72.0, + }, + { + id: 9, + code: "PP8001", + type: "Material", + name: "咖哩汁箱料粉", + inStockQty: 77.42, + purchaseQty: 154.84, + }, + { + id: 10, + code: "PP1096", + type: "Material", + name: "白麵撈", + inStockQty: 60.0, + purchaseQty: 120.0, + }, + { + id: 10, + code: "NA0476", + type: "Material", + name: "2磅份量三邊覆合袋 (0.1x225x260mm)個計", + inStockQty: 600.0, + purchaseQty: 200.0, + }, + ], + }, + { + id: 2, + code: "PP1193", + type: "FG", + name: "蔥油(1磅) ", + inStockQty: 1322, + productionQty: 661, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 20, + }, + { + id: 2, + code: "FA0161", + type: "Material", + name: "洋蔥粒", + inStockQty: 0, + purchaseQty: 10, + }, + ], + }, + { + id: 3, + code: " PP1188", + type: "FG", + name: "咖喱膽", + inStockQty: 1016.2, + productionQty: 508.1, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 217.72, + }, + { + id: 2, + code: "FA0161", + type: "Material", + name: "洋蔥粒", + inStockQty: 0, + purchaseQty: 18.15, + }, + { + id: 3, + code: "FA0608", + type: "Material", + name: "粗蒜茸", + inStockQty: 0, + purchaseQty: 18.15, + }, + { + id: 4, + code: "MG1288", + type: "Material", + name: "炸紅蔥頭", + inStockQty: 0, + purchaseQty: 6.05, + }, + { + id: 5, + code: "FA0210", + type: "Material", + name: "薑茸", + inStockQty: 0, + purchaseQty: 6.05, + }, + { + id: 6, + code: "MG0066", + type: "Material", + name: "咖哩料(5斤x16包+2斤/包)", + inStockQty: 0, + purchaseQty: 241.98, + }, + ], + }, + { + id: 4, + code: " PP1096", + type: "FG", + name: "白麵撈", + inStockQty: 1040, + productionQty: 520, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 190.0, + }, + { + id: 1, + code: "MH0040", + type: "Material", + name: "星加坡綠富貴花牌幼白麵粉 (50磅/包)", + inStockQty: 0, + purchaseQty: 250.0, + }, + { + id: 2, + code: "FA0161", + type: "Material", + name: "蔥油", + inStockQty: 1322, + purchaseQty: 0, + }, + ], + }, + ], +]; + +// const fakeOverallRecords = useMemo( +const fakeOverallRecords11 = [ + { + id: 1, + jobNo: "JO20250507001", + priority: 20, + code: "PP1193", + type: "FG", + name: "蔥油(1磅) ", + lastMonthAvgStock: 1320, + safetyStock: 1322, + inStockQty: 1322, + productionQty: 4627, + lines: [ + { + id: 2, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 20 * 7, + }, + { + id: 3, + code: "FA0161", + type: "Material", + name: "洋蔥粒", + inStockQty: 0, + purchaseQty: 10 * 7, + }, + ], + }, + { + id: 2, + jobNo: "JO20250507002", + priority: 25, + code: " PP1096", + type: "FG", + name: "白麵撈", + lastMonthAvgStock: 1040, + safetyStock: 1040, + inStockQty: 1040, + productionQty: 3640, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 190.0 * 7, + }, + { + id: 1, + code: "MH0040", + type: "Material", + name: "星加坡綠富貴花牌幼白麵粉 (50磅/包)", + inStockQty: 0, + purchaseQty: 250.0 * 7, + }, + { + id: 2, + code: "FA0161", + type: "Material", + name: "蔥油", + inStockQty: 1322, + purchaseQty: 0, + }, + ], + }, + { + id: 3, + jobNo: "JO20250507003", + priority: 70, + code: "PP1080", + type: "FG", + name: "咖哩汁", + lastMonthAvgStock: 2400, + safetyStock: 2400, + inStockQty: 2400, + productionQty: 8400.0 * 7, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 54.44, + purchaseQty: 544.4 * 7, + }, + { + id: 2, + code: "GI3236", + type: "Material", + name: "清水(煮過牛腩)", + inStockQty: 317.52, + purchaseQty: 3175.2 * 7, + }, + { + id: 3, + code: "MG1700", + type: "Material", + name: "STERILTOM 意大利茄粒", + inStockQty: 9.0, + purchaseQty: 90 * 7, + }, + { + id: 4, + code: "FA0533", + type: "Material", + name: "乾蔥茸", + inStockQty: 6.04, + purchaseQty: 60.4 * 7, + }, + { + id: 5, + code: "FA0210", + type: "Material", + name: "薑茸", + inStockQty: 6.04, + purchaseQty: 60.4 * 7, + }, + { + id: 6, + code: "FA0608", + type: "Material", + name: "粗蒜茸", + inStockQty: 6.04, + purchaseQty: 60.4 * 7, + }, + { + id: 7, + code: "FA0056", + type: "Material", + name: "洋蔥肉", + inStockQty: 241.98, + purchaseQty: 2419.8 * 7, + }, + { + id: 8, + code: "PP1188", + type: "Material", + name: "咖喱膽", + inStockQty: 36.0, + purchaseQty: 360 * 7, + }, + { + id: 9, + code: "PP8001", + type: "Material", + name: "咖哩汁箱料粉", + inStockQty: 77.42, + purchaseQty: 774.2 * 7, + }, + { + id: 10, + code: "PP1096", + type: "Material", + name: "白麵撈", + inStockQty: 60.0, + purchaseQty: 600 * 7, + }, + { + id: 10, + code: "NA0476", + type: "Material", + name: "2磅份量三邊覆合袋 (0.1x225x260mm)個計", + inStockQty: 600.0, + purchaseQty: 6000 * 7, + }, + ], + }, + { + id: 4, + jobNo: "JO20250507004", + priority: 80, + code: " PP1188", + type: "FG", + name: "咖喱膽", + lastMonthAvgStock: 1017, + safetyStock: 1017, + inStockQty: 1016.2, + productionQty: 3556.7, + lines: [ + { + id: 1, + code: "MH0040", + type: "Material", + name: "大豆油(1噸/桶)", + inStockQty: 0, + purchaseQty: 217.72 * 7, + }, + { + id: 2, + code: "FA0161", + type: "Material", + name: "洋蔥粒", + inStockQty: 0, + purchaseQty: 18.15 * 7, + }, + { + id: 3, + code: "FA0608", + type: "Material", + name: "粗蒜茸", + inStockQty: 0, + purchaseQty: 18.15 * 7, + }, + { + id: 4, + code: "MG1288", + type: "Material", + name: "炸紅蔥頭", + inStockQty: 0, + purchaseQty: 6.05 * 7, + }, + { + id: 5, + code: "FA0210", + type: "Material", + name: "薑茸", + inStockQty: 0, + purchaseQty: 6.05 * 7, + }, + { + id: 6, + code: "MG0066", + type: "Material", + name: "咖哩料(5斤x16包+2斤/包)", + inStockQty: 0, + purchaseQty: 241.98 * 7, + }, + ], + }, +]; \ No newline at end of file diff --git a/src/components/DetailedScheduleDetail/ViewByFGDetails.tsx b/src/components/DetailedScheduleDetail/ViewByFGDetails.tsx new file mode 100644 index 0000000..3902e39 --- /dev/null +++ b/src/components/DetailedScheduleDetail/ViewByFGDetails.tsx @@ -0,0 +1,240 @@ +"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; + isEdit: boolean; + type: ScheduleType; + onReleaseClick: (item: DetailedProdScheduleLineResult) => void; + onEditClick: (rowId: number) => void; + handleEditChange: (rowId: number, fieldName: keyof DetailedProdScheduleLineResult, newValue: number | string) => void; + onSaveClick: (item: DetailedProdScheduleLineResult) => void; + onCancelClick: (rowId: number) => void; +}; + +// export type FGRecord = { +// id: string | number; +// code: string; +// name: string; +// inStockQty: number; +// productionQty?: number; +// purchaseQty?: number; +// }; + +const ViewByFGDetails: React.FC = ({ apiRef, isEdit, type, onReleaseClick, onEditClick, handleEditChange, onSaveClick, onCancelClick }) => { + const { + t, + i18n: { language }, + } = useTranslation("schedule"); + + const { + getValues, + watch, + formState: { errors, defaultValues, touchedFields }, + } = useFormContext(); + + // 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[]>( + () => [ + { + 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", + renderCell: (row) => { + return t(row.type); + }, + // 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-number", + style: { + textAlign: "right", + }, + renderCell: (row) => { + if (typeof row.demandQty == "number") { + return integerFormatter.format(row.demandQty ?? 0); + } + return row.demandQty; + }, + }, + { + field: "prodTimeInMinute", + label: t("Estimated Production Time"), + type: "read-only", + style: { + textAlign: "right", + }, + renderCell: (row) => { + return + } + }, + { + field: "priority", + label: t("Production Priority"), + type: "read-only", + style: { + textAlign: "right", + }, + // editable: true, + }, + ], + [], + ); + + return ( + + {/* + + {t("FG Demand List (7 Days)")} + + + index={7} + items={fakeOverallRecords} + columns={overallColumns} + setPagingController={updatePagingController} + pagingController={pagingController[7]} + isAutoPaging={false} + isEditable={false} + isEdit={isEdit} + hasCollapse={true} + /> + */} + {/* {dayPeriod.map((date, index) => ( */} + + {/* + {`${t("FG Demand Date")}: ${date}`} + */} + + 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} + onReleaseClick={onReleaseClick} + onEditClick={onEditClick} + handleEditChange={handleEditChange} + onSaveClick={onSaveClick} + onCancelClick={onCancelClick} + /> + + {/* ))} */} + + ); +}; +export default ViewByFGDetails; diff --git a/src/components/DetailedScheduleDetail/index.ts b/src/components/DetailedScheduleDetail/index.ts new file mode 100644 index 0000000..e2a614f --- /dev/null +++ b/src/components/DetailedScheduleDetail/index.ts @@ -0,0 +1 @@ +export { default } from "./DetailedScheduleDetailWrapper"; diff --git a/src/components/DoSave/index.ts b/src/components/DoSave/index.ts index 2800028..8e4795e 100644 --- a/src/components/DoSave/index.ts +++ b/src/components/DoSave/index.ts @@ -1 +1 @@ -export default from "./DoSaveWrapper" \ No newline at end of file +// export default from "./DoSaveWrapper" \ No newline at end of file diff --git a/src/components/InventorySearch/InventorySearch.tsx b/src/components/InventorySearch/InventorySearch.tsx index 4b2d52d..78c7da3 100644 --- a/src/components/InventorySearch/InventorySearch.tsx +++ b/src/components/InventorySearch/InventorySearch.tsx @@ -153,7 +153,7 @@ const InventorySearch: React.FC = ({ inventories }) => { pagingController={{ pageNum: 0, pageSize: 0, - totalCount: 0, + // totalCount: 0, }} /> diff --git a/src/components/ItemsSearch/ItemsSearch.tsx b/src/components/ItemsSearch/ItemsSearch.tsx index 0e50fe5..cce9878 100644 --- a/src/components/ItemsSearch/ItemsSearch.tsx +++ b/src/components/ItemsSearch/ItemsSearch.tsx @@ -36,7 +36,7 @@ const ItemsSearch: React.FC = ({ items }) => { { label: t("Name"), paramName: "name", type: "text" }, ]; return searchCriteria; - }, [t, items]); + }, [t]); const onDetailClick = useCallback( (item: ItemsResult) => { @@ -70,7 +70,7 @@ const ItemsSearch: React.FC = ({ items }) => { onClick: onDeleteClick, }, ], - [filteredItems], + [onDeleteClick, onDetailClick, t], ); const refetchData = useCallback( @@ -102,12 +102,17 @@ const ItemsSearch: React.FC = ({ items }) => { throw error; // Rethrow the error for further handling } }, - [axiosInstance, pagingController.pageNum, pagingController.pageSize], + [pagingController.pageNum, pagingController.pageSize], ); useEffect(() => { refetchData(filterObj); - }, [filterObj, pagingController.pageNum, pagingController.pageSize]); + }, [ + filterObj, + pagingController.pageNum, + pagingController.pageSize, + refetchData, + ]); const onReset = useCallback(() => { setFilteredItems(items); diff --git a/src/components/JoSave/InfoCard.tsx b/src/components/JoSave/InfoCard.tsx new file mode 100644 index 0000000..3a28009 --- /dev/null +++ b/src/components/JoSave/InfoCard.tsx @@ -0,0 +1,84 @@ +import { JoDetail } from "@/app/api/jo"; +import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil"; +import { Box, Card, CardContent, Grid, Stack, TextField } from "@mui/material"; +import { upperFirst } from "lodash"; +import { useFormContext } from "react-hook-form"; +import { useTranslation } from "react-i18next"; + +type Props = { + +}; + +const InfoCard: React.FC = ({ + +}) => { + const { t } = useTranslation(); + + const { control, getValues, register, watch } = useFormContext(); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + ) +} + +export default InfoCard; \ No newline at end of file diff --git a/src/components/JoSave/JoSave.tsx b/src/components/JoSave/JoSave.tsx new file mode 100644 index 0000000..0bb820f --- /dev/null +++ b/src/components/JoSave/JoSave.tsx @@ -0,0 +1,107 @@ +"use client" +import { JoDetail } from "@/app/api/jo" +import { useRouter } from "next/navigation"; +import { useTranslation } from "react-i18next"; +import useUploadContext from "../UploadProvider/useUploadContext"; +import { FormProvider, SubmitErrorHandler, SubmitHandler, useForm } from "react-hook-form"; +import { useCallback, useState } from "react"; +import { Button, Stack, Typography } from "@mui/material"; +import StartIcon from "@mui/icons-material/Start"; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import { releaseJo } from "@/app/api/jo/actions"; +import InfoCard from "./InfoCard"; +import PickTable from "./PickTable"; + +type Props = { + id: number; + defaultValues: Partial | undefined; +} + +const JoSave: React.FC = ({ + defaultValues, + id, +}) => { + const { t } = useTranslation("jo") + const router = useRouter(); + const { setIsUploading } = useUploadContext(); + const [serverError, setServerError] = useState(""); + + const formProps = useForm({ + defaultValues: defaultValues + }) + + const handleBack = useCallback(() => { + router.replace(`/jo`) + }, []) + + const handleRelease = useCallback(async () => { + try { + setIsUploading(true) + if (id) { + console.log(id) + const response = await releaseJo({ id: id }) + console.log(response.entity.status) + if (response) { + formProps.setValue("status", response.entity.status) + console.log(formProps.watch("status")) + } + } + + } catch (e) { + // backend error + setServerError(t("An error has occurred. Please try again later.")); + console.log(e); + } finally { + setIsUploading(false) + } + }, []) + + const onSubmit = useCallback>(async (data, event) => { + console.log(data) + }, [t]) + + const onSubmitError = useCallback>((errors) => { + console.log(errors) + }, [t]) + + return <> + + + {serverError && ( + + {serverError} + + )} + { + formProps.watch("status").toLowerCase() === "planning" && ( + + + + )} + + + + + + + + +} + +export default JoSave; \ No newline at end of file diff --git a/src/components/JoSave/JoSaveWrapper.tsx b/src/components/JoSave/JoSaveWrapper.tsx new file mode 100644 index 0000000..edc7b4e --- /dev/null +++ b/src/components/JoSave/JoSaveWrapper.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import GeneralLoading from "../General/GeneralLoading"; +import { fetchJoDetail } from "@/app/api/jo"; +import JoSave from "./JoSave"; + +interface SubComponents { + Loading: typeof GeneralLoading; +} + +type JoSaveProps = { + id?: number; +} + +type Props = JoSaveProps + +const JoSaveWrapper: React.FC & SubComponents = async ({ + id, +}) => { + const jo = id ? await fetchJoDetail(id) : undefined + + return +} + +JoSaveWrapper.Loading = GeneralLoading; + +export default JoSaveWrapper; \ No newline at end of file diff --git a/src/components/JoSave/PickTable.tsx b/src/components/JoSave/PickTable.tsx new file mode 100644 index 0000000..e508181 --- /dev/null +++ b/src/components/JoSave/PickTable.tsx @@ -0,0 +1,91 @@ +import { JoDetail } from "@/app/api/jo"; +import { decimalFormatter } from "@/app/utils/formatUtil"; +import { GridColDef } from "@mui/x-data-grid"; +import { isEmpty, upperFirst } from "lodash"; +import { useMemo } from "react"; +import { useFormContext } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import StyledDataGrid from "../StyledDataGrid/StyledDataGrid"; + +type Props = { + +}; + +const PickTable: React.FC = ({ + +}) => { + const { t } = useTranslation("jo") + const { + watch + } = useFormContext() + + const columns = useMemo(() => [ + { + field: "code", + headerName: t("Code"), + flex: 1, + }, + { + field: "name", + headerName: t("Name"), + flex: 1, + }, + { + field: "lotNo", + headerName: t("Lot No."), + flex: 1, + renderCell: (row) => { + return isEmpty(row.value) ? "N/A" : row.value + }, + }, + { + field: "reqQty", + headerName: t("Req. Qty"), + flex: 1, + align: "right", + headerAlign: "right", + renderCell: (row) => { + return decimalFormatter.format(row.value) + }, + }, + { + field: "uom", + headerName: t("UoM"), + flex: 1, + align: "left", + headerAlign: "left", + }, + + { + field: "status", + headerName: t("Status"), + flex: 1, + renderCell: (row) => { + return upperFirst(row.value) + }, + }, + ], []) + + return ( + <> + + + ) +} + +export default PickTable; \ No newline at end of file diff --git a/src/components/JoSave/index.ts b/src/components/JoSave/index.ts new file mode 100644 index 0000000..4c1e410 --- /dev/null +++ b/src/components/JoSave/index.ts @@ -0,0 +1 @@ +export { default } from "./JoSaveWrapper"; \ No newline at end of file diff --git a/src/components/JoSearch/JoSearch.tsx b/src/components/JoSearch/JoSearch.tsx new file mode 100644 index 0000000..7fb407f --- /dev/null +++ b/src/components/JoSearch/JoSearch.tsx @@ -0,0 +1,145 @@ +"use client" +import { SearchJoResult, SearchJoResultRequest, fetchJos } from "@/app/api/jo/actions"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Criterion } from "../SearchBox"; +import SearchResults, { Column, defaultPagingController } from "../SearchResults/SearchResults"; +import { EditNote } from "@mui/icons-material"; +import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil"; +import { uniqBy, upperFirst } from "lodash"; +import SearchBox from "../SearchBox/SearchBox"; +import { useRouter } from "next/navigation"; + +interface Props { + defaultInputs: SearchJoResultRequest +} + +type SearchQuery = Partial>; + +type SearchParamNames = keyof SearchQuery; + +const JoSearch: React.FC = ({ defaultInputs }) => { + const { t } = useTranslation("jo"); + const router = useRouter() + const [filteredJos, setFilteredJos] = useState([]); + const [inputs, setInputs] = useState(defaultInputs); + const [pagingController, setPagingController] = useState( + defaultPagingController + ) + const [totalCount, setTotalCount] = useState(0) + + const searchCriteria: Criterion[] = useMemo(() => [ + { label: t("Code"), paramName: "code", type: "text" }, + { label: t("Name"), paramName: "name", type: "text" }, + ], [t]) + + const columns = useMemo[]>( + () => [ + { + name: "id", + label: t("Details"), + onClick: (record) => onDetailClick(record), + buttonIcon: , + }, + { + name: "code", + label: t("Code") + }, + { + name: "name", + label: t("Name"), + }, + { + name: "reqQty", + label: t("Req. Qty"), + align: "right", + headerAlign: "right", + renderCell: (row) => { + return integerFormatter.format(row.reqQty) + } + }, + { + name: "uom", + label: t("UoM"), + align: "left", + headerAlign: "left", + renderCell: (row) => { + return t(row.uom) + } + }, + { + name: "status", + label: t("Status"), + renderCell: (row) => { + return t(upperFirst(row.status)) + } + } + ], [] + ) + + const refetchData = useCallback(async ( + query: Record | SearchJoResultRequest, + actionType: "reset" | "search" | "paging", + ) => { + const params: SearchJoResultRequest = { + code: query.code, + name: query.name, + pageNum: pagingController.pageNum - 1, + pageSize: pagingController.pageSize, + } + const response = await fetchJos(params) + + if (response) { + setTotalCount(response.total); + switch (actionType) { + case "reset": + case "search": + setFilteredJos(() => response.records); + break; + case "paging": + setFilteredJos((fs) => + uniqBy([...fs, ...response.records], "id"), + ); + break; + } + } + }, [pagingController, setPagingController]) + + useEffect(() => { + refetchData(inputs, "paging"); + }, [pagingController]); + + const onDetailClick = useCallback((record: SearchJoResult) => { + router.push(`/jo/edit?id=${record.id}`) + }, []) + + const onSearch = useCallback((query: Record) => { + setInputs(() => ({ + code: query.code, + name: query.name + })) + refetchData(query, "search"); + }, []) + + const onReset = useCallback(() => { + refetchData(defaultInputs, "paging"); + }, []) + + return <> + + + items={filteredJos} + columns={columns} + setPagingController={setPagingController} + pagingController={pagingController} + totalCount={totalCount} + // isAutoPaging={false} + /> + +} + +export default JoSearch; \ No newline at end of file diff --git a/src/components/JoSearch/JoSearchWrapper.tsx b/src/components/JoSearch/JoSearchWrapper.tsx new file mode 100644 index 0000000..391f476 --- /dev/null +++ b/src/components/JoSearch/JoSearchWrapper.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import GeneralLoading from "../General/GeneralLoading"; +import JoSearch from "./JoSearch"; +import { SearchJoResultRequest } from "@/app/api/jo/actions"; + +interface SubComponents { + Loading: typeof GeneralLoading; +} + +const JoSearchWrapper: React.FC & SubComponents = async () => { + const defaultInputs: SearchJoResultRequest = { + code: "", + name: "", + } + + return +} + +JoSearchWrapper.Loading = GeneralLoading; + +export default JoSearchWrapper; \ No newline at end of file diff --git a/src/components/JoSearch/index.ts b/src/components/JoSearch/index.ts new file mode 100644 index 0000000..9547f6a --- /dev/null +++ b/src/components/JoSearch/index.ts @@ -0,0 +1 @@ +export { default } from "./JoSearchWrapper" \ No newline at end of file diff --git a/src/components/LoginPage/LoginForm.tsx b/src/components/LoginPage/LoginForm.tsx index 4ff916f..c65dc01 100644 --- a/src/components/LoginPage/LoginForm.tsx +++ b/src/components/LoginPage/LoginForm.tsx @@ -24,6 +24,7 @@ type LoginFields = { type SessionWithAbilities = | ({ abilities: string[]; + accessToken?: string; } & Session) | null; @@ -73,9 +74,9 @@ const LoginForm: React.FC = () => { // set auth to local storage const session = (await getSession()) as SessionWithAbilities; // @ts-ignore - window.localStorage.setItem("accessToken", session?.accessToken); - setAccessToken(session?.accessToken); - SetupAxiosInterceptors(session?.accessToken); + window.localStorage.setItem("accessToken", session?.accessToken ?? ""); + setAccessToken(session?.accessToken ?? null); + SetupAxiosInterceptors(session?.accessToken ?? null); // console.log(session) window.localStorage.setItem( "abilities", diff --git a/src/components/MailSetting/MailSetting.tsx b/src/components/MailSetting/MailSetting.tsx index 5b62292..5798657 100644 --- a/src/components/MailSetting/MailSetting.tsx +++ b/src/components/MailSetting/MailSetting.tsx @@ -31,8 +31,8 @@ import { MailTemplate } from "@/app/api/mail"; import TemplateDetails from "./TemplateDetails"; import QrCodeScanner from "../QrCodeScanner/QrCodeScanner"; import { - QcCodeScannerContext, - useQcCodeScanner, + QrCodeScannerContext, + useQrCodeScannerContext, } from "../QrCodeScannerProvider/QrCodeScannerProvider"; export interface Props { diff --git a/src/components/MailSetting/TimesheetMailDetails.tsx b/src/components/MailSetting/TimesheetMailDetails.tsx index d2c79b0..2450157 100644 --- a/src/components/MailSetting/TimesheetMailDetails.tsx +++ b/src/components/MailSetting/TimesheetMailDetails.tsx @@ -34,7 +34,7 @@ const TimesheetMailDetails: React.FC = ({ isActive }) => { {t("Timesheet Template")} - + {/* = ({ isActive }) => { })} error={Boolean(errors.template?.subject)} /> - + */} = ({ isActive }) => { // error={Boolean(errors.template?.template)} /> - + {/* = ({ isActive }) => { validate: (value) => value?.includes("${date}"), }} /> - + */} diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx index f6b123a..ce4e59a 100644 --- a/src/components/NavigationContent/NavigationContent.tsx +++ b/src/components/NavigationContent/NavigationContent.tsx @@ -52,7 +52,7 @@ const NavigationContent: React.FC = () => { { icon: , label: "Pick Order", - path: "/pickorder", + path: "/pickOrder", }, // { // icon: , @@ -179,7 +179,7 @@ const NavigationContent: React.FC = () => { { icon: , label: "Detail Scheduling", - path: "/scheduling/detail", + path: "/scheduling/detailed", }, { icon: , @@ -188,6 +188,18 @@ const NavigationContent: React.FC = () => { }, ], }, + { + icon: , + label: "Job Order", + path: "", + children: [ + { + icon: , + label: "Job Order", + path: "/jo", + }, + ], + }, { icon: , label: "Settings", @@ -263,11 +275,11 @@ const NavigationContent: React.FC = () => { label: "QC Check Template", path: "/settings/user", }, - { - icon: , - label: "Mail", - path: "/settings/mail", - }, + // { + // icon: , + // label: "Mail", + // path: "/settings/mail", + // }, { icon: , label: "Import Testing", diff --git a/src/components/PickOrderDetail/ApprovalForm.tsx b/src/components/PickOrderDetail/ApprovalForm.tsx index 19fb7ba..afe74e1 100644 --- a/src/components/PickOrderDetail/ApprovalForm.tsx +++ b/src/components/PickOrderDetail/ApprovalForm.tsx @@ -5,14 +5,14 @@ import { PickOrderQcInput, updateStockOutLine, UpdateStockOutLine, -} from "@/app/api/pickorder/actions"; +} from "@/app/api/pickOrder/actions"; import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; import QcContent from "./QcContent"; import { Box, Button, Modal, ModalProps, Stack } from "@mui/material"; import { useCallback } from "react"; import { useTranslation } from "react-i18next"; import { Check } from "@mui/icons-material"; -import { StockOutLine } from "@/app/api/pickorder"; +import { StockOutLine } from "@/app/api/pickOrder"; import dayjs from "dayjs"; import { INPUT_DATE_FORMAT, OUTPUT_TIME_FORMAT } from "@/app/utils/formatUtil"; import ApprovalContent from "./ApprovalContent"; @@ -63,7 +63,7 @@ const ApprovalForm: React.FC = ({ [onClose], ); - const onSubmit = useCallback>( + const onSubmit = useCallback>( async (data, event) => { console.log(data); // checking later diff --git a/src/components/PickOrderDetail/PickOrderDetail.tsx b/src/components/PickOrderDetail/PickOrderDetail.tsx index c520a1e..9934e5f 100644 --- a/src/components/PickOrderDetail/PickOrderDetail.tsx +++ b/src/components/PickOrderDetail/PickOrderDetail.tsx @@ -1,12 +1,7 @@ "use client"; import { - Box, Button, - ButtonProps, - Card, - CardContent, - CardHeader, CircularProgress, Grid, Stack, @@ -28,14 +23,14 @@ import { GridEditInputCell, GridRowParams, } from "@mui/x-data-grid"; -import { PlayArrow } from "@mui/icons-material"; import DoneIcon from "@mui/icons-material/Done"; import { GridRowSelectionModel } from "@mui/x-data-grid"; -import { useQcCodeScanner } from "../QrCodeScannerProvider/QrCodeScannerProvider"; +import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider"; import { completeConsoPickOrder, CreateStockOutLine, createStockOutLine, + fetchConsoStatus, fetchPickOrderLineClient, fetchStockOutLineClient, PickOrderApprovalInput, @@ -63,6 +58,7 @@ import AutoFixNormalIcon from "@mui/icons-material/AutoFixNormal"; import ApprovalForm from "./ApprovalForm"; import InfoIcon from "@mui/icons-material/Info"; import VerifiedIcon from "@mui/icons-material/Verified"; +import { isNullOrUndefined } from "html5-qrcode/esm/core"; interface Props { qc: QcItemWithChecks[]; @@ -171,9 +167,16 @@ const PickOrderDetail: React.FC = ({ consoCode, qc }) => { headerName: "location", flex: 1, renderCell: (params) => { - if (!params.row.warehouse) return <>; - const warehouseList = JSON.parse(params.row.warehouse) as string[]; - return FitAllCell(warehouseList); + // console.log(params.row.warehouse) + return params.row.warehouse; + if (isNullOrUndefined(params.row.warehouse)) { + return <>; + } else { + const warehouseList = JSON.parse( + `{${params.row.warehouse}}`, + ) as string[]; + return FitAllCell(warehouseList); + } }, }, { @@ -181,7 +184,8 @@ const PickOrderDetail: React.FC = ({ consoCode, qc }) => { headerName: "suggestedLotNo", flex: 1.2, renderCell: (params) => { - if (!params.row.suggestedLotNo) return <>; + return params.row.suggestedLotNo; + if (isNullOrUndefined(params.row.suggestedLotNo)) return <>; const suggestedLotNoList = JSON.parse( params.row.suggestedLotNo, ) as string[]; @@ -200,7 +204,14 @@ const PickOrderDetail: React.FC = ({ consoCode, qc }) => { [], ); - const [isCompletedOrder, setIsCompletedOrder] = useState(false); + const [isDisableComplete, setIsDisableComplete] = useState(true); + const [status, setStatus] = useState(""); + + const getConsoStatus = useCallback(async () => { + const status = await fetchConsoStatus(consoCode); + console.log(status); + setStatus(status.status); + }, [fetchConsoStatus]); const fetchPickOrderLine = useCallback( async (params: Record) => { @@ -215,9 +226,10 @@ const PickOrderDetail: React.FC = ({ consoCode, qc }) => { if (res) { console.log(res); console.log(res.records.every((line) => line.status == "completed")); - setIsCompletedOrder(() => - res.records.every((line) => line.status == "completed"), - ); + setIsDisableComplete(res.records[0].poStatus === "completed"); + // setIsDisableComplete(() => + // res.records.every((line) => line.status !== "completed"), + // ); setPickOrderLine(res.records); setPolTotalCount(res.total); @@ -233,21 +245,6 @@ const PickOrderDetail: React.FC = ({ consoCode, qc }) => { [fetchPickOrderLineClient, consoCode], ); - const buttonData = useMemo( - () => ({ - buttonName: "complete", - title: t("Do you want to complete?"), - confirmButtonText: t("Complete"), - successTitle: t("Complete Success"), - errorTitle: t("Complete Fail"), - buttonText: t("Complete Pick Order"), - buttonIcon: , - buttonColor: "info", - disabled: true, - }), - [], - ); - const [stockOutLine, setStockOutLine] = useState([]); const getRowId = useCallback>( @@ -505,6 +502,7 @@ const PickOrderDetail: React.FC = ({ consoCode, qc }) => { key="edit" />, } label="Delete" sx={{ @@ -593,7 +591,9 @@ const PickOrderDetail: React.FC = ({ consoCode, qc }) => { useEffect(() => { if (!qcOpen || !approvalOpen) { triggerRefetch(); + getConsoStatus(); fetchPickOrderLine(polCriteriaArgs); + // getConsoStatus() } if (selectedRow.length > 0) fetchStockOutLine(solCriteriaArgs, selectedRow); }, [ @@ -603,6 +603,7 @@ const PickOrderDetail: React.FC = ({ consoCode, qc }) => { selectedRow, triggerRefetch, polCriteriaArgs, + getConsoStatus, ]); const getLotDetail = useCallback( @@ -630,7 +631,7 @@ const PickOrderDetail: React.FC = ({ consoCode, qc }) => { setOpenScanner((prev) => !prev); }, []); - const scanner = useQcCodeScanner(); + const scanner = useQrCodeScannerContext(); useEffect(() => { if (isOpenScanner && !scanner.isScanning) { scanner.startScan(); @@ -672,6 +673,8 @@ const PickOrderDetail: React.FC = ({ consoCode, qc }) => { changeRow, addRow, getLotDetail, + scanner, + setIsUploading, ]); const mannuallyAddRow = useCallback(() => { @@ -679,7 +682,7 @@ const PickOrderDetail: React.FC = ({ consoCode, qc }) => { addRow(qrcode); // scanner.resetScan(); }); - }, [addRow, homemade_Qrcode]); + }, [addRow, getLotDetail, homemade_Qrcode.stockInLineId]); const validation = useCallback( ( @@ -739,32 +742,35 @@ const PickOrderDetail: React.FC = ({ consoCode, qc }) => { const handleCompleteOrder = useCallback(async () => { const res = await completeConsoPickOrder(consoCode); - if (res) { + if (res.message === "completed") { + console.log(res); // completed triggerRefetch(); + // setIsCompletedOrder(false) } else { // not completed triggerRefetch(); } }, [consoCode, triggerRefetch]); + return ( <> - {consoCode} + {consoCode} - {status} = ({ consoCode, qc }) => { justifyContent="end" alignItems="end" > - - */} + diff --git a/src/components/PickOrderDetail/QcContent.tsx b/src/components/PickOrderDetail/QcContent.tsx index f45bb46..79df933 100644 --- a/src/components/PickOrderDetail/QcContent.tsx +++ b/src/components/PickOrderDetail/QcContent.tsx @@ -36,7 +36,7 @@ import { NEXT_PUBLIC_API_URL } from "@/config/api"; import axiosInstance from "@/app/(main)/axios/axiosInstance"; import TwoLineCell from "../PoDetail/TwoLineCell"; import QcSelect from "../PoDetail/QcSelect"; -import { PickOrderQcInput } from "@/app/api/pickorder/actions"; +import { PickOrderQcInput } from "@/app/api/pickOrder/actions"; interface Props { qcDefaultValues: PickOrderQcInput; diff --git a/src/components/PickOrderDetail/QcForm.tsx b/src/components/PickOrderDetail/QcForm.tsx index 9a74dc1..0d3950c 100644 --- a/src/components/PickOrderDetail/QcForm.tsx +++ b/src/components/PickOrderDetail/QcForm.tsx @@ -4,14 +4,14 @@ import { PickOrderQcInput, updateStockOutLine, UpdateStockOutLine, -} from "@/app/api/pickorder/actions"; +} from "@/app/api/pickOrder/actions"; import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; import QcContent from "./QcContent"; import { Box, Button, Modal, ModalProps, Stack } from "@mui/material"; import { useCallback } from "react"; import { useTranslation } from "react-i18next"; import { Check } from "@mui/icons-material"; -import { StockOutLine } from "@/app/api/pickorder"; +import { StockOutLine } from "@/app/api/pickOrder"; import dayjs from "dayjs"; import { INPUT_DATE_FORMAT, OUTPUT_TIME_FORMAT } from "@/app/utils/formatUtil"; @@ -60,7 +60,7 @@ const QcForm: React.FC = ({ [onClose], ); - const onSubmit = useCallback>( + const onSubmit = useCallback>( async (data, event) => { console.log(data); console.log(qcDefaultValues); @@ -86,7 +86,7 @@ const QcForm: React.FC = ({ console.log("bug la"); } }, - [updateStockOutLine], + [closeHandler, qcDefaultValues], ); return ( <> diff --git a/src/components/PickOrderSearch/ConsolidatePickOrderItemSum.tsx b/src/components/PickOrderSearch/ConsolidatePickOrderItemSum.tsx index bfb2ff2..6f0c5ab 100644 --- a/src/components/PickOrderSearch/ConsolidatePickOrderItemSum.tsx +++ b/src/components/PickOrderSearch/ConsolidatePickOrderItemSum.tsx @@ -12,7 +12,7 @@ import { } from "react"; import { GridColDef } from "@mui/x-data-grid"; import { CircularProgress, Grid, Typography } from "@mui/material"; -import { ByItemsSummary } from "@/app/api/pickorder"; +import { ByItemsSummary } from "@/app/api/pickOrder"; import { useTranslation } from "react-i18next"; dayjs.extend(arraySupport); diff --git a/src/components/PickOrderSearch/ConsolidatePickOrderSum.tsx b/src/components/PickOrderSearch/ConsolidatePickOrderSum.tsx index b025055..74771f7 100644 --- a/src/components/PickOrderSearch/ConsolidatePickOrderSum.tsx +++ b/src/components/PickOrderSearch/ConsolidatePickOrderSum.tsx @@ -12,7 +12,7 @@ import { } from "react"; import { GridColDef, GridInputRowSelectionModel } from "@mui/x-data-grid"; import { Box, CircularProgress, Grid, Typography } from "@mui/material"; -import { PickOrderResult } from "@/app/api/pickorder"; +import { PickOrderResult } from "@/app/api/pickOrder"; import { useTranslation } from "react-i18next"; dayjs.extend(arraySupport); diff --git a/src/components/PickOrderSearch/ConsolidatedPickOrders.tsx b/src/components/PickOrderSearch/ConsolidatedPickOrders.tsx index 2b0057e..b5efbd3 100644 --- a/src/components/PickOrderSearch/ConsolidatedPickOrders.tsx +++ b/src/components/PickOrderSearch/ConsolidatedPickOrders.tsx @@ -29,7 +29,7 @@ import { ConsoPickOrderResult, PickOrderLine, PickOrderResult, -} from "@/app/api/pickorder"; +} from "@/app/api/pickOrder"; import { useRouter, useSearchParams } from "next/navigation"; import ConsolidatePickOrderItemSum from "./ConsolidatePickOrderItemSum"; import ConsolidatePickOrderSum from "./ConsolidatePickOrderSum"; @@ -39,7 +39,7 @@ import { fetchConsoPickOrderClient, releasePickOrder, ReleasePickOrderInputs, -} from "@/app/api/pickorder/actions"; +} from "@/app/api/pickOrder/actions"; import { EditNote } from "@mui/icons-material"; import { fetchNameList, NameList } from "@/app/api/user/actions"; import { useField } from "@mui/x-date-pickers/internals"; @@ -111,7 +111,7 @@ const ConsolidatedPickOrders: React.FC = ({ filterArgs }) => { console.log(pickOrder); const status = pickOrder.status; if (pickOrderStatusMap[status] >= 3) { - router.push(`/pickorder/detail?consoCode=${pickOrder.consoCode}`); + router.push(`/pickOrder/detail?consoCode=${pickOrder.consoCode}`); } else { openDetailModal(pickOrder.consoCode); } @@ -135,7 +135,7 @@ const ConsolidatedPickOrders: React.FC = ({ filterArgs }) => { label: t("status"), }, ], - [], + [onDetailClick, t], ); const [pagingController, setPagingController] = useState( defaultPagingController, @@ -215,18 +215,18 @@ const ConsolidatedPickOrders: React.FC = ({ filterArgs }) => { console.log(newValue); formProps.setValue("assignTo", newValue.id); }, - [], + [formProps], ); - const onSubmit = useCallback>( + const onSubmit = useCallback>( async (data, event) => { console.log(data); try { const res = await releasePickOrder(data); console.log(res); - console.log(res.status); - if (res.status === 200) { - router.push(`/pickorder/detail?consoCode=${data.consoCode}`); + if (res.consoCode.length > 0) { + console.log(res); + router.push(`/pickOrder/detail?consoCode=${res.consoCode}`); } else { console.log(res); } @@ -234,7 +234,7 @@ const ConsolidatedPickOrders: React.FC = ({ filterArgs }) => { console.log(error); } }, - [releasePickOrder], + [router], ); const onSubmitError = useCallback>( (errors) => {}, @@ -250,7 +250,7 @@ const ConsolidatedPickOrders: React.FC = ({ filterArgs }) => { fetchConso(consoCode); formProps.setValue("consoCode", consoCode); } - }, [consoCode]); + }, [consoCode, fetchConso, formProps]); return ( <> diff --git a/src/components/PickOrderSearch/CreateForm.tsx b/src/components/PickOrderSearch/CreateForm.tsx new file mode 100644 index 0000000..da0de03 --- /dev/null +++ b/src/components/PickOrderSearch/CreateForm.tsx @@ -0,0 +1,318 @@ +"use client"; + +import { PurchaseQcResult, PurchaseQCInput } from "@/app/api/po/actions"; +import { + Autocomplete, + Box, + Card, + CardContent, + FormControl, + Grid, + Stack, + TextField, + Tooltip, + Typography, +} from "@mui/material"; +import { Controller, useFormContext } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import StyledDataGrid from "../StyledDataGrid"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { + GridColDef, + GridRowIdGetter, + GridRowModel, + useGridApiContext, + GridRenderCellParams, + GridRenderEditCellParams, + useGridApiRef, +} from "@mui/x-data-grid"; +import InputDataGrid from "../InputDataGrid"; +import { TableRow } from "../InputDataGrid/InputDataGrid"; +import { GridEditInputCell } from "@mui/x-data-grid"; +import { StockInLine } from "@/app/api/po"; +import { INPUT_DATE_FORMAT, stockInLineStatusMap } from "@/app/utils/formatUtil"; +import { fetchQcItemCheck, fetchQcResult } from "@/app/api/qc/actions"; +import { QcItemWithChecks } from "@/app/api/qc"; +import axios from "@/app/(main)/axios/axiosInstance"; +import { NEXT_PUBLIC_API_URL } from "@/config/api"; +import axiosInstance from "@/app/(main)/axios/axiosInstance"; +import { SavePickOrderLineRequest, SavePickOrderRequest } from "@/app/api/pickOrder/actions"; +import TwoLineCell from "../PoDetail/TwoLineCell"; +import ItemSelect from "./ItemSelect"; +import { ItemCombo } from "@/app/api/settings/item/actions"; +import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; +import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; +import dayjs from "dayjs"; + +interface Props { + items: ItemCombo[]; +// disabled: boolean; +} +type EntryError = + | { + [field in keyof SavePickOrderLineRequest]?: string; + } + | undefined; + +type PolRow = TableRow, EntryError>; +// fetchQcItemCheck +const CreateForm: React.FC = ({ items }) => { + const { + t, + i18n: { language }, + } = useTranslation("purchaseOrder"); + const apiRef = useGridApiRef(); + const { + formState: { errors, defaultValues, touchedFields }, + watch, + control, + setValue, + } = useFormContext(); + console.log(defaultValues); + const targetDate = watch("targetDate"); + +//// validate form +// const accQty = watch("acceptedQty"); +// const validateForm = useCallback(() => { +// console.log(accQty); +// if (accQty > itemDetail.acceptedQty) { +// setError("acceptedQty", { +// message: `${t("acceptedQty must not greater than")} ${ +// itemDetail.acceptedQty +// }`, +// type: "required", +// }); +// } +// if (accQty < 1) { +// setError("acceptedQty", { +// message: t("minimal value is 1"), +// type: "required", +// }); +// } +// if (isNaN(accQty)) { +// setError("acceptedQty", { +// message: t("value must be a number"), +// type: "required", +// }); +// } +// }, [accQty]); + +// useEffect(() => { +// clearErrors(); +// validateForm(); +// }, [clearErrors, validateForm]); + + const columns = useMemo( + () => [ + { + field: "itemId", + headerName: t("Item"), + flex: 1, + editable: true, + valueFormatter(params) { + const row = params.id ? params.api.getRow(params.id) : null; + if (!row) { + return null; + } + const Item = items.find((q) => q.id === row.itemId); + return Item ? Item.label : t("Please select item"); + }, + renderCell(params: GridRenderCellParams) { + console.log(params.value); + return {params.formattedValue}; + }, + renderEditCell(params: GridRenderEditCellParams) { + const errorMessage = + params.row._error?.[params.field as keyof SavePickOrderLineRequest]; + console.log(errorMessage); + const content = ( + // <> + { + console.log(uom) + await params.api.setEditCellValue({ + id: params.id, + field: "itemId", + value: itemId, + }); + await params.api.setEditCellValue({ + id: params.id, + field: "uom", + value: uom + }) + await params.api.setEditCellValue({ + id: params.id, + field: "uomId", + value: uomId + }) + }} + /> + ); + return errorMessage ? ( + + {content} + + ) : ( + content + ); + }, + }, + { + field: "qty", + headerName: t("qty"), + flex: 1, + type: "number", + editable: true, + renderEditCell(params: GridRenderEditCellParams) { + const errorMessage = + params.row._error?.[params.field as keyof SavePickOrderLineRequest]; + const content = ; + return errorMessage ? ( + + {content} + + ) : ( + content + ); + }, + }, + { + field: "uom", + headerName: t("uom"), + flex: 1, + editable: true, + // renderEditCell(params: GridRenderEditCellParams) { + // console.log(params.row) + // const errorMessage = + // params.row._error?.[params.field as keyof SavePickOrderLineRequest]; + // const content = ; + // return errorMessage ? ( + // + // {content} + // + // ) : ( + // content + // ); + // } + } + ], + [items, t], + ); + /// validate datagrid + const validation = useCallback( + (newRow: GridRowModel): EntryError => { + const error: EntryError = {}; + const { itemId, qty } = newRow; + if (!itemId || itemId <= 0) { + error["itemId"] = t("select qc"); + } + if (!qty || qty <= 0) { + error["qty"] = t("enter a qty"); + } + return Object.keys(error).length > 0 ? error : undefined; + }, + [], + ); + + const typeList = [ + { + type: "Consumable" + } + ] + + const onChange = useCallback( + (event: React.SyntheticEvent, newValue: {type: string}) => { + console.log(newValue); + setValue("type", newValue.type); + }, + [setValue], + ); + + return ( + + + + {t("Pick Order Detail")} + + + + + + option.type} + options={typeList} + onChange={onChange} + renderInput={(params) => } + /> + + + + { + return ( + + { + console.log(date); + if (!date) return; + console.log(date.format(INPUT_DATE_FORMAT)); + setValue("targetDate", date.format(INPUT_DATE_FORMAT)); + // field.onChange(date); + }} + inputRef={field.ref} + slotProps={{ + textField: { + // required: true, + error: Boolean(errors.targetDate?.message), + helperText: errors.targetDate?.message, + }, + }} + /> + + ); + }} + /> + + + + + + apiRef={apiRef} + checkboxSelection={false} + _formKey={"pickOrderLine"} + columns={columns} + validateRow={validation} + needAdd={true} + /> + + + + ); +}; +export default CreateForm; diff --git a/src/components/PickOrderSearch/CreatePickOrderModal.tsx b/src/components/PickOrderSearch/CreatePickOrderModal.tsx new file mode 100644 index 0000000..227cfaf --- /dev/null +++ b/src/components/PickOrderSearch/CreatePickOrderModal.tsx @@ -0,0 +1,98 @@ +import { createPickOrder, SavePickOrderRequest } from "@/app/api/pickOrder/actions"; +import { Box, Button, Modal, ModalProps, Stack } from "@mui/material"; +import dayjs from "dayjs"; +import arraySupport from "dayjs/plugin/arraySupport"; +import { useCallback } from "react"; +import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import CreateForm from "./CreateForm"; +import { ItemCombo } from "@/app/api/settings/item/actions"; +import { Check } from "@mui/icons-material"; +dayjs.extend(arraySupport); + +const style = { + position: "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + overflow: "scroll", + bgcolor: "background.paper", + pt: 5, + px: 5, + pb: 10, + display: "block", + width: { xs: "60%", sm: "60%", md: "60%" }, +}; + +interface Props extends Omit { + items: ItemCombo[] +} + +const CreatePickOrderModal: React.FC = ({ + open, + onClose, + items + }) => { + const { t } = useTranslation("pickOrder"); + const formProps = useForm(); + const errors = formProps.formState.errors; + const closeHandler = useCallback>( + (...args) => { + onClose?.(...args); + // reset(); + }, + [onClose] + ); + const onSubmit = useCallback>( + async (data, event) => { + console.log(data) + try { + const res = await createPickOrder(data) + if (res.id) { + closeHandler({}, "backdropClick"); + } + } catch (error) { + console.log(error) + throw error + } + // formProps.reset() + }, + [closeHandler] + ); + return ( + <> + + + + + + + + + + + + + ); +}; +export default CreatePickOrderModal; diff --git a/src/components/PickOrderSearch/ItemSelect.tsx b/src/components/PickOrderSearch/ItemSelect.tsx new file mode 100644 index 0000000..f611e0e --- /dev/null +++ b/src/components/PickOrderSearch/ItemSelect.tsx @@ -0,0 +1,79 @@ + +import { ItemCombo } from "@/app/api/settings/item/actions"; +import { Autocomplete, TextField } from "@mui/material"; +import { useCallback, useMemo } from "react"; +import { useTranslation } from "react-i18next"; + +interface CommonProps { + allItems: ItemCombo[]; + error?: boolean; +} + +interface SingleAutocompleteProps extends CommonProps { + value: number | string | undefined; + onItemSelect: (itemId: number, uom: string, uomId: number) => void | Promise; + // multiple: false; +} + +type Props = SingleAutocompleteProps; + +const ItemSelect: React.FC = ({ + allItems, + value, + error, + onItemSelect +}) => { + const { t } = useTranslation("item"); + const filteredItems = useMemo(() => { + return allItems + }, [allItems]) + + const options = useMemo(() => { + return [ + { + value: -1, // think think sin + label: t("None"), + uom: "", + uomId: -1, + group: "default", + }, + ...filteredItems.map((i) => ({ + value: i.id as number, + label: i.label, + uom: i.uom, + uomId: i.uomId, + group: "existing", + })), + ]; + }, [t, filteredItems]); + + const currentValue = options.find((o) => o.value === value) || options[0]; + + const onChange = useCallback( + ( + event: React.SyntheticEvent, + newValue: { value: number; uom: string; uomId: number; group: string } | { uom: string; uomId: number; value: number }[], + ) => { + const singleNewVal = newValue as { + value: number; + uom: string; + uomId: number; + group: string; + }; + onItemSelect(singleNewVal.value, singleNewVal.uom, singleNewVal.uomId) + } + , [onItemSelect]) + return ( + option.label} + options={options} + renderInput={(params) => } + /> + ); +} +export default ItemSelect \ No newline at end of file diff --git a/src/components/PickOrderSearch/PickOrderSearch.tsx b/src/components/PickOrderSearch/PickOrderSearch.tsx index aa4065d..8dc3bb5 100644 --- a/src/components/PickOrderSearch/PickOrderSearch.tsx +++ b/src/components/PickOrderSearch/PickOrderSearch.tsx @@ -1,33 +1,27 @@ "use client"; -import { PickOrderResult } from "@/app/api/pickorder"; -import { SearchParams } from "@/app/utils/fetchUtil"; -import { useCallback, useMemo, useState } from "react"; +import { PickOrderResult } from "@/app/api/pickOrder"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import SearchBox, { Criterion } from "../SearchBox"; -import SearchResults, { Column } from "../SearchResults"; import { flatten, - groupBy, intersectionWith, isEmpty, - map, sortBy, - sortedUniq, uniqBy, upperCase, upperFirst, } from "lodash"; import { - arrayToDateString, arrayToDayjs, - dateStringToDayjs, } from "@/app/utils/formatUtil"; -import dayjs from "dayjs"; -import { Button, Grid, Stack, Tab, Tabs, TabsProps } from "@mui/material"; +import { Button, Grid, Stack, Tab, Tabs, TabsProps, Typography } from "@mui/material"; import PickOrders from "./PickOrders"; import ConsolidatedPickOrders from "./ConsolidatedPickOrders"; +import CreatePickOrderModal from "./CreatePickOrderModal"; +import { fetchAllItemsInClient, ItemCombo } from "@/app/api/settings/item/actions"; +import { fetchPickOrderClient } from "@/app/api/pickOrder/actions"; -import { getServerI18n } from "@/i18n"; interface Props { pickOrders: PickOrderResult[]; } @@ -41,15 +35,30 @@ type SearchParamNames = keyof SearchQuery; const PickOrderSearch: React.FC = ({ pickOrders }) => { const { t } = useTranslation("pickOrder"); + const [isOpenCreateModal, setIsOpenCreateModal] = useState(false) + const [items, setItems] = useState([]) const [filteredPickOrders, setFilteredPickOrders] = useState(pickOrders); const [filterArgs, setFilterArgs] = useState>({}); const [tabIndex, setTabIndex] = useState(0); + const [totalCount, setTotalCount] = useState(); + const handleTabChange = useCallback>( (_e, newValue) => { setTabIndex(newValue); }, [], ); + const openCreateModal = useCallback(async () => { + console.log("testing") + const res = await fetchAllItemsInClient() + console.log(res) + setItems(res) + setIsOpenCreateModal(true) + }, []) + + const closeCreateModal = useCallback(() => { + setIsOpenCreateModal(false) + }, []) const searchCriteria: Criterion[] = useMemo( () => [ @@ -113,15 +122,67 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { ), }, ], - [t], + [pickOrders, t], ); + const fetchNewPagePickOrder = useCallback( + async ( + pagingController: Record, + filterArgs: Record, + ) => { + const params = { + ...pagingController, + ...filterArgs, + }; + const res = await fetchPickOrderClient(params); + if (res) { + console.log(res); + setFilteredPickOrders(res.records); + setTotalCount(res.total); + } + }, + [], + ); + const onReset = useCallback(() => { setFilteredPickOrders(pickOrders); }, [pickOrders]); + useEffect(() => { + if (!isOpenCreateModal) { + setTabIndex(1) + setTimeout(async () => { + setTabIndex(0) + }, 200) + } + }, [isOpenCreateModal]) + return ( <> + + + + + {t("Pick Order")} + + + + + {isOpenCreateModal && + } + + + { diff --git a/src/components/PickOrderSearch/PickOrderSearchWrapper.tsx b/src/components/PickOrderSearch/PickOrderSearchWrapper.tsx index 5868197..0f6f367 100644 --- a/src/components/PickOrderSearch/PickOrderSearchWrapper.tsx +++ b/src/components/PickOrderSearch/PickOrderSearchWrapper.tsx @@ -1,4 +1,4 @@ -import { fetchPickOrders } from "@/app/api/pickorder"; +import { fetchPickOrders } from "@/app/api/pickOrder"; import GeneralLoading from "../General/GeneralLoading"; import PickOrderSearch from "./PickOrderSearch"; diff --git a/src/components/PickOrderSearch/PickOrders.tsx b/src/components/PickOrderSearch/PickOrders.tsx index 6d798dc..5fb16f9 100644 --- a/src/components/PickOrderSearch/PickOrders.tsx +++ b/src/components/PickOrderSearch/PickOrders.tsx @@ -1,16 +1,18 @@ import { Button, CircularProgress, Grid } from "@mui/material"; import SearchResults, { Column } from "../SearchResults/SearchResults"; -import { PickOrderResult } from "@/app/api/pickorder"; +import { PickOrderResult } from "@/app/api/pickOrder"; import { useTranslation } from "react-i18next"; import { useCallback, useEffect, useMemo, useState } from "react"; import { isEmpty, upperCase, upperFirst } from "lodash"; -import { arrayToDateString } from "@/app/utils/formatUtil"; +import { arrayToDateString, OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; import { consolidatePickOrder, fetchPickOrderClient, -} from "@/app/api/pickorder/actions"; +} from "@/app/api/pickOrder/actions"; import useUploadContext from "../UploadProvider/useUploadContext"; - +import dayjs from "dayjs"; +import arraySupport from "dayjs/plugin/arraySupport"; +dayjs.extend(arraySupport); interface Props { filteredPickOrders: PickOrderResult[]; filterArgs: Record; @@ -30,21 +32,6 @@ const PickOrders: React.FC = ({ filteredPickOrders, filterArgs }) => { }); const [totalCount, setTotalCount] = useState(); - const handleConsolidatedRows = useCallback(async () => { - console.log(selectedRows); - setIsUploading(true); - try { - const res = await consolidatePickOrder(selectedRows as number[]); - if (res) { - console.log(res); - } - } catch { - setIsUploading(false); - } - fetchNewPagePickOrder(pagingController, filterArgs); - setIsUploading(false); - }, [selectedRows, pagingController]); - const fetchNewPagePickOrder = useCallback( async ( pagingController: Record, @@ -66,6 +53,22 @@ const PickOrders: React.FC = ({ filteredPickOrders, filterArgs }) => { [], ); + const handleConsolidatedRows = useCallback(async () => { + console.log(selectedRows); + setIsUploading(true); + try { + const res = await consolidatePickOrder(selectedRows as number[]); + if (res) { + console.log(res); + } + } catch { + setIsUploading(false); + } + fetchNewPagePickOrder(pagingController, filterArgs); + setIsUploading(false); + }, [selectedRows, setIsUploading, fetchNewPagePickOrder, pagingController, filterArgs]); + + useEffect(() => { fetchNewPagePickOrder(pagingController, filterArgs); }, [fetchNewPagePickOrder, pagingController, filterArgs]); @@ -109,7 +112,11 @@ const PickOrders: React.FC = ({ filteredPickOrders, filterArgs }) => { name: "targetDate", label: t("Target Date"), renderCell: (params) => { - return arrayToDateString(params.targetDate); + return ( + dayjs(params.targetDate) + .add(-1, "month") + .format(OUTPUT_DATE_FORMAT) + ); }, }, { diff --git a/src/components/PickOrderSearch/UomSelect.tsx b/src/components/PickOrderSearch/UomSelect.tsx new file mode 100644 index 0000000..1fec4ab --- /dev/null +++ b/src/components/PickOrderSearch/UomSelect.tsx @@ -0,0 +1,73 @@ + +import { ItemCombo } from "@/app/api/settings/item/actions"; +import { Autocomplete, TextField } from "@mui/material"; +import { useCallback, useMemo } from "react"; +import { useTranslation } from "react-i18next"; + +interface CommonProps { + allUom: ItemCombo[]; + error?: boolean; +} + +interface SingleAutocompleteProps extends CommonProps { + value: number | string | undefined; + onUomSelect: (itemId: number) => void | Promise; + // multiple: false; +} + +type Props = SingleAutocompleteProps; + +const UomSelect: React.FC = ({ + allUom, + value, + error, + onUomSelect +}) => { + const { t } = useTranslation("item"); + const filteredUom = useMemo(() => { + return allUom + }, [allUom]) + + const options = useMemo(() => { + return [ + { + value: -1, // think think sin + label: t("None"), + group: "default", + }, + ...filteredUom.map((i) => ({ + value: i.id as number, + label: i.label, + group: "existing", + })), + ]; + }, [t, filteredUom]); + + const currentValue = options.find((o) => o.value === value) || options[0]; + + const onChange = useCallback( + ( + event: React.SyntheticEvent, + newValue: { value: number; group: string } | { value: number }[], + ) => { + const singleNewVal = newValue as { + value: number; + group: string; + }; + onUomSelect(singleNewVal.value) + } + , [onUomSelect]) + return ( + option.label} + options={options} + renderInput={(params) => } + /> + ); +} +export default UomSelect \ No newline at end of file diff --git a/src/components/PoDetail/PoQcStockInModal.tsx b/src/components/PoDetail/PoQcStockInModal.tsx index f55e4a8..25d846b 100644 --- a/src/components/PoDetail/PoQcStockInModal.tsx +++ b/src/components/PoDetail/PoQcStockInModal.tsx @@ -203,7 +203,7 @@ const PoQcStockInModal: React.FC = ({ [itemDetail, formProps], ); - const onSubmit = useCallback>( + const onSubmit = useCallback>( async (data, event) => { setBtnIsLoading(true); setIsUploading(true); diff --git a/src/components/PoDetail/PutawayForm.tsx b/src/components/PoDetail/PutawayForm.tsx index b0d3b94..753727d 100644 --- a/src/components/PoDetail/PutawayForm.tsx +++ b/src/components/PoDetail/PutawayForm.tsx @@ -47,7 +47,7 @@ import ReactQrCodeScanner, { ScannerConfig, } from "../ReactQrCodeScanner/ReactQrCodeScanner"; import { QrCodeInfo } from "@/app/api/qrcode"; -import { useQcCodeScanner } from "../QrCodeScannerProvider/QrCodeScannerProvider"; +import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider"; import dayjs from "dayjs"; import arraySupport from "dayjs/plugin/arraySupport"; dayjs.extend(arraySupport); @@ -219,7 +219,7 @@ const PutawayForm: React.FC = ({ itemDetail, warehouse, disabled }) => { ); // QR Code Scanner - const scanner = useQcCodeScanner(); + const scanner = useQrCodeScannerContext(); useEffect(() => { if (isOpenScanner) { scanner.startScan(); diff --git a/src/components/PoDetail/QrModal.tsx b/src/components/PoDetail/QrModal.tsx index e64b6cd..33bef84 100644 --- a/src/components/PoDetail/QrModal.tsx +++ b/src/components/PoDetail/QrModal.tsx @@ -27,7 +27,7 @@ import { QrCodeInfo } from "@/app/api/qrcode"; import { Check } from "@mui/icons-material"; import { useTranslation } from "react-i18next"; import { useSearchParams } from "next/navigation"; -import { useQcCodeScanner } from "../QrCodeScannerProvider/QrCodeScannerProvider"; +import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider"; interface Props extends Omit { warehouse: WarehouseResult[]; @@ -83,7 +83,7 @@ const QrModal: React.FC = ({ open, onClose, warehouse }) => { ); // QR Code Scanner - const scanner = useQcCodeScanner(); + const scanner = useQrCodeScannerContext(); useEffect(() => { if (open && !scanner.isScanning) { scanner.startScan(); @@ -136,7 +136,7 @@ const QrModal: React.FC = ({ open, onClose, warehouse }) => { if (stockInLineId) fetchStockInLine(stockInLineId); }, [stockInLineId]); - const onSubmit = useCallback>( + const onSubmit = useCallback>( async (data, event) => { const hasErrors = false; console.log("errors"); diff --git a/src/components/PoSearch/PoSearch.tsx b/src/components/PoSearch/PoSearch.tsx index 3742893..0db878d 100644 --- a/src/components/PoSearch/PoSearch.tsx +++ b/src/components/PoSearch/PoSearch.tsx @@ -15,7 +15,7 @@ import { useSession } from "next-auth/react"; import { defaultPagingController } from "../SearchResults/SearchResults"; import { fetchPoListClient, testing } from "@/app/api/po/actions"; import dayjs from "dayjs"; -import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; +import { arrayToDateString, OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; import arraySupport from "dayjs/plugin/arraySupport"; dayjs.extend(arraySupport); @@ -72,7 +72,7 @@ const PoSearch: React.FC = ({ [router], ); - const onDeleteClick = useCallback((po: PoResult) => {}, [router]); + const onDeleteClick = useCallback((po: PoResult) => {}, []); const columns = useMemo[]>( () => [ @@ -92,8 +92,8 @@ const PoSearch: React.FC = ({ renderCell: (params) => { return ( dayjs(params.orderDate) - // .add(-1, "month") - .format(OUTPUT_DATE_FORMAT) + .add(-1, "month") + .format(OUTPUT_DATE_FORMAT) ); }, }, diff --git a/src/components/ProductionProcess/DefectsSection.tsx b/src/components/ProductionProcess/DefectsSection.tsx index 3179c4e..071a0b8 100644 --- a/src/components/ProductionProcess/DefectsSection.tsx +++ b/src/components/ProductionProcess/DefectsSection.tsx @@ -37,7 +37,7 @@ const DefectsSection: React.FC = ({ if (defectToAdd) { // Check for duplicate code (skip if code is empty) const isDuplicate = - defectToAdd.code && defects.some((d) => d.code === defectToAdd.code); + defectToAdd.code && defects.some((d) => d.code === defectToAdd!.code); if (!isDuplicate) { const updatedDefects = [...defects, defectToAdd]; onDefectsChange(updatedDefects); diff --git a/src/components/QrCodeScannerProvider/QrCodeScannerProvider.tsx b/src/components/QrCodeScannerProvider/QrCodeScannerProvider.tsx index 47e8247..9e78bb3 100644 --- a/src/components/QrCodeScannerProvider/QrCodeScannerProvider.tsx +++ b/src/components/QrCodeScannerProvider/QrCodeScannerProvider.tsx @@ -8,7 +8,7 @@ import { useState, } from "react"; -interface QcCodeScanner { +interface QrCodeScanner { values: string[]; isScanning: boolean; startScan: () => void; @@ -20,14 +20,14 @@ interface QrCodeScannerProviderProps { children: ReactNode; } -export const QcCodeScannerContext = createContext( +export const QrCodeScannerContext = createContext( undefined, ); const QrCodeScannerProvider: React.FC = ({ children, }) => { - const [qcCodeScannerValues, setQrCodeScannerValues] = useState([]); + const [qrCodeScannerValues, setQrCodeScannerValues] = useState([]); const [isScanning, setIsScanning] = useState(false); const [keys, setKeys] = useState([]); const [leftCurlyBraceCount, setLeftCurlyBraceCount] = useState(0); @@ -82,7 +82,7 @@ const QrCodeScannerProvider: React.FC = ({ keys.join("").substring(startBrace, endBrace + 1), ]); console.log(keys); - console.log(qcCodeScannerValues); + console.log(qrCodeScannerValues); // reset setKeys(() => []); @@ -92,9 +92,9 @@ const QrCodeScannerProvider: React.FC = ({ }, [keys, leftCurlyBraceCount, rightCurlyBraceCount]); return ( - = ({ }} > {children} - + ); }; -export const useQcCodeScanner = (): QcCodeScanner => { - const context = useContext(QcCodeScannerContext); +export const useQrCodeScannerContext = (): QrCodeScanner => { + const context = useContext(QrCodeScannerContext); if (!context) { throw new Error( - "useQcCodeScanner must be used within a QcCodeScannerProvider", + "useQrCodeScanner must be used within a QrCodeScannerProvider", ); } return context; diff --git a/src/components/RoughSchedule/RoughSchedileSearchView.tsx b/src/components/RoughSchedule/RoughSchedileSearchView.tsx index 43b14c8..478571d 100644 --- a/src/components/RoughSchedule/RoughSchedileSearchView.tsx +++ b/src/components/RoughSchedule/RoughSchedileSearchView.tsx @@ -245,7 +245,7 @@ const RSOverview: React.FC = ({ type, defaultInputs }) => { // setFilteredSchedules(items ?? []); // setFilterObj({}); // setTempSelectedValue({}); - refetchData(inputs, "reset"); + refetchData(defaultInputs, "reset"); }, []); return ( diff --git a/src/components/RoughScheduleDetail/RoughScheduleDetailWrapper.tsx b/src/components/RoughScheduleDetail/RoughScheduleDetailWrapper.tsx index 5dcc50d..fd83c1b 100644 --- a/src/components/RoughScheduleDetail/RoughScheduleDetailWrapper.tsx +++ b/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 & SubComponents = async ({ id, type, }) => { - const prodSchedule = id ? await fetchProdScheduleDetail(id) : undefined; + const prodSchedule = id ? await fetchRoughProdScheduleDetail(id) : undefined; return ( = ({ items }) => { const searchCriteria: Criterion[] = useMemo(() => { const searchCriteria: Criterion[] = [ - { label: t("Finished Goods Name"), paramName: "fgName", type: "text" }, + { label: t("Finished Goods Name"), paramName: "name", type: "text" }, { label: t("Exclude Date"), - paramName: "excludeDate", - type: "multi-select", - options: dayOptions, - selectedValues: filterObj, + paramName: "shelfLife", + type: "select", + options: ["qcChecks"], + // selectedValues: filterObj, handleSelectionChange: handleSelectionChange, }, ]; return searchCriteria; }, [t, items]); - const searchCriteria: Criterion[] = useMemo( - () => { - var searchCriteria: Criterion[] = [ - { label: t("Finished Goods Name"), paramName: "name", type: "text" }, - { - label: t("Exclude Date"), - paramName: "excludeDate", - type: "multi-select", - options: dayOptions, - selectedValues: filterObj, - handleSelectionChange: handleSelectionChange, - }, - ] - return searchCriteria - }, - }, + // const onDetailClick = useCallback( + // (item: ItemsResult) => { + // router.push(`/settings/items/edit?id=${item.id}`); + // }, + // [router] + // ); + + const handleEditClick = useCallback((item: ItemsResult) => {}, [router]); + const onDeleteClick = useCallback((item: ItemsResult) => {}, [router]); + + const columns = useMemo[]>( + () => [ + // { + // name: "id", + // label: t("Details"), + // onClick: ()=>{}, + // buttonIcon: , + // }, + // { + // name: "name", + // label: "Finished Goods Name", + // // type: "input", + // }, + // { + // name: "excludeDate", + // label: t("Exclude Date"), + // type: "multi-select", + // options: dayOptions, + // renderCell: (item: ItemsResult) => { + // console.log("[debug] render item", item); + // const selectedValues = (item.excludeDate as number[]) ?? []; // Assuming excludeDate is an array of numbers + // const selectedLabels = dayOptions + // .filter((option) => selectedValues.includes(option.value)) + // .map((option) => option.label) + // .join(", "); // Join labels into a string + + // return ( + // handleEditClick(item.id as number)}> + // {selectedLabels || "None"}{" "} + // {/* Display "None" if no days are selected */} + // + // ); + // }, // { // name: "action", // label: t(""), @@ -113,100 +139,41 @@ const RSSOverview: React.FC = ({ items }) => { return; // Exit the function if the token is not set } - const columns = useMemo[]>( - () => [ - // { - // name: "id", - // label: t("Details"), - // onClick: ()=>{}, - // buttonIcon: , - // }, - { - field: "name", - label: t("Finished Goods Name"), - type: 'input', - }, - { - field: "excludeDate", - label: t("Exclude Date"), - type: 'multi-select', - options: dayOptions, - renderCell: (item: ItemsResult) => { - console.log("[debug] render item", item) - const selectedValues = item.excludeDate as number[] ?? []; // Assuming excludeDate is an array of numbers - const selectedLabels = dayOptions - .filter(option => selectedValues.includes(option.value)) - .map(option => option.label) - .join(", "); // Join labels into a string - - return ( - handleEditClick(item.id as number)}> - {selectedLabels || "None"} {/* Display "None" if no days are selected */} - - ); - }, - }, - // { - // name: "action", - // label: t(""), - // buttonIcon: , - // onClick: onDeleteClick, - // }, - ], - [filteredItems] - ); - - useEffect(() => { - refetchData(filterObj); - - }, [filterObj, pagingController.pageNum, pagingController.pageSize]); - - const refetchData = async (filterObj: SearchQuery) => { - - const authHeader = axiosInstance.defaults.headers['Authorization']; - if (!authHeader) { - return; // Exit the function if the token is not set - } - - const params ={ - pageNum: pagingController.pageNum, - pageSize: pagingController.pageSize, - ...filterObj, - ...tempSelectedValue, - } - - try { - const response = await axiosInstance.get(`${NEXT_PUBLIC_API_URL}/items/getRecordByPage`, { - params, - paramsSerializer: (params) => { - return Qs.stringify(params, { arrayFormat: 'repeat' }); - }, - }); - setFilteredItems(response.data.records); - setPagingController({ - ...pagingController, - totalCount: response.data.total - }) - return response; // Return the data from the response - } catch (error) { - console.error('Error fetching items:', error); - throw error; // Rethrow the error for further handling - } + const params = { + pageNum: pagingController.pageNum, + pageSize: pagingController.pageSize, + ...filterObj, + ...tempSelectedValue, }; - const onReset = useCallback(() => { - //setFilteredItems(items ?? []); - setFilterObj({}); - setTempSelectedValue({}); - refetchData(filterObj); - }, [items]); + try { + const response = await axiosInstance.get( + `${NEXT_PUBLIC_API_URL}/items/getRecordByPage`, + { + params, + paramsSerializer: (params) => { + return Qs.stringify(params, { arrayFormat: "repeat" }); + }, + }, + ); + setFilteredItems(response.data.records); + setPagingController({ + ...pagingController, + totalCount: response.data.total, + }); + return response; // Return the data from the response + } catch (error) { + console.error("Error fetching items:", error); + throw error; // Rethrow the error for further handling + } + }; const onReset = useCallback(() => { //setFilteredItems(items ?? []); setFilterObj({}); setTempSelectedValue({}); - refetchData(); - }, [items]); + refetchData(filterObj); + }, [items, filterObj]); return ( <> @@ -220,6 +187,9 @@ const RSSOverview: React.FC = ({ items }) => { onReset={onReset} /> + index={1} // bug + isEdit + isEditable items={filteredItems} columns={columns} setPagingController={setPagingController} diff --git a/src/components/ScheduleTable/BomMaterialTable.tsx b/src/components/ScheduleTable/BomMaterialTable.tsx index 1a0260e..27e8089 100644 --- a/src/components/ScheduleTable/BomMaterialTable.tsx +++ b/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; } @@ -113,7 +114,7 @@ function BomMaterialTable({ bomMaterial }: Props) { headerName: t("Available Qty"), flex: 0.5, type: "number", - editable: true, + // editable: true, align: "right", headerAlign: "right", renderCell: (row) => { @@ -125,7 +126,7 @@ function BomMaterialTable({ bomMaterial }: Props) { field: "demandQty", headerName: t("Demand Qty"), flex: 0.5, - editable: true, + // editable: true, align: "right", headerAlign: "right", renderCell: (row) => { @@ -136,7 +137,7 @@ function BomMaterialTable({ bomMaterial }: Props) { field: "status", headerName: t("status"), flex: 0.5, - editable: true, + // editable: true, align: "center", headerAlign: "center", renderCell: (params) => { @@ -219,7 +220,7 @@ function BomMaterialTable({ bomMaterial }: Props) { }, }} disableColumnMenu - editMode="row" + // editMode="row" rows={entries} rowModesModel={rowModesModel} onRowModesModelChange={setRowModesModel} diff --git a/src/components/ScheduleTable/ScheduleTable.tsx b/src/components/ScheduleTable/ScheduleTable.tsx index 5acdac7..7901d1b 100644 --- a/src/components/ScheduleTable/ScheduleTable.tsx +++ b/src/components/ScheduleTable/ScheduleTable.tsx @@ -1,11 +1,12 @@ "use client"; import React, { - CSSProperties, - DetailedHTMLProps, - HTMLAttributes, - useEffect, - useState, + CSSProperties, + DetailedHTMLProps, + HTMLAttributes, + useEffect, + useRef, + useState, } from "react"; import Paper from "@mui/material/Paper"; import Table from "@mui/material/Table"; @@ -30,363 +31,437 @@ 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"; +import { useFormContext } from "react-hook-form"; export interface ResultWithId { - id: string | number; - // id: number; + id: string | number; + // id: number; } interface BaseColumn { - field: keyof T; - label: string; - type: string; - options?: T[]; - renderCell?: (params: T) => React.ReactNode; - style?: Partial & { - [propName: string]: string; - } & CSSProperties; + field: keyof T; + label: string; + type: string; + options?: T[]; + renderCell?: (params: T) => React.ReactNode; + style?: Partial & { + [propName: string]: string; + } & CSSProperties; } interface ColumnWithAction extends BaseColumn { - 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 = - | BaseColumn - | ColumnWithAction; + | BaseColumn + | ColumnWithAction; interface Props { - index?: number; - items: T[]; - columns: Column[]; - 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[]; + 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; + onReleaseClick?: (item: T) => void; + onEditClick?: (rowId: number) => void; + handleEditChange?: (rowId: number, fieldName: keyof T, newValue: number | string) => void; + onSaveClick?: (item: T) => void; + onCancelClick?: (rowId: number) => void; } function ScheduleTable({ - 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, + onReleaseClick = undefined, + onEditClick = undefined, + handleEditChange = undefined, + onSaveClick = undefined, + onCancelClick = undefined, }: Props) { - const [page, setPage] = useState(0); - const [rowsPerPage, setRowsPerPage] = useState(10); - const [editingRowId, setEditingRowId] = useState(null); - const [editedItems, setEditedItems] = useState(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(null); + const [editedItems, setEditedItems] = useState(items); + const { t } = useTranslation("schedule"); + console.log(items) - const handleChangeRowsPerPage = ( - event: React.ChangeEvent, - ) => { - setRowsPerPage(+event.target.value); - setPage(0); - if (setPagingController) { - setPagingController({ - ...pagingController, - pageSize: +event.target.value, - pageNum: 1, - index: index, - }); - } - }; + useEffect(() => { + setEditedItems(items); + }, [items]); - const handleEditClick = (id: number) => { - setEditingRowId(id); - }; + const handleChangePage = (_event: unknown, newPage: number) => { + setPage(newPage); + if (setPagingController && pagingController) { + setPagingController({ + ...pagingController, + pageNum: newPage + 1, + index: index ?? -1, + }); + } + }; - 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 handleChangeRowsPerPage = ( + event: React.ChangeEvent, + ) => { + setRowsPerPage(+event.target.value); + setPage(0); + if (setPagingController && pagingController) { + setPagingController({ + ...pagingController, + pageSize: +event.target.value, + pageNum: 1, + index: index, + }); + } + }; - const handleInputChange = ( - id: number, - field: keyof T, - value: string | number[], - ) => { - setEditedItems((prev) => - prev.map((item) => (item.id === id ? { ...item, [field]: value } : item)), - ); - }; + const handleEditClick = (id: number) => { + setEditingRowId(id); + if (onEditClick) { + onEditClick(id) + } + }; + + const handleSaveClick = (item: T) => { + setEditingRowId(null); + // Call API or any save logic here + if (onSaveClick) { + onSaveClick(item) + } else { + setEditedItems((prev) => + prev.map((row) => (row.id === item.id ? { ...row } : row)), + ); + } + }; + + const handleReleaseClick = (item: T) => { + if (onReleaseClick) { + onReleaseClick(item) + } + } + + const handleInputChange = ( + id: number, + field: keyof T, + // value: string | number[], + value: string | number, + ) => { + if (handleEditChange) { + handleEditChange(id, field, value) + } else { + 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 - }); + const handleCancelClick = (id: number) => { + if (onCancelClick) { + onCancelClick(id) + } - setEditingRowId(null); + setEditingRowId(null) } - }, [isEdit]); - function isRoughType(type: ScheduleType): type is "rough" { - return type === "rough"; - } + 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]); - function isDetailedType(type: ScheduleType): type is "detailed" { - return type === "detailed"; - } + function isRoughType(type: ScheduleType): type is "rough" { + return type === "rough"; + } + + 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 ( - <> - - {isDetailedType(type) && ( - - - - - - )} - {(isEditable || hasCollapse) && ( - - {editingRowId === row.id ? ( - <> - {isDetailedType(type) && isEditable && ( - handleSaveClick(row)} - > - - - )} - {isDetailedType(type) && isEditable && ( - setEditingRowId(null)} - > - - - )} - {hasCollapse && ( - setOpen(!open)} - > - {open ? ( - - ) : ( - - )} - {t("View BoM")} - - )} - - ) : ( - <> - {isDetailedType(type) && isEditable && ( - handleEditClick(row.id as number)} - > - - - )} - {isDetailedType(type) && isEditable && ( - handleDeleteClick(row.id as number)} - > - - - )} - {hasCollapse && ( - setOpen(!open)} - > - {open ? ( - - ) : ( - - )} - {t("View BoM")} - - )} - - )} - - )} - {columns.map((column, idx) => { - const columnName = column.field; - return ( - - {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 ( + <> + + {isDetailedType(type) && ( + + (ele.availableQty ?? 0) >= (ele.demandQty ?? 0)) + || editingRowId === row.id + || (row as unknown as DetailedProdScheduleLineResult).approved} + onClick={() => handleReleaseClick(row)} + > + + + + )} + {(isEditable || hasCollapse) && ( + + {editingRowId === row.id ? ( + <> + {isDetailedType(type) && isEditable && ( + handleSaveClick(row)} + > + + + )} + {isDetailedType(type) && isEditable && ( + handleCancelClick(row.id as number)} + > + + + )} + {hasCollapse && ( + setOpen(!open)} + > + {open ? ( + + ) : ( + + )} + {t("View BoM")} + + )} + + ) : ( + <> + {isDetailedType(type) && isEditable && ( + handleEditClick(row.id as number)} + > + + + )} + {/* {isDetailedType(type) && isEditable && ( + handleDeleteClick(row.id as number)} + > + + + )} */} + {hasCollapse && ( + setOpen(!open)} + > + {open ? ( + + ) : ( + + )} + {t("View BoM")} + + )} + + )} + + )} + {columns.map((column, idx) => { + const columnName = column.field; return ( - - handleInputChange( - row.id as number, - columnName, - e.target.value, - ) - } - /> + + {editingRowId === row.id ? ( + (() => { + switch (column.type) { + case "input": + return ( + + handleInputChange( + row.id as number, + columnName, + e.target.value, + ) + } + /> + ); + case "input-number": + return ( + { + handleInputChange( + row.id as number, + columnName, + e.target.value, + ) + }} + // onChange={(e) => + // handleInputChange( + // row.id as number, + // columnName, + // e.target.value, + // ) + // } + /> + ); + // case 'multi-select': + // //TODO: May need update if use + // return ( + // handleInputChange(row.id as number, columnName, selectedValues)} + // /> + // ); + case "read-only": + return column.renderCell ? ( +
{column.renderCell(row)}
+ ) : {row[columnName] as string}; + default: + return null; // Handle any default case if needed + } + })() + ) : column.renderCell ? ( +
{column.renderCell(row)}
+ ) : ( +
+ + isEdit && handleEditClick(row.id as number) + } + > + {row[columnName] as string} + +
+ )} +
); - // case 'multi-select': - // //TODO: May need update if use - // return ( - // handleInputChange(row.id as number, columnName, selectedValues)} - // /> - // ); - case "read-only": - return {row[columnName] as string}; - default: - return null; // Handle any default case if needed - } - })() - ) : column.renderCell ? ( -
{column.renderCell(row)}
- ) : ( -
- - isEdit && handleEditClick(row.id as number) - } - > - {row[columnName] as string} - -
- )} -
- ); - })} -
- - {hasCollapse && ( - - - - - - - - - - + })} + + + {hasCollapse && ( + + +
+ + + + + + + +
+
+
+ )} +
+ + ); + } + + const table = ( + <> + + + + + {isDetailedType(type) && {t("Release")}} + {(isEditable || hasCollapse) && ( + {t("Actions")} + )}{" "} + {/* Action Column Header */} + {columns.map((column, idx) => ( + + {column.label} + + ))} + + + + {/* {(isAutoPaging ? editedItems.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) : editedItems).map((item) => ( */} + {editedItems + ?.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) + ?.map((item) => )} +
- - - )} - - +
+ + ); - } - - const table = ( - <> - - - - - {isDetailedType(type) && {t("Release")}} - {(isEditable || hasCollapse) && ( - {t("Actions")} - )}{" "} - {/* Action Column Header */} - {columns.map((column, idx) => ( - - {column.label} - - ))} - - - - {/* {(isAutoPaging ? editedItems.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) : editedItems).map((item) => ( */} - {editedItems - ?.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) - ?.map((item) => )} - -
-
- - - ); - return noWrapper ? table : {table}; + return noWrapper ? table : {table}; } export default ScheduleTable; diff --git a/src/components/SearchBox/MultiSelect.tsx b/src/components/SearchBox/MultiSelect.tsx index 87bb957..a42e9cd 100644 --- a/src/components/SearchBox/MultiSelect.tsx +++ b/src/components/SearchBox/MultiSelect.tsx @@ -18,6 +18,7 @@ interface MultiSelectProps { options: Option[]; selectedValues: number[]; onChange: (values: number[]) => void; + isReset?: boolean; } const MultiSelect: React.FC = ({ @@ -49,13 +50,13 @@ const MultiSelect: React.FC = ({