@evenicanpm/portal-table-ui 2.2.3 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/src/features/table/components/dashboard/table/details/details.tsx +1 -1
- package/src/features/table/components/dashboard/table/details/rows/table.tsx +1 -1
- package/src/features/table/components/dashboard/table/details/templates/invoice-details.tsx +22 -3
- package/src/features/table/components/dashboard/table/table-header.tsx +1 -1
- package/src/features/table/components/dashboard/table/table-input.tsx +11 -9
- package/src/features/table/components/dashboard/table/table.tsx +1 -1
- package/src/features/table/components/dashboard/table-dashboard.tsx +19 -17
- package/src/features/table/logic/resolvers.ts +4 -5
- package/src/features/table/logic/transformers.ts +1 -1
- package/src/features/table/types/handlers.ts +1 -1
- package/src/features/table/types/index.ts +2 -0
- package/src/features/table/types/queries.ts +1 -1
- package/src/features/table/types/schemas.ts +1 -1
- package/src/features/table/types/table-param.ts +51 -0
- package/src/features/table/types/view-config.ts +122 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evenicanpm/portal-table-ui",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "",
|
|
@@ -36,16 +36,16 @@
|
|
|
36
36
|
"@emotion/styled": "^11.11.0",
|
|
37
37
|
"@mui/icons-material": "^7.3.6",
|
|
38
38
|
"@mui/material": "^6.1.7",
|
|
39
|
+
"@tanstack/react-query": "^5.90.11",
|
|
39
40
|
"@types/react": "^18.3.24",
|
|
40
41
|
"typescript": "^5.6.3"
|
|
41
42
|
},
|
|
42
43
|
"dependencies": {
|
|
43
44
|
"@arrows/multimethod": "^2.1.0",
|
|
44
|
-
"@evenicanpm/portal-types-schemas": "^2.0.0",
|
|
45
45
|
"@evenicanpm/ui": "^2.0.0",
|
|
46
46
|
"@mui/lab": "^7.0.1-beta.19",
|
|
47
|
-
"
|
|
48
|
-
"
|
|
47
|
+
"date-fns": "^4.1.0",
|
|
48
|
+
"zod": "^4.4.3"
|
|
49
49
|
},
|
|
50
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "72bf1aeb02a3d96c587706c0d91606698e38ff6a"
|
|
51
51
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { method, multi } from "@arrows/multimethod";
|
|
3
|
-
import type { Column } from "@evenicanpm/portal-types-schemas";
|
|
4
3
|
import { FlexBox, H6, Subtitle } from "@evenicanpm/ui";
|
|
5
4
|
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
|
6
5
|
import { Box, Button } from "@mui/material";
|
|
7
6
|
import Grid from "@mui/material/Grid2";
|
|
8
7
|
import { resolvers, transformers } from "../../../../logic";
|
|
9
8
|
import type * as Types from "../../../../types";
|
|
9
|
+
import type { Column } from "../../../../types";
|
|
10
10
|
import type { StatusColorMap, StatusTextMap } from "../../../../types/schemas";
|
|
11
11
|
import { StatusPill } from "../status-pill";
|
|
12
12
|
import { DetailsRowLabel } from "./label";
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import type { Column } from "@evenicanpm/portal-types-schemas";
|
|
4
3
|
import {
|
|
5
4
|
Table,
|
|
6
5
|
TableBody,
|
|
@@ -10,6 +9,7 @@ import {
|
|
|
10
9
|
TableRow,
|
|
11
10
|
} from "@mui/material";
|
|
12
11
|
import { resolvers } from "../../../../../logic";
|
|
12
|
+
import type { Column } from "../../../../../types";
|
|
13
13
|
import type { DataMaskMap } from "../../../../../types/handlers";
|
|
14
14
|
|
|
15
15
|
interface Props {
|
|
@@ -55,7 +55,6 @@ interface InvoiceLine {
|
|
|
55
55
|
Price?: number;
|
|
56
56
|
NetAmount?: number;
|
|
57
57
|
UnitOfMeasure?: string;
|
|
58
|
-
LineDiscount?: number;
|
|
59
58
|
DiscountAmount: number;
|
|
60
59
|
HasDiscount: boolean;
|
|
61
60
|
}
|
|
@@ -67,6 +66,7 @@ interface InvoiceData {
|
|
|
67
66
|
InvoiceSubtotal?: number;
|
|
68
67
|
TotalTaxAmount?: number;
|
|
69
68
|
Amount?: number;
|
|
69
|
+
AmountBalance?: number;
|
|
70
70
|
CompanyName?: string;
|
|
71
71
|
CompanyEmail?: string;
|
|
72
72
|
CompanyAddress?: string;
|
|
@@ -143,10 +143,10 @@ export const InvoiceDetailsTemplate = ({
|
|
|
143
143
|
const columns: TableColumn[] = [
|
|
144
144
|
{ label: "#", width: 60, aligned: "left" },
|
|
145
145
|
{ label: "Product", width: 200, aligned: "left" },
|
|
146
|
-
{ label: "Qty", width:
|
|
146
|
+
{ label: "Qty", width: 40, aligned: "right" },
|
|
147
147
|
{ label: "Unit", width: 60, aligned: "right" },
|
|
148
148
|
{ label: "Unit Price", width: 120, aligned: "right" },
|
|
149
|
-
{ label: "
|
|
149
|
+
{ label: "Line Savings", width: 120, aligned: "right" },
|
|
150
150
|
{ label: "Line Total", width: 120, aligned: "right" },
|
|
151
151
|
{ label: "", width: 40, aligned: "center" },
|
|
152
152
|
];
|
|
@@ -577,6 +577,25 @@ export const InvoiceDetailsTemplate = ({
|
|
|
577
577
|
{formatCurrency(total)}
|
|
578
578
|
</Typography>
|
|
579
579
|
</Box>
|
|
580
|
+
|
|
581
|
+
{/* Paid and Remaining lines */}
|
|
582
|
+
{invoice?.Status !== 1 && (
|
|
583
|
+
<Box display="flex" justifyContent="space-between" mt={1}>
|
|
584
|
+
<Typography variant="body2">Paid</Typography>
|
|
585
|
+
<Typography variant="body2">
|
|
586
|
+
{formatCurrency(total - (invoice?.AmountBalance ?? 0))}
|
|
587
|
+
</Typography>
|
|
588
|
+
</Box>
|
|
589
|
+
)}
|
|
590
|
+
|
|
591
|
+
{invoice?.Status !== 1 && (invoice?.AmountBalance ?? 0) > 0 && (
|
|
592
|
+
<Box display="flex" justifyContent="space-between" mt={0.5}>
|
|
593
|
+
<Typography variant="body2">Remaining</Typography>
|
|
594
|
+
<Typography variant="body2">
|
|
595
|
+
{formatCurrency(invoice?.AmountBalance ?? 0)}
|
|
596
|
+
</Typography>
|
|
597
|
+
</Box>
|
|
598
|
+
)}
|
|
580
599
|
</Box>
|
|
581
600
|
</Box>
|
|
582
601
|
</>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { Column } from "@evenicanpm/portal-types-schemas";
|
|
2
1
|
import { Skeleton } from "@mui/material";
|
|
3
2
|
import Checkbox from "@mui/material/Checkbox";
|
|
4
3
|
import { styled } from "@mui/material/styles";
|
|
5
4
|
import TableCell from "@mui/material/TableCell";
|
|
6
5
|
import TableHead from "@mui/material/TableHead";
|
|
7
6
|
import TableRow from "@mui/material/TableRow";
|
|
7
|
+
import type { Column } from "../../../types";
|
|
8
8
|
|
|
9
9
|
// STYLED COMPONENTS
|
|
10
10
|
const StyledTableCell = styled(TableCell)(({ theme }) => ({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Column } from "@evenicanpm/portal-types-schemas";
|
|
2
1
|
import { SearchInput } from "@evenicanpm/ui";
|
|
3
2
|
import { MenuItem, Select } from "@mui/material";
|
|
4
3
|
import React from "react";
|
|
4
|
+
import type { Column } from "../../../types";
|
|
5
5
|
|
|
6
6
|
interface SearchProps {
|
|
7
7
|
searchTerm: string;
|
|
@@ -84,14 +84,16 @@ const SearchField = React.memo(
|
|
|
84
84
|
: "Select a column to search"
|
|
85
85
|
}
|
|
86
86
|
endAdornment={
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
87
|
+
searchCols.length > 1 ? (
|
|
88
|
+
<ColumnSelect
|
|
89
|
+
selected={selectedColumn?.Data || ""}
|
|
90
|
+
searchCols={searchCols}
|
|
91
|
+
handleChange={(value) => {
|
|
92
|
+
const col = searchCols.find((c) => c.Data === value);
|
|
93
|
+
if (col) setColumn(col);
|
|
94
|
+
}}
|
|
95
|
+
/>
|
|
96
|
+
) : undefined
|
|
95
97
|
}
|
|
96
98
|
/>
|
|
97
99
|
);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Column } from "@evenicanpm/portal-types-schemas";
|
|
2
1
|
import { FlexBox, ResponsiveTablePagination } from "@evenicanpm/ui";
|
|
3
2
|
import Table from "@mui/material/Table";
|
|
4
3
|
import TableBody from "@mui/material/TableBody";
|
|
@@ -10,6 +9,7 @@ import type React from "react";
|
|
|
10
9
|
import { useState } from "react";
|
|
11
10
|
import { resolvers, transformers } from "../../../logic";
|
|
12
11
|
import type * as Types from "../../../types";
|
|
12
|
+
import type { Column } from "../../../types";
|
|
13
13
|
import { BulkActionButton } from "./bulk-action-button";
|
|
14
14
|
import { dataMaskMap as dataMaskMapLocal } from "./data-mask-map";
|
|
15
15
|
import { Details } from "./details";
|
|
@@ -49,23 +49,25 @@ export const TableDashboard = ({
|
|
|
49
49
|
return (
|
|
50
50
|
<Box sx={{ width: "100%", minHeight: "70vh" }}>
|
|
51
51
|
<TabContext value={value}>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
{
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
52
|
+
{resources?.length > 1 && (
|
|
53
|
+
<Box
|
|
54
|
+
sx={{
|
|
55
|
+
borderBottom: 1,
|
|
56
|
+
borderColor: "divider",
|
|
57
|
+
"@media print": { display: "none" },
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
<TabList onChange={handleChange}>
|
|
61
|
+
{resources?.map((resource, i) => (
|
|
62
|
+
<Tab
|
|
63
|
+
key={resource?.name + String(i)}
|
|
64
|
+
label={resource?.label}
|
|
65
|
+
value={resource?.name}
|
|
66
|
+
/>
|
|
67
|
+
))}
|
|
68
|
+
</TabList>
|
|
69
|
+
</Box>
|
|
70
|
+
)}
|
|
69
71
|
{resources?.map((resource, i) => (
|
|
70
72
|
<Box key={resource.name + String(i)}>
|
|
71
73
|
<TabPanel
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { method, multi } from "@arrows/multimethod";
|
|
2
|
-
import type { Column } from "@evenicanpm/portal-types-schemas";
|
|
3
2
|
import type * as Types from "../types";
|
|
4
3
|
import type { TableConfig } from "../types/queries";
|
|
5
4
|
import type { UIAction, UIBulkAction } from "../types/schemas";
|
|
@@ -37,7 +36,7 @@ export const resolveFilterUI = multi(
|
|
|
37
36
|
* @internal
|
|
38
37
|
*/
|
|
39
38
|
export const resolveDataMask = multi(
|
|
40
|
-
(col: Column, handlers: Types.Handlers.DataMaskMap) => {
|
|
39
|
+
(col: Types.Column, handlers: Types.Handlers.DataMaskMap) => {
|
|
41
40
|
if (!col?.DataMask) return "SKIP";
|
|
42
41
|
if (handlers?.[col?.DataType]) return "BY_TYPE";
|
|
43
42
|
if (handlers?.[col?.Data]) return "BY_NAME";
|
|
@@ -45,12 +44,12 @@ export const resolveDataMask = multi(
|
|
|
45
44
|
},
|
|
46
45
|
method(
|
|
47
46
|
"BY_TYPE",
|
|
48
|
-
(col: Column, handlers: Types.Handlers.DataMaskMap) =>
|
|
47
|
+
(col: Types.Column, handlers: Types.Handlers.DataMaskMap) =>
|
|
49
48
|
handlers?.[col?.DataType],
|
|
50
49
|
),
|
|
51
50
|
method(
|
|
52
51
|
"BY_NAME",
|
|
53
|
-
(col: Column, handlers: Types.Handlers.DataMaskMap) =>
|
|
52
|
+
(col: Types.Column, handlers: Types.Handlers.DataMaskMap) =>
|
|
54
53
|
handlers?.[col?.Data],
|
|
55
54
|
),
|
|
56
55
|
method("SKIP", () => (value: string | number) => value),
|
|
@@ -61,7 +60,7 @@ export const resolveDataMask = multi(
|
|
|
61
60
|
* @internal
|
|
62
61
|
*/
|
|
63
62
|
export const resolveCellUI = (
|
|
64
|
-
col: Column,
|
|
63
|
+
col: Types.Column,
|
|
65
64
|
handlers: Types.Handlers.CellRenderMap,
|
|
66
65
|
) => {
|
|
67
66
|
if (Object.hasOwn(handlers, col.Data)) return handlers?.[col?.Data];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { TableViewParams } from "@evenicanpm/portal-types-schemas";
|
|
2
1
|
import type { SvgIconComponent } from "@mui/icons-material";
|
|
3
2
|
import type { UseMutationOptions } from "@tanstack/react-query";
|
|
4
3
|
import type { Table } from "..";
|
|
5
4
|
import type { DetailsTemplateKey } from "../components/dashboard/table/details/templates/template-registry";
|
|
5
|
+
import type { TableViewParams } from "../types";
|
|
6
6
|
import type {
|
|
7
7
|
CellRenderMap,
|
|
8
8
|
DataMaskMap,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const TableViewParamsSchema = z.object({
|
|
4
|
+
pageSize: z.number().default(5),
|
|
5
|
+
page: z.number().default(0),
|
|
6
|
+
filters: z.record(z.string(), z.unknown()).optional(),
|
|
7
|
+
searchTerm: z.string().optional().default(""),
|
|
8
|
+
searchColumn: z.string().default("all"),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
// Extract the TypeScript interface from the schema
|
|
12
|
+
export type TableViewParams = z.infer<typeof TableViewParamsSchema>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* convert the table state into url params.
|
|
16
|
+
* @param params Filters and params stored from table state
|
|
17
|
+
* @returns Url params.
|
|
18
|
+
*/
|
|
19
|
+
export const tableStateToQuery = (params: TableViewParams): URLSearchParams => {
|
|
20
|
+
const { page, pageSize, searchTerm, searchColumn, filters } = params;
|
|
21
|
+
const searchParams = new URLSearchParams();
|
|
22
|
+
searchParams.set("page", String(page));
|
|
23
|
+
searchParams.set("pageSize", String(pageSize));
|
|
24
|
+
|
|
25
|
+
if (searchTerm && searchColumn) {
|
|
26
|
+
searchParams.set("searchTerm", searchTerm);
|
|
27
|
+
searchParams.set("searchColumn", searchColumn);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (filters) searchParams.set("filters", JSON.stringify(filters));
|
|
31
|
+
return searchParams;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Pre-processes url params into portal format to be
|
|
36
|
+
* inserted into one of the DMS URL builders
|
|
37
|
+
*
|
|
38
|
+
* @param searchParams url params from next endpoint
|
|
39
|
+
* @returns table view params formatted filters/pagination
|
|
40
|
+
*/
|
|
41
|
+
export const queryToTableState = (
|
|
42
|
+
searchParams: URLSearchParams,
|
|
43
|
+
): TableViewParams => {
|
|
44
|
+
const page = Number(searchParams.get("page") ?? 0);
|
|
45
|
+
const pageSize = Number(searchParams.get("pageSize") ?? 5);
|
|
46
|
+
const searchTerm = searchParams.get("searchTerm") ?? "";
|
|
47
|
+
const searchColumn = searchParams.get("searchColumn") ?? "";
|
|
48
|
+
const filtersValue = searchParams.get("filters");
|
|
49
|
+
const filters = filtersValue && JSON.parse(filtersValue);
|
|
50
|
+
return { page, pageSize, searchColumn, searchTerm, filters };
|
|
51
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* ColumnDataType
|
|
4
|
+
*/
|
|
5
|
+
export const ColumnDataTypeSchema = z.enum([
|
|
6
|
+
"String",
|
|
7
|
+
"int",
|
|
8
|
+
"numeric",
|
|
9
|
+
"date",
|
|
10
|
+
"datetime",
|
|
11
|
+
"url",
|
|
12
|
+
]);
|
|
13
|
+
export type ColumnDataType = z.infer<typeof ColumnDataTypeSchema>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* DataMaskFormat
|
|
17
|
+
*/
|
|
18
|
+
export const DataMaskFormatSchema = z.enum([
|
|
19
|
+
"MM/DD/YYYY",
|
|
20
|
+
"MM-DD-YYYY",
|
|
21
|
+
"DD/MM/YYYY",
|
|
22
|
+
"DD-MM-YYYY",
|
|
23
|
+
"YYYY/MM/DD",
|
|
24
|
+
"YYYY-MM-DD HH:MI:SS",
|
|
25
|
+
"$0.00",
|
|
26
|
+
"0,00€",
|
|
27
|
+
]);
|
|
28
|
+
export type DataMaskFormat = z.infer<typeof DataMaskFormatSchema>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* FilterOperation
|
|
32
|
+
*/
|
|
33
|
+
export const FilterOperationSchema = z.enum([
|
|
34
|
+
"eq",
|
|
35
|
+
"gt",
|
|
36
|
+
"lt",
|
|
37
|
+
"gte",
|
|
38
|
+
"lte",
|
|
39
|
+
"in",
|
|
40
|
+
]);
|
|
41
|
+
export type FilterOperation = z.infer<typeof FilterOperationSchema>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* FilterOption
|
|
45
|
+
*/
|
|
46
|
+
export const FilterOptionSchema = z.object({
|
|
47
|
+
value: z.union([z.string(), z.number()]),
|
|
48
|
+
label: z.string(),
|
|
49
|
+
});
|
|
50
|
+
export type FilterOption = z.infer<typeof FilterOptionSchema>;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* FilterConfig
|
|
54
|
+
*/
|
|
55
|
+
export const FilterConfigSchema = z.object({
|
|
56
|
+
FilterOptions: z.array(FilterOptionSchema),
|
|
57
|
+
FilterType: z.string(),
|
|
58
|
+
FilterOperation: FilterOperationSchema,
|
|
59
|
+
});
|
|
60
|
+
export type FilterConfig = z.infer<typeof FilterConfigSchema>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Filter (from table metadata response)
|
|
64
|
+
*/
|
|
65
|
+
export const FilterSchema = z.object({
|
|
66
|
+
Operation: FilterOperationSchema,
|
|
67
|
+
Data: z.string(),
|
|
68
|
+
Value: z.union([z.string(), z.array(z.string()), z.number()]),
|
|
69
|
+
});
|
|
70
|
+
export type Filter = z.infer<typeof FilterSchema>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Column (recursive)
|
|
74
|
+
*/
|
|
75
|
+
export const ColumnSchema = z.object({
|
|
76
|
+
Label: z.string(),
|
|
77
|
+
Data: z.string(),
|
|
78
|
+
DataType: z.string(),
|
|
79
|
+
DataMask: z.string().optional(),
|
|
80
|
+
ColumnOrder: z.number().nullable().optional(),
|
|
81
|
+
IsSearchable: z.boolean().optional(),
|
|
82
|
+
IsFilterable: z.boolean().optional(),
|
|
83
|
+
Filters: z.array(FilterConfigSchema).optional(),
|
|
84
|
+
IsVisible: z.boolean().optional(),
|
|
85
|
+
IsSortable: z.boolean().optional(),
|
|
86
|
+
DisplayOrder: z.number().optional(),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
export type Action = {
|
|
90
|
+
Name: string;
|
|
91
|
+
Type: string;
|
|
92
|
+
IdField?: string;
|
|
93
|
+
IsEnabled: boolean;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export type BulkAction = {
|
|
97
|
+
Name: string;
|
|
98
|
+
Type: string;
|
|
99
|
+
IdField?: string;
|
|
100
|
+
IsEnabled: boolean;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export interface Column {
|
|
104
|
+
Label: string;
|
|
105
|
+
Data: string;
|
|
106
|
+
DataType: string;
|
|
107
|
+
ColumnOrder: number | undefined | null;
|
|
108
|
+
DataMask?: string;
|
|
109
|
+
IsSearchable?: boolean;
|
|
110
|
+
IsFilterable?: boolean;
|
|
111
|
+
Filters?: FilterConfig[];
|
|
112
|
+
IsVisible?: boolean;
|
|
113
|
+
IsSortable?: boolean;
|
|
114
|
+
DisplayOrder?: number;
|
|
115
|
+
Children?: Column[];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export type ViewConfig = {
|
|
119
|
+
Columns: Column[];
|
|
120
|
+
Actions?: Action[];
|
|
121
|
+
BulkActions?: BulkAction[];
|
|
122
|
+
};
|