From c9576970288adb52565c3417c719513f332f5dfa Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Thu, 28 Mar 2024 11:54:19 +0800 Subject: [PATCH] add customer master page --- src/app/(main)/customer/create/page.tsx | 27 +++ src/app/(main)/customer/page.tsx | 50 ++++ src/app/(main)/layout.tsx | 8 +- src/app/api/customer/actions.ts | 38 ++++ src/app/api/customer/index.ts | 40 ++++ src/app/utils/commonUtil.ts | 7 + src/components/Breadcrumb/Breadcrumb.tsx | 6 + .../CreateCustomer/CreateCustomer.tsx | 171 ++++++++++++++ .../CreateCustomer/CreateCustomerWrapper.tsx | 19 ++ .../CreateCustomer/CustomerDetails.tsx | 120 ++++++++++ .../CreateCustomer/SubsidiaryAllocation.tsx | 214 ++++++++++++++++++ src/components/CreateCustomer/index.ts | 1 + .../CustomerSearch/CustomerSearch.tsx | 70 ++++++ .../CustomerSearch/CustomerSearchLoading.tsx | 38 ++++ .../CustomerSearch/CustomerSearchWrapper.tsx | 18 ++ src/components/CustomerSearch/index.ts | 1 + src/components/SearchBox/SearchBox.tsx | 32 +-- .../SearchResults/SearchResults.tsx | 13 ++ src/i18n/en/breadcrumb.json | 6 + src/i18n/en/common.json | 8 +- src/i18n/en/customer.json | 42 ++++ src/i18n/zh/breadcrumb.json | 6 + src/i18n/zh/common.json | 8 +- src/i18n/zh/customer.json | 42 ++++ 24 files changed, 963 insertions(+), 22 deletions(-) create mode 100644 src/app/(main)/customer/create/page.tsx create mode 100644 src/app/(main)/customer/page.tsx create mode 100644 src/app/api/customer/actions.ts create mode 100644 src/app/api/customer/index.ts create mode 100644 src/app/utils/commonUtil.ts create mode 100644 src/components/CreateCustomer/CreateCustomer.tsx create mode 100644 src/components/CreateCustomer/CreateCustomerWrapper.tsx create mode 100644 src/components/CreateCustomer/CustomerDetails.tsx create mode 100644 src/components/CreateCustomer/SubsidiaryAllocation.tsx create mode 100644 src/components/CreateCustomer/index.ts create mode 100644 src/components/CustomerSearch/CustomerSearch.tsx create mode 100644 src/components/CustomerSearch/CustomerSearchLoading.tsx create mode 100644 src/components/CustomerSearch/CustomerSearchWrapper.tsx create mode 100644 src/components/CustomerSearch/index.ts create mode 100644 src/i18n/en/breadcrumb.json create mode 100644 src/i18n/en/customer.json create mode 100644 src/i18n/zh/breadcrumb.json create mode 100644 src/i18n/zh/customer.json 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")} + + + + + }> + + + + + ); +}; + +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 && } + + + + + + + + ); +}; + +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")} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +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 && ( + + )} + + + + + + + + + ); +}; + +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({ ); })} - - - - + + + + ); 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