@dataverse-kit/export-engine 1.4.0 → 1.6.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/dist/index.cjs +393 -52
- package/dist/index.d.cts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.mjs +393 -52
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -12810,6 +12810,7 @@ function buildV8Body(args) {
|
|
|
12810
12810
|
handlerBlock = "",
|
|
12811
12811
|
rowCommandsLiteral = "",
|
|
12812
12812
|
deleteDialogJsx = "",
|
|
12813
|
+
createDialogJsx = "",
|
|
12813
12814
|
commandBarBlock = "",
|
|
12814
12815
|
selectionModeAttr = "",
|
|
12815
12816
|
onSelectionChangedAttr = ""
|
|
@@ -12836,7 +12837,7 @@ const ${componentName}: React.FC<{ items: Record<string, unknown>[] }> = ({ item
|
|
|
12836
12837
|
editable
|
|
12837
12838
|
editTrigger="click"
|
|
12838
12839
|
getKey={(it) => String((it as { __rowIndex?: number }).__rowIndex ?? '')}${selectionModeAttr}${onSelectionChangedAttr}${onValueChangeAttr}${rowCommandsAttr}
|
|
12839
|
-
/>${editableAggregate}${editablePagination}${saveStatusJsx}${deleteDialogJsx}
|
|
12840
|
+
/>${editableAggregate}${editablePagination}${saveStatusJsx}${deleteDialogJsx}${createDialogJsx}
|
|
12840
12841
|
</div>
|
|
12841
12842
|
);
|
|
12842
12843
|
};`;
|
|
@@ -12889,6 +12890,7 @@ function buildLiveReadOnlyV8Body(args) {
|
|
|
12889
12890
|
handlerBlock = "",
|
|
12890
12891
|
rowCommandsLiteral = "",
|
|
12891
12892
|
deleteDialogJsx = "",
|
|
12893
|
+
createDialogJsx = "",
|
|
12892
12894
|
commandBarBlock = ""
|
|
12893
12895
|
} = args;
|
|
12894
12896
|
const rowCommandsAttr = rowCommandsLiteral ? `
|
|
@@ -12917,7 +12919,7 @@ const ${helperName}: React.FC = () => {
|
|
|
12917
12919
|
] as ColumnDef[]}
|
|
12918
12920
|
registry={registry}
|
|
12919
12921
|
getKey={(it) => String((it as { __rowIndex?: number }).__rowIndex ?? '')}${rowCommandsAttr}
|
|
12920
|
-
/>${flatAggregateInner}${flatPaginationInner}${deleteDialogJsx}
|
|
12922
|
+
/>${flatAggregateInner}${flatPaginationInner}${deleteDialogJsx}${createDialogJsx}
|
|
12921
12923
|
</div>
|
|
12922
12924
|
);
|
|
12923
12925
|
};`;
|
|
@@ -12963,6 +12965,7 @@ function buildCardV8LiveBody(args) {
|
|
|
12963
12965
|
handlerBlock = "",
|
|
12964
12966
|
rowCommandsLiteral = "",
|
|
12965
12967
|
deleteDialogJsx = "",
|
|
12968
|
+
createDialogJsx = "",
|
|
12966
12969
|
commandBarBlock = "",
|
|
12967
12970
|
selectionModeAttr = "",
|
|
12968
12971
|
onSelectionChangedAttr = ""
|
|
@@ -12994,7 +12997,7 @@ const ${helperName}: React.FC = () => {
|
|
|
12994
12997
|
registry={registry}
|
|
12995
12998
|
getKey={(it) => String((it as { __rowIndex?: number }).__rowIndex ?? '')}${selectionModeAttr}${onSelectionChangedAttr}
|
|
12996
12999
|
card={{ ${cardConfigLiteral} }}${rowCommandsAttr}
|
|
12997
|
-
/>${flatAggregateInner}${flatPaginationInner}${deleteDialogJsx}
|
|
13000
|
+
/>${flatAggregateInner}${flatPaginationInner}${deleteDialogJsx}${createDialogJsx}
|
|
12998
13001
|
</div>
|
|
12999
13002
|
);
|
|
13000
13003
|
};`;
|
|
@@ -13320,6 +13323,7 @@ function buildNestedSubgridBody(args) {
|
|
|
13320
13323
|
commandBarBlock,
|
|
13321
13324
|
handlerBlock,
|
|
13322
13325
|
deleteDialogJsx,
|
|
13326
|
+
createDialogJsx,
|
|
13323
13327
|
hasSelection,
|
|
13324
13328
|
hasHandler,
|
|
13325
13329
|
dataverseHooks,
|
|
@@ -13375,7 +13379,7 @@ const ${componentName}: React.FC<{ items: Record<string, unknown>[] }> = ({ item
|
|
|
13375
13379
|
calloutMaxRows: ${calloutMaxRows},
|
|
13376
13380
|
hoverDelay: ${hoverDelay},${childEditableLiteral}
|
|
13377
13381
|
}}
|
|
13378
|
-
/>${deleteDialogJsx}${childSaveStatusJsx}
|
|
13382
|
+
/>${deleteDialogJsx}${createDialogJsx}${childSaveStatusJsx}
|
|
13379
13383
|
</div>
|
|
13380
13384
|
);
|
|
13381
13385
|
};`;
|
|
@@ -13418,6 +13422,7 @@ function buildFocusedViewSubgridBody(args) {
|
|
|
13418
13422
|
commandBarBlock,
|
|
13419
13423
|
handlerBlock,
|
|
13420
13424
|
deleteDialogJsx,
|
|
13425
|
+
createDialogJsx,
|
|
13421
13426
|
hasSelection,
|
|
13422
13427
|
hasHandler,
|
|
13423
13428
|
dataverseHooks,
|
|
@@ -13471,7 +13476,7 @@ const ${componentName}: React.FC<{ items: Record<string, unknown>[] }> = ({ item
|
|
|
13471
13476
|
childSelectionMode: ${JSON.stringify(childSelectionMode)},
|
|
13472
13477
|
childSelectionGating: ${JSON.stringify(childSelectionGating)},${childEditableLiteral}
|
|
13473
13478
|
}}
|
|
13474
|
-
/>${deleteDialogJsx}${childSaveStatusJsx}
|
|
13479
|
+
/>${deleteDialogJsx}${createDialogJsx}${childSaveStatusJsx}
|
|
13475
13480
|
</div>
|
|
13476
13481
|
);
|
|
13477
13482
|
};`;
|
|
@@ -14250,14 +14255,213 @@ function effectiveMinSelection(item) {
|
|
|
14250
14255
|
if (typeof item.minSelectionCount === "number") return item.minSelectionCount;
|
|
14251
14256
|
return DEFAULT_MIN_SEL_BY_ACTION[item.actionType ?? "custom"] ?? 0;
|
|
14252
14257
|
}
|
|
14258
|
+
function buildExportColumnsLiteral(cols) {
|
|
14259
|
+
return cols.map(
|
|
14260
|
+
(c) => `{ key: ${JSON.stringify(c.fieldName)}, name: ${JSON.stringify(c.displayName ?? c.fieldName)} }`
|
|
14261
|
+
).join(", ");
|
|
14262
|
+
}
|
|
14263
|
+
var CREATE_FIELD_DENYLIST = /* @__PURE__ */ new Set([
|
|
14264
|
+
"createdon",
|
|
14265
|
+
"createdby",
|
|
14266
|
+
"createdonbehalfby",
|
|
14267
|
+
"modifiedon",
|
|
14268
|
+
"modifiedby",
|
|
14269
|
+
"modifiedonbehalfby",
|
|
14270
|
+
"owningbusinessunit",
|
|
14271
|
+
"owningteam",
|
|
14272
|
+
"owninguser",
|
|
14273
|
+
"ownerid",
|
|
14274
|
+
"versionnumber",
|
|
14275
|
+
"overriddencreatedon",
|
|
14276
|
+
"importsequencenumber",
|
|
14277
|
+
"timezoneruleversionnumber",
|
|
14278
|
+
"utcconversiontimezonecode",
|
|
14279
|
+
"statecode",
|
|
14280
|
+
"statuscode"
|
|
14281
|
+
]);
|
|
14282
|
+
function buildCreateFields(cols, primaryIdAttribute) {
|
|
14283
|
+
const primaryLower = (primaryIdAttribute ?? "").toLowerCase();
|
|
14284
|
+
const entries = [];
|
|
14285
|
+
for (const c of cols) {
|
|
14286
|
+
if (!c.fieldName) continue;
|
|
14287
|
+
const logical = (c.attributeLogicalName ?? c.fieldName).toLowerCase();
|
|
14288
|
+
if (logical === primaryLower) continue;
|
|
14289
|
+
if (c.isLocked) continue;
|
|
14290
|
+
if (c.dataType === "lookup" || c.dataType === "image") continue;
|
|
14291
|
+
if (CREATE_FIELD_DENYLIST.has(logical)) continue;
|
|
14292
|
+
const label = c.displayName ?? c.fieldName;
|
|
14293
|
+
let kind = "text";
|
|
14294
|
+
let inputType;
|
|
14295
|
+
let optionsLit = "";
|
|
14296
|
+
switch (c.dataType) {
|
|
14297
|
+
case "numeric":
|
|
14298
|
+
case "currency":
|
|
14299
|
+
kind = "number";
|
|
14300
|
+
inputType = "number";
|
|
14301
|
+
break;
|
|
14302
|
+
case "boolean":
|
|
14303
|
+
kind = "boolean";
|
|
14304
|
+
break;
|
|
14305
|
+
case "date":
|
|
14306
|
+
kind = "date";
|
|
14307
|
+
inputType = "date";
|
|
14308
|
+
break;
|
|
14309
|
+
case "datetime":
|
|
14310
|
+
kind = "date";
|
|
14311
|
+
inputType = "datetime-local";
|
|
14312
|
+
break;
|
|
14313
|
+
case "optionset": {
|
|
14314
|
+
const opts = c.rendererConfig?.options ?? [];
|
|
14315
|
+
if (opts.length === 0) {
|
|
14316
|
+
kind = "number";
|
|
14317
|
+
inputType = "number";
|
|
14318
|
+
} else {
|
|
14319
|
+
kind = "optionset";
|
|
14320
|
+
optionsLit = opts.map(
|
|
14321
|
+
(o) => `{ key: ${JSON.stringify(o.value)}, text: ${JSON.stringify(o.label)} }`
|
|
14322
|
+
).join(", ");
|
|
14323
|
+
}
|
|
14324
|
+
break;
|
|
14325
|
+
}
|
|
14326
|
+
case "email":
|
|
14327
|
+
kind = "text";
|
|
14328
|
+
inputType = "email";
|
|
14329
|
+
break;
|
|
14330
|
+
case "phone":
|
|
14331
|
+
kind = "text";
|
|
14332
|
+
inputType = "tel";
|
|
14333
|
+
break;
|
|
14334
|
+
case "url":
|
|
14335
|
+
kind = "text";
|
|
14336
|
+
inputType = "url";
|
|
14337
|
+
break;
|
|
14338
|
+
default:
|
|
14339
|
+
kind = "text";
|
|
14340
|
+
break;
|
|
14341
|
+
}
|
|
14342
|
+
const payloadKey = c.attributeLogicalName ?? c.fieldName;
|
|
14343
|
+
const parts = [
|
|
14344
|
+
`key: ${JSON.stringify(payloadKey)}`,
|
|
14345
|
+
`label: ${JSON.stringify(label)}`,
|
|
14346
|
+
`kind: ${JSON.stringify(kind)}`
|
|
14347
|
+
];
|
|
14348
|
+
if (inputType) parts.push(`inputType: ${JSON.stringify(inputType)}`);
|
|
14349
|
+
if (kind === "optionset") parts.push(`options: [${optionsLit}]`);
|
|
14350
|
+
entries.push(`{ ${parts.join(", ")} }`);
|
|
14351
|
+
}
|
|
14352
|
+
return entries.join(", ");
|
|
14353
|
+
}
|
|
14253
14354
|
function buildGridCommandHandlerBlock(args) {
|
|
14254
14355
|
const {
|
|
14255
14356
|
entityName,
|
|
14256
14357
|
entitySetName,
|
|
14257
14358
|
primaryIdAttribute,
|
|
14258
14359
|
itemsExpr,
|
|
14259
|
-
selectionExpr
|
|
14360
|
+
selectionExpr,
|
|
14361
|
+
exportColumnsLiteral,
|
|
14362
|
+
createColumnsLiteral,
|
|
14363
|
+
addExisting
|
|
14260
14364
|
} = args;
|
|
14365
|
+
const exportCase = exportColumnsLiteral ? `{
|
|
14366
|
+
const exportColumns = [${exportColumnsLiteral}];
|
|
14367
|
+
const rowsToExport = (indices.length ? indices.map((i) => items[i]) : items.slice())
|
|
14368
|
+
.filter((r): r is Record<string, unknown> => Boolean(r))
|
|
14369
|
+
.map((r) => {
|
|
14370
|
+
const formattedRow: Record<string, unknown> = {};
|
|
14371
|
+
for (const col of exportColumns) {
|
|
14372
|
+
formattedRow[col.key] = r[col.key + '@OData.Community.Display.V1.FormattedValue'] ?? r[col.key];
|
|
14373
|
+
}
|
|
14374
|
+
return formattedRow;
|
|
14375
|
+
});
|
|
14376
|
+
exportToFile(rowsToExport, exportColumns, 'csv', generateDefaultFilename(${JSON.stringify(entityName + "-export")}));
|
|
14377
|
+
return;
|
|
14378
|
+
}` : `{
|
|
14379
|
+
console.warn('[grid] Export to Excel is not yet wired in generated forms.');
|
|
14380
|
+
return;
|
|
14381
|
+
}`;
|
|
14382
|
+
const createStateBlock = createColumnsLiteral ? `
|
|
14383
|
+
const gridCreateMutation = useCreateRecord<Record<string, unknown>>('${entitySetName}');
|
|
14384
|
+
const [pendingCreate, setPendingCreate] = React.useState(false);
|
|
14385
|
+
const [createValues, setCreateValues] = React.useState<Record<string, unknown>>({});
|
|
14386
|
+
const [createError, setCreateError] = React.useState<string | null>(null);
|
|
14387
|
+
// Derived from the grid's columns; lookups/images/system fields are excluded by
|
|
14388
|
+
// the emitter. The payload is best-effort and validated server-side (errors show
|
|
14389
|
+
// in \`createError\`). NOTE: against a mock/standalone host (no Dataverse token),
|
|
14390
|
+
// MockApiService.createRecord no-ops and resolves \u2014 the dialog closes as if it
|
|
14391
|
+
// succeeded but nothing is created. Xrm is the primary path in a model-driven host.
|
|
14392
|
+
const createFields = React.useMemo<Array<{ key: string; label: string; kind: 'text' | 'number' | 'boolean' | 'optionset' | 'date'; inputType?: string; options?: Array<{ key: number; text: string }> }>>(
|
|
14393
|
+
() => [${createColumnsLiteral}],
|
|
14394
|
+
[],
|
|
14395
|
+
);
|
|
14396
|
+
const submitCreate = React.useCallback(async () => {
|
|
14397
|
+
setCreateError(null);
|
|
14398
|
+
const payload: Record<string, unknown> = {};
|
|
14399
|
+
for (const f of createFields) {
|
|
14400
|
+
const raw = createValues[f.key];
|
|
14401
|
+
if (raw === undefined || raw === null || raw === '') continue;
|
|
14402
|
+
payload[f.key] = (f.kind === 'number' || f.kind === 'optionset') ? Number(raw) : raw;
|
|
14403
|
+
}
|
|
14404
|
+
if (Object.keys(payload).length === 0) {
|
|
14405
|
+
setCreateError('Enter at least one value before creating.');
|
|
14406
|
+
return;
|
|
14407
|
+
}
|
|
14408
|
+
try {
|
|
14409
|
+
await gridCreateMutation.mutateAsync(payload);
|
|
14410
|
+
setPendingCreate(false);
|
|
14411
|
+
setCreateValues({});
|
|
14412
|
+
} catch (err) {
|
|
14413
|
+
setCreateError((err as Error)?.message ?? 'Create failed.');
|
|
14414
|
+
}
|
|
14415
|
+
}, [createValues, gridCreateMutation, createFields]);` : "";
|
|
14416
|
+
const newCase = createColumnsLiteral ? `{
|
|
14417
|
+
if (xrm?.Navigation?.openForm) {
|
|
14418
|
+
await xrm.Navigation.openForm({ entityName: '${entityName}', useQuickCreateForm: false });
|
|
14419
|
+
await refreshGrid();
|
|
14420
|
+
} else {
|
|
14421
|
+
setCreateError(null);
|
|
14422
|
+
setCreateValues({});
|
|
14423
|
+
setPendingCreate(true);
|
|
14424
|
+
}
|
|
14425
|
+
return;
|
|
14426
|
+
}` : `{
|
|
14427
|
+
if (xrm?.Navigation?.openForm) {
|
|
14428
|
+
await xrm.Navigation.openForm({ entityName: '${entityName}', useQuickCreateForm: false });
|
|
14429
|
+
await refreshGrid();
|
|
14430
|
+
} else {
|
|
14431
|
+
console.warn('[grid] New action requires Xrm.Navigation.openForm; not available in this host.');
|
|
14432
|
+
}
|
|
14433
|
+
return;
|
|
14434
|
+
}`;
|
|
14435
|
+
const addExistingStateBlock = addExisting ? `
|
|
14436
|
+
const gridAddExistingMutation = useUpdateRecord<Record<string, unknown>>('${addExisting.childEntitySet}');` : "";
|
|
14437
|
+
const addExistingCase = addExisting ? `{
|
|
14438
|
+
const parentId = selectedIds[0];
|
|
14439
|
+
if (!parentId) {
|
|
14440
|
+
console.warn('[grid] Select or open a parent record before adding an existing ${addExisting.childEntityLogical}.');
|
|
14441
|
+
return;
|
|
14442
|
+
}
|
|
14443
|
+
const lookup = (xrm as { Utility?: { lookupObjects?: (opts: unknown) => Promise<Array<{ id: string }>> } } | undefined)?.Utility;
|
|
14444
|
+
if (lookup?.lookupObjects) {
|
|
14445
|
+
const picked = await lookup.lookupObjects({ entityTypes: ['${addExisting.childEntityLogical}'], allowMultiSelect: true });
|
|
14446
|
+
if (!picked || picked.length === 0) return;
|
|
14447
|
+
for (const rec of picked) {
|
|
14448
|
+
const childId = String(rec.id).replace(/[{}]/g, '');
|
|
14449
|
+
await gridAddExistingMutation.mutateAsync({ id: childId, data: { '${addExisting.childField}@odata.bind': '/${addExisting.parentEntitySet}(' + parentId + ')' } as Record<string, unknown> });
|
|
14450
|
+
}
|
|
14451
|
+
await refreshGrid();
|
|
14452
|
+
} else {
|
|
14453
|
+
console.warn('[grid] Add Existing requires Xrm.Utility.lookupObjects; not available in this host.');
|
|
14454
|
+
}
|
|
14455
|
+
return;
|
|
14456
|
+
}` : `{
|
|
14457
|
+
if (xrm?.Navigation?.openForm) {
|
|
14458
|
+
await xrm.Navigation.openForm({ entityName: '${entityName}' });
|
|
14459
|
+
await refreshGrid();
|
|
14460
|
+
} else {
|
|
14461
|
+
console.warn('[grid] Add Existing requires Xrm.Navigation; not available in this host.');
|
|
14462
|
+
}
|
|
14463
|
+
return;
|
|
14464
|
+
}`;
|
|
14261
14465
|
return `
|
|
14262
14466
|
const queryClient = useQueryClient();
|
|
14263
14467
|
const gridUpdateMutation = useUpdateRecord<Record<string, unknown>>('${entitySetName}');
|
|
@@ -14281,7 +14485,7 @@ function buildGridCommandHandlerBlock(args) {
|
|
|
14281
14485
|
} catch (err) {
|
|
14282
14486
|
setDeleteError((err as Error)?.message ?? 'Delete failed.');
|
|
14283
14487
|
}
|
|
14284
|
-
}, [pendingDelete, gridDeleteMutation])
|
|
14488
|
+
}, [pendingDelete, gridDeleteMutation]);${createStateBlock}${addExistingStateBlock}
|
|
14285
14489
|
const handleGridCommand = React.useCallback(async (actionType: string, overrideRowIndex?: number) => {
|
|
14286
14490
|
const xrm = (typeof window !== 'undefined' ? (window as unknown as { Xrm?: any }).Xrm : undefined);
|
|
14287
14491
|
const items = ${itemsExpr} as ReadonlyArray<Record<string, unknown>>;
|
|
@@ -14306,24 +14510,8 @@ function buildGridCommandHandlerBlock(args) {
|
|
|
14306
14510
|
return true;
|
|
14307
14511
|
};
|
|
14308
14512
|
switch (actionType) {
|
|
14309
|
-
case 'new': {
|
|
14310
|
-
|
|
14311
|
-
await xrm.Navigation.openForm({ entityName: '${entityName}', useQuickCreateForm: false });
|
|
14312
|
-
await refreshGrid();
|
|
14313
|
-
} else {
|
|
14314
|
-
console.warn('[grid] New action requires Xrm.Navigation.openForm; not available in this host.');
|
|
14315
|
-
}
|
|
14316
|
-
return;
|
|
14317
|
-
}
|
|
14318
|
-
case 'addExisting': {
|
|
14319
|
-
if (xrm?.Navigation?.openForm) {
|
|
14320
|
-
await xrm.Navigation.openForm({ entityName: '${entityName}' });
|
|
14321
|
-
await refreshGrid();
|
|
14322
|
-
} else {
|
|
14323
|
-
console.warn('[grid] Add Existing requires Xrm.Navigation; not available in this host.');
|
|
14324
|
-
}
|
|
14325
|
-
return;
|
|
14326
|
-
}
|
|
14513
|
+
case 'new': ${newCase}
|
|
14514
|
+
case 'addExisting': ${addExistingCase}
|
|
14327
14515
|
case 'edit': {
|
|
14328
14516
|
if (!requireSel('edit')) return;
|
|
14329
14517
|
if (selectedIds.length > 1 && xrm?.Navigation?.openBulkEditForm) {
|
|
@@ -14359,10 +14547,7 @@ function buildGridCommandHandlerBlock(args) {
|
|
|
14359
14547
|
await refreshGrid();
|
|
14360
14548
|
return;
|
|
14361
14549
|
}
|
|
14362
|
-
case 'export': {
|
|
14363
|
-
console.warn('[grid] Export to Excel is not yet wired in generated forms.');
|
|
14364
|
-
return;
|
|
14365
|
-
}
|
|
14550
|
+
case 'export': ${exportCase}
|
|
14366
14551
|
case 'bulkEdit': {
|
|
14367
14552
|
if (!requireSel('bulk edit')) return;
|
|
14368
14553
|
if (xrm?.Navigation?.openBulkEditForm) {
|
|
@@ -14376,10 +14561,10 @@ function buildGridCommandHandlerBlock(args) {
|
|
|
14376
14561
|
default:
|
|
14377
14562
|
return;
|
|
14378
14563
|
}
|
|
14379
|
-
}, [${itemsExpr}, ${selectionExpr}, gridUpdateMutation, refreshGrid]);`;
|
|
14564
|
+
}, [${itemsExpr}, ${selectionExpr}, gridUpdateMutation, refreshGrid${addExisting ? ", gridAddExistingMutation" : ""}]);`;
|
|
14380
14565
|
}
|
|
14381
14566
|
function buildRowCommandsLiteral(contextMenuItems) {
|
|
14382
|
-
return contextMenuItems.filter((ci) => (ci.actionType ?? "custom") !== "custom").map(
|
|
14567
|
+
return contextMenuItems.filter((ci) => (ci.actionType ?? "custom") !== "custom").filter((ci) => ci.actionType !== "new").map(
|
|
14383
14568
|
(ci) => `{ key: ${JSON.stringify(ci.id)}, text: ${JSON.stringify(ci.text)}` + (ci.iconName ? `, iconName: ${JSON.stringify(ci.iconName)}` : "") + `, onClick: (item: Record<string, unknown>) => { void handleGridCommand(${JSON.stringify(ci.actionType)}, (item as { __rowIndex?: number }).__rowIndex); } }`
|
|
14384
14569
|
).join(",\n ");
|
|
14385
14570
|
}
|
|
@@ -14415,6 +14600,68 @@ function buildGridDeleteDialogJsx(entityName) {
|
|
|
14415
14600
|
</DialogFooter>
|
|
14416
14601
|
</Dialog>`;
|
|
14417
14602
|
}
|
|
14603
|
+
function buildGridCreateDialogJsx(entityName) {
|
|
14604
|
+
return `
|
|
14605
|
+
<Dialog
|
|
14606
|
+
hidden={!pendingCreate}
|
|
14607
|
+
onDismiss={() => { if (!gridCreateMutation.isPending) setPendingCreate(false); }}
|
|
14608
|
+
dialogContentProps={{
|
|
14609
|
+
type: DialogType.normal,
|
|
14610
|
+
title: ${JSON.stringify("Create " + entityName)},
|
|
14611
|
+
}}
|
|
14612
|
+
modalProps={{ isBlocking: true }}
|
|
14613
|
+
>
|
|
14614
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
|
|
14615
|
+
{createFields.map((f) => {
|
|
14616
|
+
if (f.kind === 'boolean') {
|
|
14617
|
+
return (
|
|
14618
|
+
<Checkbox
|
|
14619
|
+
key={f.key}
|
|
14620
|
+
label={f.label}
|
|
14621
|
+
checked={createValues[f.key] === true}
|
|
14622
|
+
onChange={(_ev, checked) => setCreateValues((prev) => ({ ...prev, [f.key]: !!checked }))}
|
|
14623
|
+
/>
|
|
14624
|
+
);
|
|
14625
|
+
}
|
|
14626
|
+
if (f.kind === 'optionset') {
|
|
14627
|
+
return (
|
|
14628
|
+
<Dropdown
|
|
14629
|
+
key={f.key}
|
|
14630
|
+
label={f.label}
|
|
14631
|
+
options={f.options ?? []}
|
|
14632
|
+
selectedKey={(createValues[f.key] as number | undefined) ?? null}
|
|
14633
|
+
onChange={(_ev, opt) => setCreateValues((prev) => ({ ...prev, [f.key]: opt?.key }))}
|
|
14634
|
+
/>
|
|
14635
|
+
);
|
|
14636
|
+
}
|
|
14637
|
+
return (
|
|
14638
|
+
<TextField
|
|
14639
|
+
key={f.key}
|
|
14640
|
+
label={f.label}
|
|
14641
|
+
type={f.inputType ?? 'text'}
|
|
14642
|
+
value={createValues[f.key] == null ? '' : String(createValues[f.key])}
|
|
14643
|
+
onChange={(_ev, val) => setCreateValues((prev) => ({ ...prev, [f.key]: val }))}
|
|
14644
|
+
/>
|
|
14645
|
+
);
|
|
14646
|
+
})}
|
|
14647
|
+
</div>
|
|
14648
|
+
{createError && (
|
|
14649
|
+
<div style={{ color: '#a4262c', fontSize: 12, marginTop: 8 }}>{createError}</div>
|
|
14650
|
+
)}
|
|
14651
|
+
<DialogFooter>
|
|
14652
|
+
<PrimaryButton
|
|
14653
|
+
onClick={submitCreate}
|
|
14654
|
+
text={gridCreateMutation.isPending ? 'Creating\u2026' : 'Create'}
|
|
14655
|
+
disabled={gridCreateMutation.isPending}
|
|
14656
|
+
/>
|
|
14657
|
+
<DefaultButton
|
|
14658
|
+
onClick={() => setPendingCreate(false)}
|
|
14659
|
+
text="Cancel"
|
|
14660
|
+
disabled={gridCreateMutation.isPending}
|
|
14661
|
+
/>
|
|
14662
|
+
</DialogFooter>
|
|
14663
|
+
</Dialog>`;
|
|
14664
|
+
}
|
|
14418
14665
|
function buildV9RenderCell(col) {
|
|
14419
14666
|
const safeFieldName = JSON.stringify(col.fieldName);
|
|
14420
14667
|
switch (col.rendererType) {
|
|
@@ -14777,12 +15024,18 @@ function generateLinkedSubgrid(gridDef, entityName, imports, library = "fluent-v
|
|
|
14777
15024
|
const showRefresh = gridDef.toolbar?.showRefresh ?? false;
|
|
14778
15025
|
const showColumnChooser = gridDef.toolbar?.showColumnChooser ?? false;
|
|
14779
15026
|
const hasToolbarIcons = showFilters || showViewToggle || showExport || showRefresh || showColumnChooser;
|
|
15027
|
+
const childGridDef = gridDef.nestedGridId ? _currentGridCustomizers.find((g) => g.id === gridDef.nestedGridId) : void 0;
|
|
15028
|
+
const _aeRel = gridDef.nestedRelationship;
|
|
15029
|
+
const addExistingFeasible = Boolean(
|
|
15030
|
+
childGridDef && _includeDataAccessLayer && _aeRel?.relationshipType === "OneToMany" && _aeRel?.parentField && _aeRel?.childField && gridDef.dataSource?.entitySetName && gridDef.dataSource?.fetchXml && childGridDef.dataSource?.entitySetName && childGridDef.dataSource?.entityName && childGridDef.dataSource?.fetchXml
|
|
15031
|
+
);
|
|
14780
15032
|
const barItems = (gridDef.commandBarItems ?? []).filter(
|
|
14781
15033
|
(ci) => ci.visibility === "commandBar" || ci.visibility === "both" || !ci.visibility
|
|
14782
|
-
);
|
|
15034
|
+
).filter((ci) => ci.actionType !== "addExisting" || addExistingFeasible);
|
|
14783
15035
|
const contextMenuItems = (gridDef.commandBarItems ?? []).filter(
|
|
14784
15036
|
(ci) => ci.visibility === "contextMenu" || ci.visibility === "both" || !ci.visibility
|
|
14785
|
-
);
|
|
15037
|
+
).filter((ci) => ci.actionType !== "addExisting" || addExistingFeasible);
|
|
15038
|
+
const enableAddExisting = addExistingFeasible && (barItems.some((ci) => ci.actionType === "addExisting") || contextMenuItems.some((ci) => ci.actionType === "addExisting"));
|
|
14786
15039
|
const _handlerAvailable = (
|
|
14787
15040
|
// The dispatcher calls useUpdateRecord/useDeleteRecord/useQueryClient — which only
|
|
14788
15041
|
// exist (module + provider + dep) when the data-access layer is included. Without it,
|
|
@@ -14792,6 +15045,8 @@ function generateLinkedSubgrid(gridDef, entityName, imports, library = "fluent-v
|
|
|
14792
15045
|
// need the dispatcher in scope even when no wired commandBarItems exist.
|
|
14793
15046
|
!!gridDef.toolbar?.showRefresh)
|
|
14794
15047
|
);
|
|
15048
|
+
const enableExport = _handlerAvailable && (showExport || barItems.some((ci) => ci.actionType === "export") || contextMenuItems.some((ci) => ci.actionType === "export"));
|
|
15049
|
+
const enableCreate = _handlerAvailable && barItems.some((ci) => ci.actionType === "new");
|
|
14795
15050
|
if (gridDef.showCommandBar && (barItems.length > 0 || showSearch || hasToolbarIcons)) {
|
|
14796
15051
|
if (showSearch) imports.add("SearchBox");
|
|
14797
15052
|
imports.add("DefaultButton");
|
|
@@ -14843,10 +15098,12 @@ function generateLinkedSubgrid(gridDef, entityName, imports, library = "fluent-v
|
|
|
14843
15098
|
toolbarIcons.push(
|
|
14844
15099
|
`<IconButton iconProps={{ iconName: 'ColumnOptions' }} title="Column chooser" ariaLabel="Column chooser" />`
|
|
14845
15100
|
);
|
|
14846
|
-
if (showExport)
|
|
15101
|
+
if (showExport) {
|
|
15102
|
+
const exportOnClick = _handlerAvailable ? ` onClick={() => { void handleGridCommand('export'); }}` : "";
|
|
14847
15103
|
toolbarIcons.push(
|
|
14848
|
-
`<IconButton iconProps={{ iconName: 'Download' }} title="Export" ariaLabel="Export" />`
|
|
15104
|
+
`<IconButton iconProps={{ iconName: 'Download' }} title="Export" ariaLabel="Export"${exportOnClick} />`
|
|
14849
15105
|
);
|
|
15106
|
+
}
|
|
14850
15107
|
if (showRefresh) {
|
|
14851
15108
|
const refreshOnClick = _handlerAvailable ? ` onClick={() => { void handleGridCommand('refresh'); }}` : "";
|
|
14852
15109
|
toolbarIcons.push(
|
|
@@ -14867,9 +15124,11 @@ function generateLinkedSubgrid(gridDef, entityName, imports, library = "fluent-v
|
|
|
14867
15124
|
</div>
|
|
14868
15125
|
</div>`;
|
|
14869
15126
|
}
|
|
14870
|
-
const childGridDef = gridDef.nestedGridId ? _currentGridCustomizers.find((g) => g.id === gridDef.nestedGridId) : void 0;
|
|
14871
15127
|
const isGridKitNested = !!childGridDef;
|
|
14872
15128
|
const visibleCols = [...gridDef.columns].filter((c) => c.isVisible).sort((a, b) => a.order - b.order);
|
|
15129
|
+
const exportColumnsLiteral = enableExport ? buildExportColumnsLiteral(visibleCols) : "";
|
|
15130
|
+
const primaryIdAttr = resolvePrimaryIdAttribute(gridDef);
|
|
15131
|
+
const createColumnsLiteral = enableCreate ? buildCreateFields(visibleCols, primaryIdAttr) : "";
|
|
14873
15132
|
visibleCols.filter((c) => c.rendererType !== "text");
|
|
14874
15133
|
const colEntries = isGridKitNested ? "" : generateColumnEntries(visibleCols, library);
|
|
14875
15134
|
const selectionMode = gridDef.selectionMode === "multiple" ? "SelectionMode.multiple" : gridDef.selectionMode === "single" ? "SelectionMode.single" : "SelectionMode.none";
|
|
@@ -14935,19 +15194,38 @@ ${childEntries.join(",\n")},
|
|
|
14935
15194
|
entitySetName: gridDef.dataSource.entitySetName,
|
|
14936
15195
|
primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
|
|
14937
15196
|
itemsExpr: "keyedItems",
|
|
14938
|
-
selectionExpr: "selectedIndices"
|
|
15197
|
+
selectionExpr: "selectedIndices",
|
|
15198
|
+
exportColumnsLiteral,
|
|
15199
|
+
createColumnsLiteral,
|
|
15200
|
+
// Associate an existing child to the clicked/active parent (the ONLY call site
|
|
15201
|
+
// that wires it). Shared by the nested AND focused-view emitters below — both
|
|
15202
|
+
// reuse this ngHandlerBlock. Gated on enableAddExisting (feasible AND an
|
|
15203
|
+
// addExisting item present); the && chain narrows the optional entity-set strings
|
|
15204
|
+
// to `string` (enableAddExisting ⟹ addExistingFeasible guarantees they're present).
|
|
15205
|
+
addExisting: enableAddExisting && childEntitySet && childEntityLogical && parentEntitySet && nestedRel?.childField ? {
|
|
15206
|
+
childEntitySet,
|
|
15207
|
+
childEntityLogical,
|
|
15208
|
+
childField: nestedRel.childField,
|
|
15209
|
+
parentEntitySet
|
|
15210
|
+
} : null
|
|
14939
15211
|
}) : "";
|
|
14940
15212
|
const ngHasDelete = (gridDef.commandBarItems ?? []).some(
|
|
14941
15213
|
(ci) => ci.actionType === "delete"
|
|
14942
15214
|
);
|
|
14943
15215
|
const ngDeleteDialogJsx = ngHasDelete && ngHandlerBlock !== "" ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
14944
|
-
|
|
15216
|
+
const ngCreateDialogJsx = ngHandlerBlock !== "" && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15217
|
+
if (ngDeleteDialogJsx || ngCreateDialogJsx) {
|
|
14945
15218
|
imports.add("Dialog");
|
|
14946
15219
|
imports.add("DialogType");
|
|
14947
15220
|
imports.add("DialogFooter");
|
|
14948
15221
|
imports.add("PrimaryButton");
|
|
14949
15222
|
imports.add("DefaultButton");
|
|
14950
15223
|
}
|
|
15224
|
+
if (ngCreateDialogJsx) {
|
|
15225
|
+
imports.add("TextField");
|
|
15226
|
+
imports.add("Dropdown");
|
|
15227
|
+
imports.add("Checkbox");
|
|
15228
|
+
}
|
|
14951
15229
|
const ngRowCommandsLiteral = _handlerAvailable && (ngHasCommandBar || ngHasContextMenu && useLiveNested) ? buildRowCommandsLiteral(contextMenuItems) : "";
|
|
14952
15230
|
const ngToolbarWired = (gridDef.commandBarItems ?? []).some(
|
|
14953
15231
|
(ci) => (ci.actionType ?? "custom") !== "custom"
|
|
@@ -14961,9 +15239,18 @@ ${childEntries.join(",\n")},
|
|
|
14961
15239
|
const ngDataverseHooks = [
|
|
14962
15240
|
...useLiveNested ? ["useDataverseQuery"] : [],
|
|
14963
15241
|
...useLiveNested && ngNeedsUpdate || ngHasHandler ? ["useUpdateRecord"] : [],
|
|
14964
|
-
...useLiveNested && ngNeedsDelete || ngHasHandler ? ["useDeleteRecord"] : []
|
|
15242
|
+
...useLiveNested && ngNeedsDelete || ngHasHandler ? ["useDeleteRecord"] : [],
|
|
15243
|
+
// Create mutation only when the create dialog is wired (the `new` case's
|
|
15244
|
+
// non-Xrm fallback) — gated like the export util import to stay byte-stable
|
|
15245
|
+
// for non-create handler grids.
|
|
15246
|
+
...ngHasHandler && createColumnsLiteral ? ["useCreateRecord"] : []
|
|
15247
|
+
];
|
|
15248
|
+
const ngCommandBarImports = [
|
|
15249
|
+
...useLiveNested && ngNeedsDelete || ngHasHandler ? [`import { useQueryClient } from '@tanstack/react-query';`] : [],
|
|
15250
|
+
// Export case (shared dispatcher → both nested + focused-view) calls
|
|
15251
|
+
// exportToFile/generateDefaultFilename when export is wired.
|
|
15252
|
+
...ngHasHandler && enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
|
|
14965
15253
|
];
|
|
14966
|
-
const ngCommandBarImports = useLiveNested && ngNeedsDelete || ngHasHandler ? [`import { useQueryClient } from '@tanstack/react-query';`] : [];
|
|
14967
15254
|
const ngParentSelectionMode = ngHasCommandBar ? gridDef.selectionMode === "single" ? "single" : "multiple" : gridDef.selectionMode ?? "none";
|
|
14968
15255
|
const ngChildSaveBack = Boolean(
|
|
14969
15256
|
useLiveNested && live && (childGridDef.isEditable ?? false)
|
|
@@ -15012,6 +15299,7 @@ ${childEntries.join(",\n")},
|
|
|
15012
15299
|
commandBarBlock: gridCommandBarBlock,
|
|
15013
15300
|
handlerBlock: ngHandlerBlock,
|
|
15014
15301
|
deleteDialogJsx: ngDeleteDialogJsx,
|
|
15302
|
+
createDialogJsx: ngCreateDialogJsx,
|
|
15015
15303
|
hasSelection: ngHasCommandBar,
|
|
15016
15304
|
hasHandler: ngHandlerBlock !== "",
|
|
15017
15305
|
dataverseHooks: ngDataverseHooks,
|
|
@@ -15067,6 +15355,7 @@ ${childEntries.join(",\n")},
|
|
|
15067
15355
|
commandBarBlock: gridCommandBarBlock,
|
|
15068
15356
|
handlerBlock: ngHandlerBlock,
|
|
15069
15357
|
deleteDialogJsx: ngDeleteDialogJsx,
|
|
15358
|
+
createDialogJsx: ngCreateDialogJsx,
|
|
15070
15359
|
hasSelection: ngHasCommandBar,
|
|
15071
15360
|
dataverseHooks: ngDataverseHooks,
|
|
15072
15361
|
commandBarImports: ngCommandBarImports,
|
|
@@ -15147,11 +15436,16 @@ ${childEntries.join(",\n")},
|
|
|
15147
15436
|
entitySetName: gridDef.dataSource.entitySetName,
|
|
15148
15437
|
primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
|
|
15149
15438
|
itemsExpr: "rows",
|
|
15150
|
-
selectionExpr: "selectedIndices"
|
|
15439
|
+
selectionExpr: "selectedIndices",
|
|
15440
|
+
exportColumnsLiteral,
|
|
15441
|
+
createColumnsLiteral,
|
|
15442
|
+
addExisting: null
|
|
15443
|
+
// flat (no parent relationship) → addExisting filtered out above
|
|
15151
15444
|
}) : "";
|
|
15152
15445
|
const cardSelectedIndicesState = cardEmitHandler ? "\n const [selectedIndices, setSelectedIndices] = React.useState<Set<number>>(new Set());" : "";
|
|
15153
15446
|
const cardRowCommandsLiteral = cardEmitRowCommands ? buildRowCommandsLiteral(contextMenuItems) : "";
|
|
15154
15447
|
const cardDeleteDialogJsx = cardEmitRowCommands && flatHasDelete || cardEmitToolbar && flatBarHasDelete ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15448
|
+
const cardCreateDialogJsx = cardEmitHandler && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15155
15449
|
const cardCommandBarBlock = cardEmitToolbar ? gridCommandBarBlock : "";
|
|
15156
15450
|
const cardSelectionModeAttr = cardEmitToolbar ? flatSelectionModeAttr : "";
|
|
15157
15451
|
const cardOnSelectionChangedAttr = cardEmitToolbar ? flatOnSelectionChangedAttr : "";
|
|
@@ -15170,20 +15464,26 @@ ${childEntries.join(",\n")},
|
|
|
15170
15464
|
handlerBlock: cardHandlerBlock,
|
|
15171
15465
|
rowCommandsLiteral: cardRowCommandsLiteral,
|
|
15172
15466
|
deleteDialogJsx: cardDeleteDialogJsx,
|
|
15467
|
+
createDialogJsx: cardCreateDialogJsx,
|
|
15173
15468
|
commandBarBlock: cardCommandBarBlock,
|
|
15174
15469
|
selectionModeAttr: cardSelectionModeAttr,
|
|
15175
15470
|
onSelectionChangedAttr: cardOnSelectionChangedAttr
|
|
15176
15471
|
})
|
|
15177
15472
|
];
|
|
15178
15473
|
_usesGridKit = true;
|
|
15179
|
-
const cardDataverseImport = cardEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
|
|
15180
|
-
if (cardDeleteDialogJsx) {
|
|
15474
|
+
const cardDataverseImport = cardEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord${createColumnsLiteral ? ", useCreateRecord" : ""} } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
|
|
15475
|
+
if (cardDeleteDialogJsx || cardCreateDialogJsx) {
|
|
15181
15476
|
imports.add("Dialog");
|
|
15182
15477
|
imports.add("DialogType");
|
|
15183
15478
|
imports.add("DialogFooter");
|
|
15184
15479
|
imports.add("PrimaryButton");
|
|
15185
15480
|
imports.add("DefaultButton");
|
|
15186
15481
|
}
|
|
15482
|
+
if (cardCreateDialogJsx) {
|
|
15483
|
+
imports.add("TextField");
|
|
15484
|
+
imports.add("Dropdown");
|
|
15485
|
+
imports.add("Checkbox");
|
|
15486
|
+
}
|
|
15187
15487
|
return {
|
|
15188
15488
|
imports,
|
|
15189
15489
|
v9Imports,
|
|
@@ -15191,7 +15491,10 @@ ${childEntries.join(",\n")},
|
|
|
15191
15491
|
extraImports: cardIsLive ? cardEmitHandler ? [
|
|
15192
15492
|
cardDataverseImport,
|
|
15193
15493
|
`import { useQueryClient } from '@tanstack/react-query';`,
|
|
15194
|
-
`import { CardGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit'
|
|
15494
|
+
`import { CardGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`,
|
|
15495
|
+
// Export case calls exportToFile/generateDefaultFilename (cardEmitHandler
|
|
15496
|
+
// here; the export case is only real when enableExport).
|
|
15497
|
+
...enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
|
|
15195
15498
|
] : [
|
|
15196
15499
|
`import { useDataverseQuery } from '../lib/dataverse';`,
|
|
15197
15500
|
`import { CardGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
|
|
@@ -15229,11 +15532,16 @@ ${childEntries.join(",\n")},
|
|
|
15229
15532
|
entitySetName: gridDef.dataSource.entitySetName,
|
|
15230
15533
|
primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
|
|
15231
15534
|
itemsExpr: "rows",
|
|
15232
|
-
selectionExpr: "selectedIndices"
|
|
15535
|
+
selectionExpr: "selectedIndices",
|
|
15536
|
+
exportColumnsLiteral,
|
|
15537
|
+
createColumnsLiteral,
|
|
15538
|
+
addExisting: null
|
|
15539
|
+
// flat (no parent relationship) → addExisting filtered out above
|
|
15233
15540
|
}) : "";
|
|
15234
15541
|
const roSelectedIndicesState = roEmitHandler ? "\n const [selectedIndices, setSelectedIndices] = React.useState<Set<number>>(new Set());" : "";
|
|
15235
15542
|
const roRowCommandsLiteral = roEmitRowCommands ? buildRowCommandsLiteral(contextMenuItems) : "";
|
|
15236
15543
|
const roDeleteDialogJsx = roEmitRowCommands && flatHasDelete ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15544
|
+
const roCreateDialogJsx = roEmitHandler && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15237
15545
|
const roCommandBarBlock = roEmitToolbar ? gridCommandBarBlock : "";
|
|
15238
15546
|
const helperComponents = [
|
|
15239
15547
|
buildLiveReadOnlySubgridBody({
|
|
@@ -15252,18 +15560,24 @@ ${childEntries.join(",\n")},
|
|
|
15252
15560
|
handlerBlock: roHandlerBlock,
|
|
15253
15561
|
rowCommandsLiteral: roRowCommandsLiteral,
|
|
15254
15562
|
deleteDialogJsx: roDeleteDialogJsx,
|
|
15563
|
+
createDialogJsx: roCreateDialogJsx,
|
|
15255
15564
|
commandBarBlock: roCommandBarBlock
|
|
15256
15565
|
})
|
|
15257
15566
|
];
|
|
15258
15567
|
if (!isV9) _usesGridKit = true;
|
|
15259
|
-
const roDataverseImport = roEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
|
|
15260
|
-
if (roDeleteDialogJsx) {
|
|
15568
|
+
const roDataverseImport = roEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord${createColumnsLiteral ? ", useCreateRecord" : ""} } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
|
|
15569
|
+
if (roDeleteDialogJsx || roCreateDialogJsx) {
|
|
15261
15570
|
imports.add("Dialog");
|
|
15262
15571
|
imports.add("DialogType");
|
|
15263
15572
|
imports.add("DialogFooter");
|
|
15264
15573
|
imports.add("PrimaryButton");
|
|
15265
15574
|
imports.add("DefaultButton");
|
|
15266
15575
|
}
|
|
15576
|
+
if (roCreateDialogJsx) {
|
|
15577
|
+
imports.add("TextField");
|
|
15578
|
+
imports.add("Dropdown");
|
|
15579
|
+
imports.add("Checkbox");
|
|
15580
|
+
}
|
|
15267
15581
|
return {
|
|
15268
15582
|
imports,
|
|
15269
15583
|
v9Imports,
|
|
@@ -15271,7 +15585,8 @@ ${childEntries.join(",\n")},
|
|
|
15271
15585
|
extraImports: isV9 ? [`import { useDataverseQuery } from '../lib/dataverse';`] : roEmitHandler ? [
|
|
15272
15586
|
roDataverseImport,
|
|
15273
15587
|
`import { useQueryClient } from '@tanstack/react-query';`,
|
|
15274
|
-
`import { ReadOnlyGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit'
|
|
15588
|
+
`import { ReadOnlyGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`,
|
|
15589
|
+
...enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
|
|
15275
15590
|
] : [
|
|
15276
15591
|
`import { useDataverseQuery } from '../lib/dataverse';`,
|
|
15277
15592
|
`import { ReadOnlyGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
|
|
@@ -15331,11 +15646,16 @@ const ${liveWrapperName}: React.FC = () => {
|
|
|
15331
15646
|
entitySetName: gridDef.dataSource.entitySetName,
|
|
15332
15647
|
primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
|
|
15333
15648
|
itemsExpr: "keyedItems",
|
|
15334
|
-
selectionExpr: "selectedIndices"
|
|
15649
|
+
selectionExpr: "selectedIndices",
|
|
15650
|
+
exportColumnsLiteral,
|
|
15651
|
+
createColumnsLiteral,
|
|
15652
|
+
addExisting: null
|
|
15653
|
+
// flat (no parent relationship) → addExisting filtered out above
|
|
15335
15654
|
}) : "";
|
|
15336
15655
|
const edSelectedIndicesState = edEmitHandler ? "\n const [selectedIndices, setSelectedIndices] = React.useState<Set<number>>(new Set());" : "";
|
|
15337
15656
|
const edRowCommandsLiteral = edEmitRowCommands ? buildRowCommandsLiteral(contextMenuItems) : "";
|
|
15338
15657
|
const edDeleteDialogJsx = edEmitRowCommands && flatHasDelete || edEmitToolbar && flatBarHasDelete ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15658
|
+
const edCreateDialogJsx = edEmitHandler && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15339
15659
|
const edCommandBarBlock = edEmitToolbar ? gridCommandBarBlock : "";
|
|
15340
15660
|
const edSelectionModeAttr = edEmitToolbar ? flatSelectionModeAttr : "";
|
|
15341
15661
|
const edOnSelectionChangedAttr = edEmitToolbar ? flatOnSelectionChangedAttr : "";
|
|
@@ -15352,6 +15672,7 @@ const ${liveWrapperName}: React.FC = () => {
|
|
|
15352
15672
|
handlerBlock: edHandlerBlock,
|
|
15353
15673
|
rowCommandsLiteral: edRowCommandsLiteral,
|
|
15354
15674
|
deleteDialogJsx: edDeleteDialogJsx,
|
|
15675
|
+
createDialogJsx: edCreateDialogJsx,
|
|
15355
15676
|
commandBarBlock: edCommandBarBlock,
|
|
15356
15677
|
selectionModeAttr: edSelectionModeAttr,
|
|
15357
15678
|
onSelectionChangedAttr: edOnSelectionChangedAttr
|
|
@@ -15363,18 +15684,24 @@ const ${liveWrapperName}: React.FC = () => {
|
|
|
15363
15684
|
const jsx3 = editableUsesLiveData && liveWrapperName ? `{/* Subgrid: ${safeEntityName} \u2014 ${safeGridName} (editable, live) */}${todoComment}
|
|
15364
15685
|
<${liveWrapperName} />` : `{/* Subgrid: ${safeEntityName} \u2014 ${safeGridName} (editable) */}${todoComment}
|
|
15365
15686
|
<${componentName} items={${itemsLiteral}} />`;
|
|
15366
|
-
const edDataverseImport = edEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord } from '../lib/dataverse';` : `import { useDataverseQuery, useUpdateRecord } from '../lib/dataverse';`;
|
|
15367
|
-
if (edDeleteDialogJsx) {
|
|
15687
|
+
const edDataverseImport = edEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord${createColumnsLiteral ? ", useCreateRecord" : ""} } from '../lib/dataverse';` : `import { useDataverseQuery, useUpdateRecord } from '../lib/dataverse';`;
|
|
15688
|
+
if (edDeleteDialogJsx || edCreateDialogJsx) {
|
|
15368
15689
|
imports.add("Dialog");
|
|
15369
15690
|
imports.add("DialogType");
|
|
15370
15691
|
imports.add("DialogFooter");
|
|
15371
15692
|
imports.add("PrimaryButton");
|
|
15372
15693
|
imports.add("DefaultButton");
|
|
15373
15694
|
}
|
|
15695
|
+
if (edCreateDialogJsx) {
|
|
15696
|
+
imports.add("TextField");
|
|
15697
|
+
imports.add("Dropdown");
|
|
15698
|
+
imports.add("Checkbox");
|
|
15699
|
+
}
|
|
15374
15700
|
const extraImports2 = editableUsesLiveData ? edEmitHandler ? [
|
|
15375
15701
|
edDataverseImport,
|
|
15376
15702
|
`import { useQueryClient } from '@tanstack/react-query';`,
|
|
15377
|
-
`import { DataGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit'
|
|
15703
|
+
`import { DataGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`,
|
|
15704
|
+
...enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
|
|
15378
15705
|
] : [
|
|
15379
15706
|
`import { useDataverseQuery, useUpdateRecord } from '../lib/dataverse';`,
|
|
15380
15707
|
`import { DataGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
|
|
@@ -18183,6 +18510,19 @@ ${code}
|
|
|
18183
18510
|
};
|
|
18184
18511
|
`;
|
|
18185
18512
|
}
|
|
18513
|
+
function dedupeImportStatements(content) {
|
|
18514
|
+
const isImportLine = (l) => /^import\b[^\n]*\bfrom\b[^\n]*;[ \t]*$/.test(l) || /^import\s+['"][^'"]+['"];[ \t]*$/.test(l);
|
|
18515
|
+
const seen = /* @__PURE__ */ new Set();
|
|
18516
|
+
const out = [];
|
|
18517
|
+
for (const line of content.split("\n")) {
|
|
18518
|
+
if (isImportLine(line)) {
|
|
18519
|
+
if (seen.has(line)) continue;
|
|
18520
|
+
seen.add(line);
|
|
18521
|
+
}
|
|
18522
|
+
out.push(line);
|
|
18523
|
+
}
|
|
18524
|
+
return out.join("\n");
|
|
18525
|
+
}
|
|
18186
18526
|
function generateFormCode(form, options) {
|
|
18187
18527
|
_currentGridCustomizers = options?.gridCustomizers ?? [];
|
|
18188
18528
|
_includeSampleData = options?.includeSampleData ?? false;
|
|
@@ -18213,6 +18553,7 @@ function generateFormCode(form, options) {
|
|
|
18213
18553
|
default:
|
|
18214
18554
|
content = generateMainForm(form, hasRules, library);
|
|
18215
18555
|
}
|
|
18556
|
+
content = dedupeImportStatements(content);
|
|
18216
18557
|
const files = [
|
|
18217
18558
|
{ path: `src/components/${fileName}`, content }
|
|
18218
18559
|
];
|