@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.
- package/.editorconfig +12 -0
- package/.github/copilot-instructions.md +64 -0
- package/.github/workflows/ci.yml +51 -0
- package/.husky/pre-commit +8 -0
- package/README.md +63 -0
- package/docs/api/README.md +32 -0
- package/docs/api/cell-renderers.md +121 -0
- package/docs/api/no-rows-component.md +71 -0
- package/docs/api/runtime.md +78 -0
- package/e2e/app.spec.ts +6 -0
- package/e2e/cell-renderer-setfilterstate.spec.ts +63 -0
- package/e2e/filter-sharing.spec.ts +113 -0
- package/e2e/filter-url-persistence.spec.ts +36 -0
- package/e2e/graphqlMock.ts +144 -0
- package/e2e/multi-field-filters.spec.ts +95 -0
- package/e2e/pagination.spec.ts +38 -0
- package/e2e/payment-request-email-filter.spec.ts +67 -0
- package/e2e/save-filter-splitbutton.spec.ts +68 -0
- package/e2e/simple-view-email-filter.spec.ts +67 -0
- package/e2e/simple-view-transforms.spec.ts +171 -0
- package/e2e/simple-view.spec.ts +104 -0
- package/e2e/transform-regression.spec.ts +108 -0
- package/eslint.config.js +30 -0
- package/index.html +17 -0
- package/jest.config.js +10 -0
- package/package.json +45 -0
- package/playwright.config.ts +54 -0
- package/public/vite.svg +1 -0
- package/src/App.externalRuntime.test.ts +190 -0
- package/src/App.tsx +540 -0
- package/src/assets/react.svg +1 -0
- package/src/components/AIAssistantForm.tsx +241 -0
- package/src/components/FilterForm.test.ts +82 -0
- package/src/components/FilterForm.tsx +375 -0
- package/src/components/PhoneNumberFilter.tsx +102 -0
- package/src/components/SavedFilterList.tsx +181 -0
- package/src/components/SpeechInput.tsx +67 -0
- package/src/components/Table.tsx +119 -0
- package/src/components/TablePagination.tsx +40 -0
- package/src/components/aiAssistant.test.ts +270 -0
- package/src/components/aiAssistant.ts +291 -0
- package/src/framework/cell-renderer-components/CurrencyAmount.tsx +30 -0
- package/src/framework/cell-renderer-components/LayoutHelpers.tsx +74 -0
- package/src/framework/cell-renderer-components/Link.tsx +28 -0
- package/src/framework/cell-renderer-components/Mapping.tsx +11 -0
- package/src/framework/cell-renderer-components.test.ts +353 -0
- package/src/framework/column-definition.tsx +85 -0
- package/src/framework/currency.test.ts +46 -0
- package/src/framework/currency.ts +62 -0
- package/src/framework/data.staticConditions.test.ts +46 -0
- package/src/framework/data.test.ts +167 -0
- package/src/framework/data.ts +162 -0
- package/src/framework/filter-form-state.test.ts +189 -0
- package/src/framework/filter-form-state.ts +185 -0
- package/src/framework/filter-sharing.test.ts +135 -0
- package/src/framework/filter-sharing.ts +118 -0
- package/src/framework/filters.ts +194 -0
- package/src/framework/graphql.buildHasuraConditions.test.ts +473 -0
- package/src/framework/graphql.paginationKey.test.ts +29 -0
- package/src/framework/graphql.test.ts +286 -0
- package/src/framework/graphql.ts +462 -0
- package/src/framework/native-runtime/index.tsx +33 -0
- package/src/framework/native-runtime/nativeComponents.test.ts +108 -0
- package/src/framework/runtime-reference.test.ts +172 -0
- package/src/framework/runtime.ts +15 -0
- package/src/framework/saved-filters.test.ts +422 -0
- package/src/framework/saved-filters.ts +293 -0
- package/src/framework/state.test.ts +86 -0
- package/src/framework/state.ts +148 -0
- package/src/framework/transform.test.ts +51 -0
- package/src/framework/view-parser-initialvalues.test.ts +228 -0
- package/src/framework/view-parser.ts +714 -0
- package/src/framework/view.test.ts +1805 -0
- package/src/framework/view.ts +38 -0
- package/src/index.css +6 -0
- package/src/main.tsx +99 -0
- package/src/views/index.ts +12 -0
- package/src/views/payment-requests/components/NoRowsExtendDateRange.tsx +37 -0
- package/src/views/payment-requests/components/PaymentMethod.tsx +184 -0
- package/src/views/payment-requests/components/PaymentStatusTag.tsx +61 -0
- package/src/views/payment-requests/index.ts +1 -0
- package/src/views/payment-requests/runtime.tsx +145 -0
- package/src/views/payment-requests/view.json +692 -0
- package/src/views/payment-requests-initial-values.test.ts +73 -0
- package/src/views/request-log/index.ts +2 -0
- package/src/views/request-log/runtime.tsx +47 -0
- package/src/views/request-log/view.json +123 -0
- package/src/views/simple-test-view/index.ts +3 -0
- package/src/views/simple-test-view/runtime.tsx +85 -0
- package/src/views/simple-test-view/view.json +191 -0
- package/src/vite-env.d.ts +1 -0
- package/tailwind.config.js +7 -0
- package/tsconfig.app.json +26 -0
- package/tsconfig.jest.json +6 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +24 -0
- 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
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 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 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 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 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 Union 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
|
+
};
|