@@ -2,7 +2,7 @@ | |||||
import { serverFetchJson, serverFetchString, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | import { serverFetchJson, serverFetchString, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | ||||
import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
import { revalidateTag } from "next/cache"; | |||||
import { revalidatePath, revalidateTag } from "next/cache"; | |||||
import { cache } from "react"; | import { cache } from "react"; | ||||
export interface InvoiceResult { | export interface InvoiceResult { | ||||
@@ -145,8 +145,8 @@ export const updateInvoice = async (data: any) => { | |||||
body: JSON.stringify(data), | body: JSON.stringify(data), | ||||
headers: { "Content-Type": "application/json" }, | headers: { "Content-Type": "application/json" }, | ||||
}); | }); | ||||
revalidateTag("invoices") | revalidateTag("invoices") | ||||
revalidatePath("/(main)/invoice") | |||||
return updateInvoice; | return updateInvoice; | ||||
} | } | ||||
@@ -69,7 +69,7 @@ const CreateInvoiceModal: React.FC<Props> = ({isOpen, onClose, projects, invoice | |||||
if (response === "OK") { | if (response === "OK") { | ||||
onClose() | onClose() | ||||
successDialog(t("Submit Success"), t).then(() => { | successDialog(t("Submit Success"), t).then(() => { | ||||
router.replace("/invoice"); | |||||
// router.replace("/invoice"); | |||||
}) | }) | ||||
} | } | ||||
}, t) | }, t) | ||||
@@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next"; | |||||
import SearchResults, { Column } from "../SearchResults"; | import SearchResults, { Column } from "../SearchResults"; | ||||
import EditNote from "@mui/icons-material/EditNote"; | import EditNote from "@mui/icons-material/EditNote"; | ||||
import { moneyFormatter } from "@/app/utils/formatUtil" | 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 FileUploadIcon from '@mui/icons-material/FileUpload'; | ||||
import AddIcon from '@mui/icons-material/Add'; | import AddIcon from '@mui/icons-material/Add'; | ||||
import { deleteInvoice, importIssuedInovice, importReceivedInovice, updateInvoice } from "@/app/api/invoices/actions"; | 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 { IMPORT_INVOICE, IMPORT_RECEIPT } from "@/middleware"; | ||||
import InvoiceSearchLoading from "./InvoiceSearchLoading"; | import InvoiceSearchLoading from "./InvoiceSearchLoading"; | ||||
import { NumberFormatValues, NumericFormat } from "react-number-format"; | import { NumberFormatValues, NumericFormat } from "react-number-format"; | ||||
import theme from "@/theme"; | |||||
import { SMALL_ROW_THEME } from "@/theme/colorConst"; | |||||
interface CustomMoneyComponentProps { | interface CustomMoneyComponentProps { | ||||
params: GridRenderEditCellParams; | params: GridRenderEditCellParams; | ||||
@@ -614,11 +616,13 @@ const InvoiceSearch: React.FC<Props> & SubComponents = ({ invoices, projects, ab | |||||
{ | { | ||||
!loading && | !loading && | ||||
// tabIndex == 0 && | // 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> | <Dialog open={dialogOpen} onClose={handleCloseDialog} maxWidth="lg" fullWidth> | ||||
<DialogTitle>{t("Edit Invoice")}</DialogTitle> | <DialogTitle>{t("Edit Invoice")}</DialogTitle> | ||||
@@ -1,6 +1,6 @@ | |||||
"use client"; | "use client"; | ||||
import React, { useEffect } from "react"; | |||||
import React, { useCallback, useEffect, useState } from "react"; | |||||
import Paper from "@mui/material/Paper"; | import Paper from "@mui/material/Paper"; | ||||
import Table from "@mui/material/Table"; | import Table from "@mui/material/Table"; | ||||
import TableBody from "@mui/material/TableBody"; | 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 DoneIcon from '@mui/icons-material/Done'; | ||||
import CloseIcon from '@mui/icons-material/Close'; | import CloseIcon from '@mui/icons-material/Close'; | ||||
import { Button, Link, LinkOwnProps } from "@mui/material"; | 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 { | export interface ResultWithId { | ||||
id: string | number; | id: string | number; | ||||
@@ -155,7 +158,50 @@ function SearchResults<T extends ResultWithId>({ | |||||
return column.underline ?? "always"; | 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 = ( | const table = ( | ||||
<> | <> | ||||
<TableContainer sx={{ maxHeight: 440 }}> | <TableContainer sx={{ maxHeight: 440 }}> | ||||
@@ -163,14 +209,25 @@ function SearchResults<T extends ResultWithId>({ | |||||
<TableHead> | <TableHead> | ||||
<TableRow> | <TableRow> | ||||
{columns.filter(item => item.isHidden !== true).map((column, idx) => ( | {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} | {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> | </TableCell> | ||||
))} | ))} | ||||
</TableRow> | </TableRow> | ||||
</TableHead> | </TableHead> | ||||
<TableBody> | <TableBody> | ||||
{items | |||||
{sortedItems | |||||
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) | .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) | ||||
.map((item) => { | .map((item) => { | ||||
return ( | return ( | ||||
@@ -223,7 +280,7 @@ function SearchResults<T extends ResultWithId>({ | |||||
<TablePagination | <TablePagination | ||||
rowsPerPageOptions={[10, 25, 100]} | rowsPerPageOptions={[10, 25, 100]} | ||||
component="div" | component="div" | ||||
count={items.length} | |||||
count={sortedItems.length} | |||||
rowsPerPage={rowsPerPage} | rowsPerPage={rowsPerPage} | ||||
page={page} | page={page} | ||||
onPageChange={handleChangePage} | 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({ | export const TSMS_BUTTON_THEME = createTheme({ | ||||
palette: { | palette: { | ||||
primary: { | primary: { | ||||