@kronor/dtv 0.2.9

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 (97) hide show
  1. package/.editorconfig +12 -0
  2. package/.github/copilot-instructions.md +64 -0
  3. package/.github/workflows/ci.yml +51 -0
  4. package/.husky/pre-commit +8 -0
  5. package/README.md +63 -0
  6. package/docs/api/README.md +32 -0
  7. package/docs/api/cell-renderers.md +121 -0
  8. package/docs/api/no-rows-component.md +71 -0
  9. package/docs/api/runtime.md +78 -0
  10. package/e2e/app.spec.ts +6 -0
  11. package/e2e/cell-renderer-setfilterstate.spec.ts +63 -0
  12. package/e2e/filter-sharing.spec.ts +113 -0
  13. package/e2e/filter-url-persistence.spec.ts +36 -0
  14. package/e2e/graphqlMock.ts +144 -0
  15. package/e2e/multi-field-filters.spec.ts +95 -0
  16. package/e2e/pagination.spec.ts +38 -0
  17. package/e2e/payment-request-email-filter.spec.ts +67 -0
  18. package/e2e/save-filter-splitbutton.spec.ts +68 -0
  19. package/e2e/simple-view-email-filter.spec.ts +67 -0
  20. package/e2e/simple-view-transforms.spec.ts +171 -0
  21. package/e2e/simple-view.spec.ts +104 -0
  22. package/e2e/transform-regression.spec.ts +108 -0
  23. package/eslint.config.js +30 -0
  24. package/index.html +17 -0
  25. package/jest.config.js +10 -0
  26. package/package.json +45 -0
  27. package/playwright.config.ts +54 -0
  28. package/public/vite.svg +1 -0
  29. package/src/App.externalRuntime.test.ts +190 -0
  30. package/src/App.tsx +540 -0
  31. package/src/assets/react.svg +1 -0
  32. package/src/components/AIAssistantForm.tsx +241 -0
  33. package/src/components/FilterForm.test.ts +82 -0
  34. package/src/components/FilterForm.tsx +375 -0
  35. package/src/components/PhoneNumberFilter.tsx +102 -0
  36. package/src/components/SavedFilterList.tsx +181 -0
  37. package/src/components/SpeechInput.tsx +67 -0
  38. package/src/components/Table.tsx +119 -0
  39. package/src/components/TablePagination.tsx +40 -0
  40. package/src/components/aiAssistant.test.ts +270 -0
  41. package/src/components/aiAssistant.ts +291 -0
  42. package/src/framework/cell-renderer-components/CurrencyAmount.tsx +30 -0
  43. package/src/framework/cell-renderer-components/LayoutHelpers.tsx +74 -0
  44. package/src/framework/cell-renderer-components/Link.tsx +28 -0
  45. package/src/framework/cell-renderer-components/Mapping.tsx +11 -0
  46. package/src/framework/cell-renderer-components.test.ts +353 -0
  47. package/src/framework/column-definition.tsx +85 -0
  48. package/src/framework/currency.test.ts +46 -0
  49. package/src/framework/currency.ts +62 -0
  50. package/src/framework/data.staticConditions.test.ts +46 -0
  51. package/src/framework/data.test.ts +167 -0
  52. package/src/framework/data.ts +162 -0
  53. package/src/framework/filter-form-state.test.ts +189 -0
  54. package/src/framework/filter-form-state.ts +185 -0
  55. package/src/framework/filter-sharing.test.ts +135 -0
  56. package/src/framework/filter-sharing.ts +118 -0
  57. package/src/framework/filters.ts +194 -0
  58. package/src/framework/graphql.buildHasuraConditions.test.ts +473 -0
  59. package/src/framework/graphql.paginationKey.test.ts +29 -0
  60. package/src/framework/graphql.test.ts +286 -0
  61. package/src/framework/graphql.ts +462 -0
  62. package/src/framework/native-runtime/index.tsx +33 -0
  63. package/src/framework/native-runtime/nativeComponents.test.ts +108 -0
  64. package/src/framework/runtime-reference.test.ts +172 -0
  65. package/src/framework/runtime.ts +15 -0
  66. package/src/framework/saved-filters.test.ts +422 -0
  67. package/src/framework/saved-filters.ts +293 -0
  68. package/src/framework/state.test.ts +86 -0
  69. package/src/framework/state.ts +148 -0
  70. package/src/framework/transform.test.ts +51 -0
  71. package/src/framework/view-parser-initialvalues.test.ts +228 -0
  72. package/src/framework/view-parser.ts +714 -0
  73. package/src/framework/view.test.ts +1805 -0
  74. package/src/framework/view.ts +38 -0
  75. package/src/index.css +6 -0
  76. package/src/main.tsx +99 -0
  77. package/src/views/index.ts +12 -0
  78. package/src/views/payment-requests/components/NoRowsExtendDateRange.tsx +37 -0
  79. package/src/views/payment-requests/components/PaymentMethod.tsx +184 -0
  80. package/src/views/payment-requests/components/PaymentStatusTag.tsx +61 -0
  81. package/src/views/payment-requests/index.ts +1 -0
  82. package/src/views/payment-requests/runtime.tsx +145 -0
  83. package/src/views/payment-requests/view.json +692 -0
  84. package/src/views/payment-requests-initial-values.test.ts +73 -0
  85. package/src/views/request-log/index.ts +2 -0
  86. package/src/views/request-log/runtime.tsx +47 -0
  87. package/src/views/request-log/view.json +123 -0
  88. package/src/views/simple-test-view/index.ts +3 -0
  89. package/src/views/simple-test-view/runtime.tsx +85 -0
  90. package/src/views/simple-test-view/view.json +191 -0
  91. package/src/vite-env.d.ts +1 -0
  92. package/tailwind.config.js +7 -0
  93. package/tsconfig.app.json +26 -0
  94. package/tsconfig.jest.json +6 -0
  95. package/tsconfig.json +7 -0
  96. package/tsconfig.node.json +24 -0
  97. package/vite.config.ts +11 -0
