diff --git a/src/app/(main)/customer/create/page.tsx b/src/app/(main)/customer/create/page.tsx
new file mode 100644
index 0000000..81e41c0
--- /dev/null
+++ b/src/app/(main)/customer/create/page.tsx
@@ -0,0 +1,27 @@
+import { fetchSubsidiaries, preloadAllCustomers } from "@/app/api/customer";
+import CreateCustomer from "@/components/CreateCustomer";
+// import { preloadAllTasks } from "@/app/api/tasks";
+import CreateTaskTemplate from "@/components/CreateTaskTemplate";
+import { I18nProvider, getServerI18n } from "@/i18n";
+import Typography from "@mui/material/Typography";
+import { Metadata } from "next";
+
+export const metadata: Metadata = {
+ title: "Create Customer",
+};
+
+const Projects: React.FC = async () => {
+ const { t } = await getServerI18n("customer");
+ // fetchSubsidiaries();
+
+ return (
+ <>
+ {t("Create Customer")}
+
+
+
+ >
+ );
+};
+
+export default Projects;
diff --git a/src/app/(main)/customer/page.tsx b/src/app/(main)/customer/page.tsx
new file mode 100644
index 0000000..3a451fe
--- /dev/null
+++ b/src/app/(main)/customer/page.tsx
@@ -0,0 +1,50 @@
+import { preloadAllCustomers } from "@/app/api/customer";
+import CustomerSearch from "@/components/CustomerSearch";
+import { getServerI18n } from "@/i18n";
+import Add from "@mui/icons-material/Add";
+import Button from "@mui/material/Button";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import { Metadata } from "next";
+import Link from "next/link";
+import { Suspense } from "react";
+import { I18nProvider } from "@/i18n";
+
+export const metadata: Metadata = {
+ title: "Customer",
+};
+
+const Customer: React.FC = async () => {
+ const { t } = await getServerI18n("customer");
+ preloadAllCustomers();
+
+ return (
+ <>
+
+
+ {t("Customer")}
+
+ }
+ LinkComponent={Link}
+ href="/customer/create"
+ >
+ {t("Create Customer")}
+
+
+
+ }>
+
+
+
+ >
+ );
+};
+
+export default Customer;
diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx
index b93ed10..cfaa1a9 100644
--- a/src/app/(main)/layout.tsx
+++ b/src/app/(main)/layout.tsx
@@ -31,10 +31,10 @@ export default async function MainLayout({
padding: { xs: "1rem", sm: "1.5rem", lg: "3rem" },
}}
>
-
-
- {children}
-
+
+
+ {children}
+
>
);
diff --git a/src/app/api/customer/actions.ts b/src/app/api/customer/actions.ts
new file mode 100644
index 0000000..ef9af50
--- /dev/null
+++ b/src/app/api/customer/actions.ts
@@ -0,0 +1,38 @@
+"use server";
+
+import { serverFetchJson } from "@/app/utils/fetchUtil";
+import { BASE_API_URL } from "@/config/api";
+import { Customer } from ".";
+import { revalidateTag } from "next/cache";
+
+export interface CustomerFormInputs {
+
+ // Customer details
+ name: string;
+ code: string;
+ address: string | null;
+ district: string | null;
+ email: string | null;
+ phone: string | null;
+ contactName: string | null;
+ brNo: string | null;
+
+ // Subsidiary
+ addSubsidiaryIds: number[];
+ deleteSubsidiaryIds: number[];
+}
+
+export const saveCustomer = async (data: CustomerFormInputs) => {
+ const saveCustomer = await serverFetchJson(
+ `${BASE_API_URL}/customer/save`,
+ {
+ method: "POST",
+ body: JSON.stringify(data),
+ headers: { "Content-Type": "application/json" },
+ },
+ );
+
+ revalidateTag("customers");
+
+ return saveCustomer;
+};
diff --git a/src/app/api/customer/index.ts b/src/app/api/customer/index.ts
new file mode 100644
index 0000000..4901d98
--- /dev/null
+++ b/src/app/api/customer/index.ts
@@ -0,0 +1,40 @@
+import { serverFetchJson } from "@/app/utils/fetchUtil";
+import { BASE_API_URL } from "@/config/api";
+import { cache } from "react";
+import "server-only";
+
+export interface Customer {
+ id: number;
+ code: string;
+ name: string;
+}
+
+export interface Subsidiary {
+ id: number;
+ code: string;
+ name: string;
+ description: string | null;
+ brNo: string | null;
+ contactName: string | null;
+ phone: string | null;
+ address: string | null;
+ district: string | null;
+ email: string | null;
+}
+
+export const preloadAllCustomers = () => {
+ fetchAllCustomers();
+};
+
+export const fetchAllCustomers = cache(async () => {
+ return serverFetchJson(`${BASE_API_URL}/customer`);
+});
+
+export const fetchSubsidiaries = cache(async () => {
+ return serverFetchJson(
+ `${BASE_API_URL}/subsidiary`,
+ {
+ next: { tags: ["subsidiary"] },
+ },
+ );
+});
diff --git a/src/app/utils/commonUtil.ts b/src/app/utils/commonUtil.ts
new file mode 100644
index 0000000..5af164e
--- /dev/null
+++ b/src/app/utils/commonUtil.ts
@@ -0,0 +1,7 @@
+export function getDeletedRecordWithRefList(referenceList: Array, updatedList: Array) {
+ return referenceList.filter(x => !updatedList.includes(x));
+}
+
+export function getNewRecordWithRefList(referenceList: Array, updatedList: Array) {
+ return updatedList.filter(x => !referenceList.includes(x));
+}
diff --git a/src/components/Breadcrumb/Breadcrumb.tsx b/src/components/Breadcrumb/Breadcrumb.tsx
index 8b4d131..fbb68e2 100644
--- a/src/components/Breadcrumb/Breadcrumb.tsx
+++ b/src/components/Breadcrumb/Breadcrumb.tsx
@@ -5,6 +5,7 @@ import Typography from "@mui/material/Typography";
import Link from "next/link";
import MUILink from "@mui/material/Link";
import { usePathname } from "next/navigation";
+import { useTranslation } from "react-i18next";
const pathToLabelMap: { [path: string]: string } = {
"": "Overview",
@@ -12,11 +13,15 @@ const pathToLabelMap: { [path: string]: string } = {
"/projects/create": "Create Project",
"/tasks": "Task Template",
"/tasks/create": "Create Task Template",
+ "/customer": "Customer",
+ "/customer/create": "Create Customer",
};
const Breadcrumb = () => {
const pathname = usePathname();
const segments = pathname.split("/");
+
+ // const { t } = useTranslation("customer");
return (
@@ -28,6 +33,7 @@ const Breadcrumb = () => {
return (
{label}
+ {/* {t(label)} */}
);
} else {
diff --git a/src/components/CreateCustomer/CreateCustomer.tsx b/src/components/CreateCustomer/CreateCustomer.tsx
new file mode 100644
index 0000000..2735bac
--- /dev/null
+++ b/src/components/CreateCustomer/CreateCustomer.tsx
@@ -0,0 +1,171 @@
+"use client";
+
+import Check from "@mui/icons-material/Check";
+import Close from "@mui/icons-material/Close";
+import Button from "@mui/material/Button";
+import Stack from "@mui/material/Stack";
+import Tab from "@mui/material/Tab";
+import Tabs, { TabsProps } from "@mui/material/Tabs";
+import { useRouter } from "next/navigation";
+import React, { useCallback, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { Task, TaskTemplate } from "@/app/api/tasks";
+import {
+ FieldErrors,
+ FormProvider,
+ SubmitErrorHandler,
+ SubmitHandler,
+ useForm,
+} from "react-hook-form";
+import { CreateProjectInputs, saveProject } from "@/app/api/projects/actions";
+import { Error } from "@mui/icons-material";
+import { ProjectCategory } from "@/app/api/projects";
+import { Staff } from "@/app/api/staff";
+import { Typography } from "@mui/material";
+import { CustomerFormInputs, saveCustomer } from "@/app/api/customer/actions";
+import CustomerDetails from "./CustomerDetails";
+import SubsidiaryAllocation from "./SubsidiaryAllocation";
+import { Subsidiary } from "@/app/api/customer";
+import { getDeletedRecordWithRefList } from "@/app/utils/commonUtil";
+
+export interface Props {
+ subsidiaries: Subsidiary[],
+}
+
+const hasErrorsInTab = (
+ tabIndex: number,
+ errors: FieldErrors,
+) => {
+ switch (tabIndex) {
+ case 0:
+ return errors.name;
+ default:
+ false;
+ }
+};
+
+const CreateCustomer: React.FC = ({
+ subsidiaries,
+}) => {
+ const [serverError, setServerError] = useState("");
+ const [tabIndex, setTabIndex] = useState(0);
+ const { t } = useTranslation();
+ const router = useRouter();
+ const formProps = useForm({
+ defaultValues: {
+ code: "",
+ name: "",
+ },
+ });
+
+ const handleCancel = () => {
+ router.back();
+ };
+
+ const handleTabChange = useCallback>(
+ (_e, newValue) => {
+ setTabIndex(newValue);
+ },
+ [],
+ );
+
+ const onSubmit = useCallback>(
+ async (data) => {
+ try {
+ console.log(data);
+
+ let haveError = false
+ if (data.name.length === 0) {
+ haveError = true
+ formProps.setError("name", {message: "Name is empty", type: "required"})
+ }
+
+ if (data.code.length === 0) {
+ haveError = true
+ formProps.setError("code", {message: "Code is empty", type: "required"})
+ }
+
+ if (data.email && data.email?.length > 0 && !/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/.test(data.email)) {
+ haveError = true
+ formProps.setError("email", {message: "Email format is not valid", type: "custom"})
+ }
+
+ if (data.brNo && data.brNo?.length > 0 && !/[0-9]{8}/.test(data.brNo)) {
+ haveError = true
+ formProps.setError("brNo", {message: "Br No. format is not valid", type: "custom"})
+ }
+
+ if (haveError) {
+ // go to the error tab
+ setTabIndex(0)
+ return false
+ }
+
+ data.deleteSubsidiaryIds = []
+ setServerError("");
+ await saveCustomer(data);
+ router.replace("/customer");
+ } catch (e) {
+ setServerError(t("An error has occurred. Please try again later."));
+ }
+ },
+ [router, t],
+ );
+
+ const onSubmitError = useCallback>(
+ (errors) => {
+ // Set the tab so that the focus will go there
+ if (errors.name || errors.code) {
+ setTabIndex(0);
+ }
+ },
+ [],
+ );
+
+ const errors = formProps.formState.errors;
+
+ return (
+
+
+
+
+ ) : undefined
+ }
+ iconPosition="end"
+ />
+
+
+ {serverError && (
+
+ {serverError}
+
+ )}
+ {tabIndex === 0 && }
+ {tabIndex === 1 && }
+
+
+ }
+ onClick={handleCancel}
+ >
+ {t("Cancel")}
+
+ } type="submit">
+ {t("Confirm")}
+
+
+
+
+ );
+};
+
+export default CreateCustomer;
\ No newline at end of file
diff --git a/src/components/CreateCustomer/CreateCustomerWrapper.tsx b/src/components/CreateCustomer/CreateCustomerWrapper.tsx
new file mode 100644
index 0000000..213fa71
--- /dev/null
+++ b/src/components/CreateCustomer/CreateCustomerWrapper.tsx
@@ -0,0 +1,19 @@
+// import { fetchAllTasks, fetchTaskTemplates } from "@/app/api/tasks";
+// import CreateProject from "./CreateProject";
+// import { fetchProjectCategories } from "@/app/api/projects";
+// import { fetchTeamLeads } from "@/app/api/staff";
+import { fetchSubsidiaries } from "@/app/api/customer";
+import CreateCustomer from "./CreateCustomer";
+
+const CreateCustomerWrapper: React.FC = async () => {
+ const [subsidiaries] =
+ await Promise.all([
+ fetchSubsidiaries(),
+ ]);
+
+ return (
+
+ );
+};
+
+export default CreateCustomerWrapper;
diff --git a/src/components/CreateCustomer/CustomerDetails.tsx b/src/components/CreateCustomer/CustomerDetails.tsx
new file mode 100644
index 0000000..6654f56
--- /dev/null
+++ b/src/components/CreateCustomer/CustomerDetails.tsx
@@ -0,0 +1,120 @@
+"use client";
+
+import Stack from "@mui/material/Stack";
+import Box from "@mui/material/Box";
+import Card from "@mui/material/Card";
+import CardContent from "@mui/material/CardContent";
+import Grid from "@mui/material/Grid";
+import TextField from "@mui/material/TextField";
+import Typography from "@mui/material/Typography";
+import { useTranslation } from "react-i18next";
+import CardActions from "@mui/material/CardActions";
+import RestartAlt from "@mui/icons-material/RestartAlt";
+import Button from "@mui/material/Button";
+import { useFormContext } from "react-hook-form";
+import { CustomerFormInputs } from "@/app/api/customer/actions";
+
+interface Props {
+}
+
+const CustomerDetails: React.FC = ({
+}) => {
+ const { t } = useTranslation();
+ const {
+ register,
+ formState: { errors },
+ } = useFormContext();
+
+ return (
+
+
+
+
+ {t("Customer Details")}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }>
+ {t("Reset")}
+
+
+
+
+ );
+};
+
+export default CustomerDetails;
diff --git a/src/components/CreateCustomer/SubsidiaryAllocation.tsx b/src/components/CreateCustomer/SubsidiaryAllocation.tsx
new file mode 100644
index 0000000..25f35a4
--- /dev/null
+++ b/src/components/CreateCustomer/SubsidiaryAllocation.tsx
@@ -0,0 +1,214 @@
+"use client";
+
+import { useTranslation } from "react-i18next";
+import React, { useEffect } from "react";
+import RestartAlt from "@mui/icons-material/RestartAlt";
+import SearchResults, { Column } from "../SearchResults";
+import { Search, Clear, PersonAdd, PersonRemove } from "@mui/icons-material";
+import {
+ Stack,
+ Typography,
+ Grid,
+ TextField,
+ InputAdornment,
+ IconButton,
+ FormControl,
+ InputLabel,
+ Select,
+ MenuItem,
+ Box,
+ Button,
+ Card,
+ CardActions,
+ CardContent,
+ TabsProps,
+ Tab,
+ Tabs,
+ SelectChangeEvent,
+} from "@mui/material";
+import differenceBy from "lodash/differenceBy";
+import uniq from "lodash/uniq";
+import { useFormContext } from "react-hook-form";
+import { CreateProjectInputs } from "@/app/api/projects/actions";
+import { CustomerFormInputs } from "@/app/api/customer/actions";
+import { Subsidiary } from "@/app/api/customer";
+
+interface Props {
+ subsidiaries: Subsidiary[];
+}
+
+const SubsidiaryAllocation: React.FC = ({
+ subsidiaries,
+}) => {
+ const { t } = useTranslation();
+ const { setValue, getValues } = useFormContext();
+
+ const [filteredSubsidiary, setFilteredSubsidiary] = React.useState(subsidiaries);
+ const [selectedSubsidiary, setSelectedSubsidiary] = React.useState<
+ typeof filteredSubsidiary
+ >(
+ subsidiaries.filter((subsidiary) =>
+ getValues("addSubsidiaryIds")?.includes(subsidiary.id),
+ ),
+ );
+
+ // Adding / Removing staff
+ const addSubsidiary = React.useCallback((subsidiary: Subsidiary) => {
+ setSelectedSubsidiary((subsidiaries) => [...subsidiaries, subsidiary]);
+ }, []);
+ const removeSubsidiary = React.useCallback((subsidiary: Subsidiary) => {
+ setSelectedSubsidiary((subsidiaries) => subsidiaries.filter((s) => s.id !== subsidiary.id));
+ }, []);
+ const clearSubsidiary = React.useCallback(() => {
+ setSelectedSubsidiary([]);
+ }, []);
+ // Sync with form
+ useEffect(() => {
+ setValue(
+ "addSubsidiaryIds",
+ selectedSubsidiary.map((subsidiary) => subsidiary.id),
+ );
+ }, [selectedSubsidiary, setValue]);
+
+ const subsidiaryPoolColumns = React.useMemo[]>(
+ () => [
+ {
+ label: t("Add"),
+ name: "id",
+ onClick: addSubsidiary,
+ buttonIcon: ,
+ },
+ { label: t("Subsidiary Code"), name: "code" },
+ { label: t("Subsidiary Name"), name: "name" },
+ { label: t("Subsidiary Br No."), name: "brNo" },
+ { label: t("Subsidiary Contact Name"), name: "contactName" },
+ { label: t("Subsidiary Phone"), name: "phone" },
+ { label: t("Subsidiary Address"), name: "address" },
+ { label: t("Subsidiary District"), name: "district" },
+ { label: t("Subsidiary Email"), name: "email" },
+ ],
+ [addSubsidiary, t],
+ );
+
+ const allocatedSubsidiaryColumns = React.useMemo[]>(
+ () => [
+ {
+ label: t("Remove"),
+ name: "id",
+ onClick: removeSubsidiary,
+ buttonIcon: ,
+ },
+ { label: t("Subsidiary Code"), name: "code" },
+ { label: t("Subsidiary Name"), name: "name" },
+ { label: t("Subsidiary Br No."), name: "brNo" },
+ { label: t("Subsidiary Contact Name"), name: "contactName" },
+ { label: t("Subsidiary Phone"), name: "phone" },
+ { label: t("Subsidiary Address"), name: "address" },
+ { label: t("Subsidiary District"), name: "district" },
+ { label: t("Subsidiary Email"), name: "email" },
+ ],
+ [removeSubsidiary, t],
+ );
+
+ // Query related
+ const [query, setQuery] = React.useState("");
+ const onQueryInputChange = React.useCallback<
+ React.ChangeEventHandler
+ >((e) => {
+ setQuery(e.target.value);
+ }, []);
+ const clearQueryInput = React.useCallback(() => {
+ setQuery("");
+ }, []);
+
+ React.useEffect(() => {
+ setFilteredSubsidiary(
+ subsidiaries.filter((subsidiary) => {
+ const q = query.toLowerCase();
+ return (
+ (subsidiary.name.toLowerCase().includes(q) ||
+ subsidiary.code.toString().includes(q) ||
+ (subsidiary.brNo != null && subsidiary.brNo.toLowerCase().includes(q)))
+ );
+ }),
+ );
+ }, [subsidiaries, query]);
+
+ // Tab related
+ const [tabIndex, setTabIndex] = React.useState(0);
+ const handleTabChange = React.useCallback>(
+ (_e, newValue) => {
+ setTabIndex(newValue);
+ },
+ [],
+ );
+
+ const reset = React.useCallback(() => {
+ clearQueryInput();
+ clearSubsidiary();
+ }, [clearQueryInput, clearSubsidiary]);
+
+ return (
+ <>
+
+
+
+
+ {t("Subsidiary Allocation")}
+
+
+
+
+
+
+
+
+
+ ),
+ }}
+ />
+
+
+
+
+
+
+
+ {tabIndex === 0 && (
+
+ )}
+ {tabIndex === 1 && (
+
+ )}
+
+
+
+ } onClick={reset}>
+ {t("Reset")}
+
+
+
+
+ >
+ );
+};
+
+export default SubsidiaryAllocation;
\ No newline at end of file
diff --git a/src/components/CreateCustomer/index.ts b/src/components/CreateCustomer/index.ts
new file mode 100644
index 0000000..c325905
--- /dev/null
+++ b/src/components/CreateCustomer/index.ts
@@ -0,0 +1 @@
+export { default } from "./CreateCustomerWrapper";
\ No newline at end of file
diff --git a/src/components/CustomerSearch/CustomerSearch.tsx b/src/components/CustomerSearch/CustomerSearch.tsx
new file mode 100644
index 0000000..c65075d
--- /dev/null
+++ b/src/components/CustomerSearch/CustomerSearch.tsx
@@ -0,0 +1,70 @@
+"use client";
+
+import { Customer } from "@/app/api/customer";
+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";
+
+interface Props {
+ customers: Customer[];
+}
+
+type SearchQuery = Partial>;
+type SearchParamNames = keyof SearchQuery;
+
+const CustomerSearch: React.FC = ({ customers }) => {
+ const { t } = useTranslation();
+
+ const [filteredCustomers, setFilteredCustomers] = useState(customers);
+ const searchCriteria: Criterion[] = useMemo(
+ () => [
+ { label: t("Customer Code"), paramName: "code", type: "text" },
+ { label: t("Customer Name"), paramName: "name", type: "text" },
+ ],
+ [t],
+ );
+ const onReset = useCallback(() => {
+ setFilteredCustomers(customers);
+ }, [customers]);
+
+ const onTaskClick = useCallback((customer: Customer) => {
+ console.log(customer);
+ }, []);
+
+ const columns = useMemo[]>(
+ () => [
+ {
+ name: "id",
+ label: t("Details"),
+ onClick: onTaskClick,
+ buttonIcon: ,
+ },
+ { name: "code", label: t("Customer Code") },
+ { name: "name", label: t("Customer Name") },
+ ],
+ [onTaskClick, t],
+ );
+
+ return (
+ <>
+ {
+ setFilteredCustomers(
+ customers.filter(
+ (customer) =>
+ customer.code.toLowerCase().includes(query.code.toLowerCase()) &&
+ customer.name.toLowerCase().includes(query.name.toLowerCase()),
+ ),
+ );
+ }}
+ onReset={onReset}
+ />
+
+ >
+ );
+};
+
+export default CustomerSearch;
\ No newline at end of file
diff --git a/src/components/CustomerSearch/CustomerSearchLoading.tsx b/src/components/CustomerSearch/CustomerSearchLoading.tsx
new file mode 100644
index 0000000..2182dc2
--- /dev/null
+++ b/src/components/CustomerSearch/CustomerSearchLoading.tsx
@@ -0,0 +1,38 @@
+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 CustomerSearchLoading: React.FC = () => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default CustomerSearchLoading;
\ No newline at end of file
diff --git a/src/components/CustomerSearch/CustomerSearchWrapper.tsx b/src/components/CustomerSearch/CustomerSearchWrapper.tsx
new file mode 100644
index 0000000..53ac3a6
--- /dev/null
+++ b/src/components/CustomerSearch/CustomerSearchWrapper.tsx
@@ -0,0 +1,18 @@
+import { fetchAllCustomers } from "@/app/api/customer";
+import React from "react";
+import CustomerSearch from "./CustomerSearch";
+import CustomerSearchLoading from "./CustomerSearchLoading";
+
+interface SubComponents {
+ Loading: typeof CustomerSearchLoading;
+}
+
+const CustomerSearchWrapper: React.FC & SubComponents = async () => {
+ const customers = await fetchAllCustomers();
+
+ return ;
+};
+
+CustomerSearchWrapper.Loading = CustomerSearchLoading;
+
+export default CustomerSearchWrapper;
\ No newline at end of file
diff --git a/src/components/CustomerSearch/index.ts b/src/components/CustomerSearch/index.ts
new file mode 100644
index 0000000..8769844
--- /dev/null
+++ b/src/components/CustomerSearch/index.ts
@@ -0,0 +1 @@
+export { default } from "./CustomerSearchWrapper";
\ No newline at end of file
diff --git a/src/components/SearchBox/SearchBox.tsx b/src/components/SearchBox/SearchBox.tsx
index 017410d..fe058f7 100644
--- a/src/components/SearchBox/SearchBox.tsx
+++ b/src/components/SearchBox/SearchBox.tsx
@@ -177,22 +177,22 @@ function SearchBox({
);
})}
-
- }
- onClick={handleReset}
- >
- {t("Reset")}
-
- }
- onClick={handleSearch}
- >
- {t("Search")}
-
-
+
+ }
+ onClick={handleReset}
+ >
+ {t("Reset")}
+
+ }
+ onClick={handleSearch}
+ >
+ {t("Search")}
+
+
);
diff --git a/src/components/SearchResults/SearchResults.tsx b/src/components/SearchResults/SearchResults.tsx
index 4c82280..8d3e2ca 100644
--- a/src/components/SearchResults/SearchResults.tsx
+++ b/src/components/SearchResults/SearchResults.tsx
@@ -12,6 +12,8 @@ import TablePagination, {
} from "@mui/material/TablePagination";
import TableRow from "@mui/material/TableRow";
import IconButton from "@mui/material/IconButton";
+import { ThemeProvider, createTheme } from "@mui/material";
+import { zhTW, enUS } from '@mui/material/locale';
export interface ResultWithId {
id: string | number;
@@ -65,8 +67,18 @@ function SearchResults({
setPage(0);
};
+
+ const theme = createTheme(
+
+ // locale
+ //TODO: May need to know what locale the client is using
+ // localStorage.getItem("locale")?.includes("zh") ? zhTW : enUS
+ zhTW
+ );
+
const table = (
<>
+
@@ -117,6 +129,7 @@ function SearchResults({
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
+
>
);
diff --git a/src/i18n/en/breadcrumb.json b/src/i18n/en/breadcrumb.json
new file mode 100644
index 0000000..72bdcc8
--- /dev/null
+++ b/src/i18n/en/breadcrumb.json
@@ -0,0 +1,6 @@
+{
+ "Overview": "Overview",
+
+ "customer": "Customer",
+ "Create Customer": "Create Customer"
+}
\ No newline at end of file
diff --git a/src/i18n/en/common.json b/src/i18n/en/common.json
index 2b2f3a3..24afe2d 100644
--- a/src/i18n/en/common.json
+++ b/src/i18n/en/common.json
@@ -1,3 +1,9 @@
{
- "Grade {{grade}}": "Grade {{grade}}"
+ "Grade {{grade}}": "Grade {{grade}}",
+
+ "Search": "Search",
+ "Search Criteria": "Search Criteria",
+ "Cancel": "Cancel",
+ "Confirm": "Confirm",
+ "Reset": "Reset"
}
\ No newline at end of file
diff --git a/src/i18n/en/customer.json b/src/i18n/en/customer.json
new file mode 100644
index 0000000..6220171
--- /dev/null
+++ b/src/i18n/en/customer.json
@@ -0,0 +1,42 @@
+{
+ "Customer": "Client",
+ "Create Customer": "Create Client",
+ "Edit Customer": "Edit Client",
+ "Customer Code": "Client Code",
+ "Customer Name": "Client Name",
+ "Customer Address": "Client Address",
+ "Customer District": "Client District",
+ "Customer Email": "Client Email",
+ "Customer Phone": "Client Phone",
+ "Customer Contact Name": "Client Contact Name",
+ "Customer Br No.": "Client Br No.",
+ "Customer Details": "Client Details",
+
+ "Please input correct customer code": "Please input correct client code",
+ "Please input correct customer name": "Please input correct client name",
+ "Please input correct customer email": "Please input correct client email",
+ "Please input correct customer br no.": "Please input correct client br no.",
+
+ "Subsidiary" : "Subsidiary",
+ "Subsidiary Allocation": "Subsidiary Allocation",
+ "Search by subsidiary code, name or br no.": "Search by subsidiary code, name or br no.",
+ "Subsidiary Pool": "Subsidiary Pool",
+ "Allocated Subsidiary": "Allocated Subsidiary",
+ "Subsidiary Code": "Subsidiary Code",
+ "Subsidiary Name": "Subsidiary Name",
+ "Subsidiary Address": "Subsidiary Address",
+ "Subsidiary District": "Subsidiary District",
+ "Subsidiary Email": "Subsidiary Email",
+ "Subsidiary Phone": "Subsidiary Phone",
+ "Subsidiary Contact Name": "Subsidiary Contact Name",
+ "Subsidiary Br No.": "Subsidiary Br No.",
+ "Subsidiary Details": "Subsidiary Details",
+
+ "Add": "Add",
+ "Details": "Details",
+ "Search": "Search",
+ "Search Criteria": "Search Criteria",
+ "Cancel": "Cancel",
+ "Confirm": "Confirm",
+ "Reset": "Reset"
+}
\ No newline at end of file
diff --git a/src/i18n/zh/breadcrumb.json b/src/i18n/zh/breadcrumb.json
new file mode 100644
index 0000000..0bf1c6f
--- /dev/null
+++ b/src/i18n/zh/breadcrumb.json
@@ -0,0 +1,6 @@
+{
+ "Overview": "總覽",
+
+ "customer": "客戶",
+ "Create Customer": "建立客戶"
+}
\ No newline at end of file
diff --git a/src/i18n/zh/common.json b/src/i18n/zh/common.json
index 9e26dfe..011dc8d 100644
--- a/src/i18n/zh/common.json
+++ b/src/i18n/zh/common.json
@@ -1 +1,7 @@
-{}
\ No newline at end of file
+{
+ "Search": "搜尋",
+ "Search Criteria": "搜尋條件",
+ "Cancel": "取消",
+ "Confirm": "確認",
+ "Reset": "重置"
+}
\ No newline at end of file
diff --git a/src/i18n/zh/customer.json b/src/i18n/zh/customer.json
new file mode 100644
index 0000000..90ad477
--- /dev/null
+++ b/src/i18n/zh/customer.json
@@ -0,0 +1,42 @@
+{
+ "Customer": "客戶",
+ "Create Customer": "建立客戶",
+ "Edit Customer": "編輯客戶",
+ "Customer Code": "客戶編號",
+ "Customer Name": "客戶名稱",
+ "Customer Address": "客戶地址",
+ "Customer District": "客戶地區",
+ "Customer Email": "客戶電郵",
+ "Customer Phone": "客戶電話",
+ "Customer Contact Name": "客戶聯絡名稱",
+ "Customer Br No.": "客戶商業登記號碼",
+ "Customer Details": "客戶詳請",
+
+ "Please input correct customer code": "請輸入客戶編號",
+ "Please input correct customer name": "請輸入客戶編號",
+ "Please input correct customer email": "請輸入正確客戶電郵",
+ "Please input correct customer br no.": "請輸入正確客戶商業登記號碼",
+
+ "Subsidiary": "子公司",
+ "Subsidiary Allocation": "子公司分配",
+ "Search by subsidiary code, name or br no.": "可使用關鍵字搜尋 (子公司編號, 名稱或商業登記號碼)",
+ "Subsidiary Pool": "所有子公司",
+ "Allocated Subsidiary": "已分配的子公司",
+ "Subsidiary Code": "子公司編號",
+ "Subsidiary Name": "子公司名稱",
+ "Subsidiary Address": "子公司地址",
+ "Subsidiary District": "子公司地區",
+ "Subsidiary Email": "子公司電郵",
+ "Subsidiary Phone": "子公司電話",
+ "Subsidiary Contact Name": "子公司聯絡名稱",
+ "Subsidiary Br No.": "子公司商業登記號碼",
+ "Subsidiary Details": "子公司詳請",
+
+ "Add": "新增",
+ "Details": "詳請",
+ "Search": "搜尋",
+ "Search Criteria": "搜尋條件",
+ "Cancel": "取消",
+ "Confirm": "確認",
+ "Reset": "重置"
+}
\ No newline at end of file