Explorar el Código

Import Invoice

tags/Baseline_30082024_FRONTEND_UAT
MSI\2Fi hace 1 año
padre
commit
afb31dc435
Se han modificado 19 ficheros con 611 adiciones y 92 borrados
  1. +1
    -1
      src/app/(main)/invoice/new/page.tsx
  2. +30
    -2
      src/app/api/invoices/actions.ts
  3. +113
    -0
      src/app/api/invoices/index.ts
  4. +0
    -0
      src/components/CreateInvoice_forGen/CreateInvoice.tsx
  5. +0
    -0
      src/components/CreateInvoice_forGen/CreateInvoiceWrapper.tsx
  6. +0
    -0
      src/components/CreateInvoice_forGen/InvoiceDetails.tsx
  7. +0
    -0
      src/components/CreateInvoice_forGen/ProjectDetails.tsx
  8. +0
    -0
      src/components/CreateInvoice_forGen/ProjectTotalFee.tsx
  9. +0
    -0
      src/components/CreateInvoice_forGen/index.ts
  10. +1
    -1
      src/components/CustomerSave/CustomerSave.tsx
  11. +220
    -61
      src/components/InvoiceSearch/InvoiceSearch.tsx
  12. +3
    -3
      src/components/InvoiceSearch/InvoiceSearchLoading.tsx
  13. +31
    -11
      src/components/InvoiceSearch/InvoiceSearchWrapper.tsx
  14. +104
    -0
      src/components/InvoiceSearch_forGen/InvoiceSearch.tsx
  15. +40
    -0
      src/components/InvoiceSearch_forGen/InvoiceSearchLoading.tsx
  16. +26
    -0
      src/components/InvoiceSearch_forGen/InvoiceSearchWrapper.tsx
  17. +1
    -0
      src/components/InvoiceSearch_forGen/index.ts
  18. +31
    -13
      src/components/SalarySearch/SalarySearch.tsx
  19. +10
    -0
      src/components/Swal/CustomAlerts.js

+ 1
- 1
src/app/(main)/invoice/new/page.tsx Ver fichero

@@ -5,7 +5,7 @@ import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import Link from "next/link";
import CreateInvoice from "@/components/CreateInvoice";
import CreateInvoice from "@/components/CreateInvoice_forGen";

export const metadata: Metadata = {
title: "Create Invoice",


+ 30
- 2
src/app/api/invoices/actions.ts Ver fichero

@@ -1,6 +1,6 @@
"use server"

import { serverFetchJson } from "@/app/utils/fetchUtil";
import { serverFetchJson, serverFetchString } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { cache } from "react";

@@ -64,4 +64,32 @@ export const fetchInvoiceInfoById = cache(async (id: number) => {
return serverFetchJson<InvoiceInformation[]>(`${BASE_API_URL}/invoices/getInvoiceInfo/${id}`, {
next: { tags: ["invoiceInfoById"] },
});
})
})

export const importIssuedInovice = async (data: FormData) => {
// console.log("----------------",data)
const importIssuedInovice = await serverFetchJson<any>(
`${BASE_API_URL}/invoices/import/issued`,
{
method: "POST",
body: data,
// headers: { "Content-Type": "multipart/form-data" },
},
);

return importIssuedInovice;
};

export const importReceivedInovice = async (data: FormData) => {
// console.log("----------------",data)
const importReceivedInovice = await serverFetchJson<any>(
`${BASE_API_URL}/invoices/import/received`,
{
method: "POST",
body: data,
// headers: { "Content-Type": "multipart/form-data" },
},
);

return importReceivedInovice;
};

+ 113
- 0
src/app/api/invoices/index.ts Ver fichero

@@ -15,6 +15,107 @@ export interface InvoiceResult {
reminder: string;
}

export interface issuedInvoiceResult {
id: number;
invoiceNo: string;
projectCode: string;
projectName: string;
team: string;
stage: string;
paymentMilestone: string;
paymentMilestoneDate: string;
client: string;
address: string;
attention: string;
invoiceDate: number[];
dueDate: number[];
issuedAmount: number;
}

export interface receivedInvoiceResult {
id: number;
invoiceNo: string;
projectCode: string;
projectName: string;
team: string;
receiptDate: number[];
receivedAmount: number;
}



