Browse Source

Update error checking in invoice and save

tags/Baseline_180220205_Frontend
MSI\2Fi 11 months ago
parent
commit
33bd1c8633
5 changed files with 111 additions and 64 deletions
  1. +22
    -1
      src/app/api/invoices/actions.ts
  2. +30
    -9
      src/components/InvoiceSearch/CreateInvoiceModal.tsx
  3. +15
    -39
      src/components/InvoiceSearch/InvoiceSearch.tsx
  4. +4
    -1
      src/components/InvoiceSearch/InvoiceSearchWrapper.tsx
  5. +40
    -14
      src/components/InvoiceSearch/InvoiceTable.tsx

+ 22
- 1
src/app/api/invoices/actions.ts View File

@@ -17,6 +17,12 @@ export interface InvoiceResult {
reminder: string;
}

type InvoiceListError = {
[field in keyof NewInvoice]?: string;
} & {
message?: string;
};

export type NewInvoice = {
invoiceNo: string | undefined,
projectCode: string | undefined,
@@ -24,7 +30,11 @@ export type NewInvoice = {
issuedAmount: number,
receiptDate: Date
receivedAmount: number
} & {
_isNew: boolean;
_error: InvoiceListError
}

export type InvoiceType = {
data: NewInvoice[]
}
@@ -151,4 +161,15 @@ export const deleteInvoice = async (id: number) => {

revalidateTag("invoices");
return invoice;
};
};

export const createInvoices = async (data: any) => {
console.log(data)
const createInvoices = serverFetchString<any>(`${BASE_API_URL}/invoices/create`, {
method: "Post",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
});
revalidateTag("invoices")
return createInvoices;
}

+ 30
- 9
src/components/InvoiceSearch/CreateInvoiceModal.tsx View File

@@ -10,18 +10,22 @@ import {
Card
} from '@mui/material';
import { useTranslation } from 'react-i18next';
import { FormProvider, SubmitHandler, useForm } from 'react-hook-form';
import { FormProvider, SubmitHandler, useForm, useFormContext } from 'react-hook-form';
import { Check, Close } from "@mui/icons-material";
import InvoiceTable from './InvoiceTable';
import { ProjectResult } from '@/app/api/projects';
import { InvoiceType, NewInvoice, PostInvoiceData } from '@/app/api/invoices/actions';
import { createInvoices, InvoiceType, NewInvoice, PostInvoiceData } from '@/app/api/invoices/actions';
import dayjs from 'dayjs';
import { INPUT_DATE_FORMAT } from '@/app/utils/formatUtil';
import { invoiceList } from '@/app/api/invoices';
import { submitDialog, successDialog } from '../Swal/CustomAlerts';
import { useRouter } from 'next/navigation';

interface Props {
isOpen: boolean,
onClose: () => void
projects: ProjectResult[]
onClose: () => void,
projects: ProjectResult[],
invoices: invoiceList[]
}

