@@ -1,5 +1,5 @@ | |||||
import { fetchSubsidiaries, preloadAllCustomers } from "@/app/api/customer"; | import { fetchSubsidiaries, preloadAllCustomers } from "@/app/api/customer"; | ||||
import CreateCustomer from "@/components/CreateCustomer"; | |||||
import CustomerDetail from "@/components/CustomerDetail"; | |||||
// import { preloadAllTasks } from "@/app/api/tasks"; | // import { preloadAllTasks } from "@/app/api/tasks"; | ||||
import CreateTaskTemplate from "@/components/CreateTaskTemplate"; | import CreateTaskTemplate from "@/components/CreateTaskTemplate"; | ||||
import { I18nProvider, getServerI18n } from "@/i18n"; | import { I18nProvider, getServerI18n } from "@/i18n"; | ||||
@@ -10,7 +10,7 @@ export const metadata: Metadata = { | |||||
title: "Create Customer", | title: "Create Customer", | ||||
}; | }; | ||||
const Projects: React.FC = async () => { | |||||
const CreateCustomer: React.FC = async () => { | |||||
const { t } = await getServerI18n("customer"); | const { t } = await getServerI18n("customer"); | ||||
// fetchSubsidiaries(); | // fetchSubsidiaries(); | ||||
@@ -18,10 +18,10 @@ const Projects: React.FC = async () => { | |||||
<> | <> | ||||
<Typography variant="h4">{t("Create Customer")}</Typography> | <Typography variant="h4">{t("Create Customer")}</Typography> | ||||
<I18nProvider namespaces={["customer", "common"]}> | <I18nProvider namespaces={["customer", "common"]}> | ||||
<CreateCustomer /> | |||||
<CustomerDetail /> | |||||
</I18nProvider> | </I18nProvider> | ||||
</> | </> | ||||
); | ); | ||||
}; | }; | ||||
export default Projects; | |||||
export default CreateCustomer; |
@@ -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 ( | |||||
<> | |||||
<Typography variant="h4">{t("Edit Customer")}</Typography> | |||||
<I18nProvider namespaces={["customer", "common"]}> | |||||
<CustomerDetail /> | |||||
</I18nProvider> | |||||
</> | |||||
); | |||||
}; | |||||
export default EditCustomer; |
@@ -1,9 +1,11 @@ | |||||
import { Subsidiary } from '@/app/api/customer'; | |||||
"use server"; | "use server"; | ||||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | |||||
import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
import { Contact, NewCustomerResponse } from "."; | |||||
import { Contact, Customer, SaveCustomerResponse } from "."; | |||||
import { revalidateTag } from "next/cache"; | import { revalidateTag } from "next/cache"; | ||||
import { cache } from "react"; | |||||
export interface CustomerFormInputs { | export interface CustomerFormInputs { | ||||
@@ -14,9 +16,6 @@ export interface CustomerFormInputs { | |||||
code: string; | code: string; | ||||
address: string | null; | address: string | null; | ||||
district: string | null; | district: string | null; | ||||
email: string | null; | |||||
phone: string | null; | |||||
contactName: string | null; | |||||
brNo: string | null; | brNo: string | null; | ||||
typeId: number; | typeId: number; | ||||
@@ -32,8 +31,14 @@ export interface CustomerFormInputs { | |||||
isGridEditing: boolean | null; | isGridEditing: boolean | null; | ||||
} | } | ||||
export interface CustomerResponse { | |||||
customer: Customer; | |||||
subsidiaryIds: number[]; | |||||
contacts: Contact[]; | |||||
} | |||||
export const saveCustomer = async (data: CustomerFormInputs) => { | export const saveCustomer = async (data: CustomerFormInputs) => { | ||||
const saveCustomer = await serverFetchJson<NewCustomerResponse>( | |||||
const saveCustomer = await serverFetchJson<SaveCustomerResponse>( | |||||
`${BASE_API_URL}/customer/save`, | `${BASE_API_URL}/customer/save`, | ||||
{ | { | ||||
method: "POST", | method: "POST", | ||||
@@ -46,3 +51,27 @@ export const saveCustomer = async (data: CustomerFormInputs) => { | |||||
return saveCustomer; | return saveCustomer; | ||||
}; | }; | ||||
export const fetchCustomer = async (id: number) => { | |||||
const customer = await serverFetchJson<CustomerResponse>( | |||||
`${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 | |||||
}; |
@@ -7,9 +7,13 @@ export interface Customer { | |||||
id: number; | id: number; | ||||
code: string; | code: string; | ||||
name: string; | name: string; | ||||
brNo: string | null; | |||||
address: string | null; | |||||
district: string | null; | |||||
customerType: CustomerType | |||||
} | } | ||||
export interface NewCustomerResponse { | |||||
export interface SaveCustomerResponse { | |||||
customer: Customer; | customer: Customer; | ||||
message: string; | message: string; | ||||
} | } | ||||
@@ -61,7 +65,7 @@ export const fetchCustomerTypes = cache(async () => { | |||||
return serverFetchJson<CustomerType[]>( | return serverFetchJson<CustomerType[]>( | ||||
`${BASE_API_URL}/customer/types`, | `${BASE_API_URL}/customer/types`, | ||||
{ | { | ||||
next: { tags: ["CustomerTypes"] }, | |||||
next: { tags: ["customerTypes"] }, | |||||
}, | }, | ||||
); | ); | ||||
}); | }); |
@@ -1,7 +1,7 @@ | |||||
export function getDeletedRecordWithRefList(referenceList: Array<Number>, updatedList: Array<Number>) { | |||||
export function getDeletedRecordWithRefList(referenceList: Array<number>, updatedList: Array<number>) { | |||||
return referenceList.filter(x => !updatedList.includes(x)); | return referenceList.filter(x => !updatedList.includes(x)); | ||||
} | } | ||||
export function getNewRecordWithRefList(referenceList: Array<Number>, updatedList: Array<Number>) { | |||||
export function getNewRecordWithRefList(referenceList: Array<number>, updatedList: Array<number>) { | |||||
return updatedList.filter(x => !referenceList.includes(x)); | return updatedList.filter(x => !referenceList.includes(x)); | ||||
} | } |
@@ -25,6 +25,7 @@ type FetchParams = Parameters<typeof fetch>; | |||||
export async function serverFetchJson<T>(...args: FetchParams) { | export async function serverFetchJson<T>(...args: FetchParams) { | ||||
const response = await serverFetch(...args); | const response = await serverFetch(...args); | ||||
if (response.ok) { | if (response.ok) { | ||||
return response.json() as T; | return response.json() as T; | ||||
} else { | } else { | ||||
@@ -38,6 +39,22 @@ export async function serverFetchJson<T>(...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 = () => { | export const signOutUser = () => { | ||||
const headersList = headers(); | const headersList = headers(); | ||||
const referer = headersList.get("referer"); | const referer = headersList.get("referer"); | ||||
@@ -16,6 +16,7 @@ const pathToLabelMap: { [path: string]: string } = { | |||||
"/tasks/create": "Create Task Template", | "/tasks/create": "Create Task Template", | ||||
"/settings/customer": "Customer", | "/settings/customer": "Customer", | ||||
"/settings/customer/create": "Create Customer", | "/settings/customer/create": "Create Customer", | ||||
"/settings/customer/edit": "Edit Customer", | |||||
"/settings": "Settings", | "/settings": "Settings", | ||||
"/company": "Company", | "/company": "Company", | ||||
"/settings/department": "Department", | "/settings/department": "Department", | ||||
@@ -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<Props> = ({ | |||||
customerTypes, | |||||
}) => { | |||||
const { t } = useTranslation(); | |||||
const { | |||||
register, | |||||
formState: { errors }, | |||||
control, | |||||
reset | |||||
} = useFormContext<CustomerFormInputs>(); | |||||
return ( | |||||
<> | |||||
<Card sx={{ display: "block" }}> | |||||
<CardContent component={Stack} spacing={4}> | |||||
<Box> | |||||
<Typography variant="overline" display="block" marginBlockEnd={1}> | |||||
{t("Customer Details")} | |||||
</Typography> | |||||
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Customer Code")} | |||||
fullWidth | |||||
{...register("code", { | |||||
required: true, | |||||
})} | |||||
error={Boolean(errors.code)} | |||||
helperText={Boolean(errors.code) && (errors.code?.message ? t(errors.code.message) : t("Please input correct customer code"))} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Customer Name")} | |||||
fullWidth | |||||
{...register("name", { | |||||
required: true, | |||||
})} | |||||
error={Boolean(errors.name)} | |||||
helperText={Boolean(errors.name) && t("Please input correct customer name")} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Customer Address")} | |||||
fullWidth | |||||
{...register("address")} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Customer District")} | |||||
fullWidth | |||||
{...register("district")} | |||||
/> | |||||
</Grid> | |||||
{/* <Grid item xs={6}> | |||||
<TextField | |||||
label={t("Customer Email")} | |||||
fullWidth | |||||
{...register("email", { | |||||
pattern: /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/, | |||||
})} | |||||
error={Boolean(errors.email)} | |||||
helperText={Boolean(errors.email) && t("Please input correct customer email")} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Customer Phone")} | |||||
fullWidth | |||||
{...register("phone")} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Customer Contact Name")} | |||||
fullWidth | |||||
{...register("contactName")} | |||||
/> | |||||
</Grid> */} | |||||
<Grid item xs={6}> | |||||
<FormControl fullWidth> | |||||
<InputLabel>{t("Customer Type")}</InputLabel> | |||||
<Controller | |||||
defaultValue={customerTypes[0].id} | |||||
control={control} | |||||
name="typeId" | |||||
render={({ field }) => ( | |||||
<Select label={t("Project Category")} {...field}> | |||||
{customerTypes.map((type, index) => ( | |||||
<MenuItem | |||||
key={`${type.id}-${index}`} | |||||
value={type.id} | |||||
> | |||||
{t(type.name)} | |||||
</MenuItem> | |||||
))} | |||||
</Select> | |||||
)} | |||||
/> | |||||
</FormControl> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Customer Br No.")} | |||||
fullWidth | |||||
{...register("brNo", { | |||||
pattern: /[0-9]{8}/, | |||||
})} | |||||
error={Boolean(errors.brNo)} | |||||
helperText={Boolean(errors.brNo) && t("Please input correct customer br no.")} | |||||
/> | |||||
</Grid> | |||||
</Grid> | |||||
</Box> | |||||
<CardActions sx={{ justifyContent: "flex-end" }}> | |||||
<Button onClick={() => reset()} variant="text" startIcon={<RestartAlt />}> | |||||
{t("Reset")} | |||||
</Button> | |||||
</CardActions> | |||||
</CardContent> | |||||
</Card> | |||||
<ContactDetails/> | |||||
</> | |||||
); | |||||
}; | |||||
export default CustomerDetails; |
@@ -1 +0,0 @@ | |||||
export { default } from "./CreateCustomerWrapper"; |
@@ -1,10 +1,8 @@ | |||||
"use client"; | "use client"; | ||||
import Stack from "@mui/material/Stack"; | import Stack from "@mui/material/Stack"; | ||||
import Box from "@mui/material/Box"; | |||||
import Card from "@mui/material/Card"; | import Card from "@mui/material/Card"; | ||||
import CardContent from "@mui/material/CardContent"; | import CardContent from "@mui/material/CardContent"; | ||||
import Grid from "@mui/material/Grid"; | |||||
import Typography from "@mui/material/Typography"; | import Typography from "@mui/material/Typography"; | ||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import CardActions from "@mui/material/CardActions"; | import CardActions from "@mui/material/CardActions"; | ||||
@@ -19,7 +17,6 @@ import { | |||||
GridRowsProp, | GridRowsProp, | ||||
GridRowModesModel, | GridRowModesModel, | ||||
GridRowModes, | GridRowModes, | ||||
DataGrid, | |||||
GridColDef, | GridColDef, | ||||
GridToolbarContainer, | GridToolbarContainer, | ||||
GridActionsCellItem, | GridActionsCellItem, | ||||
@@ -27,11 +24,8 @@ import { | |||||
GridRowId, | GridRowId, | ||||
GridRowModel, | GridRowModel, | ||||
GridRowEditStopReasons, | GridRowEditStopReasons, | ||||
GridPreProcessEditCellProps, | |||||
GridCellParams, | |||||
} from '@mui/x-data-grid'; | } from '@mui/x-data-grid'; | ||||
import CustomDatagrid from "../CustomDatagrid/CustomDatagrid"; | import CustomDatagrid from "../CustomDatagrid/CustomDatagrid"; | ||||
import { Contact } from "@/app/api/customer"; | |||||
import { useFieldArray, useFormContext } from "react-hook-form"; | import { useFieldArray, useFormContext } from "react-hook-form"; | ||||
import { useCallback, useEffect, useMemo, useState } from "react"; | import { useCallback, useEffect, useMemo, useState } from "react"; | ||||
@@ -69,11 +63,11 @@ function EditToolbar(props: EditToolbarProps) { | |||||
); | ); | ||||
} | } | ||||
const ContactDetails: React.FC<Props> = ({ | |||||
const ContactInfo: React.FC<Props> = ({ | |||||
}) => { | }) => { | ||||
const { t } = useTranslation(); | 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({ | const { fields } = useFieldArray({ | ||||
control, | control, | ||||
name: "addContacts" | name: "addContacts" | ||||
@@ -89,9 +83,16 @@ const ContactDetails: React.FC<Props> = ({ | |||||
}) | }) | ||||
}) | }) | ||||
const [rows, setRows] = useState(initialRows); | |||||
const [rows, setRows] = useState<GridRowsProp>([]); | |||||
const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({}); | const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({}); | ||||
useEffect(() => { | |||||
if (initialRows.length > 0 && rows.length === 0) { | |||||
console.log("first") | |||||
setRows(initialRows) | |||||
} | |||||
}, [initialRows.length > 0]) | |||||
const handleRowEditStop: GridEventListener<'rowEditStop'> = (params, event) => { | const handleRowEditStop: GridEventListener<'rowEditStop'> = (params, event) => { | ||||
if (params.reason === GridRowEditStopReasons.rowFocusOut) { | if (params.reason === GridRowEditStopReasons.rowFocusOut) { | ||||
event.defaultMuiPrevented = true; | event.defaultMuiPrevented = true; | ||||
@@ -137,6 +138,15 @@ const ContactDetails: React.FC<Props> = ({ | |||||
setRowModesModel(newRowModesModel); | setRowModesModel(newRowModesModel); | ||||
}, [rows]); | }, [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<GridColDef[]>( | const columns = useMemo<GridColDef[]>( | ||||
() => [ | () => [ | ||||
{ | { | ||||
@@ -210,6 +220,10 @@ const ContactDetails: React.FC<Props> = ({ | |||||
// check error | // check error | ||||
useEffect(() => { | useEffect(() => { | ||||
if (getValues("addContacts") !== undefined || getValues("addContacts") !== null) { | |||||
return; | |||||
} | |||||
if (getValues("addContacts").length === 0) { | if (getValues("addContacts").length === 0) { | ||||
clearErrors("addContacts") | clearErrors("addContacts") | ||||
} else { | } else { | ||||
@@ -240,13 +254,14 @@ const ContactDetails: React.FC<Props> = ({ | |||||
setValue("isGridEditing", false) | setValue("isGridEditing", false) | ||||
} | } | ||||
}, [rowModesModel]) | }, [rowModesModel]) | ||||
return ( | return ( | ||||
<Card sx={{ display: "block" }}> | <Card sx={{ display: "block" }}> | ||||
<CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}> | <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}> | ||||
<Stack gap={2}> | <Stack gap={2}> | ||||
{/* <div> */} | {/* <div> */} | ||||
<Typography variant="overline" display='inline-block' noWrap> | <Typography variant="overline" display='inline-block' noWrap> | ||||
{t("Contact Details")} | |||||
{t("Contact Info")} | |||||
</Typography> | </Typography> | ||||
{Boolean(errors.addContacts?.type === "required") && <Typography sx={(theme) => ({ color: theme.palette.error.main })} variant="overline" display='inline-block' noWrap> | {Boolean(errors.addContacts?.type === "required") && <Typography sx={(theme) => ({ color: theme.palette.error.main })} variant="overline" display='inline-block' noWrap> | ||||
{t("Please ensure all the fields are inputted and saved")} | {t("Please ensure all the fields are inputted and saved")} | ||||
@@ -275,7 +290,7 @@ const ContactDetails: React.FC<Props> = ({ | |||||
}} | }} | ||||
/> | /> | ||||
<CardActions sx={{ justifyContent: "flex-end" }}> | <CardActions sx={{ justifyContent: "flex-end" }}> | ||||
<Button variant="text" startIcon={<RestartAlt />}> | |||||
<Button variant="text" startIcon={<RestartAlt />} onClick={resetContact} disabled={Boolean(watch("isGridEditing"))}> | |||||
{t("Reset")} | {t("Reset")} | ||||
</Button> | </Button> | ||||
</CardActions> | </CardActions> | ||||
@@ -285,4 +300,4 @@ const ContactDetails: React.FC<Props> = ({ | |||||
); | ); | ||||
}; | }; | ||||
export default ContactDetails; | |||||
export default ContactInfo; |
@@ -6,8 +6,8 @@ import Button from "@mui/material/Button"; | |||||
import Stack from "@mui/material/Stack"; | import Stack from "@mui/material/Stack"; | ||||
import Tab from "@mui/material/Tab"; | import Tab from "@mui/material/Tab"; | ||||
import Tabs, { TabsProps } from "@mui/material/Tabs"; | 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 { useTranslation } from "react-i18next"; | ||||
import { | import { | ||||
FieldErrors, | FieldErrors, | ||||
@@ -18,8 +18,8 @@ import { | |||||
} from "react-hook-form"; | } from "react-hook-form"; | ||||
import { Error } from "@mui/icons-material"; | import { Error } from "@mui/icons-material"; | ||||
import { Typography } from "@mui/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 SubsidiaryAllocation from "./SubsidiaryAllocation"; | ||||
import { CustomerType, Subsidiary } from "@/app/api/customer"; | import { CustomerType, Subsidiary } from "@/app/api/customer"; | ||||
import { getDeletedRecordWithRefList } from "@/app/utils/commonUtil"; | import { getDeletedRecordWithRefList } from "@/app/utils/commonUtil"; | ||||
@@ -42,25 +42,97 @@ const hasErrorsInTab = ( | |||||
} | } | ||||
}; | }; | ||||
const CreateCustomer: React.FC<Props> = ({ | |||||
const CustomerDetail: React.FC<Props> = ({ | |||||
subsidiaries, | subsidiaries, | ||||
customerTypes, | customerTypes, | ||||
}) => { | }) => { | ||||
const [serverError, setServerError] = useState(""); | const [serverError, setServerError] = useState(""); | ||||
const [tabIndex, setTabIndex] = useState(0); | const [tabIndex, setTabIndex] = useState(0); | ||||
const [refCustomer, setRefCustomer] = useState<CustomerFormInputs>() | |||||
const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
const router = useRouter(); | 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<CustomerFormInputs>({ | const formProps = useForm<CustomerFormInputs>({ | ||||
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 = () => { | const handleCancel = () => { | ||||
router.back(); | router.back(); | ||||
}; | }; | ||||
@@ -79,7 +151,7 @@ const CreateCustomer: React.FC<Props> = ({ | |||||
warningDialog(t("Please save all the rows before submitting"), t) | warningDialog(t("Please save all the rows before submitting"), t) | ||||
return false | return false | ||||
} | } | ||||
console.log(data); | console.log(data); | ||||
let haveError = false | let haveError = false | ||||
@@ -93,10 +165,10 @@ const CreateCustomer: React.FC<Props> = ({ | |||||
formProps.setError("code", { message: "Code is empty", type: "required" }) | 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)) { | if (data.brNo && data.brNo?.length > 0 && !/[0-9]{8}/.test(data.brNo)) { | ||||
haveError = true | haveError = true | ||||
@@ -105,12 +177,12 @@ const CreateCustomer: React.FC<Props> = ({ | |||||
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) { | 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 | 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) { | 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 | 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) { | if (haveError) { | ||||
@@ -119,9 +191,9 @@ const CreateCustomer: React.FC<Props> = ({ | |||||
return false | 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(""); | setServerError(""); | ||||
submitDialog(async () => { | submitDialog(async () => { | ||||
@@ -144,7 +216,7 @@ const CreateCustomer: React.FC<Props> = ({ | |||||
setServerError(t("An error has occurred. Please try again later.")); | setServerError(t("An error has occurred. Please try again later.")); | ||||
} | } | ||||
}, | }, | ||||
[router, t], | |||||
[router, t, refCustomer], | |||||
); | ); | ||||
const onSubmitError = useCallback<SubmitErrorHandler<CustomerFormInputs>>( | const onSubmitError = useCallback<SubmitErrorHandler<CustomerFormInputs>>( | ||||
@@ -161,14 +233,14 @@ const CreateCustomer: React.FC<Props> = ({ | |||||
return ( | return ( | ||||
<FormProvider {...formProps}> | <FormProvider {...formProps}> | ||||
<Stack | |||||
{refCustomer && <Stack | |||||
spacing={2} | spacing={2} | ||||
component="form" | component="form" | ||||
onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | ||||
> | > | ||||
<Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | ||||
<Tab | <Tab | ||||
label={t("Customer Details")} | |||||
label={t("Customer Info")} | |||||
icon={ | icon={ | ||||
hasErrorsInTab(0, errors) ? ( | hasErrorsInTab(0, errors) ? ( | ||||
<Error sx={{ marginInlineEnd: 1 }} color="error" /> | <Error sx={{ marginInlineEnd: 1 }} color="error" /> | ||||
@@ -183,7 +255,7 @@ const CreateCustomer: React.FC<Props> = ({ | |||||
{serverError} | {serverError} | ||||
</Typography> | </Typography> | ||||
)} | )} | ||||
{tabIndex === 0 && <CustomerDetails customerTypes={customerTypes} />} | |||||
{tabIndex === 0 && <CustomerInfo customerTypes={customerTypes} />} | |||||
{tabIndex === 1 && <SubsidiaryAllocation subsidiaries={subsidiaries} />} | {tabIndex === 1 && <SubsidiaryAllocation subsidiaries={subsidiaries} />} | ||||
<Stack direction="row" justifyContent="flex-end" gap={1}> | <Stack direction="row" justifyContent="flex-end" gap={1}> | ||||
@@ -194,13 +266,13 @@ const CreateCustomer: React.FC<Props> = ({ | |||||
> | > | ||||
{t("Cancel")} | {t("Cancel")} | ||||
</Button> | </Button> | ||||
<Button variant="contained" startIcon={<Check />} type="submit"> | |||||
<Button variant="contained" startIcon={<Check />} type="submit" disabled={Boolean(formProps.watch("isGridEditing"))}> | |||||
{t("Confirm")} | {t("Confirm")} | ||||
</Button> | </Button> | ||||
</Stack> | </Stack> | ||||
</Stack> | |||||
</Stack>} | |||||
</FormProvider> | </FormProvider> | ||||
); | ); | ||||
}; | }; | ||||
export default CreateCustomer; | |||||
export default CustomerDetail; |
@@ -3,9 +3,18 @@ | |||||
// import { fetchProjectCategories } from "@/app/api/projects"; | // import { fetchProjectCategories } from "@/app/api/projects"; | ||||
// import { fetchTeamLeads } from "@/app/api/staff"; | // import { fetchTeamLeads } from "@/app/api/staff"; | ||||
import { fetchCustomerTypes, fetchSubsidiaries } from "@/app/api/customer"; | 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] = | const [subsidiaries, customerTypes] = | ||||
await Promise.all([ | await Promise.all([ | ||||
fetchSubsidiaries(), | fetchSubsidiaries(), | ||||
@@ -13,8 +22,8 @@ const CreateCustomerWrapper: React.FC = async () => { | |||||
]); | ]); | ||||
return ( | return ( | ||||
<CreateCustomer subsidiaries={subsidiaries} customerTypes={customerTypes}/> | |||||
<CustomerDetail subsidiaries={subsidiaries} customerTypes={customerTypes} /> | |||||
); | ); | ||||
}; | }; | ||||
export default CreateCustomerWrapper; | |||||
export default CustomerDetailWrapper; |
@@ -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<Props> = ({ | |||||
customerTypes, | |||||
}) => { | |||||
const { t } = useTranslation(); | |||||
const { | |||||
register, | |||||
formState: { errors, defaultValues }, | |||||
control, | |||||
reset, | |||||
resetField, | |||||
setValue | |||||
} = useFormContext<CustomerFormInputs>(); | |||||
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 ( | |||||
<> | |||||
<Card sx={{ display: "block" }}> | |||||
<CardContent component={Stack} spacing={4}> | |||||
<Box> | |||||
<Typography variant="overline" display="block" marginBlockEnd={1}> | |||||
{t("Customer Info")} | |||||
</Typography> | |||||
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Customer Code")} | |||||
fullWidth | |||||
{...register("code", { | |||||
required: true, | |||||
})} | |||||
error={Boolean(errors.code)} | |||||
helperText={Boolean(errors.code) && (errors.code?.message ? t(errors.code.message) : t("Please input correct customer code"))} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Customer Name")} | |||||
fullWidth | |||||
{...register("name", { | |||||
required: true, | |||||
})} | |||||
error={Boolean(errors.name)} | |||||
helperText={Boolean(errors.name) && t("Please input correct customer name")} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Customer Address")} | |||||
fullWidth | |||||
{...register("address")} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Customer District")} | |||||
fullWidth | |||||
{...register("district")} | |||||
/> | |||||
</Grid> | |||||
{/* <Grid item xs={6}> | |||||
<TextField | |||||
label={t("Customer Email")} | |||||
fullWidth | |||||
{...register("email", { | |||||
pattern: /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/, | |||||
})} | |||||
error={Boolean(errors.email)} | |||||
helperText={Boolean(errors.email) && t("Please input correct customer email")} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Customer Phone")} | |||||
fullWidth | |||||
{...register("phone")} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Customer Contact Name")} | |||||
fullWidth | |||||
{...register("contactName")} | |||||
/> | |||||
</Grid> */} | |||||
<Grid item xs={6}> | |||||
<FormControl fullWidth> | |||||
<InputLabel>{t("Customer Type")}</InputLabel> | |||||
<Controller | |||||
defaultValue={customerTypes[0].id} | |||||
control={control} | |||||
name="typeId" | |||||
render={({ field }) => ( | |||||
<Select label={t("Project Category")} {...field}> | |||||
{customerTypes.map((type, index) => ( | |||||
<MenuItem | |||||
key={`${type.id}-${index}`} | |||||
value={type.id} | |||||
> | |||||
{t(type.name)} | |||||
</MenuItem> | |||||
))} | |||||
</Select> | |||||
)} | |||||
/> | |||||
</FormControl> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Customer Br No.")} | |||||
fullWidth | |||||
{...register("brNo", { | |||||
pattern: /[0-9]{8}/, | |||||
})} | |||||
error={Boolean(errors.brNo)} | |||||
helperText={Boolean(errors.brNo) && t("Please input correct customer br no.")} | |||||
/> | |||||
</Grid> | |||||
</Grid> | |||||
</Box> | |||||
<CardActions sx={{ justifyContent: "flex-end" }}> | |||||
<Button onClick={resetCustomer} variant="text" startIcon={<RestartAlt />}> | |||||
{t("Reset")} | |||||
</Button> | |||||
</CardActions> | |||||
</CardContent> | |||||
</Card> | |||||
<ContactInfo /> | |||||
</> | |||||
); | |||||
}; | |||||
export default CustomerInfo; |
@@ -12,10 +12,6 @@ import { | |||||
TextField, | TextField, | ||||
InputAdornment, | InputAdornment, | ||||
IconButton, | IconButton, | ||||
FormControl, | |||||
InputLabel, | |||||
Select, | |||||
MenuItem, | |||||
Box, | Box, | ||||
Button, | Button, | ||||
Card, | Card, | ||||
@@ -24,12 +20,9 @@ import { | |||||
TabsProps, | TabsProps, | ||||
Tab, | Tab, | ||||
Tabs, | Tabs, | ||||
SelectChangeEvent, | |||||
} from "@mui/material"; | } from "@mui/material"; | ||||
import differenceBy from "lodash/differenceBy"; | import differenceBy from "lodash/differenceBy"; | ||||
import uniq from "lodash/uniq"; | |||||
import { useFormContext } from "react-hook-form"; | import { useFormContext } from "react-hook-form"; | ||||
import { CreateProjectInputs } from "@/app/api/projects/actions"; | |||||
import { CustomerFormInputs } from "@/app/api/customer/actions"; | import { CustomerFormInputs } from "@/app/api/customer/actions"; | ||||
import { Subsidiary } from "@/app/api/customer"; | import { Subsidiary } from "@/app/api/customer"; | ||||
@@ -41,7 +34,7 @@ const SubsidiaryAllocation: React.FC<Props> = ({ | |||||
subsidiaries, | subsidiaries, | ||||
}) => { | }) => { | ||||
const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
const { setValue, getValues } = useFormContext<CustomerFormInputs>(); | |||||
const { setValue, getValues, formState: { defaultValues }, reset, resetField } = useFormContext<CustomerFormInputs>(); | |||||
const [filteredSubsidiary, setFilteredSubsidiary] = React.useState(subsidiaries); | const [filteredSubsidiary, setFilteredSubsidiary] = React.useState(subsidiaries); | ||||
const [selectedSubsidiary, setSelectedSubsidiary] = React.useState< | const [selectedSubsidiary, setSelectedSubsidiary] = React.useState< | ||||
@@ -49,19 +42,28 @@ const SubsidiaryAllocation: React.FC<Props> = ({ | |||||
>( | >( | ||||
subsidiaries.filter((subsidiary) => | subsidiaries.filter((subsidiary) => | ||||
getValues("addSubsidiaryIds")?.includes(subsidiary.id), | getValues("addSubsidiaryIds")?.includes(subsidiary.id), | ||||
), | |||||
) | |||||
); | ); | ||||
// Adding / Removing staff | // Adding / Removing staff | ||||
const addSubsidiary = React.useCallback((subsidiary: Subsidiary) => { | const addSubsidiary = React.useCallback((subsidiary: Subsidiary) => { | ||||
setSelectedSubsidiary((subsidiaries) => [...subsidiaries, subsidiary]); | setSelectedSubsidiary((subsidiaries) => [...subsidiaries, subsidiary]); | ||||
}, []); | }, []); | ||||
const removeSubsidiary = React.useCallback((subsidiary: Subsidiary) => { | const removeSubsidiary = React.useCallback((subsidiary: Subsidiary) => { | ||||
setSelectedSubsidiary((subsidiaries) => subsidiaries.filter((s) => s.id !== subsidiary.id)); | setSelectedSubsidiary((subsidiaries) => subsidiaries.filter((s) => s.id !== subsidiary.id)); | ||||
}, []); | }, []); | ||||
const clearSubsidiary = React.useCallback(() => { | 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 | // Sync with form | ||||
useEffect(() => { | useEffect(() => { | ||||
setValue( | setValue( | ||||
@@ -81,11 +83,11 @@ const SubsidiaryAllocation: React.FC<Props> = ({ | |||||
{ label: t("Subsidiary Code"), name: "code" }, | { label: t("Subsidiary Code"), name: "code" }, | ||||
{ label: t("Subsidiary Name"), name: "name" }, | { label: t("Subsidiary Name"), name: "name" }, | ||||
{ label: t("Subsidiary Br No."), name: "brNo" }, | { 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 Address"), name: "address" }, | ||||
{ label: t("Subsidiary District"), name: "district" }, | { label: t("Subsidiary District"), name: "district" }, | ||||
{ label: t("Subsidiary Email"), name: "email" }, | |||||
// { label: t("Subsidiary Email"), name: "email" }, | |||||
], | ], | ||||
[addSubsidiary, t], | [addSubsidiary, t], | ||||
); | ); | ||||
@@ -101,11 +103,11 @@ const SubsidiaryAllocation: React.FC<Props> = ({ | |||||
{ label: t("Subsidiary Code"), name: "code" }, | { label: t("Subsidiary Code"), name: "code" }, | ||||
{ label: t("Subsidiary Name"), name: "name" }, | { label: t("Subsidiary Name"), name: "name" }, | ||||
{ label: t("Subsidiary Br No."), name: "brNo" }, | { 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 Address"), name: "address" }, | ||||
{ label: t("Subsidiary District"), name: "district" }, | { label: t("Subsidiary District"), name: "district" }, | ||||
{ label: t("Subsidiary Email"), name: "email" }, | |||||
// { label: t("Subsidiary Email"), name: "email" }, | |||||
], | ], | ||||
[removeSubsidiary, t], | [removeSubsidiary, t], | ||||
); | ); | ||||
@@ -143,14 +145,14 @@ const SubsidiaryAllocation: React.FC<Props> = ({ | |||||
[], | [], | ||||
); | ); | ||||
const reset = React.useCallback(() => { | |||||
const resetSubsidiary = React.useCallback(() => { | |||||
clearQueryInput(); | clearQueryInput(); | ||||
clearSubsidiary(); | clearSubsidiary(); | ||||
}, [clearQueryInput, clearSubsidiary]); | }, [clearQueryInput, clearSubsidiary]); | ||||
return ( | return ( | ||||
<> | <> | ||||
<Card sx={{ display: "block"}}> | |||||
<Card sx={{ display: "block" }}> | |||||
<CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}> | <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}> | ||||
<Stack gap={2}> | <Stack gap={2}> | ||||
<Typography variant="overline" display="block"> | <Typography variant="overline" display="block"> | ||||
@@ -201,7 +203,7 @@ const SubsidiaryAllocation: React.FC<Props> = ({ | |||||
</Box> | </Box> | ||||
</Stack> | </Stack> | ||||
<CardActions sx={{ justifyContent: "flex-end" }}> | <CardActions sx={{ justifyContent: "flex-end" }}> | ||||
<Button variant="text" startIcon={<RestartAlt />} onClick={reset}> | |||||
<Button variant="text" startIcon={<RestartAlt />} onClick={resetSubsidiary}> | |||||
{t("Reset")} | {t("Reset")} | ||||
</Button> | </Button> | ||||
</CardActions> | </CardActions> |
@@ -0,0 +1 @@ | |||||
export { default } from "./CustomerDetailWrapper"; |
@@ -6,6 +6,10 @@ import SearchBox, { Criterion } from "../SearchBox"; | |||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import SearchResults, { Column } from "../SearchResults"; | import SearchResults, { Column } from "../SearchResults"; | ||||
import EditNote from "@mui/icons-material/EditNote"; | 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 { | interface Props { | ||||
customers: Customer[]; | customers: Customer[]; | ||||
@@ -16,6 +20,8 @@ type SearchParamNames = keyof SearchQuery; | |||||
const CustomerSearch: React.FC<Props> = ({ customers }) => { | const CustomerSearch: React.FC<Props> = ({ customers }) => { | ||||
const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
const router = useRouter() | |||||
const searchParams = useSearchParams() | |||||
const [filteredCustomers, setFilteredCustomers] = useState(customers); | const [filteredCustomers, setFilteredCustomers] = useState(customers); | ||||
const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | ||||
@@ -30,7 +36,20 @@ const CustomerSearch: React.FC<Props> = ({ customers }) => { | |||||
}, [customers]); | }, [customers]); | ||||
const onTaskClick = useCallback((customer: Customer) => { | 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<Column<Customer>[]>( | const columns = useMemo<Column<Customer>[]>( | ||||
@@ -43,6 +62,12 @@ const CustomerSearch: React.FC<Props> = ({ customers }) => { | |||||
}, | }, | ||||
{ name: "code", label: t("Customer Code") }, | { name: "code", label: t("Customer Code") }, | ||||
{ name: "name", label: t("Customer Name") }, | { name: "name", label: t("Customer Name") }, | ||||
{ | |||||
name: "id", | |||||
label: t("Delete"), | |||||
onClick: onDeleteClick, | |||||
buttonIcon: <DeleteIcon />, | |||||
}, | |||||
], | ], | ||||
[onTaskClick, t], | [onTaskClick, t], | ||||
); | ); | ||||
@@ -50,18 +50,32 @@ export const warningDialog = (text, t) => { | |||||
}) | }) | ||||
} | } | ||||
export const submitDialog = (confirmAction, t) => { | |||||
export const submitDialog = async (confirmAction, t) => { | |||||
// const { t } = useTranslation("common") | // const { t } = useTranslation("common") | ||||
return Swal.fire({ | |||||
const result = await Swal.fire({ | |||||
icon: "question", | icon: "question", | ||||
title: t("Do you want to submit?"), | title: t("Do you want to submit?"), | ||||
cancelButtonText: t("Cancel"), | cancelButtonText: t("Cancel"), | ||||
confirmButtonText: t("Submit"), | confirmButtonText: t("Submit"), | ||||
showCancelButton: true, | showCancelButton: true, | ||||
showConfirmButton: 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(); | |||||
} | |||||
} | } |
@@ -11,6 +11,7 @@ | |||||
"Customer Contact Name": "Client Contact Name", | "Customer Contact Name": "Client Contact Name", | ||||
"Customer Br No.": "Client Br No.", | "Customer Br No.": "Client Br No.", | ||||
"Customer Details": "Client Details", | "Customer Details": "Client Details", | ||||
"Customer Info": "Client Info", | |||||
"Customer Type": "Client Type", | "Customer Type": "Client Type", | ||||
"Please input correct customer code": "Please input correct client code", | "Please input correct customer code": "Please input correct client code", | ||||
@@ -33,9 +34,11 @@ | |||||
"Subsidiary Contact Name": "Subsidiary Contact Name", | "Subsidiary Contact Name": "Subsidiary Contact Name", | ||||
"Subsidiary Br No.": "Subsidiary Br No.", | "Subsidiary Br No.": "Subsidiary Br No.", | ||||
"Subsidiary Details": "Subsidiary Details", | "Subsidiary Details": "Subsidiary Details", | ||||
"Subsidiary Info": "Subsidiary Info", | |||||
"Add Contact Person": "Add Contact Person", | "Add Contact Person": "Add Contact Person", | ||||
"Contact Details": "Contact Details", | "Contact Details": "Contact Details", | ||||
"Contact Info": "Contact Info", | |||||
"Contact Name": "Contact Name", | "Contact Name": "Contact Name", | ||||
"Contact Email": "Contact Email", | "Contact Email": "Contact Email", | ||||
"Contact Phone": "Contact Phone", | "Contact Phone": "Contact Phone", | ||||
@@ -44,13 +47,18 @@ | |||||
"Do you want to submit?": "Do you want to submit?", | "Do you want to submit?": "Do you want to submit?", | ||||
"Submit Success": "Submit Success", | "Submit Success": "Submit Success", | ||||
"Submit Fail": "Submit Fail", | |||||
"Do you want to delete?": "Do you want to delete", | |||||
"Delete Success": "Delete Success", | |||||
"Add": "Add", | "Add": "Add", | ||||
"Details": "Details", | "Details": "Details", | ||||
"Info": "Info", | |||||
"Search": "Search", | "Search": "Search", | ||||
"Search Criteria": "Search Criteria", | "Search Criteria": "Search Criteria", | ||||
"Cancel": "Cancel", | "Cancel": "Cancel", | ||||
"Confirm": "Confirm", | "Confirm": "Confirm", | ||||
"Submit": "Submit", | "Submit": "Submit", | ||||
"Reset": "Reset" | |||||
"Reset": "Reset", | |||||
"Delete": "Delete" | |||||
} | } |
@@ -11,6 +11,7 @@ | |||||
"Customer Contact Name": "客戶聯絡名稱", | "Customer Contact Name": "客戶聯絡名稱", | ||||
"Customer Br No.": "客戶商業登記號碼", | "Customer Br No.": "客戶商業登記號碼", | ||||
"Customer Details": "客戶詳請", | "Customer Details": "客戶詳請", | ||||
"Customer Info": "客戶資料", | |||||
"Customer Type": "客戶類型", | "Customer Type": "客戶類型", | ||||
"Please input correct customer code": "請輸入客戶編號", | "Please input correct customer code": "請輸入客戶編號", | ||||
@@ -33,9 +34,11 @@ | |||||
"Subsidiary Contact Name": "子公司聯絡名稱", | "Subsidiary Contact Name": "子公司聯絡名稱", | ||||
"Subsidiary Br No.": "子公司商業登記號碼", | "Subsidiary Br No.": "子公司商業登記號碼", | ||||
"Subsidiary Details": "子公司詳請", | "Subsidiary Details": "子公司詳請", | ||||
"Subsidiary Info": "子公司資料", | |||||
"Add Contact Person": "新增聯絡人", | "Add Contact Person": "新增聯絡人", | ||||
"Contact Details": "聯絡詳請", | "Contact Details": "聯絡詳請", | ||||
"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": "提交失敗", | |||||
"Do you want to delete?": "你是否確認要刪除?", | |||||
"Delete Success": "刪除成功", | |||||
"Add": "新增", | "Add": "新增", | ||||
"Details": "詳請", | "Details": "詳請", | ||||
"Info": "資料", | |||||
"Search": "搜尋", | "Search": "搜尋", | ||||
"Search Criteria": "搜尋條件", | "Search Criteria": "搜尋條件", | ||||
"Cancel": "取消", | "Cancel": "取消", | ||||
"Confirm": "確認", | "Confirm": "確認", | ||||
"Submit": "提交", | "Submit": "提交", | ||||
"Reset": "重置" | |||||
"Reset": "重置", | |||||
"Delete": "刪除" | |||||
} | } |