@@ -0,0 +1,38 @@
1
+ import React from "react";
2
+ import { ColumnDefinition } from "./column-definition";
3
+ import { FilterSchemasAndGroups } from "./filters";
4
+ import { FilterState } from "./state";
5
+ import { FilterFormState } from "./filter-form-state";
6
+ import { HasuraCondition } from "./graphql";
7
+
8
+ export type NoRowsComponentProps = {
9
+ setFilterState: (updater: (currentState: FilterState) => FilterState) => void;
10
+ filterState: FilterState;
11
+ applyFilters: () => void;
12
+ updateFilterById: (filterId: string, updater: (currentValue: FilterFormState) => FilterFormState) => void;
13
+ };
14
+
15
+ export type NoRowsComponent = (props: NoRowsComponentProps) => React.ReactNode;
16
+
17
+ export type ViewId = string;
18
+
19
+ export type View = {
20
+ title: string;
21
+ id: ViewId;
22
+ collectionName: string;
23
+ columnDefinitions: ColumnDefinition[];
24
+ filterSchema: FilterSchemasAndGroups;
25
+ boolExpType: string; // GraphQL boolean expression type for this view
26
+ orderByType: string; // GraphQL order by type for this view
27
+ paginationKey: string; // Field to use for cursor-based pagination
28
+ noRowsComponent?: NoRowsComponent;
29
+ // Optional static GraphQL conditions (Hasura boolean expressions) always applied in addition to user filters
30
+ staticConditions?: HasuraCondition[];
31
+ };
32
+
33
+
34
+ // JSON Schema types for view definitions
35
+ // These types represent the serializable structure for views that can be stored in JSON
36
+
37
+ export type { ColumnDefinitionJson, ViewJson } from './view-parser';
38
+ export { parseColumnDefinitionJson, parseViewJson } from './view-parser';
package/src/index.css ADDED
@@ -0,0 +1,6 @@
1
+ @import "tailwindcss" prefix(tw);
2
+
3
+ @import 'primereact/resources/themes/lara-light-indigo/theme.css';
4
+ @import 'primereact/resources/primereact.min.css';
5
+ @import 'primeicons/primeicons.css';
6
+ @import 'primeflex/primeflex.css';
package/src/main.tsx ADDED
@@ -0,0 +1,99 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App.tsx'
5
+ import { PrimeReactProvider } from 'primereact/api';
6
+ import { Runtime } from './framework/runtime';
7
+
8
+ export interface RenderTableViewOptions {
9
+ graphqlHost: string;
10
+ graphqlToken: string;
11
+ geminiApiKey: string;
12
+ viewsJson: string; // JSON string containing array of view definitions
13
+ showViewsMenu?: boolean; // Controls whether the views menu is shown
14
+ showViewTitle?: boolean; // Option to show/hide view title
15
+ externalRuntime?: Runtime; // Optional external runtime that takes precedence over built-in runtimes
16
+ syncFilterStateToUrl?: boolean; // When true, keeps current filter state encoded in URL param `dtv-filter-state`
17
+ }
18
+
19
+
20
+ function renderTableView(target: HTMLElement | string, options: RenderTableViewOptions) {
21
+ const reactContainer = typeof target === 'string' ? document.getElementById(target) : target;
22
+ if (!reactContainer) throw new Error('Target element not found');
23
+
24
+ createRoot(reactContainer).render(
25
+ <StrictMode>
26
+ <PrimeReactProvider value={{}}>
27
+ <App
28
+ graphqlHost={options.graphqlHost}
29
+ graphqlToken={options.graphqlToken}
30
+ geminiApiKey={options.geminiApiKey}
31
+ showViewsMenu={options.showViewsMenu ?? false}
32
+ showViewTitle={options.showViewTitle ?? false}
33
+ viewsJson={options.viewsJson}
34
+ externalRuntime={options.externalRuntime}
35
+ syncFilterStateToUrl={options.syncFilterStateToUrl ?? false}
36
+ />
37
+ </PrimeReactProvider>
38
+ </StrictMode>
39
+ );
40
+ }
41
+
42
+ // Make renderTableView available globally
43
+ // @ts-expect-error Adding renderTableView to window object for global access
44
+ window.renderTableView = renderTableView;
45
+
46
+ // In development, preload views based on URL parameter or load payment requests by default
47
+ if (import.meta.env.DEV) {
48
+ const rootEl = document.getElementById('root');
49
+ if (rootEl) {
50
+ // Async wrapper function to load view and runtime
51
+ const loadView = async (viewModule: any, runtimeModule: any) => {
52
+ const viewJson = JSON.parse(viewModule.default);
53
+ const runtime = Object.values(runtimeModule)[0] as Runtime;
54
+
55
+ const urlParams = new URLSearchParams(window.location.search);
56
+ renderTableView(rootEl, {
57
+ graphqlHost: import.meta.env.VITE_GRAPHQL_HOST,
58
+ graphqlToken: import.meta.env.VITE_GRAPHQL_TOKEN,
59
+ geminiApiKey: import.meta.env.VITE_GEMINI_API_KEY,
60
+ viewsJson: JSON.stringify([viewJson]),
61
+ showViewsMenu: false,
62
+ externalRuntime: runtime,
63
+ syncFilterStateToUrl: urlParams.get('sync-filter-state-to-url') === 'true'
64
+ });
65
+ };
66
+
67
+ (async () => {
68
+ const urlParams = new URLSearchParams(window.location.search);
69
+ const testView = urlParams.get('test-view');
70
+
71
+ switch (testView) {
72
+ case 'payment-requests':
73
+ await loadView(
74
+ await import('./views/payment-requests/view.json?raw'),
75
+ await import('./views/payment-requests/runtime')
76
+ );
77
+ break;
78
+ case 'request-log':
79
+ await loadView(
80
+ await import('./views/request-log/view.json?raw'),
81
+ await import('./views/request-log/runtime')
82
+ );
83
+ break;
84
+ case 'simple-test-view':
85
+ await loadView(
86
+ await import('./views/simple-test-view/view.json?raw'),
87
+ await import('./views/simple-test-view/runtime')
88
+ );
89
+ break;
90
+ default:
91
+ await loadView(
92
+ await import('./views/payment-requests/view.json?raw'),
93
+ await import('./views/payment-requests/runtime')
94
+ );
95
+ break;
96
+ }
97
+ })();
98
+ }
99
+ }
@@ -0,0 +1,12 @@
1
+ // New organized view exports
2
+ export { simpleTestViewRuntime } from "./simple-test-view";
3
+ export type { SimpleTestData } from "./simple-test-view";
4
+ export { requestLogViewRuntime } from "./request-log";
5
+
6
+ // View JSON imports - use specific names to avoid conflicts
7
+ export { default as simpleTestViewJson } from "./simple-test-view/view.json";
8
+ export { default as requestLogViewJson } from "./request-log/view.json";
9
+ export { default as paymentRequestsViewJson } from "./payment-requests/view.json";
10
+
11
+ // Payment requests exports (contains PaymentMethod and NoRowsExtendDateRange)
12
+ export { paymentRequestsRuntime } from "./payment-requests";
@@ -0,0 +1,37 @@
1
+ import { FlexColumn } from "../../../framework/cell-renderer-components/LayoutHelpers";
2
+ import { Button } from "primereact/button";
3
+ import { NoRowsComponentProps } from "../../../framework/view";
4
+ import { FilterFormState } from "../../../framework/filter-form-state";
5
+
6
+ const NoRowsExtendDateRange = ({ updateFilterById, applyFilters }: Pick<NoRowsComponentProps, 'updateFilterById' | 'applyFilters'>) => {
7
+ const handleExtend = () => {
8
+ // Update the first child (the lower end of the date range)
9
+ updateFilterById('date-range', (currentFilter: FilterFormState) => {
10
+ if (currentFilter.type === 'and' && currentFilter.children.length > 0) {
11
+ const firstChild = currentFilter.children[0];
12
+ if (firstChild.type === 'leaf') {
13
+ const current = new Date(firstChild.value);
14
+ current.setMonth(current.getMonth() - 1);
15
+
16
+ return {
17
+ ...currentFilter,
18
+ children: [
19
+ { ...firstChild, value: current },
20
+ ...currentFilter.children.slice(1)
21
+ ]
22
+ };
23
+ }
24
+ }
25
+ return currentFilter; // No change if not the expected structure
26
+ });
27
+ applyFilters();
28
+ };
29
+ return (
30
+ <FlexColumn align="center" justify="center" className="tw:py-8 tw:text-gray-400">
31
+ <span>No data rows match the current filter.</span>
32
+ <Button label="Extend the date range back by 1 month" onClick={handleExtend} size="small" />
33
+ </FlexColumn>
34
+ );
35
+ };
36
+
37
+ export default NoRowsExtendDateRange;
@@ -0,0 +1,184 @@
1
+ import React from "react";
2
+ import { FlexRow } from "../../../framework/cell-renderer-components/LayoutHelpers";
3
+
4
+ type PaymentMethodEnum =
5
+ | "SWISH"
6
+ | "MOBILEPAY"
7
+ | "VIPPS"
8
+ | "PAYPAL"
9
+ | "CREDIT_CARD"
10
+ | "P24"
11
+ | "DIRECT_DEBIT"
12
+ | "POINTSPAY"
13
+ | "BANK_TRANSFER"
14
+ | string;
15
+
16
+
17
+ interface Props {
18
+ cardType: string[]
19
+ paymentMethod?: PaymentMethodEnum;
20
+ darkmode: boolean;
21
+ }
22
+
23
+ const bankTransferLogo = (darkmode: boolean) =>
24
+ `https://staging.kronor.io/portal/static/assets/img/logos/${darkmode ? "light-" : ""}bank-transfer-donated-by-axel.svg`;
25
+
26
+ const cardImage = (scheme: string) => {
27
+ switch (scheme.toLowerCase()) {
28
+ case "visa":
29
+ return (
30
+ <>
31
+ <img src="https://staging.kronor.io/portal/static/assets/img/logos/Visa_Brandmark_Blue_RGB_2021.svg" width="65px" alt="Visa" title="Visa" />
32
+ Visa
33
+ </>
34
+ );
35
+ case "mc":
36
+ return (
37
+ <>
38
+ <img src="https://staging.kronor.io/portal/static/assets/img/logos/mc_symbol.svg" width="55px" alt="Mastercard" title="Mastercard" />
39
+ Mastercard
40
+ </>
41
+ );
42
+ case "dankort":
43
+ return (
44
+ <>
45
+ <img src="https://staging.kronor.io/portal/static/assets/img/logos/DK_Logo_CMYK.png" width="55px" alt="Dankort" title="Dankort" />
46
+ Dankort
47
+ </>
48
+ );
49
+ case "visa_dk":
50
+ return (
51
+ <>
52
+ <img src="https://staging.kronor.io/portal/static/assets/img/logos/DK_Logo_CMYK.png" width="55px" alt="Visa Dankort" title="Visa Dankort" />
53
+ Visa&nbsp;Dankort
54
+ </>
55
+ );
56
+ case "visa_elec":
57
+ return (
58
+ <>
59
+ <img src="https://staging.kronor.io/portal/static/assets/img/logos/vbm_elec_pos_blu_2021.png" width="45px" alt="Visa Electron" title="Visa Electron" />
60
+ Visa&nbsp;Electron
61
+ </>
62
+ );
63
+ case "amex":
64
+ return (
65
+ <>
66
+ <img src="https://staging.kronor.io/portal/static/assets/img/logos/amex_80x80.svg" width="55px" alt="American Express" title="American Express" />
67
+ American&nbsp;Express
68
+ </>
69
+ );
70
+ case "maestro":
71
+ return (
72
+ <>
73
+ <img src="https://staging.kronor.io/portal/static/assets/img/logos/maestro.svg" width="55px" alt="Maestro" title="Maestro" />
74
+ Maestro
75
+ </>
76
+ );
77
+ case "jcb":
78
+ return (
79
+ <>
80
+ <img src="https://staging.kronor.io/portal/static/assets/img/logos/jcb_67x86.gif" width="55px" alt="JCB" title="JCB" />
81
+ JCB
82
+ </>
83
+ );
84
+ case "discover":
85
+ return (
86
+ <>
87
+ <img src="https://staging.kronor.io/portal/static/assets/img/logos/discover.png" width="55px" alt="Discover" title="Discover" />
88
+ Discover
89
+ </>
90
+ );
91
+ case "ffk":
92
+ return (
93
+ <>
94
+ <img src="https://staging.kronor.io/portal/static/assets/img/logos/ffk.svg" width="55px" alt="Forbrugsforeningen" title="Forbrugsforeningen" />
95
+ Forbrugsforeningen
96
+ </>
97
+ );
98
+ case "diners":
99
+ return (
100
+ <>
101
+ <img src="https://staging.kronor.io/portal/static/assets/img/logos/diners_logo_stacked_dark.svg" width="55px" alt="Diners Club" title="Diners Club" />
102
+ Diners&nbsp;Club
103
+ </>
104
+ );
105
+ case "china_union_pay":
106
+ return (
107
+ <>
108
+ <img src="https://staging.kronor.io/portal/static/assets/img/logos/china_union_pay.png" width="55px" alt="China Union Pay" title="China Union Pay" />
109
+ China&nbsp;Union&nbsp;Pay
110
+ </>
111
+ );
112
+ default:
113
+ return <>CREDIT CARD</>;
114
+ }
115
+ };
116
+
117
+ export const PaymentMethod: React.FC<Props> = ({ cardType, paymentMethod, darkmode }) => {
118
+ switch (paymentMethod) {
119
+ case "SWISH":
120
+ return (
121
+ <FlexRow align="center" justify="center" wrap='wrap'>
122
+ <img src="https://staging.kronor.io/portal/static/assets/img/logos/swish.png" width="32px" alt="Swish" title="Swish" />
123
+ Swish
124
+ </FlexRow>
125
+ );
126
+ case "MOBILEPAY":
127
+ return (
128
+ <FlexRow align="center" justify="center" wrap='wrap'>
129
+ <img src="https://staging.kronor.io/portal/static/assets/img/logos/mobilepay.png" width="32px" alt="MobilePay" title="MobilePay" />
130
+ MobilePay
131
+ </FlexRow>
132
+ );
133
+ case "VIPPS":
134
+ return (
135
+ <FlexRow align="center" justify="center" wrap='wrap'>
136
+ <img src="https://staging.kronor.io/portal/static/assets/img/logos/vipps.svg" width="32px" alt="Vipps" title="Vipps" />
137
+ Vipps
138
+ </FlexRow>
139
+ );
140
+ case "PAYPAL":
141
+ return (
142
+ <FlexRow align="center" justify="center" wrap='wrap'>
143
+ <img src="https://staging.kronor.io/portal/static/assets/img/logos/paypal.png" width="24px" alt="PayPal" title="PayPal" />
144
+ PayPal
145
+ </FlexRow>
146
+ );
147
+ case "CREDIT_CARD":
148
+ if (cardType.length === 0) {
149
+ return <FlexRow align="center" justify="center" wrap='wrap'>CREDIT CARD</FlexRow>;
150
+ }
151
+ return <FlexRow align="center" justify="center" wrap='wrap'>{cardImage(cardType[0])}</FlexRow>;
152
+
153
+ case "P24":
154
+ return (
155
+ <FlexRow align="center" justify="center" wrap='wrap'>
156
+ <img src="https://staging.kronor.io/portal/static/assets/img/logos/Przelewy24_logo.svg" width="32px" alt="Przelewy24" title="Przelewy24" />
157
+ P24
158
+ </FlexRow>
159
+ );
160
+ case "DIRECT_DEBIT":
161
+ return (
162
+ <FlexRow align="center" justify="center" wrap='wrap'>
163
+ <img src={bankTransferLogo(darkmode)} width="32px" alt="Direct Debit" title="Direct Debit" />
164
+ Direct Debit
165
+ </FlexRow>
166
+ );
167
+ case "BANK_TRANSFER":
168
+ return (
169
+ <FlexRow align="center" justify="center" wrap='wrap'>
170
+ <img src={bankTransferLogo(darkmode)} width="32px" alt="Bank Transfer" title="Bank Transfer" />
171
+ Bank Transfer
172
+ </FlexRow>
173
+ );
174
+ case "POINTSPAY":
175
+ return (
176
+ <FlexRow align="center" justify="center" wrap='wrap'>
177
+ <img src="https://staging.kronor.io/portal/static/assets/img/logos/pointspay_logo.png" width="32px" alt="Pointspay" data-toggle="tooltip" data-placement="bottom" title="Pointspay" />
178
+ Pointspay
179
+ </FlexRow>
180
+ );
181
+ default:
182
+ return <>UNKNOWN</>;
183
+ }
184
+ };
@@ -0,0 +1,61 @@
1
+ import { Tag } from 'primereact/tag';
2
+ import React from 'react';
3
+ import { FlexRow } from '../../../framework/cell-renderer-components/LayoutHelpers';
4
+
5
+ const paymentStatusEnumToText = (status: string): string => {
6
+ switch (status) {
7
+ case 'CANCELLED': return 'Cancelled';
8
+ case 'PAID': return 'Paid';
9
+ case 'ERROR': return 'Error';
10
+ case 'DECLINED': return 'Declined';
11
+ case 'PRE_FLIGHT_CHECK': return 'Preflight Check';
12
+ case 'INITIALIZING': return 'Initializing';
13
+ case 'WAITING_FOR_PAYMENT': return 'Waiting';
14
+ case 'WAITING_FOR_PROMOTION': return 'Promoting';
15
+ case 'CANCELLING': return 'Cancelling';
16
+ case 'FLOW_COMPLETED': return 'Flow Completed';
17
+ case 'AUTHORIZED': return 'Authorized';
18
+ case 'PARTIALLY_CAPTURED': return 'Partially Captured';
19
+ case 'CAPTURE_DECLINED': return 'Capture Declined';
20
+ case 'RELEASED': return 'Payment Released';
21
+ default: return status;
22
+ }
23
+ };
24
+
25
+ type PaymentStatusSeverity = 'success' | 'warning' | 'danger' | 'secondary' | 'info' | 'contrast' | undefined | null;
26
+
27
+ const badgeForPaymentStatus = (status: string): PaymentStatusSeverity | 'primary' => {
28
+ switch (status) {
29
+ case 'PAID':
30
+ case 'FLOW_COMPLETED':
31
+ case 'AUTHORIZED':
32
+ case 'PARTIALLY_CAPTURED':
33
+ return 'success';
34
+ case 'CANCELLED':
35
+ case 'CANCELLING':
36
+ case 'RELEASED':
37
+ return 'warning';
38
+ case 'ERROR':
39
+ case 'DECLINED':
40
+ case 'CAPTURE_DECLINED':
41
+ return 'danger';
42
+ case 'PRE_FLIGHT_CHECK':
43
+ case 'INITIALIZING':
44
+ case 'WAITING_FOR_PAYMENT':
45
+ case 'WAITING_FOR_PROMOTION':
46
+ return 'primary';
47
+ default:
48
+ return 'secondary';
49
+ }
50
+ };
51
+
52
+ export const PaymentStatusTag: React.FC<{ status: string }> = ({ status }) => {
53
+ const severity = badgeForPaymentStatus(status);
54
+ // 'primary' is not a valid severity for Tag, fallback to 'info'
55
+ const validSeverity: PaymentStatusSeverity = severity === 'primary' ? 'info' : severity;
56
+ return (
57
+ <FlexRow align="center" justify="center">
58
+ <Tag value={paymentStatusEnumToText(status)} severity={validSeverity} style={{ fontSize: '.8rem', padding: '0.3em 1em' }} />
59
+ </FlexRow>
60
+ )
61
+ };
@@ -0,0 +1 @@
1
+ export { paymentRequestsRuntime } from "./runtime";
@@ -0,0 +1,145 @@
1
+ import { CellRenderer } from "../../framework/column-definition";
2
+ import { PaymentMethod } from "./components/PaymentMethod";
3
+ import { PaymentStatusTag } from './components/PaymentStatusTag';
4
+ import { Runtime } from "../../framework/runtime";
5
+
6
+ // Static runtime configuration for payment requests view
7
+ export type PaymentRequestsRuntime = Runtime & {
8
+ cellRenderers: {
9
+ transaction: CellRenderer;
10
+ merchant: CellRenderer;
11
+ placedAt: CellRenderer;
12
+ paymentProvider: CellRenderer;
13
+ initiatedBy: CellRenderer;
14
+ status: CellRenderer;
15
+ amount: CellRenderer;
16
+ };
17
+ queryTransforms: {
18
+ reference: {
19
+ toQuery: (input: any) => any;
20
+ };
21
+ amount: {
22
+ toQuery: (input: any) => any;
23
+ };
24
+ creditCardNumber: {
25
+ toQuery: (input: any) => any;
26
+ };
27
+ };
28
+ initialValues: {
29
+ dateRangeStart: Date;
30
+ dateRangeEnd: Date;
31
+ };
32
+ };
33
+
34
+ // Static object of cell renderers for payment requests
35
+ export const paymentRequestsRuntime: PaymentRequestsRuntime = {
36
+ cellRenderers: {
37
+ // Transaction cell renderer
38
+ transaction: ({ data: { transactionId, waitToken } }) => {
39
+ const url = `/portal/payment-requests/${waitToken}`;
40
+ return <a className="tw:underline" href={url}>{transactionId}</a>;
41
+ },
42
+
43
+ // Merchant cell renderer
44
+ merchant: ({ data: { merchantId }, components: { Mapping } }) =>
45
+ <Mapping value={merchantId} map={{ 1: 'Boozt', 2: 'Boozt Dev' }} />,
46
+
47
+ // Placed At cell renderer
48
+ placedAt: ({ data: { createdAt }, components: { DateTime } }) =>
49
+ <DateTime date={createdAt} options={{ dateStyle: "long", timeStyle: "medium" }} />,
50
+
51
+ // Payment Provider cell renderer
52
+ paymentProvider: ({ data }) =>
53
+ <PaymentMethod paymentMethod={data.paymentProvider} cardType={data['attempts.cardType']} darkmode={false} />,
54
+
55
+ // Initiated By cell renderer
56
+ initiatedBy: ({ data, updateFilterById, applyFilters, components: { FlexColumn, FlexRow } }) => {
57
+ const handleEmailClick = () => {
58
+ // 'customer-email' is the filter id defined in view.json
59
+ updateFilterById('customer-email', (currentFilter: any) => {
60
+ return {
61
+ ...currentFilter,
62
+ value: { operator: '_eq', value: data['customer.email'] }
63
+ };
64
+ });
65
+ applyFilters();
66
+ };
67
+
68
+ return (
69
+ <FlexRow align="center" justify="start">
70
+ <FlexColumn align="start">
71
+ <span className="tw:font-bold">{data['customer.name']}</span>
72
+ <button
73
+ className="tw:text-blue-500 tw:underline hover:tw:text-blue-700 tw:cursor-pointer tw:text-left"
74
+ onClick={handleEmailClick}
75
+ title={`Filter by email: ${data['customer.email']}`}
76
+ >
77
+ {data['customer.email']}
78
+ </button>
79
+ </FlexColumn>
80
+ </FlexRow>
81
+ );
82
+ },
83
+
84
+ // Status cell renderer
85
+ status: ({ data }) => <PaymentStatusTag status={data.currentStatus} />,
86
+
87
+ // Amount cell renderer
88
+ amount: ({ data: { currency, amount }, components: { CurrencyAmount, FlexRow }, currency: { minorToMajor } }) =>
89
+ <FlexRow align="center" justify="end">
90
+ <CurrencyAmount amount={minorToMajor(Number(amount), currency)} currency={currency} />
91
+ </FlexRow>
92
+ },
93
+
94
+ // Transform functions for filter values
95
+ queryTransforms: {
96
+ // Transform for Reference filter (starts with functionality)
97
+ reference: {
98
+ toQuery: (input: any) => {
99
+ if (input.operator === '_like' && input.value) {
100
+ return { value: { value: `${input.value}%` } };
101
+ }
102
+ return { value: input };
103
+ }
104
+ },
105
+
106
+ // Transform for Amount filter (convert between display and storage format)
107
+ amount: {
108
+ toQuery: (input: any) => {
109
+ if (input) {
110
+ return { value: input * 100 };
111
+ }
112
+ return { value: input };
113
+ }
114
+ },
115
+
116
+ // Transform for Credit Card Number filter (add wildcards)
117
+ creditCardNumber: {
118
+ toQuery: (input: any) => {
119
+ if (input) {
120
+ return { value: `%${input}%` };
121
+ }
122
+ return { value: input };
123
+ }
124
+ }
125
+ },
126
+
127
+ noRowsComponents: {},
128
+
129
+ customFilterComponents: {},
130
+
131
+ // Initial values for filters
132
+ initialValues: {
133
+ // Date range: one month back from current date
134
+ dateRangeStart: (() => {
135
+ const date = new Date();
136
+ date.setMonth(date.getMonth() - 1);
137
+ return date; // Return Date object for calendar component
138
+ })(),
139
+
140
+ dateRangeEnd: (() => {
141
+ const date = new Date();
142
+ return date; // Return Date object for calendar component
143
+ })()
144
+ }
145
+ };