@evenicanpm/portal-table-ui 1.5.0 → 1.6.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.
Files changed (37) hide show
  1. package/package.json +6 -5
  2. package/src/auth/auth-context.tsx +86 -0
  3. package/src/features/table/components/dashboard/index.tsx +1 -0
  4. package/src/features/table/components/dashboard/table/actions.ts +59 -0
  5. package/src/features/table/components/dashboard/table/data-mask-map.ts +26 -0
  6. package/src/features/table/components/dashboard/table/details/details.tsx +164 -0
  7. package/src/features/table/components/dashboard/table/details/index.ts +1 -0
  8. package/src/features/table/components/dashboard/table/details/label.tsx +26 -0
  9. package/src/features/table/components/dashboard/table/details/rows/downloads.tsx +26 -0
  10. package/src/features/table/components/dashboard/table/details/rows/index.ts +3 -0
  11. package/src/features/table/components/dashboard/table/details/rows/primitive.tsx +26 -0
  12. package/src/features/table/components/dashboard/table/details/rows/table.tsx +74 -0
  13. package/src/features/table/components/dashboard/table/details/styles.ts +18 -0
  14. package/src/features/table/components/dashboard/table/filter-render-map.tsx +9 -0
  15. package/src/features/table/components/dashboard/table/filters/date-filter.tsx +31 -0
  16. package/src/features/table/components/dashboard/table/filters/status-filter.tsx +36 -0
  17. package/src/features/table/components/dashboard/table/index.ts +2 -0
  18. package/src/features/table/components/dashboard/table/no-results.tsx +26 -0
  19. package/src/features/table/components/dashboard/table/status-pill.tsx +18 -0
  20. package/src/features/table/components/dashboard/table/table-header.tsx +62 -0
  21. package/src/features/table/components/dashboard/table/table-input.tsx +94 -0
  22. package/src/features/table/components/dashboard/table/table-row/action-button.tsx +39 -0
  23. package/src/features/table/components/dashboard/table/table-row/table-row.tsx +96 -0
  24. package/src/features/table/components/dashboard/table/table-row-skeleton.tsx +36 -0
  25. package/src/features/table/components/dashboard/table/table.tsx +250 -0
  26. package/src/features/table/components/dashboard/table-dashboard.tsx +71 -0
  27. package/src/features/table/index.ts +14 -0
  28. package/src/features/table/logic/index.ts +23 -0
  29. package/src/features/table/logic/resolvers.ts +68 -0
  30. package/src/features/table/logic/transformers.ts +56 -0
  31. package/src/features/table/logic/types.ts +8 -0
  32. package/src/features/table/types/handlers.ts +71 -0
  33. package/src/features/table/types/index.ts +70 -0
  34. package/src/features/table/types/queries.ts +34 -0
  35. package/src/features/table/types/schemas.ts +50 -0
  36. package/src/index.ts +65 -0
  37. package/src/portal.tsx +68 -0
