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/api/jo/actions.ts b/src/app/api/jo/actions.ts
index d6bdb44..e9ea350 100644
--- a/src/app/api/jo/actions.ts
+++ b/src/app/api/jo/actions.ts
@@ -1,4 +1,6 @@
"use server";
+
+import { cache } from 'react';
import { Pageable, serverFetchJson } from "@/app/utils/fetchUtil";
import { Machine, Operator } from ".";
import { BASE_API_URL } from "@/config/api";
@@ -15,7 +17,7 @@ export interface SearchJoResultResponse {
total: number;
}
-export interface SearchJoResult{
+export interface SearchJoResult {
id: number;
code: string;
name: string;
@@ -24,6 +26,15 @@ export interface SearchJoResult{
status: string;
}
+export interface ReleaseJoRequest {
+ id: number;
+}
+
+export interface ReleaseJoResponse {
+ id: number;
+ entity: { status: string }
+}
+
export interface IsOperatorExistResponse {
id: number | null;
name: string;
@@ -71,9 +82,9 @@ export const isCorrectMachineUsed = async (machineCode: string) => {
};
-export const fetchJos = async (data?: SearchJoResultRequest) => {
+export const fetchJos = cache(async (data?: SearchJoResultRequest) => {
const queryStr = convertObjToURLSearchParams(data)
- const response = await serverFetchJson(
+ const response = serverFetchJson(
`${BASE_API_URL}/jo/getRecordByPage?${queryStr}`,
{
method: "GET",
@@ -85,4 +96,13 @@ export const fetchJos = async (data?: SearchJoResultRequest) => {
)
return response
-}
\ No newline at end of file
+})
+
+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/components/DetailedScheduleDetail/DetailedScheduleDetailWrapper.tsx b/src/components/DetailedScheduleDetail/DetailedScheduleDetailWrapper.tsx
index 862fcdf..349d90b 100644
--- a/src/components/DetailedScheduleDetail/DetailedScheduleDetailWrapper.tsx
+++ b/src/components/DetailedScheduleDetail/DetailedScheduleDetailWrapper.tsx
@@ -1,5 +1,3 @@
-import { CreateItemInputs } from "@/app/api/settings/item/actions";
-import { fetchItem } from "@/app/api/settings/item";
import GeneralLoading from "@/components/General/GeneralLoading";
import DetailedScheduleDetailView from "@/components/DetailedScheduleDetail/DetailedScheduleDetailView";
import { ScheduleType, fetchDetailedProdScheduleDetail } from "@/app/api/scheduling";
diff --git a/src/components/DetailedScheduleDetail/ViewByFGDetails.tsx b/src/components/DetailedScheduleDetail/ViewByFGDetails.tsx
index 9e860dd..3902e39 100644
--- a/src/components/DetailedScheduleDetail/ViewByFGDetails.tsx
+++ b/src/components/DetailedScheduleDetail/ViewByFGDetails.tsx
@@ -163,7 +163,7 @@ const ViewByFGDetails: React.FC = ({ apiRef, isEdit, type, onReleaseClick
},
renderCell: (row) => {
if (typeof row.demandQty == "number") {
- return decimalFormatter.format(row.demandQty ?? 0);
+ return integerFormatter.format(row.demandQty ?? 0);
}
return row.demandQty;
},
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" && (
+
+ }
+ onClick={handleRelease}
+ >
+ {t("Release")}
+
+
+ )}
+
+
+
+ }
+ onClick={handleBack}
+ >
+ {t("Back")}
+
+
+
+
+ >
+}
+
+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
index 727f16a..7fb407f 100644
--- a/src/components/JoSearch/JoSearch.tsx
+++ b/src/components/JoSearch/JoSearch.tsx
@@ -5,9 +5,10 @@ import { useTranslation } from "react-i18next";
import { Criterion } from "../SearchBox";
import SearchResults, { Column, defaultPagingController } from "../SearchResults/SearchResults";
import { EditNote } from "@mui/icons-material";
-import { decimalFormatter } from "@/app/utils/formatUtil";
+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
@@ -19,6 +20,7 @@ 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(
@@ -53,7 +55,7 @@ const JoSearch: React.FC = ({ defaultInputs }) => {
align: "right",
headerAlign: "right",
renderCell: (row) => {
- return decimalFormatter.format(row.reqQty)
+ return integerFormatter.format(row.reqQty)
}
},
{
@@ -108,7 +110,7 @@ const JoSearch: React.FC = ({ defaultInputs }) => {
}, [pagingController]);
const onDetailClick = useCallback((record: SearchJoResult) => {
-
+ router.push(`/jo/edit?id=${record.id}`)
}, [])
const onSearch = useCallback((query: Record) => {