export interface issuedInvoiceList {
id: number;
invoiceNo: string;
projectCode: string;
projectName: string;
// team: string;
stage: string;
paymentMilestone: string;
// paymentMilestoneDate: string;
// client: string;
// address: string;
// attention: string;
invoiceDate: string;
dueDate: string;
issuedAmount: string;
}

export interface receivedInvoiceList {
id: number;
invoiceNo: string;
projectCode: string;
projectName: string;
team: string;
// stage: string;
// paymentMilestone: string;
// paymentMilestoneDate: string;
// client: string;
// address: string;
// attention: string;
receiptDate: string;
receivedAmount: string;
}

export interface issuedInvoiceSearchForm {
id: number;
invoiceNo: string;
projectCode: string;
projectName: string;
// team: string;
// stage: string;
// paymentMilestone: string;
// paymentMilestoneDate: string;
// client: string;
// address: string;
// attention: string;
invoiceDate: string;
invoiceDateTo: string;
dueDate: string;
dueDateTo: string;
// issuedAmount: string;
}

export interface receivedInvoiceSearchForm {
id: number;
invoiceNo: string;
projectCode: string;
projectName: string;
// team: string;
// stage: string;
// paymentMilestone: string;
// paymentMilestoneDate: string;
// client: string;
// address: string;
// attention: string;
receiptDate: string;
receiptDateTo: string;
// dueDate: string;
// dueDateTo: string;
// issuedAmount: string;
}


