@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.
Files changed (48) hide show
  1. package/.turbo/turbo-build.log +8 -8
  2. package/.turbo/turbo-lint.log +1 -1
  3. package/.turbo/turbo-test.log +17 -50
  4. package/CHANGELOG.md +19 -0
  5. package/dist/index.d.ts +2081 -2517
  6. package/dist/index.js +1247 -1579
  7. package/dist/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/components/WfoError/WfoError.tsx +2 -2
  10. package/src/components/WfoInlineNoteEdit/WfoSubscriptionDetailNoteEdit.tsx +4 -8
  11. package/src/components/WfoInlineNoteEdit/WfoSubscriptionNoteEdit.tsx +6 -7
  12. package/src/components/WfoPageTemplate/paths.ts +0 -1
  13. package/src/components/WfoSubscriptionsList/index.ts +1 -2
  14. package/src/components/WfoSubscriptionsList/subscriptionListItem.ts +14 -0
  15. package/src/components/WfoSubscriptionsList/subscriptionListTabs.ts +1 -20
  16. package/src/components/WfoSummary/WfoLatestActiveSubscriptionsSummaryCard.tsx +17 -13
  17. package/src/components/WfoSummary/WfoLatestOutOfSyncSubscriptionSummaryCard.tsx +28 -15
  18. package/src/components/WfoTable/WfoStructuredSearchTable/WfoFieldSelector.spec.tsx +0 -16
  19. package/src/components/WfoTable/WfoStructuredSearchTable/WfoFieldSelector.tsx +4 -10
  20. package/src/components/WfoTable/WfoStructuredSearchTable/WfoFilterBuilder.tsx +9 -6
  21. package/src/components/WfoTable/WfoStructuredSearchTable/WfoRestoreLoop.spec.tsx +2 -2
  22. package/src/components/WfoTable/WfoStructuredSearchTable/WfoStructuredSearchTable.tsx +3 -5
  23. package/src/components/WfoTable/WfoStructuredSearchTable/useSearchWithDebouncedCallback.spec.tsx +56 -3
  24. package/src/components/WfoTable/WfoStructuredSearchTable/utils.spec.ts +60 -1
  25. package/src/components/WfoTable/WfoStructuredSearchTable/utils.ts +27 -3
  26. package/src/configuration/version.ts +1 -1
  27. package/src/messages/en-GB.json +1 -1
  28. package/src/messages/nl-NL.json +1 -1
  29. package/src/pages/index.ts +0 -1
  30. package/src/pages/startPage/index.ts +1 -0
  31. package/src/pages/startPage/mappers.ts +15 -8
  32. package/src/pages/startPage/queryVariables.ts +0 -29
  33. package/src/pages/startPage/searchPayloads.ts +22 -0
  34. package/src/pages/subscriptions/WfoSubscriptionsListPage.tsx +511 -39
  35. package/src/rtk/endpoints/index.ts +0 -1
  36. package/src/rtk/endpoints/search.ts +3 -0
  37. package/src/rtk/endpoints/subscriptionListMutation.spec.ts +80 -0
  38. package/src/rtk/endpoints/subscriptionListMutation.ts +71 -52
  39. package/src/rtk/utils.spec.ts +30 -1
  40. package/src/rtk/utils.ts +2 -1
  41. package/src/types/search.ts +0 -1
  42. package/src/types/types.ts +0 -2
  43. package/src/utils/getDefaultTableConfig.ts +1 -6
  44. package/src/utils/getQueryParams.ts +1 -0
  45. package/src/components/WfoSubscriptionsList/WfoSubscriptionsList.tsx +0 -243
  46. package/src/components/WfoSubscriptionsList/subscriptionResultMappers.ts +0 -49
  47. package/src/pages/WfoSearchPocPage.tsx +0 -575
  48. 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 { GraphqlQueryVariables } from '@/types';
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
- async onQueryStarted(
22
- { queryName, subscriptionId, graphQlQueryVariables, ...patch },
23
- { dispatch, queryFulfilled },
24
- ) {
25
- const patchResult = dispatch(
26
- subscriptionListMutationApi.util.updateQueryData(
27
- // @ts-expect-error - Suggest ts ignore because of the type mismatch between emptyQuery and queryName
28
- queryName,
29
- graphQlQueryVariables,
30
- (draft: SubscriptionListResponse) => {
31
- const subscription = draft.subscriptions.find((item) => item.subscriptionId === subscriptionId);
32
- if (subscription) {
33
- subscription.note = patch.note;
34
- }
35
- },
36
- ),
37
- );
38
- try {
39
- await queryFulfilled;
40
- } catch {
41
- patchResult.undo();
42
- }
43
- },
44
- }),
45
- updateSubscriptionDetailNoteOptimistic: builder.mutation<
46
- { mockResponse: boolean },
47
- { queryName: string; subscriptionId: string; note: string }
48
- >({
49
- queryFn: async () => ({ data: { mockResponse: true } }),
50
- async onQueryStarted({ queryName, subscriptionId, ...patch }, { dispatch, queryFulfilled }) {
51
- const patchResult = dispatch(
52
- subscriptionListMutationApi.util.updateQueryData(
53
- // @ts-expect-error - Suggest ts ignore because of the type mismatch between emptyDetailQuery and queryName
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, useUpdateSubscriptionDetailNoteOptimisticMutation } =
74
- subscriptionListMutationApi;
93
+ export const { useUpdateSubscriptionNoteOptimisticMutation } = subscriptionListMutationApi;
@@ -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)) {
@@ -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
  };
@@ -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:
@@ -5,6 +5,7 @@ export enum WfoQueryParams {
5
5
  SORT_BY = 'sortBy',
6
6
  FILTER_BY = 'filterBy',
7
7
  QUERY_STRING = 'queryString',
8
+ FILTER_STRING = 'filterString',
8
9
  }
9
10
 
10
11
  export const getUrlWithQueryParams = (url: string, params: Partial<Record<WfoQueryParams, string>>) => {
@@ -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
- };