@vendure/dashboard 3.2.2 → 3.2.4
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/dist/plugin/utils/ast-utils.d.ts +10 -0
- package/dist/plugin/utils/ast-utils.js +96 -0
- package/dist/plugin/utils/ast-utils.spec.d.ts +1 -0
- package/dist/plugin/utils/ast-utils.spec.js +120 -0
- package/dist/plugin/{config-loader.d.ts → utils/config-loader.d.ts} +22 -8
- package/dist/plugin/utils/config-loader.js +325 -0
- package/dist/plugin/{schema-generator.d.ts → utils/schema-generator.d.ts} +5 -0
- package/dist/plugin/{schema-generator.js → utils/schema-generator.js} +6 -0
- package/dist/plugin/{ui-config.js → utils/ui-config.js} +2 -2
- package/dist/plugin/vite-plugin-admin-api-schema.js +2 -2
- package/dist/plugin/vite-plugin-config-loader.d.ts +2 -3
- package/dist/plugin/vite-plugin-config-loader.js +18 -9
- package/dist/plugin/vite-plugin-dashboard-metadata.js +12 -14
- package/dist/plugin/vite-plugin-gql-tada.js +2 -2
- package/dist/plugin/vite-plugin-ui-config.js +3 -2
- package/package.json +8 -6
- package/src/app/app-providers.tsx +8 -8
- package/src/app/main.tsx +1 -1
- package/src/app/routes/_authenticated/_assets/assets.graphql.ts +26 -0
- package/src/app/routes/_authenticated/_assets/assets.tsx +2 -2
- package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +156 -0
- package/src/app/routes/_authenticated/_orders/components/customer-address-selector.tsx +104 -0
- package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +228 -0
- package/src/app/routes/_authenticated/_orders/components/money-gross-net.tsx +18 -0
- package/src/app/routes/_authenticated/_orders/components/order-address.tsx +2 -1
- package/src/app/routes/_authenticated/_orders/components/order-line-custom-fields-form.tsx +38 -0
- package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +53 -0
- package/src/app/routes/_authenticated/_orders/components/order-table.tsx +8 -49
- package/src/app/routes/_authenticated/_orders/components/shipping-method-selector.tsx +65 -0
- package/src/app/routes/_authenticated/_orders/orders.graphql.ts +187 -1
- package/src/app/routes/_authenticated/_orders/orders.tsx +39 -18
- package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +31 -9
- package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +418 -0
- package/src/app/routes/_authenticated/_products/products.tsx +1 -1
- package/src/app/routes/_authenticated.tsx +12 -1
- package/src/lib/components/data-table/add-filter-menu.tsx +61 -0
- package/src/lib/components/data-table/data-table-column-header.tsx +0 -13
- package/src/lib/components/data-table/data-table-filter-badge.tsx +75 -0
- package/src/lib/components/data-table/data-table-filter-dialog.tsx +27 -28
- package/src/lib/components/data-table/data-table-types.ts +1 -0
- package/src/lib/components/data-table/data-table-view-options.tsx +72 -23
- package/src/lib/components/data-table/data-table.tsx +23 -24
- package/src/lib/components/data-table/filters/data-table-boolean-filter.tsx +57 -0
- package/src/lib/components/data-table/filters/data-table-datetime-filter.tsx +93 -0
- package/src/lib/components/data-table/filters/data-table-id-filter.tsx +58 -0
- package/src/lib/components/data-table/filters/data-table-number-filter.tsx +119 -0
- package/src/lib/components/data-table/filters/data-table-string-filter.tsx +62 -0
- package/src/lib/components/data-table/human-readable-operator.tsx +65 -0
- package/src/lib/components/layout/nav-user.tsx +4 -4
- package/src/lib/components/shared/asset/asset-focal-point-editor.tsx +93 -0
- package/src/lib/components/shared/{asset-gallery.tsx → asset/asset-gallery.tsx} +51 -20
- package/src/lib/components/shared/{asset-picker-dialog.tsx → asset/asset-picker-dialog.tsx} +1 -1
- package/src/lib/components/shared/{asset-preview-dialog.tsx → asset/asset-preview-dialog.tsx} +1 -7
- package/src/lib/components/shared/asset/asset-preview-selector.tsx +34 -0
- package/src/lib/components/shared/asset/asset-preview.tsx +128 -0
- package/src/lib/components/shared/asset/asset-properties.tsx +46 -0
- package/src/lib/components/shared/{focal-point-control.tsx → asset/focal-point-control.tsx} +1 -1
- package/src/lib/components/shared/custom-fields-form.tsx +4 -3
- package/src/lib/components/shared/customer-selector.tsx +13 -14
- package/src/lib/components/shared/detail-page-button.tsx +2 -2
- package/src/lib/components/shared/entity-assets.tsx +3 -3
- package/src/lib/components/shared/navigation-confirmation.tsx +39 -0
- package/src/lib/components/shared/paginated-list-data-table.tsx +9 -1
- package/src/lib/components/shared/product-variant-selector.tsx +111 -0
- package/src/lib/components/shared/vendure-image.tsx +1 -1
- package/src/lib/components/ui/calendar.tsx +508 -63
- package/src/lib/framework/document-introspection/get-document-structure.spec.ts +113 -3
- package/src/lib/framework/document-introspection/get-document-structure.ts +70 -11
- package/src/lib/framework/form-engine/use-generated-form.tsx +8 -7
- package/src/lib/framework/layout-engine/page-layout.tsx +4 -0
- package/src/lib/framework/page/list-page.tsx +23 -4
- package/src/lib/framework/page/use-detail-page.ts +1 -0
- package/src/lib/graphql/fragments.tsx +8 -0
- package/src/lib/index.ts +5 -5
- package/src/lib/providers/auth.tsx +12 -9
- package/src/lib/providers/channel-provider.tsx +1 -0
- package/src/lib/providers/server-config.tsx +7 -1
- package/src/lib/providers/user-settings.tsx +24 -0
- package/vite/utils/ast-utils.spec.ts +128 -0
- package/vite/utils/ast-utils.ts +119 -0
- package/vite/utils/config-loader.ts +410 -0
- package/vite/{schema-generator.ts → utils/schema-generator.ts} +7 -1
- package/vite/{ui-config.ts → utils/ui-config.ts} +2 -2
- package/vite/vite-plugin-admin-api-schema.ts +2 -2
- package/vite/vite-plugin-config-loader.ts +25 -13
- package/vite/vite-plugin-dashboard-metadata.ts +19 -15
- package/vite/vite-plugin-gql-tada.ts +2 -2
- package/vite/vite-plugin-ui-config.ts +3 -2
- package/dist/plugin/config-loader.js +0 -141
- package/src/lib/components/shared/asset-preview.tsx +0 -345
- package/vite/config-loader.ts +0 -181
- /package/dist/plugin/{ui-config.d.ts → utils/ui-config.d.ts} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { graphql } from 'gql.tada';
|
|
2
2
|
import { describe, expect, it, vi } from 'vitest';
|
|
3
3
|
|
|
4
|
-
import { getListQueryFields } from './get-document-structure.js';
|
|
4
|
+
import { getListQueryFields, getOperationVariablesFields } from './get-document-structure.js';
|
|
5
5
|
|
|
6
6
|
vi.mock('virtual:admin-api-schema', () => {
|
|
7
7
|
return {
|
|
@@ -12,7 +12,10 @@ vi.mock('virtual:admin-api-schema', () => {
|
|
|
12
12
|
product: ['Product', false, false, false],
|
|
13
13
|
collection: ['Collection', false, false, false],
|
|
14
14
|
},
|
|
15
|
-
Mutation: {
|
|
15
|
+
Mutation: {
|
|
16
|
+
updateProduct: ['Product', false, false, false],
|
|
17
|
+
adjustDraftOrderLine: ['Order', false, false, false],
|
|
18
|
+
},
|
|
16
19
|
|
|
17
20
|
Collection: {
|
|
18
21
|
id: ['ID', false, false, false],
|
|
@@ -126,8 +129,25 @@ vi.mock('virtual:admin-api-schema', () => {
|
|
|
126
129
|
languageCode: ['LanguageCode', false, false, false],
|
|
127
130
|
name: ['String', false, false, false],
|
|
128
131
|
},
|
|
132
|
+
Order: {
|
|
133
|
+
id: ['ID', false, false, false],
|
|
134
|
+
lines: ['OrderLine', false, true, false],
|
|
135
|
+
},
|
|
136
|
+
OrderLine: {
|
|
137
|
+
id: ['ID', false, false, false],
|
|
138
|
+
quantity: ['Int', false, false, false],
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
inputs: {
|
|
142
|
+
UpdateProductInput: {
|
|
143
|
+
id: ['ID', false, false, false],
|
|
144
|
+
name: ['String', false, false, false],
|
|
145
|
+
},
|
|
146
|
+
AdjustDraftOrderLineInput: {
|
|
147
|
+
orderLineId: ['ID', false, false, false],
|
|
148
|
+
quantity: ['Int', false, false, false],
|
|
149
|
+
},
|
|
129
150
|
},
|
|
130
|
-
inputs: {},
|
|
131
151
|
scalars: ['ID', 'String', 'Int', 'Boolean', 'Float', 'JSON', 'DateTime', 'Upload', 'Money'],
|
|
132
152
|
enums: {},
|
|
133
153
|
},
|
|
@@ -308,3 +328,93 @@ describe('getListQueryFields', () => {
|
|
|
308
328
|
]);
|
|
309
329
|
});
|
|
310
330
|
});
|
|
331
|
+
|
|
332
|
+
describe('getOperationVariablesFields', () => {
|
|
333
|
+
it('should extract fields from a simple mutation', () => {
|
|
334
|
+
const doc = graphql(`
|
|
335
|
+
mutation UpdateProduct($input: UpdateProductInput!) {
|
|
336
|
+
updateProduct(input: $input) {
|
|
337
|
+
...ProductDetail
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
fragment ProductDetail on Product {
|
|
342
|
+
id
|
|
343
|
+
name
|
|
344
|
+
}
|
|
345
|
+
`);
|
|
346
|
+
|
|
347
|
+
const fields = getOperationVariablesFields(doc, 'input');
|
|
348
|
+
expect(fields).toEqual([
|
|
349
|
+
{
|
|
350
|
+
isPaginatedList: false,
|
|
351
|
+
isScalar: true,
|
|
352
|
+
list: false,
|
|
353
|
+
name: 'id',
|
|
354
|
+
nullable: false,
|
|
355
|
+
type: 'ID',
|
|
356
|
+
typeInfo: undefined,
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
isPaginatedList: false,
|
|
360
|
+
isScalar: true,
|
|
361
|
+
list: false,
|
|
362
|
+
name: 'name',
|
|
363
|
+
nullable: false,
|
|
364
|
+
type: 'String',
|
|
365
|
+
typeInfo: undefined,
|
|
366
|
+
},
|
|
367
|
+
]);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('should handle a mutation with a nested input', () => {
|
|
371
|
+
const doc = graphql(`
|
|
372
|
+
mutation AdjustDraftOrderLine($orderId: ID!, $input: AdjustDraftOrderLineInput!) {
|
|
373
|
+
adjustDraftOrderLine(orderId: $orderId, input: $input) {
|
|
374
|
+
id
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
`);
|
|
378
|
+
|
|
379
|
+
const fields = getOperationVariablesFields(doc, undefined);
|
|
380
|
+
expect(fields).toEqual([
|
|
381
|
+
{
|
|
382
|
+
name: 'orderId',
|
|
383
|
+
isPaginatedList: false,
|
|
384
|
+
isScalar: true,
|
|
385
|
+
list: false,
|
|
386
|
+
nullable: false,
|
|
387
|
+
type: 'ID',
|
|
388
|
+
typeInfo: undefined,
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
name: 'input',
|
|
392
|
+
isPaginatedList: false,
|
|
393
|
+
isScalar: false,
|
|
394
|
+
list: false,
|
|
395
|
+
nullable: true,
|
|
396
|
+
type: 'AdjustDraftOrderLineInput',
|
|
397
|
+
typeInfo: [
|
|
398
|
+
{
|
|
399
|
+
name: 'orderLineId',
|
|
400
|
+
isPaginatedList: false,
|
|
401
|
+
isScalar: true,
|
|
402
|
+
list: false,
|
|
403
|
+
nullable: false,
|
|
404
|
+
type: 'ID',
|
|
405
|
+
typeInfo: undefined,
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
name: 'quantity',
|
|
409
|
+
isPaginatedList: false,
|
|
410
|
+
isScalar: true,
|
|
411
|
+
list: false,
|
|
412
|
+
nullable: false,
|
|
413
|
+
type: 'Int',
|
|
414
|
+
typeInfo: undefined,
|
|
415
|
+
},
|
|
416
|
+
],
|
|
417
|
+
},
|
|
418
|
+
]);
|
|
419
|
+
});
|
|
420
|
+
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
|
|
2
|
+
import { VariablesOf } from 'gql.tada';
|
|
1
3
|
import {
|
|
2
4
|
DocumentNode,
|
|
3
5
|
OperationDefinitionNode,
|
|
@@ -146,7 +148,10 @@ function findNestedPaginatedLists(
|
|
|
146
148
|
*
|
|
147
149
|
* The operation variables fields are the fields of the `UpdateProductInput` type.
|
|
148
150
|
*/
|
|
149
|
-
export function getOperationVariablesFields
|
|
151
|
+
export function getOperationVariablesFields<T extends TypedDocumentNode<any, any>>(
|
|
152
|
+
documentNode: T,
|
|
153
|
+
varName?: keyof VariablesOf<T>,
|
|
154
|
+
): FieldInfo[] {
|
|
150
155
|
const fields: FieldInfo[] = [];
|
|
151
156
|
|
|
152
157
|
const operationDefinition = documentNode.definitions.find(
|
|
@@ -154,11 +159,31 @@ export function getOperationVariablesFields(documentNode: DocumentNode): FieldIn
|
|
|
154
159
|
);
|
|
155
160
|
|
|
156
161
|
if (operationDefinition?.variableDefinitions) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
const variableDefinitions = varName
|
|
163
|
+
? operationDefinition.variableDefinitions.filter(
|
|
164
|
+
variable => variable.variable.name.value === varName,
|
|
165
|
+
)
|
|
166
|
+
: operationDefinition.variableDefinitions;
|
|
167
|
+
variableDefinitions.forEach(variableDef => {
|
|
168
|
+
const unwrappedType = unwrapVariableDefinitionType(variableDef.type);
|
|
169
|
+
const isScalar = isScalarType(unwrappedType.name.value);
|
|
170
|
+
const fieldName = variableDef.variable.name.value;
|
|
171
|
+
const typeName = unwrappedType.name.value;
|
|
172
|
+
const inputTypeInfo = isScalar
|
|
173
|
+
? {
|
|
174
|
+
name: fieldName,
|
|
175
|
+
type: typeName,
|
|
176
|
+
nullable: false,
|
|
177
|
+
list: false,
|
|
178
|
+
isScalar: true,
|
|
179
|
+
isPaginatedList: false,
|
|
180
|
+
}
|
|
181
|
+
: getInputTypeInfo(fieldName, typeName);
|
|
182
|
+
if (varName && inputTypeInfo?.name === varName) {
|
|
183
|
+
fields.push(...(inputTypeInfo.typeInfo ?? []));
|
|
184
|
+
} else {
|
|
185
|
+
fields.push(inputTypeInfo);
|
|
186
|
+
}
|
|
162
187
|
});
|
|
163
188
|
}
|
|
164
189
|
|
|
@@ -331,7 +356,9 @@ export function getOperationTypeInfo(
|
|
|
331
356
|
if (definitionNode.kind === 'OperationDefinition') {
|
|
332
357
|
const firstSelection = definitionNode?.selectionSet.selections[0];
|
|
333
358
|
if (firstSelection?.kind === 'Field') {
|
|
334
|
-
return
|
|
359
|
+
return definitionNode.operation === 'query'
|
|
360
|
+
? getQueryInfo(firstSelection.name.value)
|
|
361
|
+
: getMutationInfo(firstSelection.name.value);
|
|
335
362
|
}
|
|
336
363
|
}
|
|
337
364
|
if (definitionNode.kind === 'Field' && parentTypeName) {
|
|
@@ -349,7 +376,7 @@ export function getTypeFieldInfo(typeName: string): FieldInfo[] {
|
|
|
349
376
|
}
|
|
350
377
|
return fieldInfo;
|
|
351
378
|
})
|
|
352
|
-
.filter(x => x != null)
|
|
379
|
+
.filter(x => x != null);
|
|
353
380
|
}
|
|
354
381
|
|
|
355
382
|
function getQueryInfo(name: string): FieldInfo {
|
|
@@ -364,8 +391,40 @@ function getQueryInfo(name: string): FieldInfo {
|
|
|
364
391
|
};
|
|
365
392
|
}
|
|
366
393
|
|
|
367
|
-
function
|
|
368
|
-
|
|
394
|
+
function getMutationInfo(name: string): FieldInfo {
|
|
395
|
+
const fieldInfo = schemaInfo.types.Mutation[name];
|
|
396
|
+
return {
|
|
397
|
+
name,
|
|
398
|
+
type: fieldInfo[0],
|
|
399
|
+
nullable: fieldInfo[1],
|
|
400
|
+
list: fieldInfo[2],
|
|
401
|
+
isPaginatedList: fieldInfo[3],
|
|
402
|
+
isScalar: schemaInfo.scalars.includes(fieldInfo[0]),
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function getInputTypeInfo(name: string, type: string): FieldInfo {
|
|
407
|
+
const fieldInfo = schemaInfo.inputs[type];
|
|
408
|
+
if (!fieldInfo) {
|
|
409
|
+
throw new Error(`Input type ${type} not found`);
|
|
410
|
+
}
|
|
411
|
+
return {
|
|
412
|
+
name,
|
|
413
|
+
type,
|
|
414
|
+
nullable: true,
|
|
415
|
+
list: false,
|
|
416
|
+
isPaginatedList: false,
|
|
417
|
+
isScalar: false,
|
|
418
|
+
typeInfo: getInputTypeFields(type),
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function getInputTypeFields(name: string): FieldInfo[] {
|
|
423
|
+
const inputType = schemaInfo.inputs[name];
|
|
424
|
+
if (!inputType) {
|
|
425
|
+
throw new Error(`Input type ${name} not found`);
|
|
426
|
+
}
|
|
427
|
+
return Object.entries(inputType).map(([fieldName, fieldInfo]: [string, any]) => {
|
|
369
428
|
const type = fieldInfo[0];
|
|
370
429
|
const isScalar = isScalarType(type);
|
|
371
430
|
const isEnum = isEnumType(type);
|
|
@@ -376,7 +435,7 @@ function getInputTypeInfo(name: string): FieldInfo[] {
|
|
|
376
435
|
list: fieldInfo[2],
|
|
377
436
|
isPaginatedList: fieldInfo[3],
|
|
378
437
|
isScalar,
|
|
379
|
-
typeInfo: !isScalar && !isEnum ?
|
|
438
|
+
typeInfo: !isScalar && !isEnum ? getInputTypeFields(type) : undefined,
|
|
380
439
|
};
|
|
381
440
|
});
|
|
382
441
|
}
|
|
@@ -13,13 +13,14 @@ import { useForm } from 'react-hook-form';
|
|
|
13
13
|
|
|
14
14
|
export interface GeneratedFormOptions<
|
|
15
15
|
T extends TypedDocumentNode<any, any>,
|
|
16
|
-
VarName extends keyof VariablesOf<T> = 'input',
|
|
16
|
+
VarName extends (keyof VariablesOf<T>) | undefined = 'input',
|
|
17
17
|
E extends Record<string, any> = Record<string, any>,
|
|
18
18
|
> {
|
|
19
19
|
document?: T;
|
|
20
|
+
varName?: VarName;
|
|
20
21
|
entity: E | null | undefined;
|
|
21
|
-
setValues: (entity: NonNullable<E>) => VariablesOf<T>[VarName]
|
|
22
|
-
onSubmit?: (values: VariablesOf<T>[VarName]) => void;
|
|
22
|
+
setValues: (entity: NonNullable<E>) => VarName extends keyof VariablesOf<T> ? VariablesOf<T>[VarName] : VariablesOf<T>;
|
|
23
|
+
onSubmit?: (values: VarName extends keyof VariablesOf<T> ? VariablesOf<T>[VarName] : VariablesOf<T>) => void;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -31,13 +32,13 @@ export interface GeneratedFormOptions<
|
|
|
31
32
|
*/
|
|
32
33
|
export function useGeneratedForm<
|
|
33
34
|
T extends TypedDocumentNode<any, any>,
|
|
34
|
-
VarName extends keyof VariablesOf<T>
|
|
35
|
+
VarName extends keyof VariablesOf<T> | undefined,
|
|
35
36
|
E extends Record<string, any> = Record<string, any>,
|
|
36
37
|
>(options: GeneratedFormOptions<T, VarName, E>) {
|
|
37
|
-
const { document, entity, setValues, onSubmit } = options;
|
|
38
|
+
const { document, entity, setValues, onSubmit, varName } = options;
|
|
38
39
|
const { activeChannel } = useChannel();
|
|
39
40
|
const availableLanguages = useServerConfig()?.availableLanguages || [];
|
|
40
|
-
const updateFields = document ? getOperationVariablesFields(document) : [];
|
|
41
|
+
const updateFields = document ? getOperationVariablesFields(document, varName) : [];
|
|
41
42
|
const schema = createFormSchemaFromFields(updateFields);
|
|
42
43
|
const defaultValues = getDefaultValuesFromFields(updateFields, activeChannel?.defaultLanguageCode);
|
|
43
44
|
const processedEntity = ensureTranslationsForAllLanguages(entity, availableLanguages);
|
|
@@ -58,7 +59,7 @@ export function useGeneratedForm<
|
|
|
58
59
|
};
|
|
59
60
|
if (onSubmit) {
|
|
60
61
|
submitHandler = (event: FormEvent) => {
|
|
61
|
-
form.handleSubmit(onSubmit)(event);
|
|
62
|
+
form.handleSubmit(onSubmit as any)(event);
|
|
62
63
|
};
|
|
63
64
|
}
|
|
64
65
|
|
|
@@ -5,10 +5,13 @@ import { Form } from '@/components/ui/form.js';
|
|
|
5
5
|
import { useCustomFieldConfig } from '@/hooks/use-custom-field-config.js';
|
|
6
6
|
import { usePage } from '@/hooks/use-page.js';
|
|
7
7
|
import { cn } from '@/lib/utils.js';
|
|
8
|
+
import { NavigationConfirmation } from '@/components/shared/navigation-confirmation.js';
|
|
8
9
|
import { useMediaQuery } from '@uidotdev/usehooks';
|
|
9
10
|
import React, { ComponentProps, createContext } from 'react';
|
|
10
11
|
import { Control, UseFormReturn } from 'react-hook-form';
|
|
12
|
+
|
|
11
13
|
import { DashboardActionBarItem } from '../extension-api/extension-api-types.js';
|
|
14
|
+
|
|
12
15
|
import { getDashboardActionBarItems, getDashboardPageBlocks } from './layout-extensions.js';
|
|
13
16
|
import { LocationWrapper } from './location-wrapper.js';
|
|
14
17
|
|
|
@@ -42,6 +45,7 @@ export function Page({ children, pageId, entity, form, submitHandler, ...props }
|
|
|
42
45
|
|
|
43
46
|
const pageContentWithOptionalForm = form ? (
|
|
44
47
|
<Form {...form}>
|
|
48
|
+
<NavigationConfirmation form={form} />
|
|
45
49
|
<form onSubmit={submitHandler} className="space-y-4">
|
|
46
50
|
{pageHeader}
|
|
47
51
|
{pageContent}
|
|
@@ -11,7 +11,9 @@ import { TypedDocumentNode } from '@graphql-typed-document-node/core';
|
|
|
11
11
|
import { AnyRoute, AnyRouter, useNavigate } from '@tanstack/react-router';
|
|
12
12
|
import { ColumnFiltersState, SortingState, Table } from '@tanstack/react-table';
|
|
13
13
|
import { TableOptions } from '@tanstack/table-core';
|
|
14
|
+
import { useUserSettings } from '@/hooks/use-user-settings.js';
|
|
14
15
|
import { ResultOf } from 'gql.tada';
|
|
16
|
+
|
|
15
17
|
import { addCustomFields } from '../document-introspection/add-custom-fields.js';
|
|
16
18
|
import {
|
|
17
19
|
FullWidthPageBlock,
|
|
@@ -81,12 +83,18 @@ export function ListPage<
|
|
|
81
83
|
const route = typeof routeOrFn === 'function' ? routeOrFn() : routeOrFn;
|
|
82
84
|
const routeSearch = route.useSearch();
|
|
83
85
|
const navigate = useNavigate<AnyRouter>({ from: route.fullPath });
|
|
86
|
+
const { setTableSettings, settings } = useUserSettings();
|
|
87
|
+
const tableSettings = pageId ? settings.tableSettings?.[pageId] : undefined;
|
|
84
88
|
|
|
85
89
|
const pagination = {
|
|
86
90
|
page: routeSearch.page ? parseInt(routeSearch.page) : 1,
|
|
87
|
-
itemsPerPage: routeSearch.perPage ? parseInt(routeSearch.perPage) : 10,
|
|
91
|
+
itemsPerPage: routeSearch.perPage ? parseInt(routeSearch.perPage) : tableSettings?.pageSize ?? 10,
|
|
88
92
|
};
|
|
89
93
|
|
|
94
|
+
const columnVisibility = pageId ? tableSettings?.columnVisibility : defaultVisibility;
|
|
95
|
+
const columnOrder = pageId ? tableSettings?.columnOrder : defaultColumnOrder;
|
|
96
|
+
const columnFilters = pageId ? tableSettings?.columnFilters : routeSearch.filters;
|
|
97
|
+
|
|
90
98
|
const sorting: SortingState = (routeSearch.sort ?? '')
|
|
91
99
|
.split(',')
|
|
92
100
|
.filter((s: string) => s.length)
|
|
@@ -138,21 +146,32 @@ export function ListPage<
|
|
|
138
146
|
transformVariables={transformVariables}
|
|
139
147
|
customizeColumns={customizeColumns as any}
|
|
140
148
|
additionalColumns={additionalColumns as any}
|
|
141
|
-
defaultColumnOrder={
|
|
142
|
-
defaultVisibility={
|
|
149
|
+
defaultColumnOrder={columnOrder as any}
|
|
150
|
+
defaultVisibility={columnVisibility as any}
|
|
143
151
|
onSearchTermChange={onSearchTermChange}
|
|
144
152
|
page={pagination.page}
|
|
145
153
|
itemsPerPage={pagination.itemsPerPage}
|
|
146
154
|
sorting={sorting}
|
|
147
|
-
columnFilters={
|
|
155
|
+
columnFilters={columnFilters}
|
|
148
156
|
onPageChange={(table, page, perPage) => {
|
|
149
157
|
persistListStateToUrl(table, { page, perPage });
|
|
158
|
+
if (pageId) {
|
|
159
|
+
setTableSettings(pageId, 'pageSize', perPage);
|
|
160
|
+
}
|
|
150
161
|
}}
|
|
151
162
|
onSortChange={(table, sorting) => {
|
|
152
163
|
persistListStateToUrl(table, { sort: sorting });
|
|
153
164
|
}}
|
|
154
165
|
onFilterChange={(table, filters) => {
|
|
155
166
|
persistListStateToUrl(table, { filters });
|
|
167
|
+
if (pageId) {
|
|
168
|
+
setTableSettings(pageId, 'columnFilters', filters);
|
|
169
|
+
}
|
|
170
|
+
}}
|
|
171
|
+
onColumnVisibilityChange={(table, columnVisibility) => {
|
|
172
|
+
if (pageId) {
|
|
173
|
+
setTableSettings(pageId, 'columnVisibility', columnVisibility);
|
|
174
|
+
}
|
|
156
175
|
}}
|
|
157
176
|
facetedFilters={facetedFilters}
|
|
158
177
|
rowActions={rowActions}
|
|
@@ -195,6 +195,7 @@ export function useDetailPage<
|
|
|
195
195
|
const document = isNew ? (createDocument ?? updateDocument) : updateDocument;
|
|
196
196
|
const { form, submitHandler } = useGeneratedForm({
|
|
197
197
|
document,
|
|
198
|
+
varName: 'input',
|
|
198
199
|
entity,
|
|
199
200
|
setValues: setValuesForUpdate,
|
|
200
201
|
onSubmit(values: any) {
|
|
@@ -51,4 +51,12 @@ export const configurableOperationDefFragment = graphql(`
|
|
|
51
51
|
}
|
|
52
52
|
`);
|
|
53
53
|
|
|
54
|
+
export const errorResultFragment = graphql(`
|
|
55
|
+
fragment ErrorResult on ErrorResult {
|
|
56
|
+
errorCode
|
|
57
|
+
message
|
|
58
|
+
}
|
|
59
|
+
`);
|
|
60
|
+
|
|
61
|
+
|
|
54
62
|
export type ConfigurableOperationDefFragment = ResultOf<typeof configurableOperationDefFragment>;
|
package/src/lib/index.ts
CHANGED
|
@@ -28,10 +28,10 @@ export * from './components/layout/nav-user.js';
|
|
|
28
28
|
export * from './components/login/login-form.js';
|
|
29
29
|
export * from './components/shared/alerts.js';
|
|
30
30
|
export * from './components/shared/animated-number.js';
|
|
31
|
-
export * from './components/shared/asset-gallery.js';
|
|
32
|
-
export * from './components/shared/asset-picker-dialog.js';
|
|
33
|
-
export * from './components/shared/asset-preview-dialog.js';
|
|
34
|
-
export * from './components/shared/asset-preview.js';
|
|
31
|
+
export * from './components/shared/asset/asset-gallery.js';
|
|
32
|
+
export * from './components/shared/asset/asset-picker-dialog.js';
|
|
33
|
+
export * from './components/shared/asset/asset-preview-dialog.js';
|
|
34
|
+
export * from './components/shared/asset/asset-preview.js';
|
|
35
35
|
export * from './components/shared/assigned-facet-values.js';
|
|
36
36
|
export * from './components/shared/channel-code-label.js';
|
|
37
37
|
export * from './components/shared/channel-selector.js';
|
|
@@ -50,7 +50,7 @@ export * from './components/shared/entity-assets.js';
|
|
|
50
50
|
export * from './components/shared/error-page.js';
|
|
51
51
|
export * from './components/shared/facet-value-chip.js';
|
|
52
52
|
export * from './components/shared/facet-value-selector.js';
|
|
53
|
-
export * from './components/shared/focal-point-control.js';
|
|
53
|
+
export * from './components/shared/asset/focal-point-control.js';
|
|
54
54
|
export * from './components/shared/form-field-wrapper.js';
|
|
55
55
|
export * from './components/shared/history-timeline/history-entry.js';
|
|
56
56
|
export * from './components/shared/history-timeline/history-note-checkbox.js';
|
|
@@ -69,17 +69,20 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
|
69
69
|
const onLogoutSuccessFn = React.useRef<() => void>(() => {});
|
|
70
70
|
const isAuthenticated = status === 'authenticated';
|
|
71
71
|
|
|
72
|
-
const { data: currentUserData, isLoading } = useQuery({
|
|
73
|
-
queryKey: ['currentUser'],
|
|
72
|
+
const { data: currentUserData, isLoading , error: currentUserError} = useQuery({
|
|
73
|
+
queryKey: ['currentUser', isAuthenticated],
|
|
74
74
|
queryFn: () => api.query(CurrentUserQuery),
|
|
75
75
|
retry: false,
|
|
76
|
+
staleTime: 1000,
|
|
76
77
|
});
|
|
77
78
|
|
|
79
|
+
const currentUser = currentUserError ? undefined : currentUserData;
|
|
80
|
+
|
|
78
81
|
React.useEffect(() => {
|
|
79
|
-
if (!settings.activeChannelId &&
|
|
80
|
-
setActiveChannelId(
|
|
82
|
+
if (!settings.activeChannelId && currentUser?.me?.channels?.length) {
|
|
83
|
+
setActiveChannelId(currentUser.me.channels[0].id);
|
|
81
84
|
}
|
|
82
|
-
}, [settings.activeChannelId,
|
|
85
|
+
}, [settings.activeChannelId, currentUser?.me?.channels]);
|
|
83
86
|
|
|
84
87
|
const loginMutationFn = api.mutate(LoginMutation);
|
|
85
88
|
const loginMutation = useMutation({
|
|
@@ -123,7 +126,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
|
123
126
|
|
|
124
127
|
React.useEffect(() => {
|
|
125
128
|
if (!isLoading) {
|
|
126
|
-
if (
|
|
129
|
+
if (currentUser?.me?.id) {
|
|
127
130
|
setStatus('authenticated');
|
|
128
131
|
} else {
|
|
129
132
|
setStatus('unauthenticated');
|
|
@@ -131,7 +134,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
|
131
134
|
} else {
|
|
132
135
|
setStatus('verifying');
|
|
133
136
|
}
|
|
134
|
-
}, [isLoading,
|
|
137
|
+
}, [isLoading, currentUser]);
|
|
135
138
|
|
|
136
139
|
return (
|
|
137
140
|
<AuthContext.Provider
|
|
@@ -139,8 +142,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
|
139
142
|
isAuthenticated,
|
|
140
143
|
authenticationError,
|
|
141
144
|
status,
|
|
142
|
-
user:
|
|
143
|
-
channels:
|
|
145
|
+
user: currentUser?.activeAdministrator,
|
|
146
|
+
channels: currentUser?.me?.channels,
|
|
144
147
|
login,
|
|
145
148
|
logout,
|
|
146
149
|
}}
|
|
@@ -73,6 +73,7 @@ export function ChannelProvider({ children }: { children: React.ReactNode }) {
|
|
|
73
73
|
const { data: channelsData, isLoading: isChannelsLoading } = useQuery({
|
|
74
74
|
queryKey: ['channels'],
|
|
75
75
|
queryFn: () => api.query(ChannelsQuery),
|
|
76
|
+
retry: false,
|
|
76
77
|
});
|
|
77
78
|
|
|
78
79
|
// Set the selected channel and update localStorage
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { api } from '@/graphql/api.js';
|
|
2
2
|
import { graphql } from '@/graphql/graphql.js';
|
|
3
|
+
import { useAuth } from '@/hooks/use-auth.js';
|
|
3
4
|
import { useQuery } from '@tanstack/react-query';
|
|
4
5
|
import { ResultOf } from 'gql.tada';
|
|
5
6
|
import React from 'react';
|
|
@@ -260,9 +261,14 @@ export interface ServerConfig {
|
|
|
260
261
|
|
|
261
262
|
// create a provider for the global settings
|
|
262
263
|
export const ServerConfigProvider = ({ children }: { children: React.ReactNode }) => {
|
|
264
|
+
const { user } = useAuth();
|
|
265
|
+
const queryKey = ['getServerConfig', user?.id];
|
|
263
266
|
const { data } = useQuery({
|
|
264
|
-
queryKey
|
|
267
|
+
queryKey,
|
|
265
268
|
queryFn: () => api.query(getServerConfigDocument),
|
|
269
|
+
retry: false,
|
|
270
|
+
enabled: !!user?.id,
|
|
271
|
+
staleTime: 1000,
|
|
266
272
|
});
|
|
267
273
|
const value: ServerConfig = {
|
|
268
274
|
availableLanguages: data?.globalSettings.availableLanguages ?? [],
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import React, { createContext, useState, useEffect } from 'react';
|
|
2
2
|
import { Theme } from './theme-provider.js';
|
|
3
|
+
import { ColumnFiltersState } from '@tanstack/react-table';
|
|
4
|
+
|
|
5
|
+
export interface TableSettings {
|
|
6
|
+
columnVisibility?: Record<string, boolean>;
|
|
7
|
+
columnOrder?: string[];
|
|
8
|
+
columnFilters?: ColumnFiltersState;
|
|
9
|
+
pageSize?: number;
|
|
10
|
+
}
|
|
3
11
|
|
|
4
12
|
export interface UserSettings {
|
|
5
13
|
displayLanguage: string;
|
|
@@ -11,6 +19,7 @@ export interface UserSettings {
|
|
|
11
19
|
activeChannelId: string;
|
|
12
20
|
devMode: boolean;
|
|
13
21
|
hasSeenOnboarding: boolean;
|
|
22
|
+
tableSettings?: Record<string, TableSettings>;
|
|
14
23
|
}
|
|
15
24
|
|
|
16
25
|
const defaultSettings: UserSettings = {
|
|
@@ -23,6 +32,7 @@ const defaultSettings: UserSettings = {
|
|
|
23
32
|
activeChannelId: '',
|
|
24
33
|
devMode: false,
|
|
25
34
|
hasSeenOnboarding: false,
|
|
35
|
+
tableSettings: {},
|
|
26
36
|
};
|
|
27
37
|
|
|
28
38
|
export interface UserSettingsContextType {
|
|
@@ -36,6 +46,11 @@ export interface UserSettingsContextType {
|
|
|
36
46
|
setActiveChannelId: (channelId: string) => void;
|
|
37
47
|
setDevMode: (devMode: boolean) => void;
|
|
38
48
|
setHasSeenOnboarding: (hasSeen: boolean) => void;
|
|
49
|
+
setTableSettings: <K extends keyof TableSettings>(
|
|
50
|
+
tableId: string,
|
|
51
|
+
key: K,
|
|
52
|
+
value: TableSettings[K],
|
|
53
|
+
) => void;
|
|
39
54
|
}
|
|
40
55
|
|
|
41
56
|
export const UserSettingsContext = createContext<UserSettingsContextType | undefined>(undefined);
|
|
@@ -83,6 +98,15 @@ export const UserSettingsProvider: React.FC<React.PropsWithChildren<{}>> = ({ ch
|
|
|
83
98
|
setActiveChannelId: channelId => updateSetting('activeChannelId', channelId),
|
|
84
99
|
setDevMode: devMode => updateSetting('devMode', devMode),
|
|
85
100
|
setHasSeenOnboarding: hasSeen => updateSetting('hasSeenOnboarding', hasSeen),
|
|
101
|
+
setTableSettings: (tableId, key, value) => {
|
|
102
|
+
setSettings(prev => ({
|
|
103
|
+
...prev,
|
|
104
|
+
tableSettings: {
|
|
105
|
+
...prev.tableSettings,
|
|
106
|
+
[tableId]: { ...(prev.tableSettings?.[tableId] || {}), [key]: value },
|
|
107
|
+
},
|
|
108
|
+
}));
|
|
109
|
+
},
|
|
86
110
|
};
|
|
87
111
|
|
|
88
112
|
return <UserSettingsContext.Provider value={contextValue}>{children}</UserSettingsContext.Provider>;
|