const modalSx: SxProps= {
@@ -35,24 +39,41 @@ const modalSx: SxProps= {
bgcolor: 'background.paper',
};

const CreateInvoiceModal: React.FC<Props> = ({isOpen, onClose, projects}) => {
const CreateInvoiceModal: React.FC<Props> = ({isOpen, onClose, projects, invoices}) => {
const { t } = useTranslation()
const formProps = useForm<InvoiceType>();
const router = useRouter();

const onSubmit = useCallback<SubmitHandler<InvoiceType>>(
async ( data ) => {
const _data = data.data
// console.log(_data)
if(_data.some(data => data._error !== undefined)){
// console.log(data)
return
}
// const postData: PostInvoiceData = _data.map(item => ({
const postData: any = _data.map(item => ({
invoiceNo: item.invoiceNo || '',
projectId: projects.find(p => p.code === item.projectCode)!.id,
// projectId: projects.find(p => p.code === item.projectCode)!.id,
projectCode: item.projectCode || '',
issuedAmount: item.issuedAmount || 0,
issueDate: dayjs(item.issuedDate).format(INPUT_DATE_FORMAT),
receiptDate: item.receiptDate || null,
issuedDate: dayjs(item.issuedDate).format(INPUT_DATE_FORMAT),
receiptDate: item.receiptDate ? dayjs(item.receiptDate).format(INPUT_DATE_FORMAT) : null,
receivedAmount: item.receivedAmount || null,
}))
console.log(postData)
submitDialog(async () => {
const response = await createInvoices(postData)
console.log(response)
if (response === "OK") {
onClose()
successDialog(t("Submit Success"), t).then(() => {
router.replace("/invoice");
})
}
}, t)
}
, [])

@@ -77,7 +98,7 @@ const CreateInvoiceModal: React.FC<Props> = ({isOpen, onClose, projects}) => {
marginBlock: 2,
}}
>
<InvoiceTable projects={projects}/>
<InvoiceTable projects={projects} invoices={invoices}/>
</Box>
<CardActions sx={{ justifyContent: "flex-end" }}>
<Button


+ 15
- 39
src/components/InvoiceSearch/InvoiceSearch.tsx View File

@@ -20,12 +20,14 @@ import StyledDataGrid from "../StyledDataGrid";
import { uniq } from "lodash";
import CreateInvoiceModal from "./CreateInvoiceModal";
import { ProjectResult } from "@/app/api/projects";
import { IMPORT_INVOICE, IMPORT_RECEIPT } from "@/middleware";



interface Props {
invoices: invoiceList[];
projects: ProjectResult[];
abilities: string[];
}

type InvoiceListError = {
@@ -45,13 +47,10 @@ type SearchParamNames = keyof SearchQuery;
type SearchQuery2 = Partial<Omit<receivedInvoiceSearchForm, "id">>;
type SearchParamNames2 = keyof SearchQuery2;

const InvoiceSearch: React.FC<Props> = ({ invoices, projects }) => {
// console.log(invoices)
const InvoiceSearch: React.FC<Props> = ({ invoices, projects, abilities }) => {
console.log(abilities)
const { t } = useTranslation("Invoice");
const [tabIndex, setTabIndex] = useState(0);

// const [filteredIssuedInvoices, setFilteredIssuedInvoices] = useState(issuedInvoice);
// const [filteredReceivedInvoices, setFilteredReceivedInvoices] = useState(receivedInvoice);
const [filteredIvoices, setFilterInovices] = useState(invoices);

const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
@@ -228,31 +227,6 @@ const InvoiceSearch: React.FC<Props> = ({ invoices, projects }) => {
}, []);

const columns = useMemo<Column<issuedInvoiceList>[]>(
() => [
{ name: "invoiceNo", label: t("Invoice No") },
{ name: "projectCode", label: t("Project Code") },
{ name: "stage", label: t("Stage") },
{ name: "paymentMilestone", label: t("Payment Milestone") },
{ name: "invoiceDate", label: t("Invoice Date") },
{ name: "dueDate", label: t("Due Date") },
{ name: "issuedAmount", label: t("Amount (HKD)") },
],
[t],
);

const columns2 = useMemo<Column<receivedInvoiceList>[]>(
() => [
{ name: "invoiceNo", label: t("Invoice No") },
{ name: "projectCode", label: t("Project Code") },
{ name: "projectName", label: t("Project Name") },
{ name: "team", label: t("Team") },
{ name: "receiptDate", label: t("Receipt Date") },
{ name: "receivedAmount", label: t("Amount (HKD)") },
],
[t],
);

const [selectedRow, setSelectedRow] = useState<invoiceListRow[] | []>([]);
const [dialogOpen, setDialogOpen] = useState(false);

@@ -268,8 +242,6 @@ const InvoiceSearch: React.FC<Props> = ({ invoices, projects }) => {

const handleCloseDialog = () => {
setDialogOpen(false);
// console.log(selectedRow)
// setSelectedRow([]);
};

const handleDeleteInvoice = useCallback(() => {
@@ -380,13 +352,6 @@ const InvoiceSearch: React.FC<Props> = ({ invoices, projects }) => {
return ((!startDate || startDate === "Invalid Date") || dateToCheckObj >= startDateObj) && ((!endDate || endDate === "Invalid Date") || dateToCheckObj <= endDateObj);
}

const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
(_e, newValue) => {
setTabIndex(newValue);
},
[],
);

const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({});
const apiRef = useGridApiRef();

@@ -453,6 +418,16 @@ const InvoiceSearch: React.FC<Props> = ({ invoices, projects }) => {
[validateRow],
);

const isAddInvoiceRightExist = () => {
const importRight = [IMPORT_INVOICE].some((ability) => abilities.includes(ability))
return importRight
}

const isAddReciptRightExist = () => {
const importRight = [IMPORT_RECEIPT].some((ability) => abilities.includes(ability))
return importRight
}

return (
<>
<Stack
@@ -609,6 +584,7 @@ const InvoiceSearch: React.FC<Props> = ({ invoices, projects }) => {
isOpen={modelOpen}
onClose={handleModalClose}
projects={projects}
invoices={invoices}
/>
</>
);


+ 4
- 1
src/components/InvoiceSearch/InvoiceSearchWrapper.tsx View File

@@ -5,7 +5,7 @@ import InvoiceSearchLoading from "./InvoiceSearchLoading";
import { fetchInvoicesV3, fetchIssuedInvoices, fetchReceivedInvoices, issuedInvoiceList, issuedInvoiceResult } from "@/app/api/invoices";
import { INPUT_DATE_FORMAT, convertDateArrayToString, convertDateToString, moneyFormatter, timestampToDateString } from "@/app/utils/formatUtil";
import { fetchTeam } from "@/app/api/team";
import { fetchUserStaff } from "@/app/utils/fetchUtil";
import { fetchUserAbilities, fetchUserStaff } from "@/app/utils/fetchUtil";
import { fetchProjects } from "@/app/api/projects";


@@ -45,9 +45,12 @@ const InvoiceSearchWrapper: React.FC & SubComponents = async () => {
}
})

const abilities = await fetchUserAbilities();

return <InvoiceSearch
invoices={convertedInvoices}
projects={filteredProjects}
abilities={abilities}
/>
};


+ 40
- 14
src/components/InvoiceSearch/InvoiceTable.tsx View File

@@ -25,6 +25,8 @@ import useEnhancedEffect from "@mui/material/utils/useEnhancedEffect";

type InvoiceListError = {
[field in keyof invoiceList]?: string;
} & {
message?: string;
};

type invoiceListRow = Partial<
@@ -36,6 +38,7 @@ type invoiceListRow = Partial<

interface Props {
projects: ProjectResult[];
invoices: invoiceList[];
}

class ProcessRowUpdateError extends Error {
@@ -57,7 +60,7 @@ type project = {
label: string;
value: number;
}
const InvoiceTable: React.FC<Props> = ({ projects }) => {
const InvoiceTable: React.FC<Props> = ({ projects, invoices }) => {
// if change this to code - name,
// also change the submit function
const projectCombos = projects.map(item => item.code)
@@ -75,20 +78,34 @@ const InvoiceTable: React.FC<Props> = ({ projects }) => {
console.log(entry)
if (!entry.issuedAmount) {
error.issuedAmount = "Please input issued amount ";
error.issuedAmount = true
error.message = t("Please input issued amount ")
} else if (!entry.issuedAmount) {
error.receivedAmount = "Please input received amount";
} else if (entry.invoiceNo === "") {
error.invoiceNo = "Please input invoice number";
error.issuedAmount = true
error.message = t("Please input received amount")
} else if (!entry.issuedDate) {
error.issuedDate = "Please input issue date";
} else if (!entry.receiptDate){
error.issuedDate = true
error.message = t("Please input issue date")
} else if (!entry.projectCode){
error.projectCode = true
error.message = t("Please select project code")
} else if(!entry.invoiceNo){
error.invoiceNo = true
error.message = t("Please input invoice No")
}else if(isInvoiceNoExists(entry.invoiceNo)){
error.invoiceNo = true
error.message = t("Duplicate Invoice No")
}

return Object.keys(error).length > 0 ? error : undefined;
}

const isInvoiceNoExists = (invoiceNo: string): boolean => {
// console.log(invoices.some(i => i.invoiceNo === invoiceNo))
const result = invoices.some(i => i.invoiceNo === invoiceNo)
return result
}

const validateRow = useCallback(
(id: GridRowId) => {
const row = apiRef.current.getRowWithUpdatedValues(
@@ -112,10 +129,10 @@ const InvoiceTable: React.FC<Props> = ({ projects }) => {
params.id,
"",
)
console.log(params)
console.log(apiRef.current)
console.log(validateRow(params.id) !== undefined)
console.log(!validateRow(params.id))
// console.log(params)
// console.log(apiRef.current)
// console.log(validateRow(params.id) !== undefined)
// console.log(!validateRow(params.id))
if (validateRow(params.id) !== undefined && !validateRow(params.id)) {
setRowModesModel((model) => ({
@@ -127,7 +144,7 @@ const InvoiceTable: React.FC<Props> = ({ projects }) => {
setSelectedRow((row) => [...row] as any[])
event.defaultMuiPrevented = true;
} else {
console.log(row)
// console.log(row)
const error = validateRow(params.id)
setSelectedRow((row) => {
const updatedRow = row.map(r => r.id === params.id ? { ...r, _error: error } : r);
@@ -185,6 +202,8 @@ const InvoiceTable: React.FC<Props> = ({ projects }) => {
[validateRow],
);

const hasRowErrors = selectedRow.some(row => row._error !== undefined)

/**
* Add callback to check error
*/
@@ -205,7 +224,7 @@ const InvoiceTable: React.FC<Props> = ({ projects }) => {

function renderAutocomplete(params: GridRenderCellParams<any, number>) {
return(
<Box sx={{ display: 'flex', alignItems: 'center', pr: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', pr: 2, }}>
<Autocomplete
readOnly
sx={{ width: 300 }}
@@ -283,6 +302,7 @@ const editCombinedColumns = useMemo<GridColDef[]>(
headerName: t("Settle Date"),
editable: true,
flex: 1,
type: 'date',
},
{ field: "receivedAmount",
headerName: t("Actual Received Amount (HKD)"),
@@ -305,6 +325,12 @@ const footer = (
>
{t("Create Invoice")}
</Button>
{
hasRowErrors &&
<Typography color="warning.main" variant="body2">
{t("There are errors!")} {selectedRow.find(row => row._error !== null)?._error?.message}
</Typography>
}
</Box>
);



Loading…
Cancel
Save