diff --git a/src/app/(main)/settings/qcCategory/page.tsx b/src/app/(main)/settings/qcCategory/page.tsx
new file mode 100644
index 0000000..3f5e054
--- /dev/null
+++ b/src/app/(main)/settings/qcCategory/page.tsx
@@ -0,0 +1,44 @@
+import { Metadata } from "next";
+import { getServerI18n, I18nProvider } from "@/i18n";
+import Typography from "@mui/material/Typography";
+import { Button, Link, Stack } from "@mui/material";
+import { Add } from "@mui/icons-material";
+import { Suspense } from "react";
+import QcCategorySearch from "@/components/QcCategorySearch";
+import { preloadQcCategory } from "@/app/api/settings/qcCategory";
+
+export const metadata: Metadata = {
+ title: "Qc Category",
+};
+
+const qcCategory: React.FC = async () => {
+ const { t } = await getServerI18n("qcCategory")
+
+ preloadQcCategory()
+
+ return <>
+
+
+ {t("Qc Category")}
+
+ }
+ LinkComponent={Link}
+ href="qcCategory/create"
+ >
+ {t("Create Qc Category")}
+
+
+ }>
+
+
+ >;
+};
+
+export default qcCategory;
\ No newline at end of file
diff --git a/src/app/(main)/settings/qcItem/create/not-found.tsx b/src/app/(main)/settings/qcItem/create/not-found.tsx
new file mode 100644
index 0000000..c23e0db
--- /dev/null
+++ b/src/app/(main)/settings/qcItem/create/not-found.tsx
@@ -0,0 +1,17 @@
+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("qcItem", "common");
+
+ return (
+
+ {t("Not Found")}
+ {t("The create qc item page was not found!")}
+
+ {t("Return to all qc items")}
+
+
+ );
+}
diff --git a/src/app/(main)/settings/qcItem/create/page.tsx b/src/app/(main)/settings/qcItem/create/page.tsx
new file mode 100644
index 0000000..f404e1d
--- /dev/null
+++ b/src/app/(main)/settings/qcItem/create/page.tsx
@@ -0,0 +1,24 @@
+import { Metadata } from "next";
+import { getServerI18n, I18nProvider } from "@/i18n";
+import Typography from "@mui/material/Typography";
+import { preloadQcItem } from "@/app/api/settings/qcItem";
+import QcItemSave from "@/components/QcItemSave";
+
+export const metadata: Metadata = {
+ title: "Qc Item",
+};
+
+const qcItem: React.FC = async () => {
+ const { t } = await getServerI18n("qcItem")
+
+ return <>
+
+ {t("Create Qc Item")}
+
+
+
+
+ >;
+};
+
+export default qcItem;
\ No newline at end of file
diff --git a/src/app/(main)/settings/qcItem/edit/not-found.tsx b/src/app/(main)/settings/qcItem/edit/not-found.tsx
new file mode 100644
index 0000000..84e23e1
--- /dev/null
+++ b/src/app/(main)/settings/qcItem/edit/not-found.tsx
@@ -0,0 +1,17 @@
+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("qcItem", "common");
+
+ return (
+
+ {t("Not Found")}
+ {t("The edit qc item page was not found!")}
+
+ {t("Return to all qc items")}
+
+
+ );
+}
diff --git a/src/app/(main)/settings/qcItem/edit/page.tsx b/src/app/(main)/settings/qcItem/edit/page.tsx
new file mode 100644
index 0000000..edc7aa8
--- /dev/null
+++ b/src/app/(main)/settings/qcItem/edit/page.tsx
@@ -0,0 +1,49 @@
+import { Metadata } from "next";
+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 { isArray } from "lodash";
+import { notFound } from "next/navigation";
+import { ServerFetchError } from "@/app/utils/fetchUtil";
+
+export const metadata: Metadata = {
+ title: "Qc Item",
+};
+
+interface Props {
+ searchParams: { [key: string]: string | string[] | undefined };
+}
+
+const qcItem: React.FC = async ({ searchParams }) => {
+ const { t } = await getServerI18n("qcItem")
+
+ const id = searchParams["id"]
+
+ if (!id || isArray(id)) {
+ notFound()
+ }
+
+ try {
+ console.log("first")
+ await fetchQcItemDetails(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 Item")}
+
+
+
+
+ >;
+};
+
+export default qcItem;
\ No newline at end of file
diff --git a/src/app/api/settings/qcCategory/actions.ts b/src/app/api/settings/qcCategory/actions.ts
new file mode 100644
index 0000000..bb3277e
--- /dev/null
+++ b/src/app/api/settings/qcCategory/actions.ts
@@ -0,0 +1,17 @@
+"use server"
+
+import { serverFetchJson } from "@/app/utils/fetchUtil";
+import { BASE_API_URL } from "@/config/api";
+
+export interface CreateQcCategoryInputs {
+ code: string;
+ name: string;
+}
+
+export const saveQcCategory = async (data: CreateQcCategoryInputs) => {
+ return serverFetchJson(`${BASE_API_URL}/qcCategories/save`, {
+ method: "POST",
+ body: JSON.stringify(data),
+ headers: { "Content-Type": "application/json" },
+ })
+}
\ No newline at end of file
diff --git a/src/app/api/settings/qcCategory/index.ts b/src/app/api/settings/qcCategory/index.ts
new file mode 100644
index 0000000..0e466e8
--- /dev/null
+++ b/src/app/api/settings/qcCategory/index.ts
@@ -0,0 +1,20 @@
+import { serverFetchJson } from "@/app/utils/fetchUtil";
+import { BASE_API_URL } from "@/config/api";
+import { cache } from "react";
+import "server-only";
+
+export interface QcCategoryResult {
+ id: number;
+ code: string;
+ name: string;
+}
+
+export const preloadQcCategory = () => {
+ fetchQcCategories();
+};
+
+export const fetchQcCategories = cache(async () => {
+ return serverFetchJson(`${BASE_API_URL}/qcCategories`, {
+ next: { tags: ["qcCategories"]}
+ });
+});
\ No newline at end of file
diff --git a/src/components/QcCategorySearch/QcCategorySearch.tsx b/src/components/QcCategorySearch/QcCategorySearch.tsx
new file mode 100644
index 0000000..2aa5931
--- /dev/null
+++ b/src/components/QcCategorySearch/QcCategorySearch.tsx
@@ -0,0 +1,76 @@
+"use client";
+
+import React, { useCallback, useMemo, useState } from "react";
+import SearchBox, { Criterion } from "../SearchBox";
+import { useTranslation } from "react-i18next";
+import SearchResults, { Column } from "../SearchResults";
+import EditNote from "@mui/icons-material/EditNote";
+import { QcCategoryResult } from "@/app/api/settings/qcCategory";
+
+interface Props {
+ qcCategories: QcCategoryResult[];
+}
+
+type SearchQuery = Partial>;
+type SearchParamNames = keyof SearchQuery;
+
+const QcCategorySearch: React.FC = ({ qcCategories }) => {
+ const { t } = useTranslation("qcCategories");
+
+ // If qcCategory searching is done on the server-side, then no need for this.
+ const [filteredQcCategories, setFilteredQcCategories] = useState(qcCategories);
+
+ const searchCriteria: Criterion[] = useMemo(
+ () => [
+ { label: t("Code"), paramName: "code", type: "text" },
+ { label: t("Name"), paramName: "name", type: "text" },
+ ],
+ [t],
+ );
+
+ const onReset = useCallback(() => {
+ setFilteredQcCategories(qcCategories);
+ }, [qcCategories]);
+
+ const onQcCategoryClick = useCallback((qcCategory: QcCategoryResult) => {
+ console.log(qcCategory);
+ }, []);
+
+ const columns = useMemo[]>(
+ () => [
+ {
+ name: "id",
+ label: t("Details"),
+ onClick: onQcCategoryClick,
+ buttonIcon: ,
+ },
+ { name: "code", label: t("Code") },
+ { name: "name", label: t("Name") },
+ ],
+ [t, onQcCategoryClick],
+ );
+
+ return (
+ <>
+ {
+ setFilteredQcCategories(
+ qcCategories.filter(
+ (qc) =>
+ qc.code.toLowerCase().includes(query.code.toLowerCase()) &&
+ qc.name.toLowerCase().includes(query.name.toLowerCase())
+ ),
+ );
+ }}
+ onReset={onReset}
+ />
+
+ items={filteredQcCategories}
+ columns={columns}
+ />
+ >
+ );
+};
+
+export default QcCategorySearch;
diff --git a/src/components/QcCategorySearch/QcCategorySearchLoading.tsx b/src/components/QcCategorySearch/QcCategorySearchLoading.tsx
new file mode 100644
index 0000000..944e059
--- /dev/null
+++ b/src/components/QcCategorySearch/QcCategorySearchLoading.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 QcCategorySearchLoading: React.FC = () => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default QcCategorySearchLoading;
diff --git a/src/components/QcCategorySearch/QcCategorySearchWrapper.tsx b/src/components/QcCategorySearch/QcCategorySearchWrapper.tsx
new file mode 100644
index 0000000..b7790b9
--- /dev/null
+++ b/src/components/QcCategorySearch/QcCategorySearchWrapper.tsx
@@ -0,0 +1,23 @@
+import React from "react"
+import QcCategorySearchLoading from "./QcCategorySearchLoading"
+import QcCategorySearch from "./QcCategorySearch";
+import { fetchQcCategories } from "@/app/api/settings/qcCategory";
+
+interface SubComponents {
+ Loading: typeof QcCategorySearchLoading;
+}
+
+const QcCategorySearchWrapper: React.FC & SubComponents = async () => {
+
+ const [
+ qcCategories
+ ] = await Promise.all([
+ fetchQcCategories()
+ ]);
+
+ return ;
+};
+
+QcCategorySearchWrapper.Loading = QcCategorySearchLoading;
+
+export default QcCategorySearchWrapper;
\ No newline at end of file
diff --git a/src/components/QcCategorySearch/index.ts b/src/components/QcCategorySearch/index.ts
new file mode 100644
index 0000000..e5ca27f
--- /dev/null
+++ b/src/components/QcCategorySearch/index.ts
@@ -0,0 +1 @@
+export { default } from "./QcCategorySearchWrapper";
\ No newline at end of file