| @@ -17,7 +17,7 @@ const Projects: React.FC = async () => { | |||
| return ( | |||
| <> | |||
| <Typography variant="h4">{t("Create Customer")}</Typography> | |||
| <I18nProvider namespaces={["customer"]}> | |||
| <I18nProvider namespaces={["customer", "common"]}> | |||
| <CreateCustomer /> | |||
| </I18nProvider> | |||
| </> | |||
| @@ -33,7 +33,7 @@ const Customer: React.FC = async () => { | |||
| variant="contained" | |||
| startIcon={<Add />} | |||
| LinkComponent={Link} | |||
| href="/customer/create" | |||
| href="/settings/customer/create" | |||
| > | |||
| {t("Create Customer")} | |||
| </Button> | |||
| @@ -2,12 +2,14 @@ | |||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
| import { BASE_API_URL } from "@/config/api"; | |||
| import { Customer } from "."; | |||
| import { Contact, NewCustomerResponse } from "."; | |||
| import { revalidateTag } from "next/cache"; | |||
| export interface CustomerFormInputs { | |||
| // Customer details | |||
| id: number | null; | |||
| name: string; | |||
| code: string; | |||
| address: string | null; | |||
| @@ -16,14 +18,22 @@ export interface CustomerFormInputs { | |||
| phone: string | null; | |||
| contactName: string | null; | |||
| brNo: string | null; | |||
| typeId: number; | |||
| // Subsidiary | |||
| addSubsidiaryIds: number[]; | |||
| deleteSubsidiaryIds: number[]; | |||
| // Contact | |||
| addContacts: Contact[]; | |||
| deleteContactIds: number[]; | |||
| // is grid editing | |||
| isGridEditing: boolean | null; | |||
| } | |||
| export const saveCustomer = async (data: CustomerFormInputs) => { | |||
| const saveCustomer = await serverFetchJson<Customer>( | |||
| const saveCustomer = await serverFetchJson<NewCustomerResponse>( | |||
| `${BASE_API_URL}/customer/save`, | |||
| { | |||
| method: "POST", | |||
| @@ -9,6 +9,16 @@ export interface Customer { | |||
| name: string; | |||
| } | |||
| export interface NewCustomerResponse { | |||
| customer: Customer; | |||
| message: string; | |||
| } | |||
| export interface CustomerType { | |||
| id: number; | |||
| name: string; | |||
| } | |||
| export interface Subsidiary { | |||
| id: number; | |||
| code: string; | |||
| @@ -22,6 +32,14 @@ export interface Subsidiary { | |||
| email: string | null; | |||
| } | |||
| export interface Contact { | |||
| id: number; | |||
| name: string; | |||
| phone: string; | |||
| email: string; | |||
| isNew: boolean; | |||
| } | |||
| export const preloadAllCustomers = () => { | |||
| 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", | |||
| "/tasks": "Task Template", | |||
| "/tasks/create": "Create Task Template", | |||
| "/customer": "Customer", | |||
| "/customer/create": "Create Customer", | |||
| "/settings/customer": "Customer", | |||
| "/settings/customer/create": "Create Customer", | |||
| "/settings": "Settings", | |||
| "/company": "Company", | |||
| "/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 React, { useCallback, useState } from "react"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { Task, TaskTemplate } from "@/app/api/tasks"; | |||
| import { | |||
| FieldErrors, | |||
| FormProvider, | |||
| @@ -17,18 +16,18 @@ import { | |||
| SubmitHandler, | |||
| useForm, | |||
| } from "react-hook-form"; | |||
| import { CreateProjectInputs, saveProject } from "@/app/api/projects/actions"; | |||
| import { Error } from "@mui/icons-material"; | |||
| import { ProjectCategory } from "@/app/api/projects"; | |||
| import { Typography } from "@mui/material"; | |||
| import { CustomerFormInputs, saveCustomer } from "@/app/api/customer/actions"; | |||
| import CustomerDetails from "./CustomerDetails"; | |||
| import SubsidiaryAllocation from "./SubsidiaryAllocation"; | |||
| import { Subsidiary } from "@/app/api/customer"; | |||
| import { CustomerType, Subsidiary } from "@/app/api/customer"; | |||
| import { getDeletedRecordWithRefList } from "@/app/utils/commonUtil"; | |||
| import { errorDialog, submitDialog, successDialog, warningDialog } from "../Swal/CustomAlerts"; | |||
| export interface Props { | |||
| subsidiaries: Subsidiary[], | |||
| customerTypes: CustomerType[], | |||
| } | |||
| const hasErrorsInTab = ( | |||
| @@ -37,7 +36,7 @@ const hasErrorsInTab = ( | |||
| ) => { | |||
| switch (tabIndex) { | |||
| case 0: | |||
| return errors.name; | |||
| return Object.keys(errors).length > 0; | |||
| default: | |||
| false; | |||
| } | |||
| @@ -45,6 +44,7 @@ const hasErrorsInTab = ( | |||
| const CreateCustomer: React.FC<Props> = ({ | |||
| subsidiaries, | |||
| customerTypes, | |||
| }) => { | |||
| const [serverError, setServerError] = useState(""); | |||
| const [tabIndex, setTabIndex] = useState(0); | |||
| @@ -54,6 +54,10 @@ const CreateCustomer: React.FC<Props> = ({ | |||
| defaultValues: { | |||
| code: "", | |||
| name: "", | |||
| addContacts: [], | |||
| addSubsidiaryIds: [], | |||
| deleteSubsidiaryIds: [], | |||
| deleteContactIds: [] | |||
| }, | |||
| }); | |||
| @@ -71,27 +75,42 @@ const CreateCustomer: React.FC<Props> = ({ | |||
| const onSubmit = useCallback<SubmitHandler<CustomerFormInputs>>( | |||
| async (data) => { | |||
| try { | |||
| if (data.isGridEditing) { | |||
| warningDialog(t("Please save all the rows before submitting"), t) | |||
| return false | |||
| } | |||
| console.log(data); | |||
| let haveError = false | |||
| if (data.name.length === 0) { | |||
| 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) { | |||
| 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)) { | |||
| 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)) { | |||
| 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) { | |||
| @@ -100,11 +119,28 @@ const CreateCustomer: React.FC<Props> = ({ | |||
| return false | |||
| } | |||
| data.deleteSubsidiaryIds = [] | |||
| // data.deleteSubsidiaryIds = data.deleteSubsidiaryIds ?? [] | |||
| // data.addSubsidiaryIds = data.addSubsidiaryIds ?? [] | |||
| // data.deleteContactIds = data.deleteContactIds ?? [] | |||
| 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) { | |||
| console.log(e) | |||
| setServerError(t("An error has occurred. Please try again later.")); | |||
| } | |||
| }, | |||
| @@ -114,7 +150,7 @@ const CreateCustomer: React.FC<Props> = ({ | |||
| const onSubmitError = useCallback<SubmitErrorHandler<CustomerFormInputs>>( | |||
| (errors) => { | |||
| // Set the tab so that the focus will go there | |||
| if (errors.name || errors.code) { | |||
| if (Object.keys(errors).length > 0) { | |||
| setTabIndex(0); | |||
| } | |||
| }, | |||
| @@ -147,8 +183,8 @@ const CreateCustomer: React.FC<Props> = ({ | |||
| {serverError} | |||
| </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}> | |||
| <Button | |||
| @@ -2,17 +2,18 @@ | |||
| // import CreateProject from "./CreateProject"; | |||
| // import { fetchProjectCategories } from "@/app/api/projects"; | |||
| // import { fetchTeamLeads } from "@/app/api/staff"; | |||
| import { fetchSubsidiaries } from "@/app/api/customer"; | |||
| import { fetchCustomerTypes, fetchSubsidiaries } from "@/app/api/customer"; | |||
| import CreateCustomer from "./CreateCustomer"; | |||
| const CreateCustomerWrapper: React.FC = async () => { | |||
| const [subsidiaries] = | |||
| const [subsidiaries, customerTypes] = | |||
| await Promise.all([ | |||
| fetchSubsidiaries(), | |||
| fetchCustomerTypes(), | |||
| ]); | |||
| 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 RestartAlt from "@mui/icons-material/RestartAlt"; | |||
| 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 { 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"}}> | |||
| <> | |||
| <Card sx={{ display: "block" }}> | |||
| <CardContent component={Stack} spacing={4}> | |||
| <Box> | |||
| <Typography variant="overline" display="block" marginBlockEnd={1}> | |||
| @@ -41,7 +49,7 @@ const CustomerDetails: React.FC<Props> = ({ | |||
| required: true, | |||
| })} | |||
| 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 item xs={6}> | |||
| @@ -69,7 +77,7 @@ const CustomerDetails: React.FC<Props> = ({ | |||
| {...register("district")} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| {/* <Grid item xs={6}> | |||
| <TextField | |||
| label={t("Customer Email")} | |||
| fullWidth | |||
| @@ -93,6 +101,28 @@ const CustomerDetails: React.FC<Props> = ({ | |||
| 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 | |||
| @@ -108,12 +138,14 @@ const CustomerDetails: React.FC<Props> = ({ | |||
| </Grid> | |||
| </Box> | |||
| <CardActions sx={{ justifyContent: "flex-end" }}> | |||
| <Button variant="text" startIcon={<RestartAlt />}> | |||
| <Button onClick={() => reset()} variant="text" startIcon={<RestartAlt />}> | |||
| {t("Reset")} | |||
| </Button> | |||
| </CardActions> | |||
| </CardContent> | |||
| </Card> | |||
| <ContactDetails/> | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -99,8 +99,8 @@ const navigationItems: NavigationItem[] = [ | |||
| { | |||
| icon: <Settings />, label: "Setting", path: "", | |||
| 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: <Department />, label: "Department", path: "/settings/department" }, | |||
| { icon: <Position />, label: "Position", path: "/settings/position" }, | |||
| @@ -1,3 +1,5 @@ | |||
| import React from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import Swal from "sweetalert2"; | |||
| export const msg = (text) => { | |||
| @@ -20,3 +22,46 @@ export const msg = (text) => { | |||
| export const popup = (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", | |||
| "Cancel": "Cancel", | |||
| "Confirm": "Confirm", | |||
| "Submit": "Submit", | |||
| "Reset": "Reset" | |||
| } | |||
| @@ -11,11 +11,13 @@ | |||
| "Customer Contact Name": "Client Contact Name", | |||
| "Customer Br No.": "Client Br No.", | |||
| "Customer Details": "Client Details", | |||
| "Customer Type": "Client Type", | |||
| "Please input correct customer code": "Please input correct client code", | |||
| "Please input correct customer name": "Please input correct client name", | |||
| "Please input correct customer email": "Please input correct client email", | |||
| "Please input correct customer br no.": "Please input correct client br no.", | |||
| "The customer code has already existed": "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": "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", | |||
| "Details": "Details", | |||
| "Search": "Search", | |||
| "Search Criteria": "Search Criteria", | |||
| "Cancel": "Cancel", | |||
| "Confirm": "Confirm", | |||
| "Submit": "Submit", | |||
| "Reset": "Reset" | |||
| } | |||
| @@ -3,5 +3,6 @@ | |||
| "Search Criteria": "搜尋條件", | |||
| "Cancel": "取消", | |||
| "Confirm": "確認", | |||
| "Submit": "提交", | |||
| "Reset": "重置" | |||
| } | |||
| @@ -11,11 +11,13 @@ | |||
| "Customer Contact Name": "客戶聯絡名稱", | |||
| "Customer Br No.": "客戶商業登記號碼", | |||
| "Customer Details": "客戶詳請", | |||
| "Customer Type": "客戶類型", | |||
| "Please input correct customer code": "請輸入客戶編號", | |||
| "Please input correct customer name": "請輸入客戶編號", | |||
| "Please input correct customer email": "請輸入正確客戶電郵", | |||
| "Please input correct customer br no.": "請輸入正確客戶商業登記號碼", | |||
| "The customer code has already existed": "該客戶編號已存在", | |||
| "Subsidiary": "子公司", | |||
| "Subsidiary Allocation": "子公司分配", | |||
| @@ -32,11 +34,23 @@ | |||
| "Subsidiary Br No.": "子公司商業登記號碼", | |||
| "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": "新增", | |||
| "Details": "詳請", | |||
| "Search": "搜尋", | |||
| "Search Criteria": "搜尋條件", | |||
| "Cancel": "取消", | |||
| "Confirm": "確認", | |||
| "Submit": "提交", | |||
| "Reset": "重置" | |||
| } | |||