@@ -0,0 +1,34 @@ | |||||
import { preloadInventory } from "@/app/api/inventory"; | |||||
import InventorySearch from "@/components/InventorySearch"; | |||||
import { getServerI18n } from "@/i18n"; | |||||
import { Stack, Typography } from "@mui/material"; | |||||
import { Metadata } from "next"; | |||||
import { Suspense } from "react"; | |||||
export const metadata: Metadata = { | |||||
title: "Inventory" | |||||
} | |||||
const Inventory: React.FC = async () => { | |||||
const { t } = await getServerI18n("inventory") | |||||
preloadInventory() | |||||
return <> | |||||
<Stack | |||||
direction="row" | |||||
justifyContent={"space-between"} | |||||
flexWrap={"wrap"} | |||||
rowGap={2} | |||||
> | |||||
<Typography variant="h4" marginInlineEnd={2}> | |||||
{t("Inventory")} | |||||
</Typography> | |||||
</Stack> | |||||
<Suspense fallback={<InventorySearch.Loading />}> | |||||
<InventorySearch /> | |||||
</Suspense> | |||||
</>; | |||||
} | |||||
export default Inventory; |
@@ -1,19 +1,16 @@ | |||||
import { TypeEnum } from "@/app/utils/typeEnum"; | import { TypeEnum } from "@/app/utils/typeEnum"; | ||||
import ItemsSearch from "@/components/ItemsSearch"; | |||||
import DetailSchedule from "@/components/DetailSchedule"; | |||||
import { getServerI18n } from "@/i18n"; | import { getServerI18n } from "@/i18n"; | ||||
import Add from "@mui/icons-material/Add"; | |||||
import Button from "@mui/material/Button"; | |||||
import Stack from "@mui/material/Stack"; | import Stack from "@mui/material/Stack"; | ||||
import Typography from "@mui/material/Typography"; | import Typography from "@mui/material/Typography"; | ||||
import { Metadata } from "next"; | import { Metadata } from "next"; | ||||
import Link from "next/link"; | |||||
import { Suspense } from "react"; | import { Suspense } from "react"; | ||||
export const metadata: Metadata = { | export const metadata: Metadata = { | ||||
title: "Detail Scheduling", | title: "Detail Scheduling", | ||||
}; | }; | ||||
const detailScheduling: React.FC = async () => { | |||||
const DetailScheduling: React.FC = async () => { | |||||
const project = TypeEnum.PRODUCT | const project = TypeEnum.PRODUCT | ||||
const { t } = await getServerI18n(project); | const { t } = await getServerI18n(project); | ||||
// preloadClaims(); | // preloadClaims(); | ||||
@@ -29,20 +26,12 @@ const detailScheduling: React.FC = async () => { | |||||
<Typography variant="h4" marginInlineEnd={2}> | <Typography variant="h4" marginInlineEnd={2}> | ||||
{t("Detail Scheduling")} | {t("Detail Scheduling")} | ||||
</Typography> | </Typography> | ||||
{/* <Button | |||||
variant="contained" | |||||
startIcon={<Add />} | |||||
LinkComponent={Link} | |||||
href="product/create" | |||||
> | |||||
{t("Create product")} | |||||
</Button> */} | |||||
</Stack> | </Stack> | ||||
<Suspense fallback={<ItemsSearch.Loading />}> | |||||
<ItemsSearch /> | |||||
<Suspense fallback={<DetailSchedule.Loading />}> | |||||
<DetailSchedule /> | |||||
</Suspense> | </Suspense> | ||||
</> | </> | ||||
); | ); | ||||
}; | }; | ||||
export default detailScheduling; | |||||
export default DetailScheduling; |
@@ -0,0 +1,30 @@ | |||||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
import { BASE_API_URL } from "@/config/api"; | |||||
import { cache } from "react"; | |||||
import "server-only"; | |||||
export interface InventoryResult { | |||||
id: number; | |||||
code: string; | |||||
name: string; | |||||
type: string; | |||||
qty: number; | |||||
uomCode: string; | |||||
uomUdfudesc: string; | |||||
germPerSmallestUnit: number; | |||||
qtyPerSmallestUnit: number; | |||||
smallestUnit: string; | |||||
price: number; | |||||
currencyName: string; | |||||
status: string; | |||||
} | |||||
export const preloadInventory = () => { | |||||
fetchInventories(); | |||||
} | |||||
export const fetchInventories = cache(async() => { | |||||
return serverFetchJson<InventoryResult[]>(`${BASE_API_URL}/inventory/list`, { | |||||
next: { tags: ["inventories"]} | |||||
}) | |||||
}) |
@@ -18,6 +18,7 @@ const pathToLabelMap: { [path: string]: string } = { | |||||
"/scheduling/rough/edit": "FG & Material Demand Forecast Detail", | "/scheduling/rough/edit": "FG & Material Demand Forecast Detail", | ||||
"/scheduling/detail": "Detail Scheduling", | "/scheduling/detail": "Detail Scheduling", | ||||
"/scheduling/detail/edit": "FG Production Schedule", | "/scheduling/detail/edit": "FG Production Schedule", | ||||
"/inventory": "Inventory", | |||||
}; | }; | ||||
const Breadcrumb = () => { | const Breadcrumb = () => { | ||||
@@ -101,14 +101,14 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit }) => { | |||||
() => [ | () => [ | ||||
[ | [ | ||||
{ | { | ||||
id: 1, jobNo: "JO20250507001", priority: 85, code: "PP1193", type: "FG", name: "蔥油(1磅) ", inStockQty: 1322, productionQty: 661, | |||||
id: 1, jobNo: "JO20250507001", estimatedProductionTime: "1 hr", priority: 85, code: "PP1193", type: "FG", name: "蔥油(1磅) ", inStockQty: 1322, productionQty: 661, | |||||
lines: [ | lines: [ | ||||
{ id: 1, code: "MH0040", type: "Material", name: "大豆油(1噸/桶)", inStockQty: 100, purchaseQty: 20 }, | { id: 1, code: "MH0040", type: "Material", name: "大豆油(1噸/桶)", inStockQty: 100, purchaseQty: 20 }, | ||||
{ id: 2, code: "FA0161", type: "Material", name: "洋蔥粒", inStockQty: 80, purchaseQty: 10 } | { id: 2, code: "FA0161", type: "Material", name: "洋蔥粒", inStockQty: 80, purchaseQty: 10 } | ||||
] | ] | ||||
}, | }, | ||||
{ | { | ||||
id: 2, jobNo: "JO20250507002", priority: 80, code: " PP1096", type: "FG", name: "白麵撈", inStockQty: 1040, productionQty: 520, | |||||
id: 2, jobNo: "JO20250507002", estimatedProductionTime: "2 hrs", priority: 80, code: " PP1096", type: "FG", name: "白麵撈", inStockQty: 1040, productionQty: 520, | |||||
lines: [ | lines: [ | ||||
{ id: 1, code: "MH0040", type: "Material", name: "大豆油(1噸/桶)", inStockQty: 1000, purchaseQty: 190.00 }, | { id: 1, code: "MH0040", type: "Material", name: "大豆油(1噸/桶)", inStockQty: 1000, purchaseQty: 190.00 }, | ||||
{ id: 1, code: "MH0040", type: "Material", name: "星加坡綠富貴花牌幼白麵粉 (50磅/包)", inStockQty: 1000, purchaseQty: 250.00 }, | { id: 1, code: "MH0040", type: "Material", name: "星加坡綠富貴花牌幼白麵粉 (50磅/包)", inStockQty: 1000, purchaseQty: 250.00 }, | ||||
@@ -116,7 +116,7 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit }) => { | |||||
] | ] | ||||
}, | }, | ||||
{ | { | ||||
id: 3, jobNo: "JO20250507003", priority: 35, code: "PP1080", type: "FG", name: "咖哩汁", inStockQty: 2400, productionQty: 1200.0, | |||||
id: 3, jobNo: "JO20250507003", estimatedProductionTime: "5 hrs : 15 mins", priority: 35, code: "PP1080", type: "FG", name: "咖哩汁", inStockQty: 2400, productionQty: 1200.0, | |||||
lines: [ | lines: [ | ||||
{ id: 1, code: "MH0040", type: "Material", name: "大豆油(1噸/桶)", inStockQty: 0, purchaseQty: 108.88 }, | { id: 1, code: "MH0040", type: "Material", name: "大豆油(1噸/桶)", inStockQty: 0, purchaseQty: 108.88 }, | ||||
{ id: 2, code: "GI3236", type: "Material", name: "清水(煮過牛腩)", inStockQty: 317.52, purchaseQty: 635.04 }, | { id: 2, code: "GI3236", type: "Material", name: "清水(煮過牛腩)", inStockQty: 317.52, purchaseQty: 635.04 }, | ||||
@@ -132,7 +132,7 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit }) => { | |||||
] | ] | ||||
}, | }, | ||||
{ | { | ||||
id: 4, jobNo: "JO20250507004", priority: 20, code: " PP1188", type: "FG", name: "咖喱膽", inStockQty: 1016.2, productionQty: 508.1, | |||||
id: 4, jobNo: "JO20250507004", estimatedProductionTime: "3 hrs", priority: 20, code: " PP1188", type: "FG", name: "咖喱膽", inStockQty: 1016.2, productionQty: 508.1, | |||||
lines: [ | lines: [ | ||||
{ id: 1, code: "MH0040", type: "Material", name: "大豆油(1噸/桶)", inStockQty: 0, purchaseQty: 217.72 }, | { id: 1, code: "MH0040", type: "Material", name: "大豆油(1噸/桶)", inStockQty: 0, purchaseQty: 217.72 }, | ||||
{ id: 2, code: "FA0161", type: "Material", name: "洋蔥粒", inStockQty: 0, purchaseQty: 18.15 }, | { id: 2, code: "FA0161", type: "Material", name: "洋蔥粒", inStockQty: 0, purchaseQty: 18.15 }, | ||||
@@ -525,6 +525,14 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit }) => { | |||||
return row.productionQty | return row.productionQty | ||||
} | } | ||||
}, | }, | ||||
{ | |||||
field: "estimatedProductionTime", | |||||
label: "Estimated Production Time", | |||||
type: "read-only", | |||||
style: { | |||||
textAlign: "right", | |||||
} | |||||
}, | |||||
{ | { | ||||
field: "priority", | field: "priority", | ||||
label: "Production Priority", | label: "Production Priority", | ||||
@@ -0,0 +1,139 @@ | |||||
"use client" | |||||
import { InventoryResult } from "@/app/api/inventory"; | |||||
import { useTranslation } from "react-i18next"; | |||||
import SearchBox, { Criterion } from "../SearchBox"; | |||||
import { useCallback, useMemo, useState } from "react"; | |||||
import { uniq } from "lodash"; | |||||
import SearchResults, { Column } from "../SearchResults"; | |||||
import { CheckCircleOutline, DoDisturb } from "@mui/icons-material"; | |||||
interface Props { | |||||
inventories: InventoryResult[]; | |||||
} | |||||
type SearchQuery = Partial<Omit<InventoryResult, | |||||
| "id" | |||||
| "qty" | |||||
| "uomCode" | |||||
| "uomUdfudesc" | |||||
| "germPerSmallestUnit" | |||||
| "qtyPerSmallestUnit" | |||||
| "itemSmallestUnit" | |||||
| "price" | |||||
| "description" | |||||
| "category">>; | |||||
type SearchParamNames = keyof SearchQuery; | |||||
const InventorySearch: React.FC<Props> = ({ | |||||
inventories, | |||||
}) => { | |||||
const { t } = useTranslation("inventories"); | |||||
const [filteredInventories, setFilteredInventories] = useState(inventories) | |||||
const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => [ | |||||
{ label: t("Code"), paramName: "code", type: "text" }, | |||||
{ label: t("Name"), paramName: "name", type: "text" }, | |||||
{ label: t("Type"), paramName: "type", type: "select", options: uniq(inventories.map(i => i.type)) }, | |||||
{ label: t("Status"), paramName: "status", type: "select", options: uniq(inventories.map(i => i.status)) }, | |||||
], [t] | |||||
); | |||||
const onReset = useCallback(() => { | |||||
setFilteredInventories(inventories) | |||||
}, [inventories]) | |||||
const columns = useMemo<Column<InventoryResult>[]>( | |||||
() => [ | |||||
{ | |||||
name: "code", | |||||
label: t("Code"), | |||||
}, | |||||
{ | |||||
name: "name", | |||||
label: t("Name"), | |||||
}, | |||||
{ | |||||
name: "type", | |||||
label: t("Type"), | |||||
}, | |||||
{ | |||||
name: "qty", | |||||
label: t("Qty"), | |||||
align: "right", | |||||
headerAlign: "right", | |||||
type: "integer" | |||||
}, | |||||
{ | |||||
name: "uomUdfudesc", | |||||
label: t("UoM"), | |||||
}, | |||||
{ | |||||
name: "qtyPerSmallestUnit", | |||||
label: t("Qty Per Smallest Unit"), | |||||
align: "right", | |||||
headerAlign: "right", | |||||
type: "decimal" | |||||
}, | |||||
{ | |||||
name: "smallestUnit", | |||||
label: t("Smallest Unit"), | |||||
}, | |||||
// { | |||||
// name: "price", | |||||
// label: t("Price"), | |||||
// align: "right", | |||||
// sx: { | |||||
// alignItems: "right", | |||||
// justifyContent: "end", | |||||
// } | |||||
// }, | |||||
// { | |||||
// name: "currencyName", | |||||
// label: t("Currency"), | |||||
// }, | |||||
// { | |||||
// name: "status", | |||||
// label: t("Status"), | |||||
// type: "icon", | |||||
// icons: { | |||||
// available: <CheckCircleOutline fontSize="small"/>, | |||||
// unavailable: <DoDisturb fontSize="small"/>, | |||||
// }, | |||||
// colors: { | |||||
// available: "success", | |||||
// unavailable: "error", | |||||
// } | |||||
// }, | |||||
], [t] | |||||
) | |||||
return ( | |||||
<> | |||||
<SearchBox | |||||
criteria={searchCriteria} | |||||
onSearch={(query) => { | |||||
console.log(query) | |||||
console.log(inventories) | |||||
setFilteredInventories( | |||||
inventories.filter( | |||||
(i) => | |||||
i.code.toLowerCase().includes(query.code.toLowerCase()) && | |||||
i.name.toLowerCase().includes(query.name.toLowerCase()) && | |||||
(query.type == "All" || i.type.toLowerCase().includes(query.type.toLowerCase())) && | |||||
(query.status == "All" || i.status.toLowerCase().includes(query.status.toLowerCase())) | |||||
) | |||||
) | |||||
}} | |||||
onReset={onReset} | |||||
/> | |||||
<SearchResults<InventoryResult> items={filteredInventories} columns={columns} pagingController={{ | |||||
pageNum: 0, | |||||
pageSize: 0, | |||||
totalCount: 0, | |||||
}} /> | |||||
</> | |||||
) | |||||
} | |||||
export default InventorySearch |
@@ -0,0 +1,24 @@ | |||||
import React from "react"; | |||||
import GeneralLoading from "../General/GeneralLoading" | |||||
import { fetchInventories } from "@/app/api/inventory"; | |||||
import InventorySearch from "./InventorySearch"; | |||||
interface SubComponents { | |||||
Loading: typeof GeneralLoading; | |||||
} | |||||
const InventorySearchWrapper: React.FC & SubComponents = async () => { | |||||
const [ | |||||
inventories | |||||
] = await Promise.all([ | |||||
fetchInventories() | |||||
]) | |||||
return <InventorySearch inventories={inventories}/> | |||||
} | |||||
InventorySearchWrapper.Loading = GeneralLoading; | |||||
export default InventorySearchWrapper |
@@ -0,0 +1 @@ | |||||
export { default } from "./InventorySearchWrapper" |
@@ -76,8 +76,8 @@ const NavigationContent: React.FC = () => { | |||||
}, | }, | ||||
{ | { | ||||
icon: <RequestQuote />, | icon: <RequestQuote />, | ||||
label: "View item In-out And invertory Ledger", | |||||
path: "", | |||||
label: "View item In-out And inventory Ledger", | |||||
path: "/inventory", | |||||
}, | }, | ||||
], | ], | ||||
}, | }, | ||||
@@ -28,7 +28,7 @@ interface BaseCriterion<T extends string> { | |||||
label2?: string; | label2?: string; | ||||
paramName: T; | paramName: T; | ||||
paramName2?: T; | paramName2?: T; | ||||
options?: T[]; | |||||
options?: T[] | string[]; | |||||
filterObj?: T; | filterObj?: T; | ||||
handleSelectionChange?: (selectedOptions: T[]) => void; | handleSelectionChange?: (selectedOptions: T[]) => void; | ||||
} | } | ||||
@@ -39,11 +39,11 @@ interface TextCriterion<T extends string> extends BaseCriterion<T> { | |||||
interface SelectCriterion<T extends string> extends BaseCriterion<T> { | interface SelectCriterion<T extends string> extends BaseCriterion<T> { | ||||
type: "select"; | type: "select"; | ||||
options: T[]; | |||||
options: string[]; | |||||
} | } | ||||
interface MultiSelectCriterion<T extends string> extends BaseCriterion<T> { | interface MultiSelectCriterion<T extends string> extends BaseCriterion<T> { | ||||
type: "select"; | |||||
type: "multi-select"; | |||||
options: T[]; | options: T[]; | ||||
selectedOptions: T[]; | selectedOptions: T[]; | ||||
handleSelectionChange: (selectedOptions: T[]) => void; | handleSelectionChange: (selectedOptions: T[]) => void; | ||||
@@ -4,32 +4,62 @@ import React 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"; | ||||
import TableCell from "@mui/material/TableCell"; | |||||
import TableCell, { TableCellProps } from "@mui/material/TableCell"; | |||||
import TableContainer from "@mui/material/TableContainer"; | import TableContainer from "@mui/material/TableContainer"; | ||||
import TableHead from "@mui/material/TableHead"; | import TableHead from "@mui/material/TableHead"; | ||||
import TablePagination, { | import TablePagination, { | ||||
TablePaginationProps, | TablePaginationProps, | ||||
} from "@mui/material/TablePagination"; | } from "@mui/material/TablePagination"; | ||||
import TableRow from "@mui/material/TableRow"; | import TableRow from "@mui/material/TableRow"; | ||||
import IconButton, {IconButtonOwnProps} from "@mui/material/IconButton"; | |||||
import IconButton, { IconButtonOwnProps } from "@mui/material/IconButton"; | |||||
import { ButtonOwnProps, Icon, IconOwnProps, SxProps, Theme } from "@mui/material"; | |||||
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; | |||||
import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil"; | |||||
export interface ResultWithId { | export interface ResultWithId { | ||||
id: string | number; | id: string | number; | ||||
} | } | ||||
type ColumnType = "icon" | "decimal" | "integer"; | |||||
interface BaseColumn<T extends ResultWithId> { | interface BaseColumn<T extends ResultWithId> { | ||||
name: keyof T; | name: keyof T; | ||||
label: string; | label: string; | ||||
align?: TableCellProps["align"]; | |||||
headerAlign?: TableCellProps["align"]; | |||||
sx?: SxProps<Theme> | undefined; | |||||
style?: Partial<HTMLElement["style"]> & { [propName: string]: string }; | |||||
type?: ColumnType; | |||||
} | |||||
interface IconColumn<T extends ResultWithId> extends BaseColumn<T> { | |||||
name: keyof T; | |||||
type: "icon"; | |||||
icon?: React.ReactNode; | |||||
icons?: { [columnValue in keyof T]: React.ReactNode }; | |||||
color?: IconOwnProps["color"]; | |||||
colors?: { [columnValue in keyof T]: IconOwnProps["color"] }; | |||||
} | |||||
interface DecimalColumn<T extends ResultWithId> extends BaseColumn<T> { | |||||
type: "decimal"; | |||||
} | |||||
interface IntegerColumn<T extends ResultWithId> extends BaseColumn<T> { | |||||
type: "integer"; | |||||
} | } | ||||
interface ColumnWithAction<T extends ResultWithId> extends BaseColumn<T> { | interface ColumnWithAction<T extends ResultWithId> extends BaseColumn<T> { | ||||
onClick: (item: T) => void; | onClick: (item: T) => void; | ||||
buttonIcon: React.ReactNode; | buttonIcon: React.ReactNode; | ||||
buttonIcons: { [columnValue in keyof T]: React.ReactNode }; | |||||
buttonColor?: IconButtonOwnProps["color"]; | buttonColor?: IconButtonOwnProps["color"]; | ||||
} | } | ||||
export type Column<T extends ResultWithId> = | export type Column<T extends ResultWithId> = | ||||
| BaseColumn<T> | | BaseColumn<T> | ||||
| IconColumn<T> | |||||
| DecimalColumn<T> | |||||
| ColumnWithAction<T>; | | ColumnWithAction<T>; | ||||
interface Props<T extends ResultWithId> { | interface Props<T extends ResultWithId> { | ||||
@@ -42,7 +72,7 @@ interface Props<T extends ResultWithId> { | |||||
totalCount: number | totalCount: number | ||||
}) | { pageNum: number; pageSize: number; totalCount: number })) => void, | }) | { pageNum: number; pageSize: number; totalCount: number })) => void, | ||||
pagingController: { pageNum: number; pageSize: number; totalCount: number }, | pagingController: { pageNum: number; pageSize: number; totalCount: number }, | ||||
isAutoPaging: boolean | |||||
isAutoPaging?: boolean | |||||
} | } | ||||
function isActionColumn<T extends ResultWithId>( | function isActionColumn<T extends ResultWithId>( | ||||
@@ -51,14 +81,67 @@ function isActionColumn<T extends ResultWithId>( | |||||
return Boolean((column as ColumnWithAction<T>).onClick); | return Boolean((column as ColumnWithAction<T>).onClick); | ||||
} | } | ||||
function isIconColumn<T extends ResultWithId>( | |||||
column: Column<T>, | |||||
): column is IconColumn<T> { | |||||
return column.type === "icon"; | |||||
} | |||||
function isDecimalColumn<T extends ResultWithId>( | |||||
column: Column<T>, | |||||
): column is DecimalColumn<T> { | |||||
return column.type === "decimal"; | |||||
} | |||||
function isIntegerColumn<T extends ResultWithId>( | |||||
column: Column<T>, | |||||
): column is IntegerColumn<T> { | |||||
return column.type === "integer"; | |||||
} | |||||
// Icon Component Functions | |||||
function convertObjectKeysToLowercase<T extends object>(obj: T): object | undefined { | |||||
return obj ? Object.fromEntries( | |||||
Object.entries(obj).map(([key, value]) => [key.toLowerCase(), value]) | |||||
) : undefined; | |||||
} | |||||
function handleIconColors<T extends ResultWithId>( | |||||
column: IconColumn<T>, | |||||
value: T[keyof T], | |||||
): IconOwnProps["color"] { | |||||
const colors = convertObjectKeysToLowercase(column.colors ?? {}); | |||||
const valueKey = String(value).toLowerCase() as keyof typeof colors; | |||||
if (colors && valueKey in colors) { | |||||
return colors[valueKey]; | |||||
} | |||||
return column.color ?? "primary"; | |||||
}; | |||||
function handleIconIcons<T extends ResultWithId>( | |||||
column: IconColumn<T>, | |||||
value: T[keyof T], | |||||
): React.ReactNode { | |||||
const icons = convertObjectKeysToLowercase(column.icons ?? {}); | |||||
const valueKey = String(value).toLowerCase() as keyof typeof icons; | |||||
if (icons && valueKey in icons) { | |||||
return icons[valueKey]; | |||||
} | |||||
return column.icon ?? <CheckCircleOutlineIcon fontSize="small" />; | |||||
}; | |||||
function SearchResults<T extends ResultWithId>({ | function SearchResults<T extends ResultWithId>({ | ||||
items, | |||||
columns, | |||||
noWrapper, | |||||
pagingController, | |||||
setPagingController, | |||||
isAutoPaging = true, | |||||
}: Props<T>) { | |||||
items, | |||||
columns, | |||||
noWrapper, | |||||
pagingController, | |||||
setPagingController, | |||||
isAutoPaging = true, | |||||
}: Props<T>) { | |||||
const [page, setPage] = React.useState(0); | const [page, setPage] = React.useState(0); | ||||
const [rowsPerPage, setRowsPerPage] = React.useState(10); | const [rowsPerPage, setRowsPerPage] = React.useState(10); | ||||
@@ -90,12 +173,12 @@ function SearchResults<T extends ResultWithId>({ | |||||
const table = ( | const table = ( | ||||
<> | <> | ||||
<TableContainer sx={{maxHeight: 440}}> | |||||
<TableContainer sx={{ maxHeight: 440 }}> | |||||
<Table stickyHeader> | <Table stickyHeader> | ||||
<TableHead> | <TableHead> | ||||
<TableRow> | <TableRow> | ||||
{columns.map((column, idx) => ( | {columns.map((column, idx) => ( | ||||
<TableCell key={`${column.name.toString()}${idx}`}> | |||||
<TableCell align={column.headerAlign} sx={column.sx} key={`${column.name.toString()}${idx}`}> | |||||
{column.label} | {column.label} | ||||
</TableCell> | </TableCell> | ||||
))} | ))} | ||||
@@ -113,18 +196,7 @@ function SearchResults<T extends ResultWithId>({ | |||||
const columnName = column.name; | const columnName = column.name; | ||||
return ( | return ( | ||||
<TableCell key={`${columnName.toString()}-${idx}`}> | |||||
{isActionColumn(column) ? ( | |||||
<IconButton | |||||
color={column.buttonColor ?? "primary"} | |||||
onClick={() => column.onClick(item)} | |||||
> | |||||
{column.buttonIcon} | |||||
</IconButton> | |||||
) : ( | |||||
<>{item[columnName] as string}</> | |||||
)} | |||||
</TableCell> | |||||
<TabelCells key={`${columnName.toString()}-${idx}`} column={column} columnName={columnName} idx={idx} item={item}/> | |||||
); | ); | ||||
})} | })} | ||||
</TableRow> | </TableRow> | ||||
@@ -139,19 +211,8 @@ function SearchResults<T extends ResultWithId>({ | |||||
const columnName = column.name; | const columnName = column.name; | ||||
return ( | return ( | ||||
<TableCell key={`${columnName.toString()}-${idx}`}> | |||||
{isActionColumn(column) ? ( | |||||
<IconButton | |||||
color={column.buttonColor ?? "primary"} | |||||
onClick={() => column.onClick(item)} | |||||
> | |||||
{column.buttonIcon} | |||||
</IconButton> | |||||
) : ( | |||||
<>{item[columnName] as string}</> | |||||
)} | |||||
</TableCell> | |||||
); | |||||
<TabelCells key={`${columnName.toString()}-${idx}`} column={column} columnName={columnName} idx={idx} item={item}/> | |||||
); | |||||
})} | })} | ||||
</TableRow> | </TableRow> | ||||
); | ); | ||||
@@ -172,7 +233,49 @@ function SearchResults<T extends ResultWithId>({ | |||||
</> | </> | ||||
); | ); | ||||
return noWrapper ? table : <Paper sx={{overflow: "hidden"}}>{table}</Paper>; | |||||
return noWrapper ? table : <Paper sx={{ overflow: "hidden" }}>{table}</Paper>; | |||||
} | |||||
// Table cells | |||||
interface TableCellsProps<T extends ResultWithId> { | |||||
column: Column<T>, | |||||
columnName: keyof T, | |||||
idx: number, | |||||
item: T, | |||||
} | |||||
function TabelCells<T extends ResultWithId>({ | |||||
column, | |||||
columnName, | |||||
idx, | |||||
item | |||||
}: TableCellsProps<T>) { | |||||
return ( | |||||
<TableCell align={column.align} sx={column.sx} key={`${columnName.toString()}-${idx}`}> | |||||
{isActionColumn(column) ? ( | |||||
<IconButton | |||||
color={column.buttonColor ?? "primary"} | |||||
onClick={() => column.onClick(item)} | |||||
> | |||||
{column.buttonIcon} | |||||
</IconButton> | |||||
) : | |||||
isIconColumn(column) ? ( | |||||
<Icon | |||||
color={handleIconColors(column, item[columnName])} | |||||
> | |||||
{handleIconIcons(column, item[columnName])} | |||||
</Icon> | |||||
) : | |||||
isDecimalColumn(column) ? ( | |||||
<>{decimalFormatter.format(Number(item[columnName]))}</> | |||||
) : | |||||
isIntegerColumn(column) ? ( | |||||
<>{integerFormatter.format(Number(item[columnName]))}</> | |||||
) : ( | |||||
<>{item[columnName] as string}</> | |||||
)} | |||||
</TableCell>) | |||||
} | } | ||||
export default SearchResults; | export default SearchResults; |