diff --git a/src/app/(main)/expense/page.tsx b/src/app/(main)/expense/page.tsx
index 6cf1645..9d24cf1 100644
--- a/src/app/(main)/expense/page.tsx
+++ b/src/app/(main)/expense/page.tsx
@@ -1,15 +1,16 @@
+import { preloadExpense } from "@/app/api/expenses"
import ExpenseSearch from "@/components/ExpenseSearch"
import { getServerI18n, I18nProvider } from "@/i18n"
import { Stack, Typography } from "@mui/material"
import { Suspense } from "react"
const Expense: React.FC = async () => {
+ preloadExpense()
const { t } = await getServerI18n("expense")
return(
{
+ fetchExpenses()
+ };
+
+export const fetchExpenses = cache(async () => {
+ return serverFetchJson(`${BASE_API_URL}/expense`, {
+ next: { tags: ["expense"] },
+ });
+ });
\ No newline at end of file
diff --git a/src/components/ExpenseSearch/ExpenseSearch.tsx b/src/components/ExpenseSearch/ExpenseSearch.tsx
index 8a0dc1d..d1ce72a 100644
--- a/src/components/ExpenseSearch/ExpenseSearch.tsx
+++ b/src/components/ExpenseSearch/ExpenseSearch.tsx
@@ -1,443 +1,144 @@
"use client";
-
-import React, { useCallback, useMemo, useState } from "react";
-import SearchBox, { Criterion } from "../SearchBox";
+import { ExpensesResult } from "@/app/api/expenses";
+import { useCallback, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
+import SearchBox, { Criterion } from "../SearchBox";
import SearchResults, { Column } from "../SearchResults";
-import { moneyFormatter } from "@/app/utils/formatUtil"
-import {
- Button, ButtonGroup, Stack,
- Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions,
- CardContent, Typography, Divider, Card
+import { useRouter } from "next/navigation";
+import {
+ Button,
+ ButtonGroup,
+ Card,
+ CardContent,
+ Divider,
+ Grid,
+ Stack,
+ Typography,
} from "@mui/material";
+import { moneyFormatter } from "@/app/utils/formatUtil";
+import { EditNote } from "@mui/icons-material";
import AddIcon from '@mui/icons-material/Add';
-import { deleteInvoice, updateInvoice } from "@/app/api/invoices/actions";
-import { deleteDialog, errorDialogWithContent, successDialog } from "../Swal/CustomAlerts";
-import { invoiceList, issuedInvoiceList, issuedInvoiceSearchForm, receivedInvoiceList } from "@/app/api/invoices";
-import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
-import { GridCellParams, GridColDef, GridEventListener, GridRowId, GridRowModes, GridRowModesModel } from "@mui/x-data-grid";
-import { useGridApiRef } from "@mui/x-data-grid";
-import StyledDataGrid from "../StyledDataGrid";
-
-import { uniq } from "lodash";
-import CreateInvoiceModal from "./CreateExpenseModal";
-import { ProjectResult } from "@/app/api/projects";
-
-
interface Props {
- invoices: invoiceList[];
- projects: ProjectResult[];
+ expenses: ExpensesResult[]
}
-type InvoiceListError = {
- [field in keyof invoiceList]?: string;
-};
-
-type invoiceListRow = Partial<
- invoiceList & {
- _isNew: boolean;
- _error: InvoiceListError;
- }
->;
-
-type SearchQuery = Partial>;
+type SearchQuery = Partial>;
type SearchParamNames = keyof SearchQuery;
-
-const ExpenseSearch: React.FC = ({ invoices, projects }) => {
- // console.log(invoices)
- const { t } = useTranslation("expense");
-
-
- const [filteredIvoices, setFilterInovices] = useState(invoices);
+const ExpenseSearch: React.FC = ({ expenses }) => {
+ console.log(expenses)
+ const router = useRouter();
+ const { t } = useTranslation("expenses");
+ const [filteredExpenses, setFilteredExpenses] = useState(expenses);
const searchCriteria: Criterion[] = useMemo(
() => [
- { label: t("Invoice No"), paramName: "invoiceNo", type: "text" },
+ // { label: t("Expense No"), paramName: "ExpenseNo", type: "text" },
{ label: t("Project Code"), paramName: "projectCode", type: "text" },
+ { label: t("Project Name"), paramName: "projectName", type: "text" },
{
- label: t("Team"),
- paramName: "team",
- type: "select",
- options: uniq(invoices.map((invoice) => invoice.teamCodeName)),
+ label: t("Verified Date"),
+ label2: t("Verified Date To"),
+ paramName: "verifiedDatetime",
+ type: "dateRange",
},
- { label: t("Issue Date"), label2: t("Issue Date To"), paramName: "invoiceDate", type: "dateRange" },
- { label: t("Settle Date"), label2: t("Settle Date To"), paramName: "dueDate", type: "dateRange" },
- ],
- [t, invoices],
- );
-
- const onReset = useCallback(() => {
- setFilterInovices(invoices)
- }, [invoices]);
-
- const [modelOpen, setModelOpen] = useState(false);
-
- const handleAddInvoiceClick = useCallback(() => {
- setModelOpen(true)
- },[])
-
- const handleModalClose = useCallback(() => {
- setModelOpen(false)
- },[])
-
- const showErrorDialog = (title: string, content: string) => {
- errorDialogWithContent(title, content, t).then(() => {
- window.location.reload();
- });
- };
-
- const columns = useMemo[]>(
- () => [
- { 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[]>(
- () => [
- { 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 onExpenseClick = useCallback(
+ (expenses?: ExpensesResult) => {},
+ [router]
);
- const [selectedRow, setSelectedRow] = useState([]);
- const [dialogOpen, setDialogOpen] = useState(false);
-
- const handleButtonClick = (row: invoiceList) => {
- console.log(row)
- setSelectedRow([row]);
- setDialogOpen(true);
- setRowModesModel((model) => ({
- ...model,
- [row.id]: { mode: GridRowModes.Edit, fieldToFocus: "issuedAmount" },
- }));
- };
-
- const handleCloseDialog = () => {
- setDialogOpen(false);
- };
-
- const handleDeleteInvoice = useCallback(() => {
- deleteDialog(async() => {
- //console.log(selectedRow[0])
- await deleteInvoice(selectedRow[0].id!!)
- setDialogOpen(false);
- const result = await successDialog("Delete Success", t);
- if (result) {
- window.location.reload()
- }
- }, t)
- }, [selectedRow]);
-
-
- const handleSaveDialog = async () => {
- await updateInvoice(selectedRow[0])
- setDialogOpen(false);
- successDialog(t("Update Success"), t).then(() => {
- window.location.reload()
- })
- };
-
- const combinedColumns = useMemo[]>(
+ const columns = useMemo[]>(
() => [
- {
- name: "invoiceNo",
- label: t("Edit"),
- onClick: (row: invoiceList) => (
- handleButtonClick(row)
- ),
- buttonIcon:
+ {
+ name: "id",
+ label: t("Details"),
+ onClick: onExpenseClick,
+ buttonIcon: ,
+ // disabled: !abilities.includes(MAINTAIN_PROJECT),
},
- { name: "invoiceNo", label: t("Invoice No") },
{ name: "projectCode", label: t("Project Code") },
{ name: "projectName", label: t("Project Name") },
- { name: "team", label: t("Team") },
- { name: "issuedDate", label: t("Issue Date") },
- { name: "issuedAmount", label: t("Amount (HKD)"), type: 'money', needTranslation: true },
- { name: "receiptDate", label: t("Settle Date") },
- { name: "receivedAmount", label: t("Actual Received Amount (HKD)"), type: 'money', needTranslation: true },
- ],
- [t]
- )
-
- const editCombinedColumns = useMemo(
- () => [
- { field: "invoiceNo", headerName: t("Invoice No"), editable: true, flex: 0.5 },
- { field: "projectCode", headerName: t("Project Code"), editable: false, flex: 0.3 },
- { field: "projectName", headerName: t("Project Name"), flex: 1 },
- { field: "team", headerName: t("Team"), flex: 0.2 },
- { field: "issuedDate",
- headerName: t("Issue Date"),
- editable: true,
- flex: 0.4,
- // type: 'date',
- // valueGetter: (params) => {
- // // console.log(params.row.issuedDate)
- // return new Date(params.row.issuedDate)
- // },
- },
- { field: "issuedAmount",
- headerName: t("Amount (HKD)"),
- editable: true,
- flex: 0.5,
- type: 'number'
- },
- {
- field: "receiptDate",
- headerName: t("Settle Date"),
- editable: true,
- flex: 0.4,
- // renderCell: (params) => {
- // console.log(params)
- // return (
- //
- //
- //
- // );
- // }
- },
- { field: "receivedAmount",
- headerName: t("Actual Received Amount (HKD)"),
- editable: true,
- flex: 0.5,
- type: 'number'
- },
+ { name: "verifiedDatetime", label: t("verifiedDatetime") },
],
- [t]
- )
-
- function isDateInRange(dateToCheck: string, startDate: string, endDate: string): boolean {
-
- if ((!startDate || startDate === "Invalid Date") && (!endDate || endDate === "Invalid Date")) {
- return true;
- }
-
- const dateToCheckObj = new Date(dateToCheck);
- const startDateObj = new Date(startDate);
- const endDateObj = new Date(endDate);
-
- return ((!startDate || startDate === "Invalid Date") || dateToCheckObj >= startDateObj) && ((!endDate || endDate === "Invalid Date") || dateToCheckObj <= endDateObj);
- }
-
- const [rowModesModel, setRowModesModel] = useState({});
- const apiRef = useGridApiRef();
-
- const validateInvoiceEntry = (
- entry: Partial,
- ): InvoiceListError | undefined => {
- // Test for errors
- const error: InvoiceListError = {};
-
- console.log(entry)
- if (!entry.issuedAmount) {
- error.issuedAmount = "Please input issued amount ";
- } else if (!entry.issuedAmount) {
- error.receivedAmount = "Please input received amount";
- } else if (entry.invoiceNo === "") {
- error.invoiceNo = "Please input invoice number";
- } else if (!entry.issuedDate) {
- error.issuedDate = "Please input issue date";
- } else if (!entry.receiptDate){
- error.receiptDate = "Please input receipt date";
- }
-
-
- return Object.keys(error).length > 0 ? error : undefined;
- }
-
- const validateRow = useCallback(
- (id: GridRowId) => {
- const row = apiRef.current.getRowWithUpdatedValues(
- id,
- "",
- )
-
- const error = validateInvoiceEntry(row);
- console.log(error)
- // Test for warnings
-
- apiRef.current.updateRows([{ id, _error: error }]);
- return !error;
- },
- [apiRef],
- );
-
- const handleEditStop = useCallback>(
- (params, event) => {
- // console.log(params.id)
- if (validateRow(params.id) !== undefined || !validateRow(params.id)) {
-
- setRowModesModel((model) => ({
- ...model,
- [params.id]: { mode: GridRowModes.View},
- }));
-
- const row = apiRef.current.getRowWithUpdatedValues(
- params.id,
- "",
- )
- console.log(row)
- setSelectedRow([{...row}] as invoiceList[])
- event.defaultMuiPrevented = true;
- }
- // console.log(row)
- },
- [validateRow],
+ [t, onExpenseClick]
);
+ const onReset = useCallback(() => {
+ // setFilteredExpenses();
+ }, []);
return (
- <>
-
- }
- variant="contained"
- component="label"
- onClick={handleAddInvoiceClick}
- >
- {t("Create Expense")}
-
-
-
- {
- // tabIndex == 0 &&
- {
- // console.log(query)
- setFilterInovices(
- invoices.filter(
- (s) => (s.invoiceNo.toLowerCase().includes(query.invoiceNo.toLowerCase()))
- && (s.projectCode.toLowerCase().includes(query.projectCode.toLowerCase()))
- && (query.team === "All" || query.team.toLowerCase().includes(s.team.toLowerCase()))
- && (isDateInRange(s.issuedDate, query.invoiceDate ?? undefined, query.invoiceDateTo ?? undefined))
- && (isDateInRange(s.receiptDate, query.dueDate ?? undefined, query.dueDateTo ?? undefined))
- ),
- );
- }}
- onReset={onReset}
- />
- }
-
+
+
+
+ }
+ variant="outlined"
+ component="label"
+ onClick={() => console.log()}
+ >
+ {t("Create expense")}
+
+
+
+
+ {
+ // setFilteredExpenses(
+ // projects.filter(
+ // (p) =>
+ // p.code.toLowerCase().includes(query.code.toLowerCase()) &&
+ // p.name.toLowerCase().includes(query.name.toLowerCase()) &&
+ // (query.client === "All" || p.client === query.client) &&
+ // (query.category === "All" || p.category === query.category) &&
+ // // (query.team === "All" || p.team === query.team) &&
+ // (query.team === "All" || query.team.toLowerCase().includes(p.team.toLowerCase())) &&
+ // (query.status === "All" || p.status === query.status),
+ // ),
+ // );
+ }}
+ onReset={onReset}
+ />
-
- {t('Total Issued Amount (HKD)')}:
- {moneyFormatter.format(filteredIvoices.reduce((acc, current) => (acc + current.issuedAmount), 0))}
-
-
- {t('Total Received Amount (HKD)')}:
- {moneyFormatter.format(filteredIvoices.reduce((acc, current) => (acc + current.receivedAmount), 0))}
-
+
+
+ {t("Total Issued Amount (HKD)")}:
+
+
+ {/* {moneyFormatter.format(filteredExpenses.reduce((acc, curr) => (acc + curr.issuedAmount), 0))} */}
+
+
+
+
+ {t("Total Received Amount (HKD)")}:
+
+
+ {/* {moneyFormatter.format(filteredExpenses.reduce((acc, curr) => (acc + curr.receivedAmount), 0))} */}
+
+
-
-
-
- items={filteredIvoices}
- columns={combinedColumns}
- autoRedirectToFirstPage
- />
-
-
-
- >
+
);
};
-
export default ExpenseSearch;
diff --git a/src/components/ExpenseSearch/ExpenseSearchWrapper.tsx b/src/components/ExpenseSearch/ExpenseSearchWrapper.tsx
index 1cc2282..a39b2f7 100644
--- a/src/components/ExpenseSearch/ExpenseSearchWrapper.tsx
+++ b/src/components/ExpenseSearch/ExpenseSearchWrapper.tsx
@@ -1,18 +1,33 @@
import React from "react";
import ExpenseSearch from "./ExpenseSearch";
import ExpenseSearchLoading from "./ExpenseSearchLoading";
+import { fetchExpenses } from "@/app/api/expenses";
+import dayjs from "dayjs";
+import arraySupport from "dayjs/plugin/arraySupport";
+import { OUTPUT_DATE_FORMAT, OUTPUT_TIME_FORMAT } from "@/app/utils/formatUtil";
interface SubComponents {
Loading: typeof ExpenseSearchLoading;
}
+dayjs.extend(arraySupport)
const ExpenseSearchWrapper: React.FC & SubComponents = async () => {
-
-
+ const [
+ Expenses
+ ] = await Promise.all([
+ fetchExpenses(),
+ ]);
+ const _expenses = Expenses.map((e) => {
+ const date: number[] = e.verifiedDatetime as number[];
+ const formattedDate = dayjs([date[0], date[1], date[2]].join()).format(OUTPUT_DATE_FORMAT)
+ return ({
+ ...e,
+ verifiedDatetime: formattedDate
+ })
+ })
return
};
diff --git a/src/components/InvoiceSearch/InvoiceTable.tsx b/src/components/InvoiceSearch/InvoiceTable.tsx
index 3f33f44..42a7b2b 100644
--- a/src/components/InvoiceSearch/InvoiceTable.tsx
+++ b/src/components/InvoiceSearch/InvoiceTable.tsx
@@ -58,11 +58,8 @@ type project = {
value: number;
}
const InvoiceTable: React.FC = ({ projects }) => {
- console.log(projects)
- // const projectCombos: project[] = projects.map(item => ({
- // value: item.id,
- // label: item.code
- // }))
+ // if change this to code - name,
+ // also change the submit function
const projectCombos = projects.map(item => item.code)
const { t } = useTranslation()
const [rowModesModel, setRowModesModel] = useState({});