@@ -0,0 +1,68 @@
1
+ import { method, multi } from "@arrows/multimethod";
2
+ import type { Column } from "@evenicanpm/portal-types-schemas";
3
+ import type * as Types from "../types";
4
+ import type { FilterRequest } from "./types";
5
+
6
+ /** MULTIMETHOD FOR FILTERS
7
+ * Allows multiple implementations
8
+ * based on multiple column properties.
9
+ * Priotizes column name, but also can
10
+ * use data type
11
+ * @internal
12
+ */
13
+ export const resolveFilterUI = multi(
14
+ (req: FilterRequest, handlers: Types.Handlers.FilterRenderMap) => {
15
+ if (handlers?.[req.filterName]) return "BY_NAME";
16
+ if (handlers?.[req.filterType]) return "BY_TYPE";
17
+ return "NONE";
18
+ },
19
+ method(
20
+ "BY_NAME",
21
+ (req: FilterRequest, handlers: Types.Handlers.FilterRenderMap) =>
22
+ handlers[req.filterName],
23
+ ),
24
+ method(
25
+ "BY_TYPE",
26
+ (req: FilterRequest, handlers: Types.Handlers.FilterRenderMap) =>
27
+ handlers[req.filterType],
28
+ ),
29
+ method("NONE", () => () => null),
30
+ );
31
+
32
+ /**
33
+ * Dispatch identifies which properties are
34
+ * in handler, then provides corresponding method
35
+ * @internal
36
+ */
37
+ export const resolveDataMask = multi(
38
+ (col: Column, handlers: Types.Handlers.DataMaskMap) => {
39
+ if (!col?.DataMask) return "SKIP";
40
+ if (handlers?.[col?.DataType]) return "BY_TYPE";
41
+ if (handlers?.[col?.Data]) return "BY_NAME";
42
+ return "SKIP";
43
+ },
44
+ method(
45
+ "BY_TYPE",
46
+ (col: Column, handlers: Types.Handlers.DataMaskMap) =>
47
+ handlers?.[col?.DataType],
48
+ ),
49
+ method(
50
+ "BY_NAME",
51
+ (col: Column, handlers: Types.Handlers.DataMaskMap) =>
52
+ handlers?.[col?.Data],
53
+ ),
54
+ method("SKIP", () => (value: string | number) => value),
55
+ );
56
+
57
+ /**
58
+ * Simpler, doesn't need multimethod cause only dispatches on column name
59
+ * @internal
60
+ */
61
+ export const resolveCellUI = (
62
+ col: Column,
63
+ handlers: Types.Handlers.CellRenderMap,
64
+ ) => {
65
+ if (Object.hasOwn(handlers, col.Data)) return handlers?.[col?.Data];
66
+
67
+ return (value: string | number) => value;
68
+ };
@@ -0,0 +1,56 @@
1
+ import type { Column } from "@evenicanpm/portal-types-schemas";
2
+
3
+ /**
4
+ * These are pure static functions that include business rules
5
+ * and data manipulation to get the data into the correct
6
+ * form for rendering.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+
11
+ /**
12
+ * Gets filters and returns an array of the
13
+ * active filters and corresponding options
14
+ * in FilterRequest
15
+ */
16
+ export const getActiveFilters = (columns: Column[] | undefined) => {
17
+ return columns
18
+ ?.filter((c) => c?.IsFilterable)
19
+ ?.flatMap((c) =>
20
+ c?.Filters?.map((f) => ({
21
+ filterName: c.Data,
22
+ filterOptions: f.FilterOptions,
23
+ filterType: f.FilterType ?? c.DataType,
24
+ })),
25
+ );
26
+ };
27
+
28
+ /**
29
+ * Filters and sorts visible cols
30
+ */
31
+ export const getVisibleColumns = (columns: Column[] | undefined) =>
32
+ columns
33
+ ?.filter(
34
+ (column): column is Column & { ColumnOrder: number } =>
35
+ typeof column.ColumnOrder === "number",
36
+ )
37
+ .sort((a, b) => a.ColumnOrder - b.ColumnOrder);
38
+
39
+ /**
40
+ *
41
+ */
42
+ export const getVisibleDetails = (columns: Column[] | undefined) =>
43
+ columns
44
+ ?.filter((col) => col.DisplayOrder)
45
+ ?.sort((a, b) => {
46
+ if (a?.DisplayOrder && b?.DisplayOrder) {
47
+ return a?.DisplayOrder - b?.DisplayOrder;
48
+ }
49
+ return -1;
50
+ });
51
+
52
+ /**
53
+ * Filters and sorts visible cols
54
+ */
55
+ export const getSearchableColumns = (columns: Column[] | undefined) =>
56
+ columns?.filter((column) => column?.IsSearchable);
@@ -0,0 +1,8 @@
1
+ /**
2
+ * FILTER REQUEST to RESOLVER
3
+ */
4
+ export interface FilterRequest {
5
+ filterName: string;
6
+ filterOptions: string[];
7
+ filterType: string;
8
+ }
@@ -0,0 +1,71 @@
1
+ import type { FilterOption } from "@evenicanpm/portal-types-schemas";
2
+
3
+ /**
4
+ *
5
+ * @group View Handlers
6
+ */
7
+ export type DataMaskMap = Record<string, MaskingFunction>;
8
+ export type MaskingFunction = (
9
+ value: string | number,
10
+ dataMask: string,
11
+ ) => string;
12
+
13
+ /**
14
+ * Uses column names as keys and maps out
15
+ * value with specific JSX for specific column names.
16
+ *
17
+ *
18
+ * e.g. Status -> MUIChip
19
+ *
20
+ * @group View Handlers
21
+ */
22
+ export interface CellRenderMap {
23
+ [ColumnName: string]: (
24
+ value: string | number | string[],
25
+ ) => React.ReactElement;
26
+ }
27
+
28
+ export interface FilterProps {
29
+ filterName: string;
30
+ filterOptions: FilterOption[];
31
+ value: Record<string, string>;
32
+ setValue: React.Dispatch<React.SetStateAction<Record<string, string>>>;
33
+ resetPagination: () => void;
34
+ }
35
+
36
+ /**
37
+ *
38
+ * Provides render methods for particular filter
39
+ * types. Filter types currently come from DataTypes.
40
+ *
41
+ * Note: Naive solution is to trust that project injects filter
42
+ * render methods for all columns with filters.We can then have a fallback for
43
+ * general data types.
44
+ *
45
+ * @group View Handlers
46
+ */
47
+ export interface FilterRenderMap {
48
+ [ColumnName: string]: (props: FilterProps) => React.ReactElement;
49
+ }
50
+
51
+ /**
52
+ *
53
+ * @group View Handlers
54
+ */
55
+ export interface EmptyStateProps {
56
+ label: string;
57
+ }
58
+
59
+ export type NoResultsComponent = (props: EmptyStateProps) => React.ReactElement;
60
+
61
+ export interface DetailsRenderMap {
62
+ [columnName: string]: (props: {
63
+ [columnName: string]:
64
+ | string
65
+ | number
66
+ | object
67
+ | string[]
68
+ | number[]
69
+ | object[];
70
+ }) => React.ReactNode;
71
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * # Table System: Module Registry
3
+ *
4
+ * This registry defines the structural blueprints for the Table system modules.
5
+ * Architecture driven by an array of Resource types which are equipped with
6
+ * configuration and render handlers responsible for rendering a table.
7
+ *
8
+ * ---
9
+ *
10
+ * ### 1. Queries (queries.ts)
11
+ * API Contracts. The raw shapes of server responses. These act as the transport
12
+ * layer for data before it is mapped into the system's primary models.
13
+ *
14
+ * ### 2. Schemas (schemas.ts)
15
+ * System Models. The primary blueprints—such as Column, Filter, and Resource—often
16
+ * derived from API definitions. These are the vocabulary used to drive the module.
17
+ *
18
+ * ### 3. State (state.ts)
19
+ * View Configuration. The dynamic parameters tracking how resources are
20
+ * filtered, sorted, and paginated in the current view.
21
+ *
22
+ * ### 4. Handlers (handlers.ts)
23
+ * Logic Registries. Objects indexed by column or resource type that map
24
+ * logic (like filtering and data masking) to specific parts of the system.
25
+ *
26
+ * ---
27
+ *
28
+ * @remarks
29
+ * ## File descriptions
30
+ * ```
31
+ * ├── components // UI
32
+ * │ └── dashboard // displays multiple tables with tabs
33
+ * │ ├── index.tsx
34
+ * │ ├── table // individual table component
35
+ * │ │ ├── details
36
+ * │ │ │ ├── details.tsx // the details page
37
+ * │ │ │ ├── index.ts
38
+ * │ │ │ ├── label.tsx // labels used on details page
39
+ * │ │ │ ├── rows // individual rows for different details
40
+ * │ │ │ │ ├── downloads.tsx // download button for "documents" field
41
+ * │ │ │ │ ├── index.ts
42
+ * │ │ │ │ ├── primitive.tsx // simple text or number details field
43
+ * │ │ │ │ └── table.tsx // for complex object data (displays in table)
44
+ * │ │ │ └── styles.ts
45
+ * │ │ ├── status-pill.tsx // status pill used across table and details
46
+ * │ │ ├── table-input.tsx // table search bar
47
+ * │ │ └── table.tsx // actual table component
48
+ * │ └── table-dashboard.tsx // actual dashboard component
49
+ * ├── index.ts
50
+ * ├── logic // handles data transformations
51
+ * │ ├── index.ts
52
+ * │ ├── resolvers.ts // multi methods that just handle resolving the maps
53
+ * │ ├── transformers.ts // pure functions just for simple data transformations
54
+ * │ └── types.ts // types specifically for the transformations
55
+ * ├── queries // handles data fetching with react-query
56
+ * │ └── useTableQueries.tsx
57
+ * └── types // global types for feature
58
+ * ├── handlers.ts // types for handler maps (masks, renders, filters)
59
+ * ├── index.ts
60
+ * ├── queries.ts // types for data coming over the wire
61
+ * ├── schemas.ts // core system types (Resource)
62
+ * └── state.ts // types for transient state data (filter state)
63
+ * ```
64
+ *
65
+ * @packageDocumentation
66
+ */
67
+
68
+ export * as Handlers from "./handlers";
69
+ export * as Queries from "./queries";
70
+ export * as Schemas from "./schemas";
@@ -0,0 +1,34 @@
1
+ import type { ViewConfig } from "@evenicanpm/portal-types-schemas";
2
+
3
+ /**
4
+ *
5
+ * These are data types returned from API Backend
6
+ *
7
+ */
8
+
9
+ /**
10
+ *
11
+ * A single row in the data table response object.
12
+ *
13
+ */
14
+ export type Row = Record<
15
+ string,
16
+ string | number | string[] | Record<string, string>[] | unknown
17
+ >;
18
+
19
+ /**
20
+ *
21
+ * The response type defined in TDD for row data.
22
+ *
23
+ */
24
+ export interface TableRows {
25
+ items: number;
26
+ data: Row[];
27
+ }
28
+
29
+ /**
30
+ *
31
+ * Response type of data from table config
32
+ *
33
+ */
34
+ export type TableConfig = ViewConfig;
@@ -0,0 +1,50 @@
1
+ import type { TableViewParams } from "@evenicanpm/portal-types-schemas";
2
+ import type {
3
+ CellRenderMap,
4
+ DataMaskMap,
5
+ DetailsRenderMap,
6
+ FilterRenderMap,
7
+ NoResultsComponent,
8
+ } from "./handlers";
9
+ import type { TableRows } from "./queries";
10
+
11
+ /**
12
+ * These are primary domain models and the source of truth for table system.
13
+ */
14
+
15
+ /**
16
+ * Status maps
17
+ */
18
+ export type StatusTextMap = Record<number | string, string>;
19
+
20
+ export type StatusColor = "success" | "primary" | "warning" | "error";
21
+
22
+ export type StatusColorMap = Record<number | string, StatusColor>;
23
+
24
+ /**
25
+ * Resource Definition
26
+ */
27
+ export interface Resource {
28
+ name: string;
29
+ label: string;
30
+ idField: string;
31
+
32
+ // Data Handlers
33
+ rowsQueryFn: (displaySettings: TableViewParams) => {
34
+ queryKey: readonly unknown[];
35
+ queryFn: () => Promise<TableRows>;
36
+ };
37
+
38
+ // UI Renderers & Mappings
39
+ cellRenderMap?: CellRenderMap;
40
+ dataMaskMap?: DataMaskMap;
41
+ filterRenderMap?: FilterRenderMap;
42
+ noResultsComponent?: NoResultsComponent;
43
+
44
+ // Optional Details view
45
+ detailsRenderMap?: DetailsRenderMap;
46
+
47
+ // State Visuals
48
+ statusTextMap: StatusTextMap;
49
+ statusColorMap: StatusColorMap;
50
+ }
package/src/index.ts ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Main entry point for the **e4Portal** library.
3
+ * All public functions are exported from here.
4
+ *
5
+ * The primary export of this package is the `Portal` React compound-component.
6
+ * The `Portal` component is the top-level that contains the auth, rbac and any other
7
+ * functionality that is not related to specific features. Specific features (e.g. `Table`)
8
+ * are modularized and exported as compond components attached to the main `Portal` component.
9
+ *
10
+ * ## Usage
11
+ *
12
+ * ```tsx
13
+ *
14
+ * import { Portal } from "@evenicanpm/portal";
15
+ *
16
+ * return (
17
+ * <Portal>
18
+ * <Portal.Table resources={resources}/>
19
+ * </Portal>
20
+ * );
21
+ *```
22
+ * To explore package hierarchy, navigate to the `Portal` link in the components group below.
23
+ * `TableProps` will be the main interface used to setup the `Table` feature for Copperstate implementation.
24
+ *
25
+ * ## Table Feature Usage
26
+ * ```tsx
27
+ * const PortalView = () => {
28
+ * const { data: session, status } = useSession(); // authentication + token
29
+ * const { data: userData, isPending } = getUser.useData(); // D365 User Info
30
+ * const { mutateAsync } = useSessionLogout();
31
+ *
32
+ *
33
+ * const invoicesResource = {
34
+ * name: "invoices",
35
+ * label: "Invoices",
36
+ * detailsFields: ["Code", "AccountName", "Total", "Status"],
37
+ * rowsQueryFn: getInvoices,
38
+ * configQueryFn: getInvoicesTable,
39
+ * idField: "Code",
40
+ * dataMaskHandlers,
41
+ * columnRenderHandlers: fieldRenderMap,
42
+ * filterRenderHandlers: filterRenderHandlers,
43
+ * };
44
+ *
45
+ * const resources = [
46
+ * invoicesResource,
47
+ * // DEFINE MORE RESOURCES HERE
48
+ * ];
49
+ *
50
+ * return (
51
+ * <Container>
52
+ * <Portal session={authObject} logout={handleLogout}>
53
+ * <Portal.Table resources={resources} />
54
+ * </Portal>
55
+ * </Container>
56
+ * )
57
+ *};
58
+ * ```
59
+ * @module e4 Portal
60
+ */
61
+
62
+ export type { Session } from "./auth/auth-context";
63
+ export { Portal, type PortalProps } from "./portal";
64
+
65
+ export { Table } from "./features/table";
package/src/portal.tsx ADDED
@@ -0,0 +1,68 @@
1
+ import { FlexBox } from "@evenicanpm/ui";
2
+ import type { PropsWithChildren } from "react";
3
+ import {
4
+ type AuthProviderProps,
5
+ PortalAuthProvider,
6
+ } from "./auth/auth-context";
7
+ import { TableDashboard } from "./features/table/components/dashboard/table-dashboard";
8
+
9
+ /**
10
+ * Main Entry point properties into portal package
11
+ *
12
+ *
13
+ */
14
+ interface PortalProps extends AuthProviderProps {
15
+ // Add Portal Specific Properties Here
16
+ // ie. roles
17
+ }
18
+
19
+ /**
20
+ * The main **e4Portal** entry point.
21
+ * * This is a React Functional Component that provides the core context
22
+ * and layout for the application. It is required to wrap all components
23
+ * that use the library's hooks or sub-components.
24
+ *
25
+ *
26
+ * @param props - The properties passed to the Portal component.
27
+ * @returns A React Element rendering the main application structure.
28
+ */
29
+ const Portal = ({
30
+ session,
31
+ logout,
32
+ children,
33
+ }: PropsWithChildren<PortalProps>) => {
34
+ // Not Logged In
35
+ if (!session?.email) {
36
+ return (
37
+ <FlexBox mt={5} width="100%" justifyContent="center">
38
+ Please login to view the portal.
39
+ </FlexBox>
40
+ );
41
+ }
42
+
43
+ // Logged In yes
44
+ return (
45
+ <PortalAuthProvider session={session} logout={logout}>
46
+ {children}
47
+ </PortalAuthProvider>
48
+ );
49
+ };
50
+
51
+ /**
52
+ *
53
+ * # Feature: Table
54
+ * This is the table feature component.
55
+ * @params props
56
+ *
57
+ */
58
+ Portal.Table = TableDashboard;
59
+
60
+ /**
61
+ *
62
+ * # Feature: Settings
63
+ * This is the table feature component.
64
+ * @params props
65
+ *
66
+ */
67
+
68
+ export { Portal, type PortalProps };