@orchestrator-ui/orchestrator-ui-components 8.9.3 → 9.0.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/.turbo/turbo-build.log +8 -8
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-test.log +17 -50
- package/CHANGELOG.md +19 -0
- package/dist/index.d.ts +2081 -2517
- package/dist/index.js +1247 -1579
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/WfoError/WfoError.tsx +2 -2
- package/src/components/WfoInlineNoteEdit/WfoSubscriptionDetailNoteEdit.tsx +4 -8
- package/src/components/WfoInlineNoteEdit/WfoSubscriptionNoteEdit.tsx +6 -7
- package/src/components/WfoPageTemplate/paths.ts +0 -1
- package/src/components/WfoSubscriptionsList/index.ts +1 -2
- package/src/components/WfoSubscriptionsList/subscriptionListItem.ts +14 -0
- package/src/components/WfoSubscriptionsList/subscriptionListTabs.ts +1 -20
- package/src/components/WfoSummary/WfoLatestActiveSubscriptionsSummaryCard.tsx +17 -13
- package/src/components/WfoSummary/WfoLatestOutOfSyncSubscriptionSummaryCard.tsx +28 -15
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFieldSelector.spec.tsx +0 -16
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFieldSelector.tsx +4 -10
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFilterBuilder.tsx +9 -6
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoRestoreLoop.spec.tsx +2 -2
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoStructuredSearchTable.tsx +3 -5
- package/src/components/WfoTable/WfoStructuredSearchTable/useSearchWithDebouncedCallback.spec.tsx +56 -3
- package/src/components/WfoTable/WfoStructuredSearchTable/utils.spec.ts +60 -1
- package/src/components/WfoTable/WfoStructuredSearchTable/utils.ts +27 -3
- package/src/configuration/version.ts +1 -1
- package/src/messages/en-GB.json +1 -1
- package/src/messages/nl-NL.json +1 -1
- package/src/pages/index.ts +0 -1
- package/src/pages/startPage/index.ts +1 -0
- package/src/pages/startPage/mappers.ts +15 -8
- package/src/pages/startPage/queryVariables.ts +0 -29
- package/src/pages/startPage/searchPayloads.ts +22 -0
- package/src/pages/subscriptions/WfoSubscriptionsListPage.tsx +511 -39
- package/src/rtk/endpoints/index.ts +0 -1
- package/src/rtk/endpoints/search.ts +3 -0
- package/src/rtk/endpoints/subscriptionListMutation.spec.ts +80 -0
- package/src/rtk/endpoints/subscriptionListMutation.ts +71 -52
- package/src/rtk/utils.spec.ts +30 -1
- package/src/rtk/utils.ts +2 -1
- package/src/types/search.ts +0 -1
- package/src/types/types.ts +0 -2
- package/src/utils/getDefaultTableConfig.ts +1 -6
- package/src/utils/getQueryParams.ts +1 -0
- package/src/components/WfoSubscriptionsList/WfoSubscriptionsList.tsx +0 -243
- package/src/components/WfoSubscriptionsList/subscriptionResultMappers.ts +0 -49
- package/src/pages/WfoSearchPocPage.tsx +0 -575
- package/src/rtk/endpoints/subscriptionListSummary.ts +0 -66
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { configureStore } from '@reduxjs/toolkit';
|
|
2
|
+
|
|
3
|
+
import { orchestratorApi } from '@/rtk/api';
|
|
4
|
+
import { EntityKind, PaginatedSearchResults } from '@/types';
|
|
5
|
+
|
|
6
|
+
// Importing the endpoint modules injects them into orchestratorApi
|
|
7
|
+
import './search';
|
|
8
|
+
import './subscriptionDetail';
|
|
9
|
+
import './subscriptionListMutation';
|
|
10
|
+
|
|
11
|
+
const SUBSCRIPTION_ID = 'sub-1';
|
|
12
|
+
const OTHER_SUBSCRIPTION_ID = 'sub-2';
|
|
13
|
+
|
|
14
|
+
// The injected endpoints are not part of orchestratorApi's static type, hence the casts below
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
+
const api = orchestratorApi as any;
|
|
17
|
+
|
|
18
|
+
const searchResponse = (note: string): PaginatedSearchResults => ({
|
|
19
|
+
data: [SUBSCRIPTION_ID, OTHER_SUBSCRIPTION_ID].map((id) => ({
|
|
20
|
+
entity_id: id,
|
|
21
|
+
entity_type: EntityKind.SUBSCRIPTION,
|
|
22
|
+
entity_title: id,
|
|
23
|
+
score: 1,
|
|
24
|
+
perfect_match: 1,
|
|
25
|
+
response_columns: { 'subscription.subscription_id': id, 'subscription.note': note },
|
|
26
|
+
})),
|
|
27
|
+
cursor: null,
|
|
28
|
+
page_info: { has_next_page: false, next_page_cursor: null },
|
|
29
|
+
search_metadata: { search_type: null, description: null },
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const getSearchPayload = (query: string) => ({
|
|
33
|
+
query,
|
|
34
|
+
limit: 10,
|
|
35
|
+
entity_type: EntityKind.SUBSCRIPTION,
|
|
36
|
+
response_columns: [],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const getNotesFromSearchCache = (state: unknown, payload: object): string[] =>
|
|
40
|
+
api.endpoints.search
|
|
41
|
+
.select(payload)(state)
|
|
42
|
+
.data.data.map(
|
|
43
|
+
({ response_columns }: PaginatedSearchResults['data'][number]) => response_columns['subscription.note'],
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
describe('updateSubscriptionNoteOptimistic', () => {
|
|
47
|
+
it('patches the note in every cached search result set and in the detail cache', async () => {
|
|
48
|
+
const store = configureStore({
|
|
49
|
+
reducer: { [orchestratorApi.reducerPath]: orchestratorApi.reducer },
|
|
50
|
+
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(orchestratorApi.middleware),
|
|
51
|
+
});
|
|
52
|
+
const firstSearch = getSearchPayload('first');
|
|
53
|
+
const secondSearch = getSearchPayload('second');
|
|
54
|
+
const detailArgs = { subscriptionId: SUBSCRIPTION_ID };
|
|
55
|
+
|
|
56
|
+
// upsertQueryData resolves asynchronously through the query thunk
|
|
57
|
+
await Promise.all([
|
|
58
|
+
store.dispatch(api.util.upsertQueryData('search', firstSearch, searchResponse('old'))),
|
|
59
|
+
store.dispatch(api.util.upsertQueryData('search', secondSearch, searchResponse('old'))),
|
|
60
|
+
store.dispatch(
|
|
61
|
+
api.util.upsertQueryData('getSubscriptionDetail', detailArgs, {
|
|
62
|
+
subscription: { subscriptionId: SUBSCRIPTION_ID, note: 'old' },
|
|
63
|
+
pageInfo: {},
|
|
64
|
+
}),
|
|
65
|
+
),
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
await store.dispatch(
|
|
69
|
+
api.endpoints.updateSubscriptionNoteOptimistic.initiate({
|
|
70
|
+
subscriptionId: SUBSCRIPTION_ID,
|
|
71
|
+
note: 'new',
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const state = store.getState();
|
|
76
|
+
expect(getNotesFromSearchCache(state, firstSearch)).toEqual(['new', 'old']);
|
|
77
|
+
expect(getNotesFromSearchCache(state, secondSearch)).toEqual(['new', 'old']);
|
|
78
|
+
expect(api.endpoints.getSubscriptionDetail.select(detailArgs)(state).data.subscription.note).toBe('new');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -1,74 +1,93 @@
|
|
|
1
|
-
import { SubscriptionListItem } from '@/components/WfoSubscriptionsList';
|
|
2
1
|
import { SubscriptionDetailResponse, SubscriptionListResponse, orchestratorApi } from '@/rtk';
|
|
3
|
-
import {
|
|
2
|
+
import { PaginatedSearchResults } from '@/types';
|
|
3
|
+
|
|
4
|
+
const SEARCH_NOTE_COLUMN = 'subscription.note';
|
|
5
|
+
const SEARCH_ID_COLUMN = 'subscription.subscription_id';
|
|
6
|
+
|
|
7
|
+
// The note edit is rendered by both the GraphQL based lists (surf NMS pages) and the search based
|
|
8
|
+
// subscriptions list, whose cached responses have a different shape. The draft is inspected
|
|
9
|
+
// instead of typed per caller so the note edit needs no knowledge of the query behind it.
|
|
10
|
+
const patchNoteInListDraft = (
|
|
11
|
+
draft: SubscriptionListResponse | PaginatedSearchResults,
|
|
12
|
+
subscriptionId: string,
|
|
13
|
+
note: string,
|
|
14
|
+
) => {
|
|
15
|
+
if ('subscriptions' in draft) {
|
|
16
|
+
const subscription = draft.subscriptions.find((item) => item.subscriptionId === subscriptionId);
|
|
17
|
+
if (subscription) {
|
|
18
|
+
subscription.note = note;
|
|
19
|
+
}
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const searchResult = draft.data?.find(
|
|
23
|
+
({ response_columns }) => response_columns[SEARCH_ID_COLUMN] === subscriptionId,
|
|
24
|
+
);
|
|
25
|
+
if (searchResult) {
|
|
26
|
+
searchResult.response_columns[SEARCH_NOTE_COLUMN] = note;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const SEARCH_ENDPOINT_NAME = 'search';
|
|
4
31
|
|
|
5
32
|
const subscriptionListMutationApi = orchestratorApi.injectEndpoints({
|
|
6
33
|
endpoints: (builder) => ({
|
|
7
|
-
emptyQuery: builder.query<SubscriptionListResponse, GraphqlQueryVariables<SubscriptionListItem>>({
|
|
8
|
-
query: () => ({}),
|
|
9
|
-
}),
|
|
10
|
-
emptyDetailQuery: builder.query<SubscriptionDetailResponse, { subscriptionId: string }>({ query: () => ({}) }),
|
|
11
34
|
updateSubscriptionNoteOptimistic: builder.mutation<
|
|
12
35
|
{ mockResponse: boolean },
|
|
13
36
|
{
|
|
14
|
-
queryName: string;
|
|
15
37
|
subscriptionId: string;
|
|
16
|
-
graphQlQueryVariables: GraphqlQueryVariables<SubscriptionListItem>;
|
|
17
38
|
note: string;
|
|
39
|
+
// Cache key of a GraphQL list query showing the note (surf NMS pages). The search based
|
|
40
|
+
// subscriptions list needs no key: every cached search result set is patched.
|
|
41
|
+
listQuery?: { queryName: string; queryVariables: object };
|
|
18
42
|
}
|
|
19
43
|
>({
|
|
20
44
|
queryFn: async () => ({ data: { mockResponse: true } }),
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
) {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
queryName,
|
|
55
|
-
{ subscriptionId: subscriptionId },
|
|
56
|
-
(draft: SubscriptionDetailResponse) => {
|
|
57
|
-
if (draft) {
|
|
58
|
-
draft.subscription.note = patch.note;
|
|
59
|
-
}
|
|
60
|
-
},
|
|
45
|
+
// Patches the detail cache, all cached search result sets and the optional GraphQL list so
|
|
46
|
+
// the note stays consistent when navigating between pages. updateQueryData is a no-op for
|
|
47
|
+
// a cache entry that does not exist, and patching the same entry twice is harmless.
|
|
48
|
+
async onQueryStarted({ subscriptionId, note, listQuery }, { dispatch, getState, queryFulfilled }) {
|
|
49
|
+
const patchList = (queryName: string, queryVariables: object) =>
|
|
50
|
+
dispatch(
|
|
51
|
+
subscriptionListMutationApi.util.updateQueryData(
|
|
52
|
+
// @ts-expect-error - queryName is a runtime string, not a known endpoint name
|
|
53
|
+
queryName,
|
|
54
|
+
queryVariables,
|
|
55
|
+
(draft: SubscriptionListResponse | PaginatedSearchResults) =>
|
|
56
|
+
patchNoteInListDraft(draft, subscriptionId, note),
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const cachedSearchArgs = subscriptionListMutationApi.util.selectCachedArgsForQuery(
|
|
61
|
+
getState(),
|
|
62
|
+
// @ts-expect-error - search is injected by another slice, unknown to this one
|
|
63
|
+
SEARCH_ENDPOINT_NAME,
|
|
64
|
+
) as object[];
|
|
65
|
+
|
|
66
|
+
const patchResults = [
|
|
67
|
+
dispatch(
|
|
68
|
+
subscriptionListMutationApi.util.updateQueryData(
|
|
69
|
+
// @ts-expect-error - getSubscriptionDetail is injected by another slice, unknown to this one
|
|
70
|
+
'getSubscriptionDetail',
|
|
71
|
+
{ subscriptionId },
|
|
72
|
+
(draft: SubscriptionDetailResponse) => {
|
|
73
|
+
if (draft?.subscription) {
|
|
74
|
+
draft.subscription.note = note;
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
),
|
|
61
78
|
),
|
|
62
|
-
|
|
79
|
+
...cachedSearchArgs.map((searchArgs) => patchList(SEARCH_ENDPOINT_NAME, searchArgs)),
|
|
80
|
+
...(listQuery ? [patchList(listQuery.queryName, listQuery.queryVariables)] : []),
|
|
81
|
+
];
|
|
82
|
+
|
|
63
83
|
try {
|
|
64
84
|
await queryFulfilled;
|
|
65
85
|
} catch {
|
|
66
|
-
patchResult.undo();
|
|
86
|
+
patchResults.forEach((patchResult) => patchResult.undo());
|
|
67
87
|
}
|
|
68
88
|
},
|
|
69
89
|
}),
|
|
70
90
|
}),
|
|
71
91
|
});
|
|
72
92
|
|
|
73
|
-
export const { useUpdateSubscriptionNoteOptimisticMutation
|
|
74
|
-
subscriptionListMutationApi;
|
|
93
|
+
export const { useUpdateSubscriptionNoteOptimisticMutation } = subscriptionListMutationApi;
|
package/src/rtk/utils.spec.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { stripUndefined } from '@/rtk/utils';
|
|
1
|
+
import { mapRtkErrorToWfoError, stripUndefined } from '@/rtk/utils';
|
|
2
2
|
|
|
3
3
|
describe('stripUndefined', () => {
|
|
4
4
|
it('should remove properties with undefined values', () => {
|
|
@@ -82,3 +82,32 @@ describe('stripUndefined', () => {
|
|
|
82
82
|
expect(result).toEqual(obj);
|
|
83
83
|
});
|
|
84
84
|
});
|
|
85
|
+
|
|
86
|
+
describe('mapRtkErrorToWfoError', () => {
|
|
87
|
+
it('should return the detail of a rejected request', () => {
|
|
88
|
+
const detail =
|
|
89
|
+
'1 validation error for SelectQuery\n Value error, Hybrid retriever requested but no query text provided.';
|
|
90
|
+
|
|
91
|
+
expect(mapRtkErrorToWfoError({ status: 422, data: { detail } })).toEqual([{ extensions: {}, message: detail }]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should fall back to the status code when the response body has no detail', () => {
|
|
95
|
+
expect(mapRtkErrorToWfoError({ status: 500, data: {} })).toEqual([{ extensions: {}, message: '500' }]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should fall back to the status code when the detail is not a string', () => {
|
|
99
|
+
const error = { status: 422, data: { detail: [{ loc: ['body', 'query'], msg: 'field required' }] } };
|
|
100
|
+
|
|
101
|
+
expect(mapRtkErrorToWfoError(error)).toEqual([{ extensions: {}, message: '422' }]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should return the message of a serialized error', () => {
|
|
105
|
+
expect(mapRtkErrorToWfoError({ name: 'AbortError', message: 'Aborted' })).toEqual([
|
|
106
|
+
{ extensions: {}, message: 'Aborted' },
|
|
107
|
+
]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should return undefined when there is no error', () => {
|
|
111
|
+
expect(mapRtkErrorToWfoError(undefined)).toBeUndefined();
|
|
112
|
+
});
|
|
113
|
+
});
|
package/src/rtk/utils.ts
CHANGED
|
@@ -58,10 +58,11 @@ export const mapRtkErrorToWfoError = (
|
|
|
58
58
|
};
|
|
59
59
|
});
|
|
60
60
|
} else if (error && 'status' in error && error.status !== undefined) {
|
|
61
|
+
const detail = isRecord(error.data) && typeof error.data.detail === 'string' ? error.data.detail : undefined;
|
|
61
62
|
return [
|
|
62
63
|
{
|
|
63
64
|
extensions: {},
|
|
64
|
-
message: String(error.status),
|
|
65
|
+
message: detail || String(error.status),
|
|
65
66
|
},
|
|
66
67
|
];
|
|
67
68
|
} else if (isSerializedError(error)) {
|
package/src/types/search.ts
CHANGED
|
@@ -217,7 +217,6 @@ export type FieldToOperatorMap = Map<string, string[]>;
|
|
|
217
217
|
|
|
218
218
|
export type WfoQueryBuilderContext = {
|
|
219
219
|
onFieldSelected: (field: string, operators: string[], pathInfo?: PathInfo) => void;
|
|
220
|
-
prefilledFieldOptions: FieldToOperatorMap;
|
|
221
220
|
fieldPathInfoMap: Map<string, PathInfo>;
|
|
222
221
|
useAdvancedNestedSearch: boolean;
|
|
223
222
|
};
|
package/src/types/types.ts
CHANGED
|
@@ -475,8 +475,6 @@ export type Subscription = {
|
|
|
475
475
|
metadata: object;
|
|
476
476
|
};
|
|
477
477
|
|
|
478
|
-
export type SubscriptionSummary = Pick<Subscription, 'subscriptionId' | 'description' | 'startDate'>;
|
|
479
|
-
|
|
480
478
|
export type SubscriptionDropdownOption = {
|
|
481
479
|
description: Subscription['description'];
|
|
482
480
|
subscriptionId: Subscription['subscriptionId'];
|
|
@@ -97,12 +97,7 @@ export const getDefaultTableConfig = <T>(storageKey: string) => {
|
|
|
97
97
|
return getTableConfig<T>(completedTasksColumns as (keyof T)[]);
|
|
98
98
|
}
|
|
99
99
|
case SUBSCRIPTIONS_TABLE_LOCAL_STORAGE_KEY: {
|
|
100
|
-
const subscriptionColumns: (keyof SubscriptionListItem)[] = [
|
|
101
|
-
'productName',
|
|
102
|
-
'customerId',
|
|
103
|
-
'customerFullname',
|
|
104
|
-
'metadata',
|
|
105
|
-
];
|
|
100
|
+
const subscriptionColumns: (keyof SubscriptionListItem)[] = ['productName', 'customerFullname', 'metadata'];
|
|
106
101
|
return getTableConfig<T>(subscriptionColumns as (keyof T)[]);
|
|
107
102
|
}
|
|
108
103
|
default:
|
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
import React, { FC } from 'react';
|
|
2
|
-
|
|
3
|
-
import { useTranslations } from 'next-intl';
|
|
4
|
-
import Link from 'next/link';
|
|
5
|
-
import { useRouter } from 'next/router';
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
ColumnType,
|
|
9
|
-
FilterQuery,
|
|
10
|
-
PATH_SUBSCRIPTIONS,
|
|
11
|
-
Pagination,
|
|
12
|
-
WfoAdvancedTable,
|
|
13
|
-
WfoAdvancedTableColumnConfig,
|
|
14
|
-
WfoDateTime,
|
|
15
|
-
WfoInlineJson,
|
|
16
|
-
WfoInsyncIcon,
|
|
17
|
-
WfoJsonCodeBlock,
|
|
18
|
-
WfoSubscriptionActions,
|
|
19
|
-
WfoSubscriptionNoteEdit,
|
|
20
|
-
WfoSubscriptionStatusBadge,
|
|
21
|
-
getPageIndexChangeHandler,
|
|
22
|
-
getPageSizeChangeHandler,
|
|
23
|
-
} from '@/components';
|
|
24
|
-
import {
|
|
25
|
-
SubscriptionListItem,
|
|
26
|
-
mapGraphQlSubscriptionsResultToPageInfo,
|
|
27
|
-
mapGraphQlSubscriptionsResultToSubscriptionListItems,
|
|
28
|
-
} from '@/components';
|
|
29
|
-
import { mapSortableAndFilterableValuesToTableColumnConfig } from '@/components/WfoTable/WfoTable/utils';
|
|
30
|
-
import { DataDisplayParams, useShowToastMessage } from '@/hooks';
|
|
31
|
-
import { useGetSubscriptionListQuery, useLazyGetSubscriptionListQuery } from '@/rtk/endpoints/subscriptionList';
|
|
32
|
-
import { mapRtkErrorToWfoError } from '@/rtk/utils';
|
|
33
|
-
import { GraphqlQueryVariables, SortOrder } from '@/types';
|
|
34
|
-
import { getQueryVariablesForExport, getTypedFieldFromObject, parseDateToLocaleDateTimeString } from '@/utils';
|
|
35
|
-
import { csvDownloadHandler, getCsvFileNameWithDate } from '@/utils/csvDownload';
|
|
36
|
-
|
|
37
|
-
import {
|
|
38
|
-
DEFAULT_PAGE_SIZES,
|
|
39
|
-
SUBSCRIPTIONS_TABLE_LOCAL_STORAGE_KEY,
|
|
40
|
-
TableColumnKeys,
|
|
41
|
-
WfoDataSorting,
|
|
42
|
-
WfoFirstPartUUID,
|
|
43
|
-
getDataSortHandler,
|
|
44
|
-
getQueryStringHandler,
|
|
45
|
-
} from '../WfoTable';
|
|
46
|
-
|
|
47
|
-
export type WfoSubscriptionsListProps = {
|
|
48
|
-
alwaysOnFilters?: FilterQuery<SubscriptionListItem>[];
|
|
49
|
-
dataDisplayParams: DataDisplayParams<SubscriptionListItem>;
|
|
50
|
-
setDataDisplayParam: <DisplayParamKey extends keyof DataDisplayParams<SubscriptionListItem>>(
|
|
51
|
-
prop: DisplayParamKey,
|
|
52
|
-
value: DataDisplayParams<SubscriptionListItem>[DisplayParamKey],
|
|
53
|
-
) => void;
|
|
54
|
-
hiddenColumns: TableColumnKeys<SubscriptionListItem> | undefined;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
export const WfoSubscriptionsList: FC<WfoSubscriptionsListProps> = ({
|
|
58
|
-
alwaysOnFilters,
|
|
59
|
-
dataDisplayParams,
|
|
60
|
-
setDataDisplayParam,
|
|
61
|
-
hiddenColumns,
|
|
62
|
-
}) => {
|
|
63
|
-
const router = useRouter();
|
|
64
|
-
const t = useTranslations('subscriptions.index');
|
|
65
|
-
const tError = useTranslations('errors');
|
|
66
|
-
const { showToastMessage } = useShowToastMessage();
|
|
67
|
-
|
|
68
|
-
const { sortBy, queryString, pageIndex, pageSize } = dataDisplayParams;
|
|
69
|
-
|
|
70
|
-
const graphqlQueryVariables: GraphqlQueryVariables<SubscriptionListItem> = {
|
|
71
|
-
first: pageSize,
|
|
72
|
-
after: pageIndex * pageSize,
|
|
73
|
-
sortBy,
|
|
74
|
-
filterBy: alwaysOnFilters,
|
|
75
|
-
query: queryString || undefined,
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const { data, isFetching, error, endpointName } = useGetSubscriptionListQuery(graphqlQueryVariables);
|
|
79
|
-
|
|
80
|
-
const subscriptionList = mapGraphQlSubscriptionsResultToSubscriptionListItems(data);
|
|
81
|
-
|
|
82
|
-
const tableColumnConfig: WfoAdvancedTableColumnConfig<SubscriptionListItem> = {
|
|
83
|
-
actions: {
|
|
84
|
-
columnType: ColumnType.CONTROL,
|
|
85
|
-
width: '50px',
|
|
86
|
-
renderControl: (row) => <WfoSubscriptionActions compactMode={true} subscriptionId={row.subscriptionId} />,
|
|
87
|
-
},
|
|
88
|
-
subscriptionId: {
|
|
89
|
-
columnType: ColumnType.DATA,
|
|
90
|
-
label: t('id'),
|
|
91
|
-
width: '100px',
|
|
92
|
-
renderData: (value) => <WfoFirstPartUUID UUID={value} />,
|
|
93
|
-
renderDetails: (value) => value,
|
|
94
|
-
renderTooltip: (value) => value,
|
|
95
|
-
},
|
|
96
|
-
description: {
|
|
97
|
-
columnType: ColumnType.DATA,
|
|
98
|
-
label: t('description'),
|
|
99
|
-
width: '500px',
|
|
100
|
-
renderData: (value, record) => <Link href={`/subscriptions/${record.subscriptionId}`}>{value}</Link>,
|
|
101
|
-
renderTooltip: (value) => value,
|
|
102
|
-
},
|
|
103
|
-
status: {
|
|
104
|
-
columnType: ColumnType.DATA,
|
|
105
|
-
label: t('status'),
|
|
106
|
-
width: '120px',
|
|
107
|
-
renderData: (value) => <WfoSubscriptionStatusBadge status={value} />,
|
|
108
|
-
},
|
|
109
|
-
insync: {
|
|
110
|
-
columnType: ColumnType.DATA,
|
|
111
|
-
label: t('insync'),
|
|
112
|
-
width: '75px',
|
|
113
|
-
renderData: (value) => <WfoInsyncIcon inSync={value} />,
|
|
114
|
-
},
|
|
115
|
-
productName: {
|
|
116
|
-
columnType: ColumnType.DATA,
|
|
117
|
-
width: '260px',
|
|
118
|
-
label: t('product'),
|
|
119
|
-
},
|
|
120
|
-
tag: {
|
|
121
|
-
columnType: ColumnType.DATA,
|
|
122
|
-
label: t('tag'),
|
|
123
|
-
width: '100px',
|
|
124
|
-
},
|
|
125
|
-
customerId: {
|
|
126
|
-
columnType: ColumnType.DATA,
|
|
127
|
-
label: t('customerId'),
|
|
128
|
-
width: '100px',
|
|
129
|
-
},
|
|
130
|
-
customerFullname: {
|
|
131
|
-
columnType: ColumnType.DATA,
|
|
132
|
-
label: t('customerFullname'),
|
|
133
|
-
},
|
|
134
|
-
customerShortcode: {
|
|
135
|
-
columnType: ColumnType.DATA,
|
|
136
|
-
label: t('customerShortcode'),
|
|
137
|
-
width: '150px',
|
|
138
|
-
},
|
|
139
|
-
startDate: {
|
|
140
|
-
columnType: ColumnType.DATA,
|
|
141
|
-
label: t('startDate'),
|
|
142
|
-
width: '100px',
|
|
143
|
-
renderData: (value) => <WfoDateTime dateOrIsoString={value} />,
|
|
144
|
-
renderDetails: parseDateToLocaleDateTimeString,
|
|
145
|
-
clipboardText: parseDateToLocaleDateTimeString,
|
|
146
|
-
renderTooltip: (cellValue) => cellValue?.toString(),
|
|
147
|
-
},
|
|
148
|
-
endDate: {
|
|
149
|
-
columnType: ColumnType.DATA,
|
|
150
|
-
label: t('endDate'),
|
|
151
|
-
width: '100px',
|
|
152
|
-
renderData: (value) => <WfoDateTime dateOrIsoString={value} />,
|
|
153
|
-
renderDetails: parseDateToLocaleDateTimeString,
|
|
154
|
-
clipboardText: parseDateToLocaleDateTimeString,
|
|
155
|
-
renderTooltip: (cellValue) => cellValue?.toString(),
|
|
156
|
-
},
|
|
157
|
-
note: {
|
|
158
|
-
columnType: ColumnType.DATA,
|
|
159
|
-
label: t('note'),
|
|
160
|
-
width: '300px',
|
|
161
|
-
renderData: (cellValue, row) => {
|
|
162
|
-
return (
|
|
163
|
-
<WfoSubscriptionNoteEdit
|
|
164
|
-
onlyShowOnHover={true}
|
|
165
|
-
endpointName={endpointName}
|
|
166
|
-
queryVariables={graphqlQueryVariables}
|
|
167
|
-
subscriptionId={row.subscriptionId}
|
|
168
|
-
note={cellValue}
|
|
169
|
-
/>
|
|
170
|
-
);
|
|
171
|
-
},
|
|
172
|
-
},
|
|
173
|
-
metadata: {
|
|
174
|
-
columnType: ColumnType.DATA,
|
|
175
|
-
label: t('metadata'),
|
|
176
|
-
width: '100px',
|
|
177
|
-
renderData: (value) => <WfoInlineJson data={value} />,
|
|
178
|
-
renderDetails: (value) => value && <WfoJsonCodeBlock data={value} isBasicStyle />,
|
|
179
|
-
renderTooltip: (value) => value && <WfoJsonCodeBlock data={value} isBasicStyle={false} />,
|
|
180
|
-
},
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
const [getSubscriptionListTrigger, { isFetching: isFetchingCsv }] = useLazyGetSubscriptionListQuery();
|
|
184
|
-
const getSubscriptionListForExport = () =>
|
|
185
|
-
getSubscriptionListTrigger(getQueryVariablesForExport(graphqlQueryVariables)).unwrap();
|
|
186
|
-
|
|
187
|
-
const sortedColumnId = getTypedFieldFromObject(sortBy?.field, tableColumnConfig);
|
|
188
|
-
if (!sortedColumnId) {
|
|
189
|
-
router.replace(PATH_SUBSCRIPTIONS);
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const dataSorting: WfoDataSorting<SubscriptionListItem> = {
|
|
194
|
-
field: sortedColumnId,
|
|
195
|
-
sortOrder: dataDisplayParams.sortBy?.order ?? SortOrder.ASC,
|
|
196
|
-
};
|
|
197
|
-
const { totalItems, sortFields, filterFields } = data?.pageInfo ?? {};
|
|
198
|
-
|
|
199
|
-
const pageChange = getPageIndexChangeHandler<SubscriptionListItem>(setDataDisplayParam);
|
|
200
|
-
const pageSizeChange = getPageSizeChangeHandler<SubscriptionListItem>(setDataDisplayParam);
|
|
201
|
-
const updateQuery = getQueryStringHandler<SubscriptionListItem>(setDataDisplayParam);
|
|
202
|
-
const updateSorting = getDataSortHandler<SubscriptionListItem>(setDataDisplayParam);
|
|
203
|
-
|
|
204
|
-
const pagination: Pagination = {
|
|
205
|
-
pageIndex: dataDisplayParams.pageIndex,
|
|
206
|
-
pageSize: dataDisplayParams.pageSize,
|
|
207
|
-
pageSizeOptions: DEFAULT_PAGE_SIZES,
|
|
208
|
-
totalItemCount: totalItems ?? 0,
|
|
209
|
-
onChangePage: pageChange,
|
|
210
|
-
onChangeItemsPerPage: pageSizeChange,
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
const exportData = csvDownloadHandler(
|
|
214
|
-
getSubscriptionListForExport,
|
|
215
|
-
mapGraphQlSubscriptionsResultToSubscriptionListItems,
|
|
216
|
-
mapGraphQlSubscriptionsResultToPageInfo,
|
|
217
|
-
Object.keys(tableColumnConfig),
|
|
218
|
-
getCsvFileNameWithDate('Subscriptions'),
|
|
219
|
-
showToastMessage,
|
|
220
|
-
tError,
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
const tableConfig = mapSortableAndFilterableValuesToTableColumnConfig(tableColumnConfig, sortFields, filterFields);
|
|
224
|
-
|
|
225
|
-
return (
|
|
226
|
-
<WfoAdvancedTable
|
|
227
|
-
queryString={dataDisplayParams.queryString}
|
|
228
|
-
onUpdateQueryString={updateQuery}
|
|
229
|
-
data={subscriptionList}
|
|
230
|
-
tableColumnConfig={tableConfig}
|
|
231
|
-
defaultHiddenColumns={hiddenColumns}
|
|
232
|
-
dataSorting={[dataSorting]}
|
|
233
|
-
isLoading={isFetching}
|
|
234
|
-
localStorageKey={SUBSCRIPTIONS_TABLE_LOCAL_STORAGE_KEY}
|
|
235
|
-
detailModalTitle={'Details - Subscription'}
|
|
236
|
-
pagination={pagination}
|
|
237
|
-
error={mapRtkErrorToWfoError(error)}
|
|
238
|
-
onUpdateDataSorting={updateSorting}
|
|
239
|
-
onExportData={exportData}
|
|
240
|
-
exportDataIsLoading={isFetchingCsv}
|
|
241
|
-
/>
|
|
242
|
-
);
|
|
243
|
-
};
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { SubscriptionListResponse } from '@/rtk/endpoints/subscriptionList';
|
|
2
|
-
import { Subscription } from '@/types';
|
|
3
|
-
import { parseDate } from '@/utils';
|
|
4
|
-
|
|
5
|
-
export type SubscriptionListItem = Pick<
|
|
6
|
-
Subscription,
|
|
7
|
-
'subscriptionId' | 'description' | 'status' | 'insync' | 'note'
|
|
8
|
-
> & {
|
|
9
|
-
startDate: Date | null;
|
|
10
|
-
endDate: Date | null;
|
|
11
|
-
productName: string;
|
|
12
|
-
tag: string | null;
|
|
13
|
-
customerFullname: string;
|
|
14
|
-
customerShortcode: string;
|
|
15
|
-
customerId: string;
|
|
16
|
-
metadata: object | null;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export const mapGraphQlSubscriptionsResultToPageInfo = (graphqlResponse: SubscriptionListResponse) =>
|
|
20
|
-
graphqlResponse.pageInfo;
|
|
21
|
-
|
|
22
|
-
export const mapGraphQlSubscriptionsResultToSubscriptionListItems = (
|
|
23
|
-
graphqlResponse: SubscriptionListResponse | undefined,
|
|
24
|
-
): SubscriptionListItem[] => {
|
|
25
|
-
if (!graphqlResponse) return [];
|
|
26
|
-
return graphqlResponse.subscriptions.map((subscription) => {
|
|
27
|
-
const { description, insync, product, startDate, endDate, status, subscriptionId, note, customer, metadata } =
|
|
28
|
-
subscription;
|
|
29
|
-
|
|
30
|
-
const { name: productName, tag } = product;
|
|
31
|
-
const { fullname: customerFullname, shortcode: customerShortcode, customerId } = customer;
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
subscriptionId,
|
|
35
|
-
description,
|
|
36
|
-
status,
|
|
37
|
-
insync,
|
|
38
|
-
startDate: parseDate(startDate),
|
|
39
|
-
endDate: parseDate(endDate),
|
|
40
|
-
note,
|
|
41
|
-
productName,
|
|
42
|
-
tag,
|
|
43
|
-
customerFullname,
|
|
44
|
-
customerShortcode,
|
|
45
|
-
customerId,
|
|
46
|
-
metadata: Object.keys(metadata).length > 0 ? metadata : null,
|
|
47
|
-
};
|
|
48
|
-
});
|
|
49
|
-
};
|