@@ -17,7 +17,7 @@ const Projects: React.FC = async () => { | |||||
return ( | return ( | ||||
<> | <> | ||||
<Typography variant="h4">{t("Create Customer")}</Typography> | <Typography variant="h4">{t("Create Customer")}</Typography> | ||||
<I18nProvider namespaces={["customer"]}> | |||||
<I18nProvider namespaces={["customer", "common"]}> | |||||
<CreateCustomer /> | <CreateCustomer /> | ||||
</I18nProvider> | </I18nProvider> | ||||
</> | </> |
@@ -33,7 +33,7 @@ const Customer: React.FC = async () => { | |||||
variant="contained" | variant="contained" | ||||
startIcon={<Add />} | startIcon={<Add />} | ||||
LinkComponent={Link} | LinkComponent={Link} | ||||
href="/customer/create" | |||||
href="/settings/customer/create" | |||||
> | > | ||||
{t("Create Customer")} | {t("Create Customer")} | ||||
</Button> | </Button> |
@@ -2,12 +2,14 @@ | |||||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | import { serverFetchJson } from "@/app/utils/fetchUtil"; | ||||
import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
import { Customer } from "."; | |||||
import { Contact, NewCustomerResponse } from "."; | |||||
import { revalidateTag } from "next/cache"; | import { revalidateTag } from "next/cache"; | ||||
export interface CustomerFormInputs { | export interface CustomerFormInputs { | ||||
// Customer details | // Customer details | ||||
id: number | null; | |||||
name: string; | name: string; | ||||
code: string; | code: string; | ||||
address: string | null; | address: string | null; | ||||
@@ -16,14 +18,22 @@ export interface CustomerFormInputs { | |||||
phone: string | null; | phone: string | null; | ||||
contactName: string | null; | contactName: string | null; | ||||
brNo: string | null; | brNo: string | null; | ||||
typeId: number; | |||||
// Subsidiary | // Subsidiary | ||||
addSubsidiaryIds: number[]; | addSubsidiaryIds: number[]; | ||||
deleteSubsidiaryIds: number[]; | deleteSubsidiaryIds: number[]; | ||||
// Contact | |||||
addContacts: Contact[]; | |||||
deleteContactIds: number[]; | |||||
// is grid editing | |||||
isGridEditing: boolean | null; | |||||
} | } | ||||
export const saveCustomer = async (data: CustomerFormInputs) => { | export const saveCustomer = async (data: CustomerFormInputs) => { | ||||
const saveCustomer = await serverFetchJson<Customer>( | |||||
const saveCustomer = await serverFetchJson<NewCustomerResponse>( | |||||
`${BASE_API_URL}/customer/save`, | `${BASE_API_URL}/customer/save`, | ||||
{ | { | ||||
method: "POST", | method: "POST", | ||||
@@ -9,6 +9,16 @@ export interface Customer { | |||||
name: string; | name: string; | ||||
} | } | ||||
export interface NewCustomerResponse { | |||||
customer: Customer; | |||||
message: string; | |||||
} | |||||
export interface CustomerType { | |||||
id: number; | |||||
name: string; | |||||
} | |||||
export interface Subsidiary { | export interface Subsidiary { | ||||
id: number; | id: number; | ||||
code: string; | code: string; | ||||
@@ -22,6 +32,14 @@ export interface Subsidiary { | |||||
email: string | null; | email: string | null; | ||||
} | } | ||||
export interface Contact { | |||||
id: number; | |||||
name: string; | |||||
phone: string; | |||||
email: string; | |||||
isNew: boolean; | |||||
} | |||||
export const preloadAllCustomers = () => { | export const preloadAllCustomers = () => { | ||||
fetchAllCustomers(); | fetchAllCustomers(); | ||||
}; | }; | ||||
@@ -38,3 +56,12 @@ export const fetchSubsidiaries = cache(async () => { | |||||
}, | }, | ||||
); | ); | ||||
}); | }); | ||||
export const fetchCustomerTypes = cache(async () => { | |||||
return serverFetchJson<CustomerType[]>( | |||||
`${BASE_API_URL}/customer/types`, | |||||
{ | |||||
next: { tags: ["CustomerTypes"] }, | |||||
}, | |||||
); | |||||
}); |
@@ -14,8 +14,8 @@ const pathToLabelMap: { [path: string]: string } = { | |||||
"/projects/create": "Create Project", | "/projects/create": "Create Project", | ||||
"/tasks": "Task Template", | "/tasks": "Task Template", | ||||
"/tasks/create": "Create Task Template", | "/tasks/create": "Create Task Template", | ||||
"/customer": "Customer", | |||||
"/customer/create": "Create Customer", | |||||
"/settings/customer": "Customer", | |||||
"/settings/customer/create": "Create Customer", | |||||
"/settings": "Settings", | "/settings": "Settings", | ||||
"/company": "Company", | "/company": "Company", | ||||
"/settings/department": "Department", | "/settings/department": "Department", | ||||
@@ -0,0 +1,288 @@ | |||||
"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"; | |||||
import RestartAlt from "@mui/icons-material/RestartAlt"; | |||||
import Button from "@mui/material/Button"; | |||||
import AddIcon from '@mui/icons-material/Add'; | |||||
import EditIcon from '@mui/icons-material/Edit'; | |||||
import DeleteIcon from '@mui/icons-material/DeleteOutlined'; | |||||
import SaveIcon from '@mui/icons-material/Save'; | |||||
import CancelIcon from '@mui/icons-material/Close'; | |||||
import { | |||||
GridRowsProp, | |||||
GridRowModesModel, | |||||
GridRowModes, | |||||
DataGrid, | |||||
GridColDef, | |||||
GridToolbarContainer, | |||||
GridActionsCellItem, | |||||
GridEventListener, | |||||
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"; | |||||
interface Props { | |||||
} | |||||
interface EditToolbarProps { | |||||
setRows: (newRows: (oldRows: GridRowsProp) => GridRowsProp) => void; | |||||
setRowModesModel: ( | |||||
newModel: (oldModel: GridRowModesModel) => GridRowModesModel, | |||||
) => void; | |||||
} | |||||
var rowId = -1 | |||||
function EditToolbar(props: EditToolbarProps) { | |||||
const { setRows, setRowModesModel } = props; | |||||
const { t } = useTranslation(); | |||||
const handleClick = () => { | |||||
const id = rowId; | |||||
rowId = rowId - 1; | |||||
setRows((oldRows) => [{ id, name: '', phone: '', email: '', isNew: true }, ...oldRows]); | |||||
setRowModesModel((oldModel) => ({ | |||||
...oldModel, | |||||
[id]: { mode: GridRowModes.Edit, fieldToFocus: 'name' }, | |||||
})); | |||||
}; | |||||
return ( | |||||
<GridToolbarContainer> | |||||
<Button color="primary" startIcon={<AddIcon />} onClick={handleClick}> | |||||
{t("Add Contact Person")} | |||||
</Button> | |||||
</GridToolbarContainer> | |||||
); | |||||
} | |||||
const ContactDetails: React.FC<Props> = ({ | |||||
}) => { | |||||
const { t } = useTranslation(); | |||||
const { control, setValue, getValues, formState: { errors }, setError, clearErrors } = useFormContext(); | |||||
const { fields } = useFieldArray({ | |||||
control, | |||||
name: "addContacts" | |||||
}) | |||||
const initialRows: GridRowsProp = fields.map((item, index) => { | |||||
return ({ | |||||
id: Number(getValues(`addContacts[${index}].id`)), | |||||
name: getValues(`addContacts[${index}].name`), | |||||
phone: getValues(`addContacts[${index}].phone`), | |||||
email: getValues(`addContacts[${index}].email`), | |||||
isNew: false, | |||||
}) | |||||
}) | |||||
const [rows, setRows] = useState(initialRows); | |||||
const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({}); | |||||
const handleRowEditStop: GridEventListener<'rowEditStop'> = (params, event) => { | |||||
if (params.reason === GridRowEditStopReasons.rowFocusOut) { | |||||
event.defaultMuiPrevented = true; | |||||
} | |||||
}; | |||||
const handleEditClick = (id: GridRowId) => () => { | |||||
setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.Edit } }); | |||||
}; | |||||
const handleSaveClick = (id: GridRowId) => () => { | |||||
setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View } }); | |||||
}; | |||||
const handleDeleteClick = (id: GridRowId) => () => { | |||||
const updatedRows = rows.filter((row) => row.id !== id) | |||||
setRows(updatedRows); | |||||
setValue("addContacts", updatedRows) | |||||
}; | |||||
const handleCancelClick = (id: GridRowId) => () => { | |||||
setRowModesModel({ | |||||
...rowModesModel, | |||||
[id]: { mode: GridRowModes.View, ignoreModifications: true }, | |||||
}); | |||||
const editedRow = rows.find((row) => row.id === id); | |||||
if (editedRow!.isNew) { | |||||
setRows(rows.filter((row) => row.id !== id)); | |||||
} | |||||
}; | |||||
const processRowUpdate = useCallback((newRow: GridRowModel) => { | |||||
const updatedRow = { ...newRow }; | |||||
const updatedRows = rows.map((row) => (row.id === newRow.id ? updatedRow : row)) | |||||
setRows(updatedRows); | |||||
setValue("addContacts", updatedRows) | |||||
return updatedRow; | |||||
}, [rows]); | |||||
const handleRowModesModelChange = useCallback((newRowModesModel: GridRowModesModel) => { | |||||
setRowModesModel(newRowModesModel); | |||||
}, [rows]); | |||||
const columns = useMemo<GridColDef[]>( | |||||
() => [ | |||||
{ | |||||
field: 'name', | |||||
headerName: t('Contact Name'), | |||||
editable: true, | |||||
flex: 1, | |||||
}, | |||||
{ | |||||
field: 'phone', | |||||
headerName: t('Contact Phone'), | |||||
editable: true, | |||||
flex: 1, | |||||
}, | |||||
{ | |||||
field: 'email', | |||||
headerName: t('Contact Email'), | |||||
editable: true, | |||||
flex: 1, | |||||
}, | |||||
{ | |||||
field: 'actions', | |||||
type: 'actions', | |||||
headerName: '', | |||||
flex: 0.6, | |||||
// width: 100, | |||||
cellClassName: 'actions', | |||||
getActions: ({ id, ...params }) => { | |||||
const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit; | |||||
if (isInEditMode) { | |||||
return [ | |||||
<GridActionsCellItem | |||||
icon={<SaveIcon />} | |||||
label="Save" | |||||
sx={{ | |||||
color: 'primary.main', | |||||
}} | |||||
onClick={handleSaveClick(id)} | |||||
/>, | |||||
<GridActionsCellItem | |||||
icon={<CancelIcon />} | |||||
label="Cancel" | |||||
className="textPrimary" | |||||
onClick={handleCancelClick(id)} | |||||
color="inherit" | |||||
/>, | |||||
]; | |||||
} | |||||
return [ | |||||
<GridActionsCellItem | |||||
icon={<EditIcon />} | |||||
label="Edit" | |||||
className="textPrimary" | |||||
onClick={handleEditClick(id)} | |||||
color="inherit" | |||||
/>, | |||||
<GridActionsCellItem | |||||
icon={<DeleteIcon />} | |||||
label="Delete" | |||||
onClick={handleDeleteClick(id)} | |||||
color="inherit" | |||||
/>, | |||||
]; | |||||
}, | |||||
}, | |||||
], | |||||
[rows, rowModesModel, t], | |||||
); | |||||
// check error | |||||
useEffect(() => { | |||||
if (getValues("addContacts").length === 0) { | |||||
clearErrors("addContacts") | |||||
} else { | |||||
const errorRows = rows.filter(row => String(row.name).trim().length === 0 || String(row.phone).trim().length === 0 || String(row.email).trim().length === 0) | |||||
if (errorRows.length > 0) { | |||||
setError("addContacts", { message: "Contact details include empty fields", type: "required" }) | |||||
} else { | |||||
const errorRows_EmailFormat = rows.filter(row => !/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/.test(String(row.email))) | |||||
if (errorRows_EmailFormat.length > 0) { | |||||
setError("addContacts", { message: "Contact details include empty fields", type: "email_format" }) | |||||
} else { | |||||
clearErrors("addContacts") | |||||
} | |||||
} | |||||
} | |||||
}, [rows]) | |||||
// check editing | |||||
useEffect(() => { | |||||
const filteredByKey = Object.fromEntries( | |||||
Object.entries(rowModesModel).filter(([key, value]) => rowModesModel[key].mode === 'edit')) | |||||
if (Object.keys(filteredByKey).length > 0) { | |||||
setValue("isGridEditing", true) | |||||
} else { | |||||
setValue("isGridEditing", false) | |||||
} | |||||
}, [rowModesModel]) | |||||
return ( | |||||
<Card sx={{ display: "block" }}> | |||||
<CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}> | |||||
<Stack gap={2}> | |||||
{/* <div> */} | |||||
<Typography variant="overline" display='inline-block' noWrap> | |||||
{t("Contact Details")} | |||||
</Typography> | |||||
{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")} | |||||
</Typography>} | |||||
{Boolean(errors.addContacts?.type === "email_format") && <Typography sx={(theme) => ({ color: theme.palette.error.main })} variant="overline" display='inline-block' noWrap> | |||||
{t("Please ensure all the email formats are correct")} | |||||
</Typography>} | |||||
{/* </div> */} | |||||
<CustomDatagrid | |||||
rows={[...rows]} | |||||
columns={columns} | |||||
editMode="row" | |||||
rowModesModel={rowModesModel} | |||||
onRowEditStop={handleRowEditStop} | |||||
processRowUpdate={processRowUpdate} | |||||
// onProcessRowUpdateError={handleProcessRowUpdateError} | |||||
onRowModesModelChange={handleRowModesModelChange} | |||||
slots={{ | |||||
toolbar: EditToolbar, | |||||
}} | |||||
slotProps={{ | |||||
toolbar: { setRows, setRowModesModel }, | |||||
}} | |||||
sx={{ | |||||
height: '100%' | |||||
}} | |||||
/> | |||||
<CardActions sx={{ justifyContent: "flex-end" }}> | |||||
<Button variant="text" startIcon={<RestartAlt />}> | |||||
{t("Reset")} | |||||
</Button> | |||||
</CardActions> | |||||
</Stack> | |||||
</CardContent> | |||||
</Card> | |||||
); | |||||
}; | |||||
export default ContactDetails; |
@@ -9,7 +9,6 @@ import Tabs, { TabsProps } from "@mui/material/Tabs"; | |||||
import { useRouter } from "next/navigation"; | import { useRouter } from "next/navigation"; | ||||
import React, { useCallback, useState } from "react"; | import React, { useCallback, useState } from "react"; | ||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import { Task, TaskTemplate } from "@/app/api/tasks"; | |||||
import { | import { | ||||
FieldErrors, | FieldErrors, | ||||
FormProvider, | FormProvider, | ||||
@@ -17,18 +16,18 @@ import { | |||||
SubmitHandler, | SubmitHandler, | ||||
useForm, | useForm, | ||||
} from "react-hook-form"; | } from "react-hook-form"; | ||||
import { CreateProjectInputs, saveProject } from "@/app/api/projects/actions"; | |||||
import { Error } from "@mui/icons-material"; | import { Error } from "@mui/icons-material"; | ||||
import { ProjectCategory } from "@/app/api/projects"; | |||||
import { Typography } from "@mui/material"; | import { Typography } from "@mui/material"; | ||||
import { CustomerFormInputs, saveCustomer } from "@/app/api/customer/actions"; | import { CustomerFormInputs, saveCustomer } from "@/app/api/customer/actions"; | ||||
import CustomerDetails from "./CustomerDetails"; | import CustomerDetails from "./CustomerDetails"; | ||||
import SubsidiaryAllocation from "./SubsidiaryAllocation"; | import SubsidiaryAllocation from "./SubsidiaryAllocation"; | ||||
import { Subsidiary } from "@/app/api/customer"; | |||||
import { CustomerType, Subsidiary } from "@/app/api/customer"; | |||||
import { getDeletedRecordWithRefList } from "@/app/utils/commonUtil"; | import { getDeletedRecordWithRefList } from "@/app/utils/commonUtil"; | ||||
import { errorDialog, submitDialog, successDialog, warningDialog } from "../Swal/CustomAlerts"; | |||||
export interface Props { | export interface Props { | ||||
subsidiaries: Subsidiary[], | subsidiaries: Subsidiary[], | ||||
customerTypes: CustomerType[], | |||||
} | } | ||||
const hasErrorsInTab = ( | const hasErrorsInTab = ( | ||||
@@ -37,7 +36,7 @@ const hasErrorsInTab = ( | |||||
) => { | ) => { | ||||
switch (tabIndex) { | switch (tabIndex) { | ||||
case 0: | case 0: | ||||
return errors.name; | |||||
return Object.keys(errors).length > 0; | |||||
default: | default: | ||||
false; | false; | ||||
} | } | ||||
@@ -45,6 +44,7 @@ const hasErrorsInTab = ( | |||||
const CreateCustomer: React.FC<Props> = ({ | const CreateCustomer: React.FC<Props> = ({ | ||||
subsidiaries, | subsidiaries, | ||||
customerTypes, | |||||
}) => { | }) => { | ||||
const [serverError, setServerError] = useState(""); | const [serverError, setServerError] = useState(""); | ||||
const [tabIndex, setTabIndex] = useState(0); | const [tabIndex, setTabIndex] = useState(0); | ||||
@@ -54,6 +54,10 @@ const CreateCustomer: React.FC<Props> = ({ | |||||
defaultValues: { | defaultValues: { | ||||
code: "", | code: "", | ||||
name: "", | name: "", | ||||
addContacts: [], | |||||
addSubsidiaryIds: [], | |||||
deleteSubsidiaryIds: [], | |||||
deleteContactIds: [] | |||||
}, | }, | ||||
}); | }); | ||||
@@ -71,27 +75,42 @@ const CreateCustomer: React.FC<Props> = ({ | |||||
const onSubmit = useCallback<SubmitHandler<CustomerFormInputs>>( | const onSubmit = useCallback<SubmitHandler<CustomerFormInputs>>( | ||||
async (data) => { | async (data) => { | ||||
try { | try { | ||||
if (data.isGridEditing) { | |||||
warningDialog(t("Please save all the rows before submitting"), t) | |||||
return false | |||||
} | |||||
console.log(data); | console.log(data); | ||||
let haveError = false | let haveError = false | ||||
if (data.name.length === 0) { | if (data.name.length === 0) { | ||||
haveError = true | haveError = true | ||||
formProps.setError("name", {message: "Name is empty", type: "required"}) | |||||
formProps.setError("name", { message: "Name is empty", type: "required" }) | |||||
} | } | ||||
if (data.code.length === 0) { | if (data.code.length === 0) { | ||||
haveError = true | haveError = true | ||||
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)) { | if (data.email && data.email?.length > 0 && !/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/.test(data.email)) { | ||||
haveError = true | haveError = true | ||||
formProps.setError("email", {message: "Email format is not valid", type: "custom"}) | |||||
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 | ||||
formProps.setError("brNo", {message: "Br No. format is not valid", type: "custom"}) | |||||
formProps.setError("brNo", { message: "Br No. format is not valid", type: "custom" }) | |||||
} | |||||
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" }) | |||||
} | |||||
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" }) | |||||
} | } | ||||
if (haveError) { | if (haveError) { | ||||
@@ -100,11 +119,28 @@ const CreateCustomer: React.FC<Props> = ({ | |||||
return false | return false | ||||
} | } | ||||
data.deleteSubsidiaryIds = [] | |||||
// data.deleteSubsidiaryIds = data.deleteSubsidiaryIds ?? [] | |||||
// data.addSubsidiaryIds = data.addSubsidiaryIds ?? [] | |||||
// data.deleteContactIds = data.deleteContactIds ?? [] | |||||
setServerError(""); | setServerError(""); | ||||
await saveCustomer(data); | |||||
router.replace("/customer"); | |||||
submitDialog(async () => { | |||||
const response = await saveCustomer(data); | |||||
if (response.message === "Success") { | |||||
successDialog(t("Submit Success"), t).then(() => { | |||||
router.replace("/settings/customer"); | |||||
}) | |||||
} else { | |||||
errorDialog(t("Submit Fail"), t).then(() => { | |||||
formProps.setError("code", { message: response.message, type: "custom" }) | |||||
setTabIndex(0) | |||||
return false | |||||
}) | |||||
} | |||||
}, t) | |||||
} catch (e) { | } catch (e) { | ||||
console.log(e) | |||||
setServerError(t("An error has occurred. Please try again later.")); | setServerError(t("An error has occurred. Please try again later.")); | ||||
} | } | ||||
}, | }, | ||||
@@ -114,7 +150,7 @@ const CreateCustomer: React.FC<Props> = ({ | |||||
const onSubmitError = useCallback<SubmitErrorHandler<CustomerFormInputs>>( | const onSubmitError = useCallback<SubmitErrorHandler<CustomerFormInputs>>( | ||||
(errors) => { | (errors) => { | ||||
// Set the tab so that the focus will go there | // Set the tab so that the focus will go there | ||||
if (errors.name || errors.code) { | |||||
if (Object.keys(errors).length > 0) { | |||||
setTabIndex(0); | setTabIndex(0); | ||||
} | } | ||||
}, | }, | ||||
@@ -147,8 +183,8 @@ const CreateCustomer: React.FC<Props> = ({ | |||||
{serverError} | {serverError} | ||||
</Typography> | </Typography> | ||||
)} | )} | ||||
{tabIndex === 0 && <CustomerDetails/>} | |||||
{tabIndex === 1 && <SubsidiaryAllocation subsidiaries={subsidiaries}/>} | |||||
{tabIndex === 0 && <CustomerDetails customerTypes={customerTypes} />} | |||||
{tabIndex === 1 && <SubsidiaryAllocation subsidiaries={subsidiaries} />} | |||||
<Stack direction="row" justifyContent="flex-end" gap={1}> | <Stack direction="row" justifyContent="flex-end" gap={1}> | ||||
<Button | <Button | ||||
@@ -2,17 +2,18 @@ | |||||
// import CreateProject from "./CreateProject"; | // import CreateProject from "./CreateProject"; | ||||
// 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 { fetchSubsidiaries } from "@/app/api/customer"; | |||||
import { fetchCustomerTypes, fetchSubsidiaries } from "@/app/api/customer"; | |||||
import CreateCustomer from "./CreateCustomer"; | import CreateCustomer from "./CreateCustomer"; | ||||
const CreateCustomerWrapper: React.FC = async () => { | const CreateCustomerWrapper: React.FC = async () => { | ||||
const [subsidiaries] = | |||||
const [subsidiaries, customerTypes] = | |||||
await Promise.all([ | await Promise.all([ | ||||
fetchSubsidiaries(), | fetchSubsidiaries(), | ||||
fetchCustomerTypes(), | |||||
]); | ]); | ||||
return ( | return ( | ||||
<CreateCustomer subsidiaries={subsidiaries}/> | |||||
<CreateCustomer subsidiaries={subsidiaries} customerTypes={customerTypes}/> | |||||
); | ); | ||||
}; | }; | ||||
@@ -11,22 +11,30 @@ import { useTranslation } from "react-i18next"; | |||||
import CardActions from "@mui/material/CardActions"; | import CardActions from "@mui/material/CardActions"; | ||||
import RestartAlt from "@mui/icons-material/RestartAlt"; | import RestartAlt from "@mui/icons-material/RestartAlt"; | ||||
import Button from "@mui/material/Button"; | import Button from "@mui/material/Button"; | ||||
import { useFormContext } from "react-hook-form"; | |||||
import { Controller, useFormContext } from "react-hook-form"; | |||||
import { CustomerFormInputs } from "@/app/api/customer/actions"; | 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 { | interface Props { | ||||
customerTypes: CustomerType[], | |||||
} | } | ||||
const CustomerDetails: React.FC<Props> = ({ | const CustomerDetails: React.FC<Props> = ({ | ||||
customerTypes, | |||||
}) => { | }) => { | ||||
const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
const { | const { | ||||
register, | register, | ||||
formState: { errors }, | formState: { errors }, | ||||
control, | |||||
reset | |||||
} = useFormContext<CustomerFormInputs>(); | } = useFormContext<CustomerFormInputs>(); | ||||
return ( | return ( | ||||
<Card sx={{ display: "block"}}> | |||||
<> | |||||
<Card sx={{ display: "block" }}> | |||||
<CardContent component={Stack} spacing={4}> | <CardContent component={Stack} spacing={4}> | ||||
<Box> | <Box> | ||||
<Typography variant="overline" display="block" marginBlockEnd={1}> | <Typography variant="overline" display="block" marginBlockEnd={1}> | ||||
@@ -41,7 +49,7 @@ const CustomerDetails: React.FC<Props> = ({ | |||||
required: true, | required: true, | ||||
})} | })} | ||||
error={Boolean(errors.code)} | error={Boolean(errors.code)} | ||||
helperText={Boolean(errors.code) && t("Please input correct customer code")} | |||||
helperText={Boolean(errors.code) && (errors.code?.message ? t(errors.code.message) : t("Please input correct customer code"))} | |||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={6}> | <Grid item xs={6}> | ||||
@@ -69,7 +77,7 @@ const CustomerDetails: React.FC<Props> = ({ | |||||
{...register("district")} | {...register("district")} | ||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={6}> | |||||
{/* <Grid item xs={6}> | |||||
<TextField | <TextField | ||||
label={t("Customer Email")} | label={t("Customer Email")} | ||||
fullWidth | fullWidth | ||||
@@ -93,6 +101,28 @@ const CustomerDetails: React.FC<Props> = ({ | |||||
fullWidth | fullWidth | ||||
{...register("contactName")} | {...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> | ||||
<Grid item xs={6}> | <Grid item xs={6}> | ||||
<TextField | <TextField | ||||
@@ -108,12 +138,14 @@ const CustomerDetails: React.FC<Props> = ({ | |||||
</Grid> | </Grid> | ||||
</Box> | </Box> | ||||
<CardActions sx={{ justifyContent: "flex-end" }}> | <CardActions sx={{ justifyContent: "flex-end" }}> | ||||
<Button variant="text" startIcon={<RestartAlt />}> | |||||
<Button onClick={() => reset()} variant="text" startIcon={<RestartAlt />}> | |||||
{t("Reset")} | {t("Reset")} | ||||
</Button> | </Button> | ||||
</CardActions> | </CardActions> | ||||
</CardContent> | </CardContent> | ||||
</Card> | </Card> | ||||
<ContactDetails/> | |||||
</> | |||||
); | ); | ||||
}; | }; | ||||
@@ -99,8 +99,8 @@ const navigationItems: NavigationItem[] = [ | |||||
{ | { | ||||
icon: <Settings />, label: "Setting", path: "", | icon: <Settings />, label: "Setting", path: "", | ||||
children: [ | children: [ | ||||
{ icon: <GroupIcon />, label: "Customer", path: "/customer" }, | |||||
{ icon: <Staff />, label: "Staff", path: "/staff" }, | |||||
{ icon: <GroupIcon />, label: "Customer", path: "/settings/customer" }, | |||||
{ icon: <Staff />, label: "Staff", path: "/settings/staff" }, | |||||
{ icon: <Company />, label: "Company", path: "/settings/company" }, | { icon: <Company />, label: "Company", path: "/settings/company" }, | ||||
{ icon: <Department />, label: "Department", path: "/settings/department" }, | { icon: <Department />, label: "Department", path: "/settings/department" }, | ||||
{ icon: <Position />, label: "Position", path: "/settings/position" }, | { icon: <Position />, label: "Position", path: "/settings/position" }, | ||||
@@ -1,3 +1,5 @@ | |||||
import React from 'react'; | |||||
import { useTranslation } from 'react-i18next'; | |||||
import Swal from "sweetalert2"; | import Swal from "sweetalert2"; | ||||
export const msg = (text) => { | export const msg = (text) => { | ||||
@@ -20,3 +22,46 @@ export const msg = (text) => { | |||||
export const popup = (text) => { | export const popup = (text) => { | ||||
Swal.fire(text); | Swal.fire(text); | ||||
}; | }; | ||||
export const successDialog = (text, t) => { | |||||
return Swal.fire({ | |||||
icon: "success", | |||||
title: text, | |||||
confirmButtonText: t("Confirm"), | |||||
showConfirmButton: true, | |||||
}) | |||||
} | |||||
export const errorDialog = (text, t) => { | |||||
return Swal.fire({ | |||||
icon: "error", | |||||
title: text, | |||||
confirmButtonText: t("Confirm"), | |||||
showConfirmButton: true, | |||||
}) | |||||
} | |||||
export const warningDialog = (text, t) => { | |||||
return Swal.fire({ | |||||
icon: "warning", | |||||
title: text, | |||||
confirmButtonText: t("Confirm"), | |||||
showConfirmButton: true, | |||||
}) | |||||
} | |||||
export const submitDialog = (confirmAction, t) => { | |||||
// const { t } = useTranslation("common") | |||||
return 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() | |||||
} | |||||
}) | |||||
} |
@@ -5,5 +5,6 @@ | |||||
"Search Criteria": "Search Criteria", | "Search Criteria": "Search Criteria", | ||||
"Cancel": "Cancel", | "Cancel": "Cancel", | ||||
"Confirm": "Confirm", | "Confirm": "Confirm", | ||||
"Submit": "Submit", | |||||
"Reset": "Reset" | "Reset": "Reset" | ||||
} | } |
@@ -11,11 +11,13 @@ | |||||
"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 Type": "Client Type", | |||||
"Please input correct customer code": "Please input correct client code", | "Please input correct customer code": "Please input correct client code", | ||||
"Please input correct customer name": "Please input correct client name", | "Please input correct customer name": "Please input correct client name", | ||||
"Please input correct customer email": "Please input correct client email", | "Please input correct customer email": "Please input correct client email", | ||||
"Please input correct customer br no.": "Please input correct client br no.", | "Please input correct customer br no.": "Please input correct client br no.", | ||||
"The customer code has already existed": "The customer code has already existed", | |||||
"Subsidiary" : "Subsidiary", | "Subsidiary" : "Subsidiary", | ||||
"Subsidiary Allocation": "Subsidiary Allocation", | "Subsidiary Allocation": "Subsidiary Allocation", | ||||
@@ -32,11 +34,23 @@ | |||||
"Subsidiary Br No.": "Subsidiary Br No.", | "Subsidiary Br No.": "Subsidiary Br No.", | ||||
"Subsidiary Details": "Subsidiary Details", | "Subsidiary Details": "Subsidiary Details", | ||||
"Add Contact Person": "Add Contact Person", | |||||
"Contact Details": "Contact Details", | |||||
"Contact Name": "Contact Name", | |||||
"Contact Email": "Contact Email", | |||||
"Contact Phone": "Contact Phone", | |||||
"Please ensure all the fields are inputted and saved": "Please ensure all the fields are inputted and saved", | |||||
"Please ensure all the email formats are correct": "Please ensure all the email formats are correct", | |||||
"Do you want to submit?": "Do you want to submit?", | |||||
"Submit Success": "Submit Success", | |||||
"Add": "Add", | "Add": "Add", | ||||
"Details": "Details", | "Details": "Details", | ||||
"Search": "Search", | "Search": "Search", | ||||
"Search Criteria": "Search Criteria", | "Search Criteria": "Search Criteria", | ||||
"Cancel": "Cancel", | "Cancel": "Cancel", | ||||
"Confirm": "Confirm", | "Confirm": "Confirm", | ||||
"Submit": "Submit", | |||||
"Reset": "Reset" | "Reset": "Reset" | ||||
} | } |
@@ -3,5 +3,6 @@ | |||||
"Search Criteria": "搜尋條件", | "Search Criteria": "搜尋條件", | ||||
"Cancel": "取消", | "Cancel": "取消", | ||||
"Confirm": "確認", | "Confirm": "確認", | ||||
"Submit": "提交", | |||||
"Reset": "重置" | "Reset": "重置" | ||||
} | } |
@@ -11,11 +11,13 @@ | |||||
"Customer Contact Name": "客戶聯絡名稱", | "Customer Contact Name": "客戶聯絡名稱", | ||||
"Customer Br No.": "客戶商業登記號碼", | "Customer Br No.": "客戶商業登記號碼", | ||||
"Customer Details": "客戶詳請", | "Customer Details": "客戶詳請", | ||||
"Customer Type": "客戶類型", | |||||
"Please input correct customer code": "請輸入客戶編號", | "Please input correct customer code": "請輸入客戶編號", | ||||
"Please input correct customer name": "請輸入客戶編號", | "Please input correct customer name": "請輸入客戶編號", | ||||
"Please input correct customer email": "請輸入正確客戶電郵", | "Please input correct customer email": "請輸入正確客戶電郵", | ||||
"Please input correct customer br no.": "請輸入正確客戶商業登記號碼", | "Please input correct customer br no.": "請輸入正確客戶商業登記號碼", | ||||
"The customer code has already existed": "該客戶編號已存在", | |||||
"Subsidiary": "子公司", | "Subsidiary": "子公司", | ||||
"Subsidiary Allocation": "子公司分配", | "Subsidiary Allocation": "子公司分配", | ||||
@@ -32,11 +34,23 @@ | |||||
"Subsidiary Br No.": "子公司商業登記號碼", | "Subsidiary Br No.": "子公司商業登記號碼", | ||||
"Subsidiary Details": "子公司詳請", | "Subsidiary Details": "子公司詳請", | ||||
"Add Contact Person": "新增聯絡人", | |||||
"Contact Details": "聯絡詳請", | |||||
"Contact Name": "聯絡姓名", | |||||
"Contact Email": "聯絡電郵", | |||||
"Contact Phone": "聯絡電話", | |||||
"Please ensure all the fields are inputted and saved": "請確保所有欄位已輸入及儲存", | |||||
"Please ensure all the email formats are correct": "請確保所有電郵格式輸入正確", | |||||
"Do you want to submit?": "你是否確認要提交?", | |||||
"Submit Success": "提交成功", | |||||
"Add": "新增", | "Add": "新增", | ||||
"Details": "詳請", | "Details": "詳請", | ||||
"Search": "搜尋", | "Search": "搜尋", | ||||
"Search Criteria": "搜尋條件", | "Search Criteria": "搜尋條件", | ||||
"Cancel": "取消", | "Cancel": "取消", | ||||
"Confirm": "確認", | "Confirm": "確認", | ||||
"Submit": "提交", | |||||
"Reset": "重置" | "Reset": "重置" | ||||
} | } |