@@ -2,7 +2,7 @@ | |||
import { serverFetchJson, serverFetchString, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | |||
import { BASE_API_URL } from "@/config/api"; | |||
import { revalidateTag } from "next/cache"; | |||
import { revalidatePath, revalidateTag } from "next/cache"; | |||
import { cache } from "react"; | |||
export interface InvoiceResult { | |||
@@ -145,8 +145,8 @@ export const updateInvoice = async (data: any) => { | |||
body: JSON.stringify(data), | |||
headers: { "Content-Type": "application/json" }, | |||
}); | |||
revalidateTag("invoices") | |||
revalidatePath("/(main)/invoice") | |||
return updateInvoice; | |||
} | |||
@@ -69,7 +69,7 @@ const CreateInvoiceModal: React.FC<Props> = ({isOpen, onClose, projects, invoice | |||
if (response === "OK") { | |||
onClose() | |||
successDialog(t("Submit Success"), t).then(() => { | |||
router.replace("/invoice"); | |||
// router.replace("/invoice"); | |||
}) | |||
} | |||
}, t) | |||
@@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next"; | |||
import SearchResults, { Column } from "../SearchResults"; | |||
import EditNote from "@mui/icons-material/EditNote"; | |||
import { moneyFormatter } from "@/app/utils/formatUtil" | |||
import { Button, ButtonGroup, Stack, Tab, Tabs, TabsProps, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, TextField, CardContent, Typography, Divider, Card } from "@mui/material"; | |||
import { Button, ButtonGroup, Stack, Tab, Tabs, TabsProps, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, TextField, CardContent, Typography, Divider, Card, ThemeProvider } from "@mui/material"; | |||
import FileUploadIcon from '@mui/icons-material/FileUpload'; | |||
import AddIcon from '@mui/icons-material/Add'; | |||
import { deleteInvoice, importIssuedInovice, importReceivedInovice, updateInvoice } from "@/app/api/invoices/actions"; | |||
@@ -23,6 +23,8 @@ import { ProjectResult } from "@/app/api/projects"; | |||
import { IMPORT_INVOICE, IMPORT_RECEIPT } from "@/middleware"; | |||
import InvoiceSearchLoading from "./InvoiceSearchLoading"; | |||
import { NumberFormatValues, NumericFormat } from "react-number-format"; | |||
import theme from "@/theme"; | |||
import { SMALL_ROW_THEME } from "@/theme/colorConst"; | |||
interface CustomMoneyComponentProps { | |||
params: GridRenderEditCellParams; | |||
@@ -614,11 +616,13 @@ const InvoiceSearch: React.FC<Props> & SubComponents = ({ invoices, projects, ab | |||
{ | |||
!loading && | |||
// tabIndex == 0 && | |||
<SearchResults<invoiceList> | |||
items={filteredIvoices} | |||
columns={combinedColumns} | |||
autoRedirectToFirstPage | |||
/> | |||
<ThemeProvider theme={SMALL_ROW_THEME}> | |||
<SearchResults<invoiceList> | |||
items={filteredIvoices} | |||
columns={combinedColumns} | |||
autoRedirectToFirstPage | |||
/> | |||
</ThemeProvider> | |||
} | |||
<Dialog open={dialogOpen} onClose={handleCloseDialog} maxWidth="lg" fullWidth> | |||
<DialogTitle>{t("Edit Invoice")}</DialogTitle> | |||
@@ -1,6 +1,6 @@ | |||
"use client"; | |||
import React, { useEffect } from "react"; | |||
import React, { useCallback, useEffect, useState } from "react"; | |||
import Paper from "@mui/material/Paper"; | |||
import Table from "@mui/material/Table"; | |||
import TableBody from "@mui/material/TableBody"; | |||
@@ -18,6 +18,9 @@ import { convertDateArrayToString, moneyFormatter } from "@/app/utils/formatUtil | |||
import DoneIcon from '@mui/icons-material/Done'; | |||
import CloseIcon from '@mui/icons-material/Close'; | |||
import { Button, Link, LinkOwnProps } from "@mui/material"; | |||
import { orderBy, sortBy } from "lodash"; | |||
import ArrowUp from '@mui/icons-material/KeyboardArrowUp'; | |||
import ArrowDown from '@mui/icons-material/KeyboardArrowDown'; | |||
export interface ResultWithId { | |||
id: string | number; | |||
@@ -155,7 +158,50 @@ function SearchResults<T extends ResultWithId>({ | |||
return column.underline ?? "always"; | |||
}; | |||
type OrderProps = Record<keyof T, Boolean> | |||
const [sortedItems, setSortedItems] = useState(items) | |||
const [orderProps, setOrderProps] = useState<OrderProps>(() => { | |||
if (items.length === 0) { | |||
return {} as OrderProps | |||
} | |||
return Object.keys(sortedItems[0]).reduce((acc, key) => { | |||
if (key === "deleted" || key === "id") return acc | |||
acc[key as keyof T] = false; | |||
return acc; | |||
}, {} as OrderProps); | |||
}); | |||
const changeOrder = useCallback((key: keyof T) => { | |||
// preserve all column sorting | |||
// setOrderProps( | |||
// (prev) => ({ | |||
// [...prev]: false, | |||
// [key]: !prev[key] | |||
// }) | |||
// ) | |||
// only sort 1 column | |||
setOrderProps( | |||
(prev) => { | |||
const newOrderProps = Object.keys(prev).reduce((acc, currKey) => { | |||
acc[currKey as keyof T] = currKey === key ? !prev[currKey as keyof T] : false; | |||
return acc; | |||
}, {} as OrderProps); | |||
return newOrderProps; | |||
} | |||
) | |||
}, []) | |||
const sortingItems = useCallback( | |||
(key: keyof T) => { | |||
// true === asc | |||
// false === desc | |||
console.log(orderProps) | |||
if (orderProps[key]) { | |||
return orderBy(sortedItems, key, 'asc') | |||
} else { | |||
return orderBy(sortedItems, key, 'desc') | |||
} | |||
} | |||
, [sortedItems, orderProps] | |||
) | |||
const table = ( | |||
<> | |||
<TableContainer sx={{ maxHeight: 440 }}> | |||
@@ -163,14 +209,25 @@ function SearchResults<T extends ResultWithId>({ | |||
<TableHead> | |||
<TableRow> | |||
{columns.filter(item => item.isHidden !== true).map((column, idx) => ( | |||
<TableCell key={`${column.name.toString()}${idx}`}> | |||
<TableCell | |||
key={`${column.name.toString()}${idx}`} | |||
onClick={() => { | |||
changeOrder(column.name) | |||
setSortedItems(sortingItems(column.name)) | |||
}} | |||
> | |||
{column?.type === "money" ? <div style={{display: "flex", justifyContent: "flex-end"}}>{column.label}</div> : column.label} | |||
{(() => { | |||
const isAscending = orderProps[column.name]; | |||
if (isAscending === undefined) return undefined; | |||
return isAscending ? <ArrowUp /> : <ArrowDown />; | |||
})()} | |||
</TableCell> | |||
))} | |||
</TableRow> | |||
</TableHead> | |||
<TableBody> | |||
{items | |||
{sortedItems | |||
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) | |||
.map((item) => { | |||
return ( | |||
@@ -223,7 +280,7 @@ function SearchResults<T extends ResultWithId>({ | |||
<TablePagination | |||
rowsPerPageOptions={[10, 25, 100]} | |||
component="div" | |||
count={items.length} | |||
count={sortedItems.length} | |||
rowsPerPage={rowsPerPage} | |||
page={page} | |||
onPageChange={handleChangePage} | |||
@@ -176,6 +176,19 @@ export const PW_RULE_THEME = createTheme({ | |||
}, | |||
}); | |||
export const SMALL_ROW_THEME = createTheme({ | |||
components: { | |||
MuiTableRow: { | |||
styleOverrides: { | |||
root: { | |||
// padding: "0px", | |||
height: "0px" | |||
} | |||
} | |||
} | |||
} | |||
}) | |||
export const TSMS_BUTTON_THEME = createTheme({ | |||
palette: { | |||
primary: { | |||