export interface InvoiceInformatio{
id: number;
address: string;
@@ -32,4 +133,16 @@ export const fetchInvoices = cache(async () => {
return serverFetchJson<InvoiceResult[]>(`${BASE_API_URL}/invoices`, {
next: { tags: ["invoices"] },
});
});

export const fetchIssuedInvoices = cache(async () => {
return serverFetchJson<issuedInvoiceResult[]>(`${BASE_API_URL}/invoices/v2/allInvoices`, {
next: { tags: ["invoices"] },
});
});

export const fetchReceivedInvoices = cache(async () => {
return serverFetchJson<receivedInvoiceResult[]>(`${BASE_API_URL}/invoices/v2/allInvoices/received`, {
next: { tags: ["invoices"] },
});
});

src/components/CreateInvoice/CreateInvoice.tsx → src/components/CreateInvoice_forGen/CreateInvoice.tsx Ver fichero


src/components/CreateInvoice/CreateInvoiceWrapper.tsx → src/components/CreateInvoice_forGen/CreateInvoiceWrapper.tsx Ver fichero


src/components/CreateInvoice/InvoiceDetails.tsx → src/components/CreateInvoice_forGen/InvoiceDetails.tsx Ver fichero


src/components/CreateInvoice/ProjectDetails.tsx → src/components/CreateInvoice_forGen/ProjectDetails.tsx Ver fichero


src/components/CreateInvoice/ProjectTotalFee.tsx → src/components/CreateInvoice_forGen/ProjectTotalFee.tsx Ver fichero


src/components/CreateInvoice/index.ts → src/components/CreateInvoice_forGen/index.ts Ver fichero


+ 1
- 1
src/components/CustomerSave/CustomerSave.tsx Ver fichero

@@ -212,7 +212,7 @@ const CustomerSave: React.FC<Props> = ({
return false
})
}
}, t)
}, t, {})
} catch (e) {
console.log(e)
setServerError(t("An error has occurred. Please try again later."));


+ 220
- 61
src/components/InvoiceSearch/InvoiceSearch.tsx Ver fichero

@@ -5,98 +5,257 @@ import SearchBox, { Criterion } from "../SearchBox";
import { useTranslation } from "react-i18next";
import SearchResults, { Column } from "../SearchResults";
import EditNote from "@mui/icons-material/EditNote";
import { InvoiceResult } from "@/app/api/invoices";
import { useRouter } from "next/navigation";
import { convertLocaleStringToNumber } from "@/app/utils/formatUtil"
import { Button, ButtonGroup, Stack, Tab, Tabs, TabsProps } from "@mui/material";
import FileDownloadIcon from '@mui/icons-material/FileDownload';
import FileUploadIcon from '@mui/icons-material/FileUpload';
import { dateInRange, downloadFile } from "@/app/utils/commonUtil";
import { importIssuedInovice, importReceivedInovice } from "@/app/api/invoices/actions";
import { errorDialogWithContent, successDialog } from "../Swal/CustomAlerts";
import { issuedInvoiceList, issuedInvoiceResult, issuedInvoiceSearchForm, receivedInvoiceList, receivedInvoiceSearchForm } from "@/app/api/invoices";

interface Props {
invoices: InvoiceResult[];
issuedInvoice: issuedInvoiceList[];
receivedInvoice: receivedInvoiceList[];
}

type SearchQuery = Partial<Omit<InvoiceResult, "id">>;
type SearchQuery = Partial<Omit<issuedInvoiceSearchForm, "id">>;
type SearchParamNames = keyof SearchQuery;

const InvoiceSearch: React.FC<Props> = ({ invoices }) => {
type SearchQuery2 = Partial<Omit<receivedInvoiceSearchForm, "id">>;
type SearchParamNames2 = keyof SearchQuery2;

const InvoiceSearch: React.FC<Props> = ({ issuedInvoice, receivedInvoice }) => {
const { t } = useTranslation("invoices");
const router = useRouter();
const [tabIndex, setTabIndex] = useState(0);

const [filteredInvoices, setFilteredInvoices] = useState(invoices);
const [filteredIssuedInvoices, setFilteredIssuedInvoices] = useState(issuedInvoice);
const [filteredReceivedInvoices, setFilteredReceivedInvoices] = useState(receivedInvoice);

const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [
{ label: t("Project code"), paramName: "projectCode", type: "text" },
{ label: t("Project name"), paramName: "projectName", type: "text" },
// { label: t("Stage"), paramName: "stage", type: "text" },
{
label: t("Coming payment milestone from"),
label2: t("Coming payment milestone to"),
paramName: "comingPaymentMileStone",
type: "dateRange"
},
{
label: t("Payment date from"),
label2: t("Payment date to"),
paramName: "paymentMilestoneDate",
type: "dateRange"
},
// { label: t("Resource utilization %"), paramName: "resourceUsage", type: "text" },
// { label: t("Unbilled hours"), paramName: "unbilledHours", type: "text" },
// { label: t("Reminder to issue invoice"), paramName: "reminder", type: "text" },
{ label: t("Invoice No"), paramName: "invoiceNo", type: "text" },
{ label: t("Project Code"), paramName: "projectCode", type: "text" },
{ label: t("Invoice Date"), label2: t("Invoice Date To"), paramName: "invoiceDate", type: "dateRange" },
{ label: t("Due Date"), label2: t("Due Date To"), paramName: "dueDate", type: "dateRange" },
],
[t, issuedInvoice],
);

const searchCriteria2: Criterion<SearchParamNames2>[] = useMemo(
() => [
{ label: t("Invoice No"), paramName: "invoiceNo", type: "text" },
{ label: t("Project Code"), paramName: "projectCode", type: "text" },
{ label: t("Recipt Date"), label2: t("Recipt Date To"), paramName: "receiptDate", type: "dateRange" },
],
[t, invoices],
[t, issuedInvoice],
);

const onReset = useCallback(() => {
setFilteredInvoices(invoices);
}, [invoices]);
setFilteredIssuedInvoices(issuedInvoice);
}, [issuedInvoice]);

const handleImportClick = useCallback(async (event:any) => {
// console.log(event)
try {

const file = event.target.files[0];

if (!file) {
console.log('No file selected');
return;
}

if (file.type !== 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
console.log('Invalid file format. Only XLSX files are allowed.');
return;
}

const formData = new FormData();
formData.append('multipartFileList', file);

const response = await importIssuedInovice(formData);
// response: status, message, projectList, emptyRowList, invoiceList

console.log(response)
if (response.status) {
successDialog(t("Import Success"), t).then(() => {
window.location.reload()
})
}else{
if (response.emptyRowList.length >= 1){
errorDialogWithContent(t("Import Fail"),
t(`Please fill the mandatory field at Row <br> ${response.emptyRowList.join(", ")}`), t)
.then(() => {
window.location.reload()
})
}
else if (response.projectList.length >= 1){
errorDialogWithContent(t("Import Fail"),
t(`Please check the corresponding project code <br> ${response.projectList.join(", ")}`), t)
.then(() => {
// window.location.reload()
})
}
else if (response.invoiceList.length >= 1){
errorDialogWithContent(t("Import Fail"),
t(`Please check the corresponding Invoice No. <br>`)+ `${response.invoiceList.join(", ")}`, t)
.then(() => {
window.location.reload()
})
}
}

} catch (err) {
console.log(err)
return false
}
}, []);

