diff --git a/src/app/(main)/settings/customer/create/page.tsx b/src/app/(main)/settings/customer/create/page.tsx index bb6cb94..99bc845 100644 --- a/src/app/(main)/settings/customer/create/page.tsx +++ b/src/app/(main)/settings/customer/create/page.tsx @@ -1,5 +1,5 @@ import { fetchSubsidiaries, preloadAllCustomers } from "@/app/api/customer"; -import CreateCustomer from "@/components/CreateCustomer"; +import CustomerDetail from "@/components/CustomerDetail"; // import { preloadAllTasks } from "@/app/api/tasks"; import CreateTaskTemplate from "@/components/CreateTaskTemplate"; import { I18nProvider, getServerI18n } from "@/i18n"; @@ -10,7 +10,7 @@ export const metadata: Metadata = { title: "Create Customer", }; -const Projects: React.FC = async () => { +const CreateCustomer: React.FC = async () => { const { t } = await getServerI18n("customer"); // fetchSubsidiaries(); @@ -18,10 +18,10 @@ const Projects: React.FC = async () => { <> {t("Create Customer")} - + ); }; -export default Projects; +export default CreateCustomer; diff --git a/src/app/(main)/settings/customer/edit/page.tsx b/src/app/(main)/settings/customer/edit/page.tsx new file mode 100644 index 0000000..270194e --- /dev/null +++ b/src/app/(main)/settings/customer/edit/page.tsx @@ -0,0 +1,27 @@ +import { fetchSubsidiaries, preloadAllCustomers } from "@/app/api/customer"; +import CustomerDetail from "@/components/CustomerDetail"; +// 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: "Edit Customer", +}; + +const EditCustomer: React.FC = async () => { + const { t } = await getServerI18n("customer"); + // fetchSubsidiaries(); + + return ( + <> + {t("Edit Customer")} + + + + + ); +}; + +export default EditCustomer; diff --git a/src/app/api/customer/actions.ts b/src/app/api/customer/actions.ts index dc0ae57..fd030d5 100644 --- a/src/app/api/customer/actions.ts +++ b/src/app/api/customer/actions.ts @@ -1,9 +1,11 @@ +import { Subsidiary } from '@/app/api/customer'; "use server"; -import { serverFetchJson } from "@/app/utils/fetchUtil"; +import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; import { BASE_API_URL } from "@/config/api"; -import { Contact, NewCustomerResponse } from "."; +import { Contact, Customer, SaveCustomerResponse } from "."; import { revalidateTag } from "next/cache"; +import { cache } from "react"; export interface CustomerFormInputs { @@ -14,9 +16,6 @@ export interface CustomerFormInputs { code: string; address: string | null; district: string | null; - email: string | null; - phone: string | null; - contactName: string | null; brNo: string | null; typeId: number; @@ -32,8 +31,14 @@ export interface CustomerFormInputs { isGridEditing: boolean | null; } +export interface CustomerResponse { + customer: Customer; + subsidiaryIds: number[]; + contacts: Contact[]; +} + export const saveCustomer = async (data: CustomerFormInputs) => { - const saveCustomer = await serverFetchJson( + const saveCustomer = await serverFetchJson( `${BASE_API_URL}/customer/save`, { method: "POST", @@ -46,3 +51,27 @@ export const saveCustomer = async (data: CustomerFormInputs) => { return saveCustomer; }; + +export const fetchCustomer = async (id: number) => { + const customer = await serverFetchJson( + `${BASE_API_URL}/customer/${id}`, + { + method: "GET", + headers: { "Content-Type": "application/json" }, + }, + ); + + return customer +}; + +export const deleteCustomer = async (id: number) => { + const customer = await serverFetchWithNoContent( + `${BASE_API_URL}/customer/${id}`, + { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + }, + ); + + return customer +}; \ No newline at end of file diff --git a/src/app/api/customer/index.ts b/src/app/api/customer/index.ts index 8b05170..b35bac0 100644 --- a/src/app/api/customer/index.ts +++ b/src/app/api/customer/index.ts @@ -7,9 +7,13 @@ export interface Customer { id: number; code: string; name: string; + brNo: string | null; + address: string | null; + district: string | null; + customerType: CustomerType } -export interface NewCustomerResponse { +export interface SaveCustomerResponse { customer: Customer; message: string; } @@ -61,7 +65,7 @@ export const fetchCustomerTypes = cache(async () => { return serverFetchJson( `${BASE_API_URL}/customer/types`, { - next: { tags: ["CustomerTypes"] }, + next: { tags: ["customerTypes"] }, }, ); }); \ No newline at end of file diff --git a/src/app/utils/commonUtil.ts b/src/app/utils/commonUtil.ts index 5af164e..480c9fe 100644 --- a/src/app/utils/commonUtil.ts +++ b/src/app/utils/commonUtil.ts @@ -1,7 +1,7 @@ -export function getDeletedRecordWithRefList(referenceList: Array, updatedList: Array) { +export function getDeletedRecordWithRefList(referenceList: Array, updatedList: Array) { return referenceList.filter(x => !updatedList.includes(x)); } -export function getNewRecordWithRefList(referenceList: Array, updatedList: Array) { +export function getNewRecordWithRefList(referenceList: Array, updatedList: Array) { return updatedList.filter(x => !referenceList.includes(x)); } diff --git a/src/app/utils/fetchUtil.ts b/src/app/utils/fetchUtil.ts index 2bafa9e..ca12311 100644 --- a/src/app/utils/fetchUtil.ts +++ b/src/app/utils/fetchUtil.ts @@ -25,6 +25,7 @@ type FetchParams = Parameters; export async function serverFetchJson(...args: FetchParams) { const response = await serverFetch(...args); + if (response.ok) { return response.json() as T; } else { @@ -38,6 +39,22 @@ export async function serverFetchJson(...args: FetchParams) { } } +export async function serverFetchWithNoContent(...args: FetchParams) { + const response = await serverFetch(...args); + + if (response.ok) { + return response.status; // 204 No Content, e.g. for delete data + } else { + switch (response.status) { + case 401: + signOutUser(); + default: + console.error(await response.text()); + throw Error("Something went wrong fetching data in server."); + } + } +} + export const signOutUser = () => { const headersList = headers(); const referer = headersList.get("referer"); diff --git a/src/components/Breadcrumb/Breadcrumb.tsx b/src/components/Breadcrumb/Breadcrumb.tsx index 30dd0d9..091f6f3 100644 --- a/src/components/Breadcrumb/Breadcrumb.tsx +++ b/src/components/Breadcrumb/Breadcrumb.tsx @@ -16,6 +16,7 @@ const pathToLabelMap: { [path: string]: string } = { "/tasks/create": "Create Task Template", "/settings/customer": "Customer", "/settings/customer/create": "Create Customer", + "/settings/customer/edit": "Edit Customer", "/settings": "Settings", "/company": "Company", "/settings/department": "Department", diff --git a/src/components/CreateCustomer/CustomerDetails.tsx b/src/components/CreateCustomer/CustomerDetails.tsx deleted file mode 100644 index 630e39b..0000000 --- a/src/components/CreateCustomer/CustomerDetails.tsx +++ /dev/null @@ -1,152 +0,0 @@ -"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 { Controller, useFormContext } from "react-hook-form"; -import { CustomerFormInputs } from "@/app/api/customer/actions"; -import { CustomerType } from "@/app/api/customer"; -import { FormControl, InputLabel, MenuItem, Select } from "@mui/material"; -import ContactDetails from "./ContactDetails"; - -interface Props { - customerTypes: CustomerType[], -} - -const CustomerDetails: React.FC = ({ - customerTypes, -}) => { - const { t } = useTranslation(); - const { - register, - formState: { errors }, - control, - reset - } = useFormContext(); - - return ( - <> - - - - - {t("Customer Details")} - - - - - - - - - - - - - - - {/* - - - - - - - - */} - - - {t("Customer Type")} - ( - - )} - /> - - - - - - - - - - - - - - - ); -}; - -export default CustomerDetails; diff --git a/src/components/CreateCustomer/index.ts b/src/components/CreateCustomer/index.ts deleted file mode 100644 index c325905..0000000 --- a/src/components/CreateCustomer/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./CreateCustomerWrapper"; \ No newline at end of file diff --git a/src/components/CreateCustomer/ContactDetails.tsx b/src/components/CustomerDetail/ContactInfo.tsx similarity index 90% rename from src/components/CreateCustomer/ContactDetails.tsx rename to src/components/CustomerDetail/ContactInfo.tsx index 40569de..ba722c6 100644 --- a/src/components/CreateCustomer/ContactDetails.tsx +++ b/src/components/CustomerDetail/ContactInfo.tsx @@ -1,10 +1,8 @@ "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 Typography from "@mui/material/Typography"; import { useTranslation } from "react-i18next"; import CardActions from "@mui/material/CardActions"; @@ -19,7 +17,6 @@ import { GridRowsProp, GridRowModesModel, GridRowModes, - DataGrid, GridColDef, GridToolbarContainer, GridActionsCellItem, @@ -27,11 +24,8 @@ import { GridRowId, GridRowModel, GridRowEditStopReasons, - GridPreProcessEditCellProps, - GridCellParams, } from '@mui/x-data-grid'; import CustomDatagrid from "../CustomDatagrid/CustomDatagrid"; -import { Contact } from "@/app/api/customer"; import { useFieldArray, useFormContext } from "react-hook-form"; import { useCallback, useEffect, useMemo, useState } from "react"; @@ -69,11 +63,11 @@ function EditToolbar(props: EditToolbarProps) { ); } -const ContactDetails: React.FC = ({ +const ContactInfo: React.FC = ({ }) => { const { t } = useTranslation(); - const { control, setValue, getValues, formState: { errors }, setError, clearErrors } = useFormContext(); + const { control, setValue, getValues, formState: { errors, defaultValues }, setError, clearErrors, reset, watch, resetField } = useFormContext(); const { fields } = useFieldArray({ control, name: "addContacts" @@ -89,9 +83,16 @@ const ContactDetails: React.FC = ({ }) }) - const [rows, setRows] = useState(initialRows); + const [rows, setRows] = useState([]); const [rowModesModel, setRowModesModel] = useState({}); + useEffect(() => { + if (initialRows.length > 0 && rows.length === 0) { + console.log("first") + setRows(initialRows) + } + }, [initialRows.length > 0]) + const handleRowEditStop: GridEventListener<'rowEditStop'> = (params, event) => { if (params.reason === GridRowEditStopReasons.rowFocusOut) { event.defaultMuiPrevented = true; @@ -137,6 +138,15 @@ const ContactDetails: React.FC = ({ setRowModesModel(newRowModesModel); }, [rows]); + const resetContact = useCallback(() => { + if (defaultValues !== undefined) { + resetField("addContacts") + // reset({addContacts: defaultValues.addContacts}) + setRows((prev) => defaultValues.addContacts) + setRowModesModel(rows.reduce((acc, row) => ({...acc, [row.id]: { mode: GridRowModes.View } }), {})) + } + }, [defaultValues]) + const columns = useMemo( () => [ { @@ -210,6 +220,10 @@ const ContactDetails: React.FC = ({ // check error useEffect(() => { + if (getValues("addContacts") !== undefined || getValues("addContacts") !== null) { + return; + } + if (getValues("addContacts").length === 0) { clearErrors("addContacts") } else { @@ -240,13 +254,14 @@ const ContactDetails: React.FC = ({ setValue("isGridEditing", false) } }, [rowModesModel]) + return ( {/*
*/} - {t("Contact Details")} + {t("Contact Info")} {Boolean(errors.addContacts?.type === "required") && ({ color: theme.palette.error.main })} variant="overline" display='inline-block' noWrap> {t("Please ensure all the fields are inputted and saved")} @@ -275,7 +290,7 @@ const ContactDetails: React.FC = ({ }} /> - @@ -285,4 +300,4 @@ const ContactDetails: React.FC = ({ ); }; -export default ContactDetails; \ No newline at end of file +export default ContactInfo; \ No newline at end of file diff --git a/src/components/CreateCustomer/CreateCustomer.tsx b/src/components/CustomerDetail/CustomerDetail.tsx similarity index 60% rename from src/components/CreateCustomer/CreateCustomer.tsx rename to src/components/CustomerDetail/CustomerDetail.tsx index 02ee033..bfc85b0 100644 --- a/src/components/CreateCustomer/CreateCustomer.tsx +++ b/src/components/CustomerDetail/CustomerDetail.tsx @@ -6,8 +6,8 @@ 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 { useRouter, useSearchParams } from "next/navigation"; +import React, { useCallback, useEffect, useLayoutEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { FieldErrors, @@ -18,8 +18,8 @@ import { } from "react-hook-form"; import { Error } from "@mui/icons-material"; import { Typography } from "@mui/material"; -import { CustomerFormInputs, saveCustomer } from "@/app/api/customer/actions"; -import CustomerDetails from "./CustomerDetails"; +import { CustomerFormInputs, fetchCustomer, saveCustomer } from "@/app/api/customer/actions"; +import CustomerInfo from "./CustomerInfo"; import SubsidiaryAllocation from "./SubsidiaryAllocation"; import { CustomerType, Subsidiary } from "@/app/api/customer"; import { getDeletedRecordWithRefList } from "@/app/utils/commonUtil"; @@ -42,25 +42,97 @@ const hasErrorsInTab = ( } }; -const CreateCustomer: React.FC = ({ +const CustomerDetail: React.FC = ({ subsidiaries, customerTypes, }) => { const [serverError, setServerError] = useState(""); const [tabIndex, setTabIndex] = useState(0); + const [refCustomer, setRefCustomer] = useState() const { t } = useTranslation(); const router = useRouter(); + const searchParams = useSearchParams() + + const fetchCurrentCustomer = async () => { + const id = searchParams.get('id') + try { + const defaultCustomer = { + id: null, + code: "", + name: "", + brNo: null, + address: null, + district: null, + typeId: 1, + addContacts: [], + addSubsidiaryIds: [], + deleteSubsidiaryIds: [], + deleteContactIds: [], + isGridEditing: false + } + + if (id !== null && parseInt(id) > 0) { + + const customer = await fetchCustomer(parseInt(id)) + + if (customer !== null && Object.keys(customer).length > 0) { + const tempCustomerInput = { + id: customer.customer.id, + code: customer.customer.code ?? "", + name: customer.customer.name ?? "", + brNo: customer.customer.brNo ?? "", + address: customer.customer.address ?? "", + district: customer.customer.district ?? "", + typeId: customer.customer.customerType.id, + addContacts: customer.contacts ?? [], + addSubsidiaryIds: customer.subsidiaryIds ?? [], + deleteSubsidiaryIds: [], + deleteContactIds: [], + isGridEditing: false + } + setRefCustomer(tempCustomerInput) + } else { + setRefCustomer(defaultCustomer) + } + } else { + setRefCustomer(defaultCustomer) + } + } catch (e) { + console.log(e) + setServerError(t("An error has occurred. Please try again later.")); + } + } + + useLayoutEffect(() => { + fetchCurrentCustomer() + }, []) + const formProps = useForm({ - defaultValues: { - code: "", - name: "", - addContacts: [], - addSubsidiaryIds: [], - deleteSubsidiaryIds: [], - deleteContactIds: [] - }, + // defaultValues: useMemo(() => { + // return refCustomer; + // }, [refCustomer]) + // defaultValues: { + // id: null, + // code: "", + // name: "", + // brNo: null, + // address: null, + // district: null, + // typeId: 1, + // addContacts: [], + // addSubsidiaryIds: [], + // deleteSubsidiaryIds: [], + // deleteContactIds: [], + // isGridEditing: false + // } }); + useEffect(() => { + if (refCustomer !== null && refCustomer !== undefined) { + formProps.reset(refCustomer) + } + }, [refCustomer]) + const handleCancel = () => { router.back(); }; @@ -79,7 +151,7 @@ const CreateCustomer: React.FC = ({ warningDialog(t("Please save all the rows before submitting"), t) return false } - + console.log(data); let haveError = false @@ -93,10 +165,10 @@ const CreateCustomer: React.FC = ({ 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.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 @@ -105,12 +177,12 @@ const CreateCustomer: React.FC = ({ if (data.addContacts.length === 0 || data.addContacts.filter(row => String(row.name).trim().length === 0 || String(row.phone).trim().length === 0 || String(row.email).trim().length === 0).length > 0) { haveError = true - formProps.setError("addContacts", { message: "Contact details include empty fields", type: "required" }) + formProps.setError("addContacts", { message: "Contact info include empty fields", type: "required" }) } if (data.addContacts.length > 0 && data.addContacts.filter(row => !/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/.test(String(row.email))).length > 0) { haveError = true - formProps.setError("addContacts", { message: "Contact details include empty fields", type: "email_format" }) + formProps.setError("addContacts", { message: "Contact info include empty fields", type: "email_format" }) } if (haveError) { @@ -119,9 +191,9 @@ const CreateCustomer: React.FC = ({ return false } - // data.deleteSubsidiaryIds = data.deleteSubsidiaryIds ?? [] - // data.addSubsidiaryIds = data.addSubsidiaryIds ?? [] - // data.deleteContactIds = data.deleteContactIds ?? [] + data.deleteContactIds = getDeletedRecordWithRefList(refCustomer?.addContacts.map(contact => contact.id)!!, data.addContacts.map(contact => contact.id)!!) + data.deleteSubsidiaryIds = getDeletedRecordWithRefList(refCustomer?.addSubsidiaryIds!!, data.addSubsidiaryIds) + setServerError(""); submitDialog(async () => { @@ -144,7 +216,7 @@ const CreateCustomer: React.FC = ({ setServerError(t("An error has occurred. Please try again later.")); } }, - [router, t], + [router, t, refCustomer], ); const onSubmitError = useCallback>( @@ -161,14 +233,14 @@ const CreateCustomer: React.FC = ({ return ( - @@ -183,7 +255,7 @@ const CreateCustomer: React.FC = ({ {serverError} )} - {tabIndex === 0 && } + {tabIndex === 0 && } {tabIndex === 1 && } @@ -194,13 +266,13 @@ const CreateCustomer: React.FC = ({ > {t("Cancel")} - - + } ); }; -export default CreateCustomer; \ No newline at end of file +export default CustomerDetail; \ No newline at end of file diff --git a/src/components/CreateCustomer/CreateCustomerWrapper.tsx b/src/components/CustomerDetail/CustomerDetailWrapper.tsx similarity index 52% rename from src/components/CreateCustomer/CreateCustomerWrapper.tsx rename to src/components/CustomerDetail/CustomerDetailWrapper.tsx index d4c85b2..0e1b3e1 100644 --- a/src/components/CreateCustomer/CreateCustomerWrapper.tsx +++ b/src/components/CustomerDetail/CustomerDetailWrapper.tsx @@ -3,9 +3,18 @@ // import { fetchProjectCategories } from "@/app/api/projects"; // import { fetchTeamLeads } from "@/app/api/staff"; import { fetchCustomerTypes, fetchSubsidiaries } from "@/app/api/customer"; -import CreateCustomer from "./CreateCustomer"; +import CustomerDetail from "./CustomerDetail"; +import { getServerSideProps } from "next/dist/build/templates/pages"; -const CreateCustomerWrapper: React.FC = async () => { +// type Props = { +// params: { +// id: string | undefined; +// }; +// }; + +const CustomerDetailWrapper: React.FC = async () => { + // const { params } = props + // console.log(params) const [subsidiaries, customerTypes] = await Promise.all([ fetchSubsidiaries(), @@ -13,8 +22,8 @@ const CreateCustomerWrapper: React.FC = async () => { ]); return ( - + ); }; -export default CreateCustomerWrapper; +export default CustomerDetailWrapper; diff --git a/src/components/CustomerDetail/CustomerInfo.tsx b/src/components/CustomerDetail/CustomerInfo.tsx new file mode 100644 index 0000000..695f6d5 --- /dev/null +++ b/src/components/CustomerDetail/CustomerInfo.tsx @@ -0,0 +1,177 @@ +"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 { Controller, useFormContext } from "react-hook-form"; +import { CustomerFormInputs } from "@/app/api/customer/actions"; +import { CustomerType } from "@/app/api/customer"; +import { FormControl, InputLabel, MenuItem, Select } from "@mui/material"; +import ContactInfo from "./ContactInfo"; +import { useCallback } from "react"; + +interface Props { + customerTypes: CustomerType[], +} + +const CustomerInfo: React.FC = ({ + customerTypes, +}) => { + const { t } = useTranslation(); + const { + register, + formState: { errors, defaultValues }, + control, + reset, + resetField, + setValue + } = useFormContext(); + + const resetCustomer = useCallback(() => { + console.log(defaultValues) + if (defaultValues !== undefined) { + resetField("code") + resetField("name") + resetField("address") + resetField("district") + resetField("typeId") + resetField("brNo") + + // setValue("code", defaultValues.code ?? "") + // reset({ + // code: defaultValues.code, + // name: defaultValues.name, + // address: defaultValues.address, + // district: defaultValues.district, + // typeId: defaultValues.typeId, + // brNo: defaultValues.brNo + // }) + } + }, [defaultValues]) + + return ( + <> + + + + + {t("Customer Info")} + + + + + + + + + + + + + + + {/* + + + + + + + + */} + + + {t("Customer Type")} + ( + + )} + /> + + + + + + + + + + + + + + + ); +}; + +export default CustomerInfo; diff --git a/src/components/CreateCustomer/SubsidiaryAllocation.tsx b/src/components/CustomerDetail/SubsidiaryAllocation.tsx similarity index 86% rename from src/components/CreateCustomer/SubsidiaryAllocation.tsx rename to src/components/CustomerDetail/SubsidiaryAllocation.tsx index 25f35a4..2b54294 100644 --- a/src/components/CreateCustomer/SubsidiaryAllocation.tsx +++ b/src/components/CustomerDetail/SubsidiaryAllocation.tsx @@ -12,10 +12,6 @@ import { TextField, InputAdornment, IconButton, - FormControl, - InputLabel, - Select, - MenuItem, Box, Button, Card, @@ -24,12 +20,9 @@ import { 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"; @@ -41,7 +34,7 @@ const SubsidiaryAllocation: React.FC = ({ subsidiaries, }) => { const { t } = useTranslation(); - const { setValue, getValues } = useFormContext(); + const { setValue, getValues, formState: { defaultValues }, reset, resetField } = useFormContext(); const [filteredSubsidiary, setFilteredSubsidiary] = React.useState(subsidiaries); const [selectedSubsidiary, setSelectedSubsidiary] = React.useState< @@ -49,19 +42,28 @@ const SubsidiaryAllocation: React.FC = ({ >( 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([]); - }, []); + if (defaultValues !== undefined) { + // reset({ addSubsidiaryIds: defaultValues.addSubsidiaryIds }) + resetField("addSubsidiaryIds") + setSelectedSubsidiary(subsidiaries.filter((subsidiary) => + defaultValues.addSubsidiaryIds?.includes(subsidiary.id), + )) + } + }, [defaultValues]); + // Sync with form useEffect(() => { setValue( @@ -81,11 +83,11 @@ const SubsidiaryAllocation: React.FC = ({ { 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 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" }, + // { label: t("Subsidiary Email"), name: "email" }, ], [addSubsidiary, t], ); @@ -101,11 +103,11 @@ const SubsidiaryAllocation: React.FC = ({ { 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 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" }, + // { label: t("Subsidiary Email"), name: "email" }, ], [removeSubsidiary, t], ); @@ -143,14 +145,14 @@ const SubsidiaryAllocation: React.FC = ({ [], ); - const reset = React.useCallback(() => { + const resetSubsidiary = React.useCallback(() => { clearQueryInput(); clearSubsidiary(); }, [clearQueryInput, clearSubsidiary]); return ( <> - + @@ -201,7 +203,7 @@ const SubsidiaryAllocation: React.FC = ({ - diff --git a/src/components/CustomerDetail/index.ts b/src/components/CustomerDetail/index.ts new file mode 100644 index 0000000..a8811e6 --- /dev/null +++ b/src/components/CustomerDetail/index.ts @@ -0,0 +1 @@ +export { default } from "./CustomerDetailWrapper"; \ No newline at end of file diff --git a/src/components/CustomerSearch/CustomerSearch.tsx b/src/components/CustomerSearch/CustomerSearch.tsx index c65075d..d4d1a30 100644 --- a/src/components/CustomerSearch/CustomerSearch.tsx +++ b/src/components/CustomerSearch/CustomerSearch.tsx @@ -6,6 +6,10 @@ import SearchBox, { Criterion } from "../SearchBox"; import { useTranslation } from "react-i18next"; import SearchResults, { Column } from "../SearchResults"; import EditNote from "@mui/icons-material/EditNote"; +import DeleteIcon from '@mui/icons-material/Delete'; +import { useRouter, useSearchParams } from "next/navigation"; +import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; +import { deleteCustomer } from "@/app/api/customer/actions"; interface Props { customers: Customer[]; @@ -16,6 +20,8 @@ type SearchParamNames = keyof SearchQuery; const CustomerSearch: React.FC = ({ customers }) => { const { t } = useTranslation(); + const router = useRouter() + const searchParams = useSearchParams() const [filteredCustomers, setFilteredCustomers] = useState(customers); const searchCriteria: Criterion[] = useMemo( @@ -30,7 +36,20 @@ const CustomerSearch: React.FC = ({ customers }) => { }, [customers]); const onTaskClick = useCallback((customer: Customer) => { - console.log(customer); + const params = new URLSearchParams(searchParams.toString()) + params.set("id", customer.id.toString()) + router.replace(`/settings/customer/edit?${params.toString()}`); + }, []); + + const onDeleteClick = useCallback((customer: Customer) => { + + deleteDialog(async() => { + await deleteCustomer(customer.id) + + successDialog("Delete Success", t) + + setFilteredCustomers((prev) => prev.filter((obj) => obj.id !== customer.id)) + }, t) }, []); const columns = useMemo[]>( @@ -43,6 +62,12 @@ const CustomerSearch: React.FC = ({ customers }) => { }, { name: "code", label: t("Customer Code") }, { name: "name", label: t("Customer Name") }, + { + name: "id", + label: t("Delete"), + onClick: onDeleteClick, + buttonIcon: , + }, ], [onTaskClick, t], ); diff --git a/src/components/Swal/CustomAlerts.js b/src/components/Swal/CustomAlerts.js index c6154e3..6eddfb2 100644 --- a/src/components/Swal/CustomAlerts.js +++ b/src/components/Swal/CustomAlerts.js @@ -50,18 +50,32 @@ export const warningDialog = (text, t) => { }) } -export const submitDialog = (confirmAction, t) => { +export const submitDialog = async (confirmAction, t) => { // const { t } = useTranslation("common") - return Swal.fire({ + const result = await Swal.fire({ icon: "question", title: t("Do you want to submit?"), cancelButtonText: t("Cancel"), confirmButtonText: t("Submit"), showCancelButton: true, showConfirmButton: true, - }).then((result) => { - if (result.isConfirmed) { - confirmAction() - } - }) + }); + if (result.isConfirmed) { + confirmAction(); + } +} + +export const deleteDialog = async (confirmAction, t) => { + // const { t } = useTranslation("common") + const result = await Swal.fire({ + icon: "question", + title: t("Do you want to delete?"), + cancelButtonText: t("Cancel"), + confirmButtonText: t("Delete"), + showCancelButton: true, + showConfirmButton: true, + }); + if (result.isConfirmed) { + confirmAction(); + } } diff --git a/src/i18n/en/customer.json b/src/i18n/en/customer.json index 5a740d1..06d7a6e 100644 --- a/src/i18n/en/customer.json +++ b/src/i18n/en/customer.json @@ -11,6 +11,7 @@ "Customer Contact Name": "Client Contact Name", "Customer Br No.": "Client Br No.", "Customer Details": "Client Details", + "Customer Info": "Client Info", "Customer Type": "Client Type", "Please input correct customer code": "Please input correct client code", @@ -33,9 +34,11 @@ "Subsidiary Contact Name": "Subsidiary Contact Name", "Subsidiary Br No.": "Subsidiary Br No.", "Subsidiary Details": "Subsidiary Details", + "Subsidiary Info": "Subsidiary Info", "Add Contact Person": "Add Contact Person", "Contact Details": "Contact Details", + "Contact Info": "Contact Info", "Contact Name": "Contact Name", "Contact Email": "Contact Email", "Contact Phone": "Contact Phone", @@ -44,13 +47,18 @@ "Do you want to submit?": "Do you want to submit?", "Submit Success": "Submit Success", + "Submit Fail": "Submit Fail", + "Do you want to delete?": "Do you want to delete", + "Delete Success": "Delete Success", "Add": "Add", "Details": "Details", + "Info": "Info", "Search": "Search", "Search Criteria": "Search Criteria", "Cancel": "Cancel", "Confirm": "Confirm", "Submit": "Submit", - "Reset": "Reset" + "Reset": "Reset", + "Delete": "Delete" } \ No newline at end of file diff --git a/src/i18n/zh/customer.json b/src/i18n/zh/customer.json index c39d417..6eb4e0d 100644 --- a/src/i18n/zh/customer.json +++ b/src/i18n/zh/customer.json @@ -11,6 +11,7 @@ "Customer Contact Name": "客戶聯絡名稱", "Customer Br No.": "客戶商業登記號碼", "Customer Details": "客戶詳請", + "Customer Info": "客戶資料", "Customer Type": "客戶類型", "Please input correct customer code": "請輸入客戶編號", @@ -33,9 +34,11 @@ "Subsidiary Contact Name": "子公司聯絡名稱", "Subsidiary Br No.": "子公司商業登記號碼", "Subsidiary Details": "子公司詳請", + "Subsidiary Info": "子公司資料", "Add Contact Person": "新增聯絡人", "Contact Details": "聯絡詳請", + "Contact Info": "聯絡資料", "Contact Name": "聯絡姓名", "Contact Email": "聯絡電郵", "Contact Phone": "聯絡電話", @@ -44,13 +47,18 @@ "Do you want to submit?": "你是否確認要提交?", "Submit Success": "提交成功", + "Submit Fail": "提交失敗", + "Do you want to delete?": "你是否確認要刪除?", + "Delete Success": "刪除成功", "Add": "新增", "Details": "詳請", + "Info": "資料", "Search": "搜尋", "Search Criteria": "搜尋條件", "Cancel": "取消", "Confirm": "確認", "Submit": "提交", - "Reset": "重置" + "Reset": "重置", + "Delete": "刪除" } \ No newline at end of file