diff --git a/src/app/(main)/settings/qcCategory/edit/page.tsx b/src/app/(main)/settings/qcCategory/edit/page.tsx new file mode 100644 index 0000000..cadcd7a --- /dev/null +++ b/src/app/(main)/settings/qcCategory/edit/page.tsx @@ -0,0 +1,53 @@ +import { Metadata } from "next"; +import { getServerI18n, I18nProvider } from "@/i18n"; +import Typography from "@mui/material/Typography"; +import { fetchQcCategoryDetails, preloadQcCategory } from "@/app/api/settings/qcCategory"; +import QcCategorySave from "@/components/QcCategorySave"; +import { isArray } from "lodash"; +import { notFound } from "next/navigation"; +import { ServerFetchError } from "@/app/utils/fetchUtil"; + +export const metadata: Metadata = { + title: "Qc Category", +}; + +interface Props { + searchParams: { [key: string]: string | string[] | undefined }; +} + +const qcCategory: React.FC = async ({ searchParams }) => { + const { t } = await getServerI18n("qcCategory"); + + const id = searchParams["id"]; + + if (!id || isArray(id)) { + notFound(); + } + + try { + console.log("first"); + await fetchQcCategoryDetails(id); + console.log("firsts"); + } catch (e) { + if ( + e instanceof ServerFetchError && + (e.response?.status === 404 || e.response?.status === 400) + ) { + console.log(e); + notFound(); + } + } + + return ( + <> + + {t("Edit Qc Category")} + + + + + + ); +}; + +export default qcCategory; diff --git a/src/app/api/settings/qcCategory/actions.ts b/src/app/api/settings/qcCategory/actions.ts index e564628..0e3462d 100644 --- a/src/app/api/settings/qcCategory/actions.ts +++ b/src/app/api/settings/qcCategory/actions.ts @@ -2,16 +2,55 @@ import { serverFetchJson } from "@/app/utils/fetchUtil"; import { BASE_API_URL } from "@/config/api"; +import { revalidatePath, revalidateTag } from "next/cache"; +import { QcCategoryResult } from "."; export interface CreateQcCategoryInputs { code: string; name: string; } -export const saveQcCategory = async (data: CreateQcCategoryInputs) => { - return serverFetchJson(`${BASE_API_URL}/qcCategories/save`, { +export const saveQcCategory = async (data: SaveQcCategoryInputs) => { + return serverFetchJson(`${BASE_API_URL}/qcCategories/save`, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }); }; + +export interface SaveQcItemsMapping { + id: number; + order: number; +} + +export interface SaveQcCategoryInputs { + id?: number; + code: string; + name: string; + description?: string; + qcItems: SaveQcItemsMapping[]; +} + +export interface SaveQcCategoryResponse { + id?: number; + code: string; + name: string; + description?: string; + errors: Record; + // qcItems: SaveQcItemsMapping[]; +} + +export const deleteQcCategory = async (id: number) => { + const response = await serverFetchJson( + `${BASE_API_URL}/qcCategories/${id}`, + { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + }, + ); + + revalidateTag("qcCategories"); + revalidatePath("/(main)/settings/qcCategory"); + + return response; +}; diff --git a/src/app/api/settings/qcCategory/index.ts b/src/app/api/settings/qcCategory/index.ts index b96bb9c..9e38697 100644 --- a/src/app/api/settings/qcCategory/index.ts +++ b/src/app/api/settings/qcCategory/index.ts @@ -2,11 +2,13 @@ import { serverFetchJson } from "@/app/utils/fetchUtil"; import { BASE_API_URL } from "@/config/api"; import { cache } from "react"; import "server-only"; +import { SaveQcCategoryInputs } from "./actions"; export interface QcCategoryResult { id: number; code: string; name: string; + description?: string; } export interface QcCategoryCombo { @@ -25,6 +27,16 @@ export const fetchQcCategories = cache(async () => { }); }); +export const fetchQcCategoryDetails = cache(async (qcCategoryId: string) => { + return serverFetchJson( + `${BASE_API_URL}/qcCategories/details/${qcCategoryId}`, + { + next: { tags: [`qcCategoryDetails_${qcCategoryId}`] }, + }, + ); +}); + + export const fetchQcCategoryCombo = cache(async () => { return serverFetchJson(`${BASE_API_URL}/qcCategories/combo`, { next: { tags: ["qcCategoryCombo"] }, diff --git a/src/components/QcCategorySave/QcCategoryDetails.tsx b/src/components/QcCategorySave/QcCategoryDetails.tsx new file mode 100644 index 0000000..896c088 --- /dev/null +++ b/src/components/QcCategorySave/QcCategoryDetails.tsx @@ -0,0 +1,63 @@ +import { SaveQcCategoryInputs } from "@/app/api/settings/qcCategory/actions"; +import { + Box, + Card, + CardContent, + Grid, + Stack, + TextField, + Typography, +} from "@mui/material"; +import { useFormContext } from "react-hook-form"; +import { useTranslation } from "react-i18next"; + +const QcCategoryDetails = () => { + const { t } = useTranslation("qcCategory"); + const { register } = useFormContext(); + + return ( + + + + {/* + {t("Qc Item Details")} + */} + + + + + + + + + + + + + + + ); +}; + +export default QcCategoryDetails; diff --git a/src/components/QcCategorySave/QcCategorySave.tsx b/src/components/QcCategorySave/QcCategorySave.tsx new file mode 100644 index 0000000..a2c67c1 --- /dev/null +++ b/src/components/QcCategorySave/QcCategorySave.tsx @@ -0,0 +1,121 @@ +"use client"; + +import { + deleteQcCategory, + saveQcCategory, + SaveQcCategoryInputs, +} from "@/app/api/settings/qcCategory/actions"; +import { Button, Stack } from "@mui/material"; +import { useCallback } from "react"; +import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; +import { + deleteDialog, + errorDialogWithContent, + submitDialog, + successDialog, +} from "../Swal/CustomAlerts"; +import { useTranslation } from "react-i18next"; +import { useRouter } from "next/navigation"; +import QcCategoryDetails from "./QcCategoryDetails"; +import { Check, Close, Delete } from "@mui/icons-material"; + +interface Props { + defaultInputs?: SaveQcCategoryInputs; +} + +const QcCategorySave: React.FC = ({ defaultInputs }) => { + const { t } = useTranslation("qcCategory"); + const router = useRouter(); + + const formProps = useForm({ + defaultValues: { + ...defaultInputs, + }, + }); + + const handleSubmit = useCallback(async (data: SaveQcCategoryInputs) => { + const response = await saveQcCategory(data); + + const errors = response.errors; + if (errors) { + let errorContents = ""; + for (const [key, value] of Object.entries(errors)) { + formProps.setError(key as keyof SaveQcCategoryInputs, { + type: "custom", + message: value, + }); + errorContents = errorContents + t(value) + "
"; + } + + errorDialogWithContent(t("Submit Error"), errorContents, t); + } else { + await successDialog(t("Submit Success"), t, () => + router.push("/settings/qcCategory"), + ); + } + }, []); + + const onSubmit = useCallback>( + async (data) => { + await submitDialog(() => handleSubmit(data), t); + }, + [], + ); + + const handleCancel = () => { + router.replace("/settings/qcCategory"); + }; + + const handleDelete = () => { + deleteDialog(async () => { + await deleteQcCategory(formProps.getValues("id")!); + + await successDialog(t("Delete Success"), t, () => + router.replace("/settings/qcCategory"), + ); + }, t); + }; + + return ( + <> + + + + + {defaultInputs?.id && ( + + )} + + + + + + + ); +}; + +export default QcCategorySave; diff --git a/src/components/QcCategorySave/QcCategorySaveLoading.tsx b/src/components/QcCategorySave/QcCategorySaveLoading.tsx new file mode 100644 index 0000000..01c9782 --- /dev/null +++ b/src/components/QcCategorySave/QcCategorySaveLoading.tsx @@ -0,0 +1,40 @@ +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import Skeleton from "@mui/material/Skeleton"; +import Stack from "@mui/material/Stack"; +import React from "react"; + +// Can make this nicer +export const QcItemSaveLoading: React.FC = () => { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default QcItemSaveLoading; diff --git a/src/components/QcCategorySave/QcCategorySaveWrapper.tsx b/src/components/QcCategorySave/QcCategorySaveWrapper.tsx new file mode 100644 index 0000000..8b76876 --- /dev/null +++ b/src/components/QcCategorySave/QcCategorySaveWrapper.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import QcCategorySaveLoading from "./QcCategorySaveLoading"; +import QcCategorySave from "./QcCategorySave"; +import { fetchQcCategoryDetails } from "@/app/api/settings/qcCategory"; + +interface SubComponents { + Loading: typeof QcCategorySaveLoading; +} + +type SaveQcCategoryProps = { + id?: string; +}; + +type Props = SaveQcCategoryProps; + +const QcCategorySaveWrapper: React.FC & SubComponents = async (props) => { + const qcCategory = props.id ? await fetchQcCategoryDetails(props.id) : undefined; + + return ; +}; + +QcCategorySaveWrapper.Loading = QcCategorySaveLoading; + +export default QcCategorySaveWrapper; diff --git a/src/components/QcCategorySave/index.ts b/src/components/QcCategorySave/index.ts new file mode 100644 index 0000000..55fba21 --- /dev/null +++ b/src/components/QcCategorySave/index.ts @@ -0,0 +1 @@ +export { default } from "./QcCategorySaveWrapper"; diff --git a/src/components/QcCategorySearch/QcCategorySearch.tsx b/src/components/QcCategorySearch/QcCategorySearch.tsx index f6d91b5..f00fcd3 100644 --- a/src/components/QcCategorySearch/QcCategorySearch.tsx +++ b/src/components/QcCategorySearch/QcCategorySearch.tsx @@ -6,6 +6,10 @@ import { useTranslation } from "react-i18next"; import SearchResults, { Column } from "../SearchResults"; import EditNote from "@mui/icons-material/EditNote"; import { QcCategoryResult } from "@/app/api/settings/qcCategory"; +import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; +import { deleteQcCategory } from "@/app/api/settings/qcCategory/actions"; +import Delete from "@mui/icons-material/Delete"; +import { usePathname, useRouter } from "next/navigation"; interface Props { qcCategories: QcCategoryResult[]; @@ -15,7 +19,9 @@ type SearchQuery = Partial>; type SearchParamNames = keyof SearchQuery; const QcCategorySearch: React.FC = ({ qcCategories }) => { - const { t } = useTranslation("qcCategories"); + const { t } = useTranslation("qcCategory"); + const router = useRouter(); + const pathname = usePathname(); // If qcCategory searching is done on the server-side, then no need for this. const [filteredQcCategories, setFilteredQcCategories] = @@ -34,8 +40,21 @@ const QcCategorySearch: React.FC = ({ qcCategories }) => { }, [qcCategories]); const onQcCategoryClick = useCallback((qcCategory: QcCategoryResult) => { - console.log(qcCategory); + router.push(`${pathname}/edit?id=${qcCategory.id}`); + }, [router]); + + const handleDelete = useCallback((qcCategory: QcCategoryResult) => { + deleteDialog(async () => { + qcCategories = await deleteQcCategory(qcCategory.id); + setFilteredQcCategories(qcCategories); + + await successDialog(t("Delete Success"), t); + }, t); }, []); + + const columnWidthSx = (width = "10%") => { + return { width: width, whiteSpace: "nowrap" }; + }; const columns = useMemo[]>( () => [ @@ -44,9 +63,19 @@ const QcCategorySearch: React.FC = ({ qcCategories }) => { label: t("Details"), onClick: onQcCategoryClick, buttonIcon: , + sx: columnWidthSx("5%"), + }, + { name: "code", label: t("Code"), sx: columnWidthSx("15%"), }, + { name: "name", label: t("Name"), sx: columnWidthSx("30%"), }, + // { name: "description", label: t("Description"), sx: columnWidthSx("50%"), }, + { + name: "id", + label: t("Delete"), + onClick: handleDelete, + buttonIcon: , + buttonColor: "error", + sx: columnWidthSx("5%"), }, - { name: "code", label: t("Code") }, - { name: "name", label: t("Name") }, ], [t, onQcCategoryClick], ); diff --git a/src/components/QcItemSave/QcItemDetails.tsx b/src/components/QcItemSave/QcItemDetails.tsx index fd51e06..7f0f298 100644 --- a/src/components/QcItemSave/QcItemDetails.tsx +++ b/src/components/QcItemSave/QcItemDetails.tsx @@ -12,16 +12,16 @@ import { useFormContext } from "react-hook-form"; import { useTranslation } from "react-i18next"; const QcItemDetails = () => { - const { t } = useTranslation(); + const { t } = useTranslation("qcItem"); const { register } = useFormContext(); return ( - + {/* {t("Qc Item Details")} - + */} = ({ defaultInputs }) => { ); const handleCancel = () => { - router.replace("/qcItem"); + router.replace("/settings/qcItem"); }; const handleDelete = () => { diff --git a/src/components/QcItemSearch/QcItemSearch.tsx b/src/components/QcItemSearch/QcItemSearch.tsx index 3256b6c..e357bd6 100644 --- a/src/components/QcItemSearch/QcItemSearch.tsx +++ b/src/components/QcItemSearch/QcItemSearch.tsx @@ -61,16 +61,22 @@ const QcItemSearch: React.FC = ({ qcItems }) => { }, t); }, []); + const columnWidthSx = (width = "10%") => { + return { width: width, whiteSpace: "nowrap" }; + }; + const columns = useMemo[]>( () => [ { name: "id", label: t("Details"), + sx: columnWidthSx("150px"), onClick: onQcItemClick, buttonIcon: , }, - { name: "code", label: t("Code") }, - { name: "name", label: t("Name") }, + { name: "code", label: t("Code"), sx: columnWidthSx() }, + { name: "name", label: t("Name"), sx: columnWidthSx() }, + { name: "description", label: t("Description") }, { name: "id", label: t("Delete"), diff --git a/src/i18n/zh/common.json b/src/i18n/zh/common.json index 16d5b4b..f38e4c2 100644 --- a/src/i18n/zh/common.json +++ b/src/i18n/zh/common.json @@ -50,8 +50,10 @@ "Delivery Order":"送貨訂單", "Detail Scheduling":"詳細排程", "Customer":"客戶", - "QC Check Item":"QC檢查項目", - "QC Category":"QC分類", + "qcItem":"品檢項目", + "QC Check Item":"QC品檢項目", + "QC Category":"QC品檢模板", + "qcCategory":"品檢模板", "QC Check Template":"QC檢查模板", "Mail":"郵件", "Import Testing":"匯入測試", diff --git a/src/i18n/zh/qcCategory.json b/src/i18n/zh/qcCategory.json index fbd0481..b608a76 100644 --- a/src/i18n/zh/qcCategory.json +++ b/src/i18n/zh/qcCategory.json @@ -1,9 +1,20 @@ { - "Qc Category": "QC 類別", + "Qc Category": "品檢模板", "Qc Category List": "QC 類別列表", "Qc Category Name": "QC 類別名稱", "Qc Category Description": "QC 類別描述", "Qc Category Status": "QC 類別狀態", "Qc Category Created At": "QC 類別創建時間", - "Qc Category Updated At": "QC 類別更新時間" + "Qc Category Updated At": "QC 類別更新時間", + "Name": "名稱", + "Code": "編號", + "Description": "描述", + "Details": "詳情", + "Delete": "刪除", + "Qc Item": "QC 項目", + "Create Qc Category": "新增品檢模板", + "Edit Qc Item": "編輯品檢項目", + "Qc Item Details": "品檢項目詳情", + "Cancel": "取消", + "Submit": "儲存" } \ No newline at end of file diff --git a/src/i18n/zh/qcItem.json b/src/i18n/zh/qcItem.json index 4ffc45c..129c9b8 100644 --- a/src/i18n/zh/qcItem.json +++ b/src/i18n/zh/qcItem.json @@ -1,8 +1,13 @@ { - "Name": "名稱", - "Code": "代碼", - "Details": "詳細資料", - "Delete": "刪除", - "Qc Item": "QC 項目", - "Create Qc Item": "新增 QC 項目" -} \ No newline at end of file + "Name": "名稱", + "Code": "編號", + "Description": "描述", + "Details": "詳情", + "Delete": "刪除", + "Qc Item": "QC 項目", + "Create Qc Item": "新增 QC 項目", + "Edit Qc Item": "編輯品檢項目", + "Qc Item Details": "品檢項目詳情", + "Cancel": "取消", + "Submit": "儲存" +}