const handleExportClick = useCallback(async (event:any) => {
try {

const file = event.target.files[0];

const onProjectClick = useCallback((project: InvoiceResult) => {
console.log(project);
router.push(`/invoice/new?id=${project.id}`)
}, [router, t]);
if (!file) {
console.log('No file selected');
return;
}

const columns = useMemo<Column<InvoiceResult>[]>(
if (file.type !== 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
console.log('Invalid file format. Only XLSX files are allowed.');
return;
}

const formData = new FormData();
formData.append('multipartFileList', file);

const response = await importReceivedInovice(formData)
}catch(error){
console.log(error)
}
}, []);

const columns = useMemo<Column<issuedInvoiceList>[]>(
() => [
{
name: "id",
label: t("Details"),
onClick: onProjectClick,
buttonIcon: <EditNote />,
},
{ name: "projectCode", label: t("Project code") },
{ name: "projectName", label: t("Project name") },
{ name: "invoiceNo", label: t("Invoice No") },
{ name: "projectCode", label: t("Project Code") },
{ name: "stage", label: t("Stage") },
{ name: "comingPaymentMileStone", label: t("Coming payment milestone") },
{ name: "paymentMilestoneDate", label: t("Payment date") },
{ name: "resourceUsage", label: t("Resource utilization %") },
{ name: "unbilledHours", label: t("Unbilled hours") },
{ name: "reminder", label: t("Reminder to issue invoice") },
{ name: "paymentMilestone", label: t("Payment Milestone") },
{ name: "invoiceDate", label: t("Invocie Date") },
{ name: "dueDate", label: t("Due Date") },
{ name: "issuedAmount", label: t("Amount (HKD") },
],
[t, onProjectClick],
[t],
);

function isDateInRange(dateToCheck: string, startDate: string, endDate: string): boolean {

if (!startDate || !endDate) {
return false;
}

// console.log(dateToCheck, startDate, endDate)
const dateToCheckObj = new Date(dateToCheck);
const startDateObj = new Date(startDate);
const endDateObj = new Date(endDate);
// console.log(dateToCheckObj >= startDateObj && dateToCheckObj <= endDateObj)
return dateToCheckObj >= startDateObj && dateToCheckObj <= endDateObj;
}

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

return (
<>
<SearchBox
criteria={searchCriteria}
<Stack
direction="row"
justifyContent="right"
flexWrap="wrap"
spacing={2}
>
{/* <ButtonGroup variant="contained"> */}
<Button startIcon={<FileUploadIcon />} variant="contained" component="label">
<input
id='importExcel'
type='file'
accept='.xlsx, .csv'
hidden
onChange={(event) => {handleImportClick(event)}}
/>
{t("Import Invoice Issue Summary")}
</Button>
<Button startIcon={<FileUploadIcon />} component="label" variant="contained">
<input
id='importExcel'
type='file'
accept='.xlsx, .csv'
hidden
onChange={(event) => {handleExportClick(event)}}
/>
{t("Import Invoice Amount Receive Summary")}
</Button>
{/* </ButtonGroup> */}
</Stack>
{
tabIndex == 0 &&
<SearchBox
criteria={searchCriteria}
onSearch={(query) => {
console.log(query)
setFilteredIssuedInvoices(
issuedInvoice.filter(
(s) =>
(isDateInRange(s.invoiceDate, query.invoiceDate ?? undefined, query.invoiceDateTo ?? undefined)) ||
(isDateInRange(s.dueDate, query.dueDate ?? undefined, query.dueDateTo ?? undefined)) ||
(s.invoiceNo === query.invoiceNo) ||
(s.projectCode === query.projectCode)
),
);
}}
onReset={onReset}
/>
}
{
tabIndex == 1 &&
<SearchBox
criteria={searchCriteria2}
onSearch={(query) => {
console.log(query)
setFilteredInvoices(
invoices.filter(
(d) =>
d.projectCode.toLowerCase().includes(query.projectCode.toLowerCase()) &&
d.projectName.toLowerCase().includes(query.projectName.toLowerCase()) &&
{/*(query.client === "All" || p.client === query.client) &&
(query.category === "All" || p.category === query.category) &&
(query.team === "All" || p.team === query.team), **/}
setFilteredIssuedInvoices(
issuedInvoice.filter(
(s) =>
(isDateInRange(s.invoiceDate, query.receiptDate ?? undefined, query.receiptDateTo ?? undefined)) ||
(s.invoiceNo === query.invoiceNo) ||
(s.projectCode === query.projectCode)
),
);
}}
onReset={onReset}
/>
<SearchResults<InvoiceResult>
items={filteredInvoices}
columns={columns}
/>
}
<Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
<Tab label={t("Issued Invoices")}/>
<Tab label={t("Recieved Invoices")}/>
</Tabs>
{
tabIndex == 0 &&
<SearchResults<issuedInvoiceList>
items={filteredIssuedInvoices}
columns={columns}
/>
}
{
tabIndex == 1 &&
<p>Todo</p>
}
</>
);
};


+ 3
- 3
src/components/InvoiceSearch/InvoiceSearchLoading.tsx Ver fichero

@@ -5,7 +5,7 @@ import Stack from "@mui/material/Stack";
import React from "react";

// Can make this nicer
export const InvoiceSearchLoading: React.FC = () => {
export const SalarySearchLoading: React.FC = () => {
return (
<>
<Card>
@@ -23,7 +23,7 @@ export const InvoiceSearchLoading: React.FC = () => {
</Stack>
</CardContent>
</Card>
<Card>Invoice
<Card>Salary
<CardContent>
<Stack spacing={2}>
<Skeleton variant="rounded" height={40} />
@@ -37,4 +37,4 @@ export const InvoiceSearchLoading: React.FC = () => {
);
};

export default InvoiceSearchLoading;
export default SalarySearchLoading;

+ 31
- 11
src/components/InvoiceSearch/InvoiceSearchWrapper.tsx Ver fichero

@@ -1,24 +1,44 @@
// import { fetchInvoiceCategories, fetchInvoices } from "@/app/api/companys";
import React from "react";
import InvoiceSearch from "./InvoiceSearch";
import InvoiceSearchLoading from "./InvoiceSearchLoading";
import { fetchInvoices } from "@/app/api/invoices";
import { timestampToDateString } from "@/app/utils/formatUtil";
import { fetchIssuedInvoices, fetchReceivedInvoices, issuedInvoiceList, issuedInvoiceResult } from "@/app/api/invoices";
import { INPUT_DATE_FORMAT, convertDateArrayToString, moneyFormatter } from "@/app/utils/formatUtil";


interface SubComponents {
Loading: typeof InvoiceSearchLoading;
}

const InvoiceSearchWrapper: React.FC & SubComponents = async () => {
const Invoices = await fetchInvoices();

const temp = Invoices.map((invoice) => ({
...invoice,
paymentMilestoneDate: timestampToDateString(invoice.paymentMilestoneDate)
}))
// function calculateHourlyRate(loweLimit: number, upperLimit: number, numOfWorkingDay: number, workingHour: number){
// const hourlyRate = (loweLimit + upperLimit)/2/numOfWorkingDay/workingHour
// return hourlyRate.toLocaleString()
// }

return <InvoiceSearch invoices={temp} />;
const InvoiceSearchWrapper: React.FC & SubComponents = async () => {
const issuedInvoices = await fetchIssuedInvoices()
// const receivedInvoices = await fetchReceivedInvoices()
const convertedIssedInvoices = issuedInvoices.map((invoice)=>{
return{
id: invoice.id,
invoiceNo: invoice.invoiceNo,
projectCode: invoice.projectCode,
projectName: invoice.projectName,
stage: invoice.stage,
paymentMilestone: invoice.paymentMilestone,
invoiceDate: convertDateArrayToString(invoice.invoiceDate, INPUT_DATE_FORMAT, false)!!,
dueDate: convertDateArrayToString(invoice.dueDate, INPUT_DATE_FORMAT, false)!!,
issuedAmount: moneyFormatter.format(invoice.issuedAmount)
}
})


return <InvoiceSearch
issuedInvoice={convertedIssedInvoices}
receivedInvoice={[]}
/>
};

InvoiceSearchWrapper.Loading = InvoiceSearchLoading;


+ 104
- 0
src/components/InvoiceSearch_forGen/InvoiceSearch.tsx Ver fichero

@@ -0,0 +1,104 @@
"use client";

import React, { useCallback, useMemo, useState } from "react";
import SearchBox, { Criterion } from "../SearchBox";
import { useTranslation } from "react-i18next";
import SearchResults, { Column } from "../SearchResults";
import EditNote from "@mui/icons-material/EditNote";
import { InvoiceResult } from "@/app/api/invoices";
import { useRouter } from "next/navigation";

interface Props {
invoices: InvoiceResult[];
}

type SearchQuery = Partial<Omit<InvoiceResult, "id">>;
type SearchParamNames = keyof SearchQuery;

const InvoiceSearch: React.FC<Props> = ({ invoices }) => {
const { t } = useTranslation("invoices");
const router = useRouter();

const [filteredInvoices, setFilteredInvoices] = useState(invoices);

const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [
{ label: t("Project code"), paramName: "projectCode", type: "text" },
{ label: t("Project name"), paramName: "projectName", type: "text" },
// { label: t("Stage"), paramName: "stage", type: "text" },
{
label: t("Coming payment milestone from"),
label2: t("Coming payment milestone to"),
paramName: "comingPaymentMileStone",
type: "dateRange"
},
{
label: t("Payment date from"),
label2: t("Payment date to"),
paramName: "paymentMilestoneDate",
type: "dateRange"
},
// { label: t("Resource utilization %"), paramName: "resourceUsage", type: "text" },
// { label: t("Unbilled hours"), paramName: "unbilledHours", type: "text" },
// { label: t("Reminder to issue invoice"), paramName: "reminder", type: "text" },
],
[t, invoices],
);

const onReset = useCallback(() => {
setFilteredInvoices(invoices);
}, [invoices]);

const onProjectClick = useCallback((project: InvoiceResult) => {
console.log(project);
router.push(`/invoice/new?id=${project.id}`)
}, [router, t]);

const columns = useMemo<Column<InvoiceResult>[]>(
() => [
{
name: "id",
label: t("Details"),
onClick: onProjectClick,
buttonIcon: <EditNote />,
},
{ name: "projectCode", label: t("Project code") },
{ name: "projectName", label: t("Project name") },
{ name: "stage", label: t("Stage") },
{ name: "comingPaymentMileStone", label: t("Coming payment milestone") },
{ name: "paymentMilestoneDate", label: t("Payment date") },
{ name: "resourceUsage", label: t("Resource utilization %") },
{ name: "unbilledHours", label: t("Unbilled hours") },
{ name: "reminder", label: t("Reminder to issue invoice") },
],
[t, onProjectClick],
);

return (
<>
<SearchBox
criteria={searchCriteria}
onSearch={(query) => {
console.log(query)
setFilteredInvoices(
invoices.filter(
(d) =>
d.projectCode.toLowerCase().includes(query.projectCode.toLowerCase()) &&
d.projectName.toLowerCase().includes(query.projectName.toLowerCase()) &&
{/*(query.client === "All" || p.client === query.client) &&
(query.category === "All" || p.category === query.category) &&
(query.team === "All" || p.team === query.team), **/}
),
);
}}
onReset={onReset}
/>
<SearchResults<InvoiceResult>
items={filteredInvoices}
columns={columns}
/>
</>
);
};

export default InvoiceSearch;

+ 40
- 0
src/components/InvoiceSearch_forGen/InvoiceSearchLoading.tsx Ver fichero

@@ -0,0 +1,40 @@
import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent";
import Skeleton from "@mui/material/Skeleton";
import Stack from "@mui/material/Stack";
import React from "react";

// Can make this nicer
export const InvoiceSearchLoading: React.FC = () => {
return (
<>
<Card>
<CardContent>
<Stack spacing={2}>
<Skeleton variant="rounded" height={60} />
<Skeleton variant="rounded" height={60} />
<Skeleton variant="rounded" height={60} />
<Skeleton
variant="rounded"
height={50}
width={100}
sx={{ alignSelf: "flex-end" }}
/>
</Stack>
</CardContent>
</Card>
<Card>Invoice
<CardContent>
<Stack spacing={2}>
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
</Stack>
</CardContent>
</Card>
</>
);
};

export default InvoiceSearchLoading;

+ 26
- 0
src/components/InvoiceSearch_forGen/InvoiceSearchWrapper.tsx Ver fichero

@@ -0,0 +1,26 @@

import React from "react";
import InvoiceSearch from "./InvoiceSearch";
import InvoiceSearchLoading from "./InvoiceSearchLoading";
import { fetchInvoices } from "@/app/api/invoices";
import { timestampToDateString } from "@/app/utils/formatUtil";


interface SubComponents {
Loading: typeof InvoiceSearchLoading;
}

const InvoiceSearchWrapper: React.FC & SubComponents = async () => {
const Invoices = await fetchInvoices();

const temp = Invoices.map((invoice) => ({
...invoice,
paymentMilestoneDate: timestampToDateString(invoice.paymentMilestoneDate)
}))

return <InvoiceSearch invoices={temp} />;
};

InvoiceSearchWrapper.Loading = InvoiceSearchLoading;

export default InvoiceSearchWrapper;

+ 1
- 0
src/components/InvoiceSearch_forGen/index.ts Ver fichero

@@ -0,0 +1 @@
export { default } from "./InvoiceSearchWrapper";

+ 31
- 13
src/components/SalarySearch/SalarySearch.tsx Ver fichero

@@ -12,6 +12,7 @@ import FileDownloadIcon from '@mui/icons-material/FileDownload';
import FileUploadIcon from '@mui/icons-material/FileUpload';
import { exportSalary, importSalarys } from "@/app/api/salarys/actions";
import { downloadFile } from "@/app/utils/commonUtil";
import { errorDialog, successDialog } from "../Swal/CustomAlerts";

interface Props {
salarys: SalaryResult[];
@@ -63,7 +64,13 @@ const SalarySearch: React.FC<Props> = ({ salarys }) => {
const response = await importSalarys(formData);

if (response === "OK") {
window.location.reload()
successDialog(t("Import Success"), t).then(()=>{
window.location.reload()
})
}else{
errorDialog(t("Import Fail")).then(()=>{
window.location.reload()
})
}

} catch (err) {
@@ -74,20 +81,24 @@ const SalarySearch: React.FC<Props> = ({ salarys }) => {

const handleExportClick = useCallback(async (event:any) => {
// console.log(event);
const response = await exportSalary()
if (response) {
downloadFile(new Uint8Array(response.blobValue), response.filename!!)
try{
const response = await exportSalary()
if (response) {
downloadFile(new Uint8Array(response.blobValue), response.filename!!)
}
}catch(error){
console.log(error)
}
}, []);

const columns = useMemo<Column<SalaryResult>[]>(
() => [
{
name: "id",
label: t("Details"),
onClick: onSalaryClick,
buttonIcon: <EditNote />,
},
// {
// name: "id",
// label: t("Details"),
// onClick: onSalaryClick,
// buttonIcon: <EditNote />,
// },
{ name: "salaryPoint", label: t("Salary Point") },
{ name: "lowerLimit", label: t("Lower Limit") },
{ name: "upperLimit", label: t("Upper Limit") },
@@ -123,12 +134,19 @@ const SalarySearch: React.FC<Props> = ({ salarys }) => {
<SearchBox
criteria={searchCriteria}
onSearch={(query) => {
// console.log(Number(query.salaryPoint))
setFilteredSalarys(
salarys.filter(
(s) =>
((convertLocaleStringToNumber(s.lowerLimit) <= Number(query.salary))&&
(convertLocaleStringToNumber(s.upperLimit) >= Number(query.salary)))||
(s.salaryPoint === Number(query.salaryPoint))
{
// console.log(s)
return (
((convertLocaleStringToNumber(s.lowerLimit) <= Number(query.salary))&&
(convertLocaleStringToNumber(s.upperLimit) >= Number(query.salary)))||
(s.salaryPoint === Number(query.salaryPoint))
)
}
),
);
}}


+ 10
- 0
src/components/Swal/CustomAlerts.js Ver fichero

@@ -42,6 +42,16 @@ export const errorDialog = (text, t) => {
})
}

export const errorDialogWithContent = (title, text, t) => {
return Swal.fire({
icon: "error",
title: title,
html: text,
confirmButtonText: t("Confirm"),
showConfirmButton: true,
})
}

export const warningDialog = (text, t) => {
return Swal.fire({
icon: "warning",


Cargando…
Cancelar
Guardar