@dataverse-kit/export-engine 1.4.0 → 1.5.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 +321 -39
- package/dist/index.mjs +321 -39
- 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,182 @@ 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
|
|
14260
14363
|
} = args;
|
|
14364
|
+
const exportCase = exportColumnsLiteral ? `{
|
|
14365
|
+
const exportColumns = [${exportColumnsLiteral}];
|
|
14366
|
+
const rowsToExport = (indices.length ? indices.map((i) => items[i]) : items.slice())
|
|
14367
|
+
.filter((r): r is Record<string, unknown> => Boolean(r))
|
|
14368
|
+
.map((r) => {
|
|
14369
|
+
const formattedRow: Record<string, unknown> = {};
|
|
14370
|
+
for (const col of exportColumns) {
|
|
14371
|
+
formattedRow[col.key] = r[col.key + '@OData.Community.Display.V1.FormattedValue'] ?? r[col.key];
|
|
14372
|
+
}
|
|
14373
|
+
return formattedRow;
|
|
14374
|
+
});
|
|
14375
|
+
exportToFile(rowsToExport, exportColumns, 'csv', generateDefaultFilename(${JSON.stringify(entityName + "-export")}));
|
|
14376
|
+
return;
|
|
14377
|
+
}` : `{
|
|
14378
|
+
console.warn('[grid] Export to Excel is not yet wired in generated forms.');
|
|
14379
|
+
return;
|
|
14380
|
+
}`;
|
|
14381
|
+
const createStateBlock = createColumnsLiteral ? `
|
|
14382
|
+
const gridCreateMutation = useCreateRecord<Record<string, unknown>>('${entitySetName}');
|
|
14383
|
+
const [pendingCreate, setPendingCreate] = React.useState(false);
|
|
14384
|
+
const [createValues, setCreateValues] = React.useState<Record<string, unknown>>({});
|
|
14385
|
+
const [createError, setCreateError] = React.useState<string | null>(null);
|
|
14386
|
+
// Derived from the grid's columns; lookups/images/system fields are excluded by
|
|
14387
|
+
// the emitter. The payload is best-effort and validated server-side (errors show
|
|
14388
|
+
// in \`createError\`). NOTE: against a mock/standalone host (no Dataverse token),
|
|
14389
|
+
// MockApiService.createRecord no-ops and resolves \u2014 the dialog closes as if it
|
|
14390
|
+
// succeeded but nothing is created. Xrm is the primary path in a model-driven host.
|
|
14391
|
+
const createFields = React.useMemo<Array<{ key: string; label: string; kind: 'text' | 'number' | 'boolean' | 'optionset' | 'date'; inputType?: string; options?: Array<{ key: number; text: string }> }>>(
|
|
14392
|
+
() => [${createColumnsLiteral}],
|
|
14393
|
+
[],
|
|
14394
|
+
);
|
|
14395
|
+
const submitCreate = React.useCallback(async () => {
|
|
14396
|
+
setCreateError(null);
|
|
14397
|
+
const payload: Record<string, unknown> = {};
|
|
14398
|
+
for (const f of createFields) {
|
|
14399
|
+
const raw = createValues[f.key];
|
|
14400
|
+
if (raw === undefined || raw === null || raw === '') continue;
|
|
14401
|
+
payload[f.key] = (f.kind === 'number' || f.kind === 'optionset') ? Number(raw) : raw;
|
|
14402
|
+
}
|
|
14403
|
+
if (Object.keys(payload).length === 0) {
|
|
14404
|
+
setCreateError('Enter at least one value before creating.');
|
|
14405
|
+
return;
|
|
14406
|
+
}
|
|
14407
|
+
try {
|
|
14408
|
+
await gridCreateMutation.mutateAsync(payload);
|
|
14409
|
+
setPendingCreate(false);
|
|
14410
|
+
setCreateValues({});
|
|
14411
|
+
} catch (err) {
|
|
14412
|
+
setCreateError((err as Error)?.message ?? 'Create failed.');
|
|
14413
|
+
}
|
|
14414
|
+
}, [createValues, gridCreateMutation, createFields]);` : "";
|
|
14415
|
+
const newCase = createColumnsLiteral ? `{
|
|
14416
|
+
if (xrm?.Navigation?.openForm) {
|
|
14417
|
+
await xrm.Navigation.openForm({ entityName: '${entityName}', useQuickCreateForm: false });
|
|
14418
|
+
await refreshGrid();
|
|
14419
|
+
} else {
|
|
14420
|
+
setCreateError(null);
|
|
14421
|
+
setCreateValues({});
|
|
14422
|
+
setPendingCreate(true);
|
|
14423
|
+
}
|
|
14424
|
+
return;
|
|
14425
|
+
}` : `{
|
|
14426
|
+
if (xrm?.Navigation?.openForm) {
|
|
14427
|
+
await xrm.Navigation.openForm({ entityName: '${entityName}', useQuickCreateForm: false });
|
|
14428
|
+
await refreshGrid();
|
|
14429
|
+
} else {
|
|
14430
|
+
console.warn('[grid] New action requires Xrm.Navigation.openForm; not available in this host.');
|
|
14431
|
+
}
|
|
14432
|
+
return;
|
|
14433
|
+
}`;
|
|
14261
14434
|
return `
|
|
14262
14435
|
const queryClient = useQueryClient();
|
|
14263
14436
|
const gridUpdateMutation = useUpdateRecord<Record<string, unknown>>('${entitySetName}');
|
|
@@ -14281,7 +14454,7 @@ function buildGridCommandHandlerBlock(args) {
|
|
|
14281
14454
|
} catch (err) {
|
|
14282
14455
|
setDeleteError((err as Error)?.message ?? 'Delete failed.');
|
|
14283
14456
|
}
|
|
14284
|
-
}, [pendingDelete, gridDeleteMutation])
|
|
14457
|
+
}, [pendingDelete, gridDeleteMutation]);${createStateBlock}
|
|
14285
14458
|
const handleGridCommand = React.useCallback(async (actionType: string, overrideRowIndex?: number) => {
|
|
14286
14459
|
const xrm = (typeof window !== 'undefined' ? (window as unknown as { Xrm?: any }).Xrm : undefined);
|
|
14287
14460
|
const items = ${itemsExpr} as ReadonlyArray<Record<string, unknown>>;
|
|
@@ -14306,15 +14479,7 @@ function buildGridCommandHandlerBlock(args) {
|
|
|
14306
14479
|
return true;
|
|
14307
14480
|
};
|
|
14308
14481
|
switch (actionType) {
|
|
14309
|
-
case 'new': {
|
|
14310
|
-
if (xrm?.Navigation?.openForm) {
|
|
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
|
-
}
|
|
14482
|
+
case 'new': ${newCase}
|
|
14318
14483
|
case 'addExisting': {
|
|
14319
14484
|
if (xrm?.Navigation?.openForm) {
|
|
14320
14485
|
await xrm.Navigation.openForm({ entityName: '${entityName}' });
|
|
@@ -14359,10 +14524,7 @@ function buildGridCommandHandlerBlock(args) {
|
|
|
14359
14524
|
await refreshGrid();
|
|
14360
14525
|
return;
|
|
14361
14526
|
}
|
|
14362
|
-
case 'export': {
|
|
14363
|
-
console.warn('[grid] Export to Excel is not yet wired in generated forms.');
|
|
14364
|
-
return;
|
|
14365
|
-
}
|
|
14527
|
+
case 'export': ${exportCase}
|
|
14366
14528
|
case 'bulkEdit': {
|
|
14367
14529
|
if (!requireSel('bulk edit')) return;
|
|
14368
14530
|
if (xrm?.Navigation?.openBulkEditForm) {
|
|
@@ -14379,7 +14541,7 @@ function buildGridCommandHandlerBlock(args) {
|
|
|
14379
14541
|
}, [${itemsExpr}, ${selectionExpr}, gridUpdateMutation, refreshGrid]);`;
|
|
14380
14542
|
}
|
|
14381
14543
|
function buildRowCommandsLiteral(contextMenuItems) {
|
|
14382
|
-
return contextMenuItems.filter((ci) => (ci.actionType ?? "custom") !== "custom").map(
|
|
14544
|
+
return contextMenuItems.filter((ci) => (ci.actionType ?? "custom") !== "custom").filter((ci) => ci.actionType !== "new").map(
|
|
14383
14545
|
(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
14546
|
).join(",\n ");
|
|
14385
14547
|
}
|
|
@@ -14415,6 +14577,68 @@ function buildGridDeleteDialogJsx(entityName) {
|
|
|
14415
14577
|
</DialogFooter>
|
|
14416
14578
|
</Dialog>`;
|
|
14417
14579
|
}
|
|
14580
|
+
function buildGridCreateDialogJsx(entityName) {
|
|
14581
|
+
return `
|
|
14582
|
+
<Dialog
|
|
14583
|
+
hidden={!pendingCreate}
|
|
14584
|
+
onDismiss={() => { if (!gridCreateMutation.isPending) setPendingCreate(false); }}
|
|
14585
|
+
dialogContentProps={{
|
|
14586
|
+
type: DialogType.normal,
|
|
14587
|
+
title: ${JSON.stringify("Create " + entityName)},
|
|
14588
|
+
}}
|
|
14589
|
+
modalProps={{ isBlocking: true }}
|
|
14590
|
+
>
|
|
14591
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
|
|
14592
|
+
{createFields.map((f) => {
|
|
14593
|
+
if (f.kind === 'boolean') {
|
|
14594
|
+
return (
|
|
14595
|
+
<Checkbox
|
|
14596
|
+
key={f.key}
|
|
14597
|
+
label={f.label}
|
|
14598
|
+
checked={createValues[f.key] === true}
|
|
14599
|
+
onChange={(_ev, checked) => setCreateValues((prev) => ({ ...prev, [f.key]: !!checked }))}
|
|
14600
|
+
/>
|
|
14601
|
+
);
|
|
14602
|
+
}
|
|
14603
|
+
if (f.kind === 'optionset') {
|
|
14604
|
+
return (
|
|
14605
|
+
<Dropdown
|
|
14606
|
+
key={f.key}
|
|
14607
|
+
label={f.label}
|
|
14608
|
+
options={f.options ?? []}
|
|
14609
|
+
selectedKey={(createValues[f.key] as number | undefined) ?? null}
|
|
14610
|
+
onChange={(_ev, opt) => setCreateValues((prev) => ({ ...prev, [f.key]: opt?.key }))}
|
|
14611
|
+
/>
|
|
14612
|
+
);
|
|
14613
|
+
}
|
|
14614
|
+
return (
|
|
14615
|
+
<TextField
|
|
14616
|
+
key={f.key}
|
|
14617
|
+
label={f.label}
|
|
14618
|
+
type={f.inputType ?? 'text'}
|
|
14619
|
+
value={createValues[f.key] == null ? '' : String(createValues[f.key])}
|
|
14620
|
+
onChange={(_ev, val) => setCreateValues((prev) => ({ ...prev, [f.key]: val }))}
|
|
14621
|
+
/>
|
|
14622
|
+
);
|
|
14623
|
+
})}
|
|
14624
|
+
</div>
|
|
14625
|
+
{createError && (
|
|
14626
|
+
<div style={{ color: '#a4262c', fontSize: 12, marginTop: 8 }}>{createError}</div>
|
|
14627
|
+
)}
|
|
14628
|
+
<DialogFooter>
|
|
14629
|
+
<PrimaryButton
|
|
14630
|
+
onClick={submitCreate}
|
|
14631
|
+
text={gridCreateMutation.isPending ? 'Creating\u2026' : 'Create'}
|
|
14632
|
+
disabled={gridCreateMutation.isPending}
|
|
14633
|
+
/>
|
|
14634
|
+
<DefaultButton
|
|
14635
|
+
onClick={() => setPendingCreate(false)}
|
|
14636
|
+
text="Cancel"
|
|
14637
|
+
disabled={gridCreateMutation.isPending}
|
|
14638
|
+
/>
|
|
14639
|
+
</DialogFooter>
|
|
14640
|
+
</Dialog>`;
|
|
14641
|
+
}
|
|
14418
14642
|
function buildV9RenderCell(col) {
|
|
14419
14643
|
const safeFieldName = JSON.stringify(col.fieldName);
|
|
14420
14644
|
switch (col.rendererType) {
|
|
@@ -14792,6 +15016,8 @@ function generateLinkedSubgrid(gridDef, entityName, imports, library = "fluent-v
|
|
|
14792
15016
|
// need the dispatcher in scope even when no wired commandBarItems exist.
|
|
14793
15017
|
!!gridDef.toolbar?.showRefresh)
|
|
14794
15018
|
);
|
|
15019
|
+
const enableExport = _handlerAvailable && (showExport || barItems.some((ci) => ci.actionType === "export") || contextMenuItems.some((ci) => ci.actionType === "export"));
|
|
15020
|
+
const enableCreate = _handlerAvailable && barItems.some((ci) => ci.actionType === "new");
|
|
14795
15021
|
if (gridDef.showCommandBar && (barItems.length > 0 || showSearch || hasToolbarIcons)) {
|
|
14796
15022
|
if (showSearch) imports.add("SearchBox");
|
|
14797
15023
|
imports.add("DefaultButton");
|
|
@@ -14843,10 +15069,12 @@ function generateLinkedSubgrid(gridDef, entityName, imports, library = "fluent-v
|
|
|
14843
15069
|
toolbarIcons.push(
|
|
14844
15070
|
`<IconButton iconProps={{ iconName: 'ColumnOptions' }} title="Column chooser" ariaLabel="Column chooser" />`
|
|
14845
15071
|
);
|
|
14846
|
-
if (showExport)
|
|
15072
|
+
if (showExport) {
|
|
15073
|
+
const exportOnClick = _handlerAvailable ? ` onClick={() => { void handleGridCommand('export'); }}` : "";
|
|
14847
15074
|
toolbarIcons.push(
|
|
14848
|
-
`<IconButton iconProps={{ iconName: 'Download' }} title="Export" ariaLabel="Export" />`
|
|
15075
|
+
`<IconButton iconProps={{ iconName: 'Download' }} title="Export" ariaLabel="Export"${exportOnClick} />`
|
|
14849
15076
|
);
|
|
15077
|
+
}
|
|
14850
15078
|
if (showRefresh) {
|
|
14851
15079
|
const refreshOnClick = _handlerAvailable ? ` onClick={() => { void handleGridCommand('refresh'); }}` : "";
|
|
14852
15080
|
toolbarIcons.push(
|
|
@@ -14870,6 +15098,9 @@ function generateLinkedSubgrid(gridDef, entityName, imports, library = "fluent-v
|
|
|
14870
15098
|
const childGridDef = gridDef.nestedGridId ? _currentGridCustomizers.find((g) => g.id === gridDef.nestedGridId) : void 0;
|
|
14871
15099
|
const isGridKitNested = !!childGridDef;
|
|
14872
15100
|
const visibleCols = [...gridDef.columns].filter((c) => c.isVisible).sort((a, b) => a.order - b.order);
|
|
15101
|
+
const exportColumnsLiteral = enableExport ? buildExportColumnsLiteral(visibleCols) : "";
|
|
15102
|
+
const primaryIdAttr = resolvePrimaryIdAttribute(gridDef);
|
|
15103
|
+
const createColumnsLiteral = enableCreate ? buildCreateFields(visibleCols, primaryIdAttr) : "";
|
|
14873
15104
|
visibleCols.filter((c) => c.rendererType !== "text");
|
|
14874
15105
|
const colEntries = isGridKitNested ? "" : generateColumnEntries(visibleCols, library);
|
|
14875
15106
|
const selectionMode = gridDef.selectionMode === "multiple" ? "SelectionMode.multiple" : gridDef.selectionMode === "single" ? "SelectionMode.single" : "SelectionMode.none";
|
|
@@ -14935,19 +15166,27 @@ ${childEntries.join(",\n")},
|
|
|
14935
15166
|
entitySetName: gridDef.dataSource.entitySetName,
|
|
14936
15167
|
primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
|
|
14937
15168
|
itemsExpr: "keyedItems",
|
|
14938
|
-
selectionExpr: "selectedIndices"
|
|
15169
|
+
selectionExpr: "selectedIndices",
|
|
15170
|
+
exportColumnsLiteral,
|
|
15171
|
+
createColumnsLiteral
|
|
14939
15172
|
}) : "";
|
|
14940
15173
|
const ngHasDelete = (gridDef.commandBarItems ?? []).some(
|
|
14941
15174
|
(ci) => ci.actionType === "delete"
|
|
14942
15175
|
);
|
|
14943
15176
|
const ngDeleteDialogJsx = ngHasDelete && ngHandlerBlock !== "" ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
14944
|
-
|
|
15177
|
+
const ngCreateDialogJsx = ngHandlerBlock !== "" && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15178
|
+
if (ngDeleteDialogJsx || ngCreateDialogJsx) {
|
|
14945
15179
|
imports.add("Dialog");
|
|
14946
15180
|
imports.add("DialogType");
|
|
14947
15181
|
imports.add("DialogFooter");
|
|
14948
15182
|
imports.add("PrimaryButton");
|
|
14949
15183
|
imports.add("DefaultButton");
|
|
14950
15184
|
}
|
|
15185
|
+
if (ngCreateDialogJsx) {
|
|
15186
|
+
imports.add("TextField");
|
|
15187
|
+
imports.add("Dropdown");
|
|
15188
|
+
imports.add("Checkbox");
|
|
15189
|
+
}
|
|
14951
15190
|
const ngRowCommandsLiteral = _handlerAvailable && (ngHasCommandBar || ngHasContextMenu && useLiveNested) ? buildRowCommandsLiteral(contextMenuItems) : "";
|
|
14952
15191
|
const ngToolbarWired = (gridDef.commandBarItems ?? []).some(
|
|
14953
15192
|
(ci) => (ci.actionType ?? "custom") !== "custom"
|
|
@@ -14961,9 +15200,18 @@ ${childEntries.join(",\n")},
|
|
|
14961
15200
|
const ngDataverseHooks = [
|
|
14962
15201
|
...useLiveNested ? ["useDataverseQuery"] : [],
|
|
14963
15202
|
...useLiveNested && ngNeedsUpdate || ngHasHandler ? ["useUpdateRecord"] : [],
|
|
14964
|
-
...useLiveNested && ngNeedsDelete || ngHasHandler ? ["useDeleteRecord"] : []
|
|
15203
|
+
...useLiveNested && ngNeedsDelete || ngHasHandler ? ["useDeleteRecord"] : [],
|
|
15204
|
+
// Create mutation only when the create dialog is wired (the `new` case's
|
|
15205
|
+
// non-Xrm fallback) — gated like the export util import to stay byte-stable
|
|
15206
|
+
// for non-create handler grids.
|
|
15207
|
+
...ngHasHandler && createColumnsLiteral ? ["useCreateRecord"] : []
|
|
15208
|
+
];
|
|
15209
|
+
const ngCommandBarImports = [
|
|
15210
|
+
...useLiveNested && ngNeedsDelete || ngHasHandler ? [`import { useQueryClient } from '@tanstack/react-query';`] : [],
|
|
15211
|
+
// Export case (shared dispatcher → both nested + focused-view) calls
|
|
15212
|
+
// exportToFile/generateDefaultFilename when export is wired.
|
|
15213
|
+
...ngHasHandler && enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
|
|
14965
15214
|
];
|
|
14966
|
-
const ngCommandBarImports = useLiveNested && ngNeedsDelete || ngHasHandler ? [`import { useQueryClient } from '@tanstack/react-query';`] : [];
|
|
14967
15215
|
const ngParentSelectionMode = ngHasCommandBar ? gridDef.selectionMode === "single" ? "single" : "multiple" : gridDef.selectionMode ?? "none";
|
|
14968
15216
|
const ngChildSaveBack = Boolean(
|
|
14969
15217
|
useLiveNested && live && (childGridDef.isEditable ?? false)
|
|
@@ -15012,6 +15260,7 @@ ${childEntries.join(",\n")},
|
|
|
15012
15260
|
commandBarBlock: gridCommandBarBlock,
|
|
15013
15261
|
handlerBlock: ngHandlerBlock,
|
|
15014
15262
|
deleteDialogJsx: ngDeleteDialogJsx,
|
|
15263
|
+
createDialogJsx: ngCreateDialogJsx,
|
|
15015
15264
|
hasSelection: ngHasCommandBar,
|
|
15016
15265
|
hasHandler: ngHandlerBlock !== "",
|
|
15017
15266
|
dataverseHooks: ngDataverseHooks,
|
|
@@ -15067,6 +15316,7 @@ ${childEntries.join(",\n")},
|
|
|
15067
15316
|
commandBarBlock: gridCommandBarBlock,
|
|
15068
15317
|
handlerBlock: ngHandlerBlock,
|
|
15069
15318
|
deleteDialogJsx: ngDeleteDialogJsx,
|
|
15319
|
+
createDialogJsx: ngCreateDialogJsx,
|
|
15070
15320
|
hasSelection: ngHasCommandBar,
|
|
15071
15321
|
dataverseHooks: ngDataverseHooks,
|
|
15072
15322
|
commandBarImports: ngCommandBarImports,
|
|
@@ -15147,11 +15397,14 @@ ${childEntries.join(",\n")},
|
|
|
15147
15397
|
entitySetName: gridDef.dataSource.entitySetName,
|
|
15148
15398
|
primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
|
|
15149
15399
|
itemsExpr: "rows",
|
|
15150
|
-
selectionExpr: "selectedIndices"
|
|
15400
|
+
selectionExpr: "selectedIndices",
|
|
15401
|
+
exportColumnsLiteral,
|
|
15402
|
+
createColumnsLiteral
|
|
15151
15403
|
}) : "";
|
|
15152
15404
|
const cardSelectedIndicesState = cardEmitHandler ? "\n const [selectedIndices, setSelectedIndices] = React.useState<Set<number>>(new Set());" : "";
|
|
15153
15405
|
const cardRowCommandsLiteral = cardEmitRowCommands ? buildRowCommandsLiteral(contextMenuItems) : "";
|
|
15154
15406
|
const cardDeleteDialogJsx = cardEmitRowCommands && flatHasDelete || cardEmitToolbar && flatBarHasDelete ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15407
|
+
const cardCreateDialogJsx = cardEmitHandler && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15155
15408
|
const cardCommandBarBlock = cardEmitToolbar ? gridCommandBarBlock : "";
|
|
15156
15409
|
const cardSelectionModeAttr = cardEmitToolbar ? flatSelectionModeAttr : "";
|
|
15157
15410
|
const cardOnSelectionChangedAttr = cardEmitToolbar ? flatOnSelectionChangedAttr : "";
|
|
@@ -15170,20 +15423,26 @@ ${childEntries.join(",\n")},
|
|
|
15170
15423
|
handlerBlock: cardHandlerBlock,
|
|
15171
15424
|
rowCommandsLiteral: cardRowCommandsLiteral,
|
|
15172
15425
|
deleteDialogJsx: cardDeleteDialogJsx,
|
|
15426
|
+
createDialogJsx: cardCreateDialogJsx,
|
|
15173
15427
|
commandBarBlock: cardCommandBarBlock,
|
|
15174
15428
|
selectionModeAttr: cardSelectionModeAttr,
|
|
15175
15429
|
onSelectionChangedAttr: cardOnSelectionChangedAttr
|
|
15176
15430
|
})
|
|
15177
15431
|
];
|
|
15178
15432
|
_usesGridKit = true;
|
|
15179
|
-
const cardDataverseImport = cardEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
|
|
15180
|
-
if (cardDeleteDialogJsx) {
|
|
15433
|
+
const cardDataverseImport = cardEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord${createColumnsLiteral ? ", useCreateRecord" : ""} } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
|
|
15434
|
+
if (cardDeleteDialogJsx || cardCreateDialogJsx) {
|
|
15181
15435
|
imports.add("Dialog");
|
|
15182
15436
|
imports.add("DialogType");
|
|
15183
15437
|
imports.add("DialogFooter");
|
|
15184
15438
|
imports.add("PrimaryButton");
|
|
15185
15439
|
imports.add("DefaultButton");
|
|
15186
15440
|
}
|
|
15441
|
+
if (cardCreateDialogJsx) {
|
|
15442
|
+
imports.add("TextField");
|
|
15443
|
+
imports.add("Dropdown");
|
|
15444
|
+
imports.add("Checkbox");
|
|
15445
|
+
}
|
|
15187
15446
|
return {
|
|
15188
15447
|
imports,
|
|
15189
15448
|
v9Imports,
|
|
@@ -15191,7 +15450,10 @@ ${childEntries.join(",\n")},
|
|
|
15191
15450
|
extraImports: cardIsLive ? cardEmitHandler ? [
|
|
15192
15451
|
cardDataverseImport,
|
|
15193
15452
|
`import { useQueryClient } from '@tanstack/react-query';`,
|
|
15194
|
-
`import { CardGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit'
|
|
15453
|
+
`import { CardGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`,
|
|
15454
|
+
// Export case calls exportToFile/generateDefaultFilename (cardEmitHandler
|
|
15455
|
+
// here; the export case is only real when enableExport).
|
|
15456
|
+
...enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
|
|
15195
15457
|
] : [
|
|
15196
15458
|
`import { useDataverseQuery } from '../lib/dataverse';`,
|
|
15197
15459
|
`import { CardGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
|
|
@@ -15229,11 +15491,14 @@ ${childEntries.join(",\n")},
|
|
|
15229
15491
|
entitySetName: gridDef.dataSource.entitySetName,
|
|
15230
15492
|
primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
|
|
15231
15493
|
itemsExpr: "rows",
|
|
15232
|
-
selectionExpr: "selectedIndices"
|
|
15494
|
+
selectionExpr: "selectedIndices",
|
|
15495
|
+
exportColumnsLiteral,
|
|
15496
|
+
createColumnsLiteral
|
|
15233
15497
|
}) : "";
|
|
15234
15498
|
const roSelectedIndicesState = roEmitHandler ? "\n const [selectedIndices, setSelectedIndices] = React.useState<Set<number>>(new Set());" : "";
|
|
15235
15499
|
const roRowCommandsLiteral = roEmitRowCommands ? buildRowCommandsLiteral(contextMenuItems) : "";
|
|
15236
15500
|
const roDeleteDialogJsx = roEmitRowCommands && flatHasDelete ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15501
|
+
const roCreateDialogJsx = roEmitHandler && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15237
15502
|
const roCommandBarBlock = roEmitToolbar ? gridCommandBarBlock : "";
|
|
15238
15503
|
const helperComponents = [
|
|
15239
15504
|
buildLiveReadOnlySubgridBody({
|
|
@@ -15252,18 +15517,24 @@ ${childEntries.join(",\n")},
|
|
|
15252
15517
|
handlerBlock: roHandlerBlock,
|
|
15253
15518
|
rowCommandsLiteral: roRowCommandsLiteral,
|
|
15254
15519
|
deleteDialogJsx: roDeleteDialogJsx,
|
|
15520
|
+
createDialogJsx: roCreateDialogJsx,
|
|
15255
15521
|
commandBarBlock: roCommandBarBlock
|
|
15256
15522
|
})
|
|
15257
15523
|
];
|
|
15258
15524
|
if (!isV9) _usesGridKit = true;
|
|
15259
|
-
const roDataverseImport = roEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
|
|
15260
|
-
if (roDeleteDialogJsx) {
|
|
15525
|
+
const roDataverseImport = roEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord${createColumnsLiteral ? ", useCreateRecord" : ""} } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
|
|
15526
|
+
if (roDeleteDialogJsx || roCreateDialogJsx) {
|
|
15261
15527
|
imports.add("Dialog");
|
|
15262
15528
|
imports.add("DialogType");
|
|
15263
15529
|
imports.add("DialogFooter");
|
|
15264
15530
|
imports.add("PrimaryButton");
|
|
15265
15531
|
imports.add("DefaultButton");
|
|
15266
15532
|
}
|
|
15533
|
+
if (roCreateDialogJsx) {
|
|
15534
|
+
imports.add("TextField");
|
|
15535
|
+
imports.add("Dropdown");
|
|
15536
|
+
imports.add("Checkbox");
|
|
15537
|
+
}
|
|
15267
15538
|
return {
|
|
15268
15539
|
imports,
|
|
15269
15540
|
v9Imports,
|
|
@@ -15271,7 +15542,8 @@ ${childEntries.join(",\n")},
|
|
|
15271
15542
|
extraImports: isV9 ? [`import { useDataverseQuery } from '../lib/dataverse';`] : roEmitHandler ? [
|
|
15272
15543
|
roDataverseImport,
|
|
15273
15544
|
`import { useQueryClient } from '@tanstack/react-query';`,
|
|
15274
|
-
`import { ReadOnlyGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit'
|
|
15545
|
+
`import { ReadOnlyGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`,
|
|
15546
|
+
...enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
|
|
15275
15547
|
] : [
|
|
15276
15548
|
`import { useDataverseQuery } from '../lib/dataverse';`,
|
|
15277
15549
|
`import { ReadOnlyGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
|
|
@@ -15331,11 +15603,14 @@ const ${liveWrapperName}: React.FC = () => {
|
|
|
15331
15603
|
entitySetName: gridDef.dataSource.entitySetName,
|
|
15332
15604
|
primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
|
|
15333
15605
|
itemsExpr: "keyedItems",
|
|
15334
|
-
selectionExpr: "selectedIndices"
|
|
15606
|
+
selectionExpr: "selectedIndices",
|
|
15607
|
+
exportColumnsLiteral,
|
|
15608
|
+
createColumnsLiteral
|
|
15335
15609
|
}) : "";
|
|
15336
15610
|
const edSelectedIndicesState = edEmitHandler ? "\n const [selectedIndices, setSelectedIndices] = React.useState<Set<number>>(new Set());" : "";
|
|
15337
15611
|
const edRowCommandsLiteral = edEmitRowCommands ? buildRowCommandsLiteral(contextMenuItems) : "";
|
|
15338
15612
|
const edDeleteDialogJsx = edEmitRowCommands && flatHasDelete || edEmitToolbar && flatBarHasDelete ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15613
|
+
const edCreateDialogJsx = edEmitHandler && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15339
15614
|
const edCommandBarBlock = edEmitToolbar ? gridCommandBarBlock : "";
|
|
15340
15615
|
const edSelectionModeAttr = edEmitToolbar ? flatSelectionModeAttr : "";
|
|
15341
15616
|
const edOnSelectionChangedAttr = edEmitToolbar ? flatOnSelectionChangedAttr : "";
|
|
@@ -15352,6 +15627,7 @@ const ${liveWrapperName}: React.FC = () => {
|
|
|
15352
15627
|
handlerBlock: edHandlerBlock,
|
|
15353
15628
|
rowCommandsLiteral: edRowCommandsLiteral,
|
|
15354
15629
|
deleteDialogJsx: edDeleteDialogJsx,
|
|
15630
|
+
createDialogJsx: edCreateDialogJsx,
|
|
15355
15631
|
commandBarBlock: edCommandBarBlock,
|
|
15356
15632
|
selectionModeAttr: edSelectionModeAttr,
|
|
15357
15633
|
onSelectionChangedAttr: edOnSelectionChangedAttr
|
|
@@ -15363,18 +15639,24 @@ const ${liveWrapperName}: React.FC = () => {
|
|
|
15363
15639
|
const jsx3 = editableUsesLiveData && liveWrapperName ? `{/* Subgrid: ${safeEntityName} \u2014 ${safeGridName} (editable, live) */}${todoComment}
|
|
15364
15640
|
<${liveWrapperName} />` : `{/* Subgrid: ${safeEntityName} \u2014 ${safeGridName} (editable) */}${todoComment}
|
|
15365
15641
|
<${componentName} items={${itemsLiteral}} />`;
|
|
15366
|
-
const edDataverseImport = edEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord } from '../lib/dataverse';` : `import { useDataverseQuery, useUpdateRecord } from '../lib/dataverse';`;
|
|
15367
|
-
if (edDeleteDialogJsx) {
|
|
15642
|
+
const edDataverseImport = edEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord${createColumnsLiteral ? ", useCreateRecord" : ""} } from '../lib/dataverse';` : `import { useDataverseQuery, useUpdateRecord } from '../lib/dataverse';`;
|
|
15643
|
+
if (edDeleteDialogJsx || edCreateDialogJsx) {
|
|
15368
15644
|
imports.add("Dialog");
|
|
15369
15645
|
imports.add("DialogType");
|
|
15370
15646
|
imports.add("DialogFooter");
|
|
15371
15647
|
imports.add("PrimaryButton");
|
|
15372
15648
|
imports.add("DefaultButton");
|
|
15373
15649
|
}
|
|
15650
|
+
if (edCreateDialogJsx) {
|
|
15651
|
+
imports.add("TextField");
|
|
15652
|
+
imports.add("Dropdown");
|
|
15653
|
+
imports.add("Checkbox");
|
|
15654
|
+
}
|
|
15374
15655
|
const extraImports2 = editableUsesLiveData ? edEmitHandler ? [
|
|
15375
15656
|
edDataverseImport,
|
|
15376
15657
|
`import { useQueryClient } from '@tanstack/react-query';`,
|
|
15377
|
-
`import { DataGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit'
|
|
15658
|
+
`import { DataGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`,
|
|
15659
|
+
...enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
|
|
15378
15660
|
] : [
|
|
15379
15661
|
`import { useDataverseQuery, useUpdateRecord } from '../lib/dataverse';`,
|
|
15380
15662
|
`import { DataGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
|
package/dist/index.mjs
CHANGED
|
@@ -12790,6 +12790,7 @@ function buildV8Body(args) {
|
|
|
12790
12790
|
handlerBlock = "",
|
|
12791
12791
|
rowCommandsLiteral = "",
|
|
12792
12792
|
deleteDialogJsx = "",
|
|
12793
|
+
createDialogJsx = "",
|
|
12793
12794
|
commandBarBlock = "",
|
|
12794
12795
|
selectionModeAttr = "",
|
|
12795
12796
|
onSelectionChangedAttr = ""
|
|
@@ -12816,7 +12817,7 @@ const ${componentName}: React.FC<{ items: Record<string, unknown>[] }> = ({ item
|
|
|
12816
12817
|
editable
|
|
12817
12818
|
editTrigger="click"
|
|
12818
12819
|
getKey={(it) => String((it as { __rowIndex?: number }).__rowIndex ?? '')}${selectionModeAttr}${onSelectionChangedAttr}${onValueChangeAttr}${rowCommandsAttr}
|
|
12819
|
-
/>${editableAggregate}${editablePagination}${saveStatusJsx}${deleteDialogJsx}
|
|
12820
|
+
/>${editableAggregate}${editablePagination}${saveStatusJsx}${deleteDialogJsx}${createDialogJsx}
|
|
12820
12821
|
</div>
|
|
12821
12822
|
);
|
|
12822
12823
|
};`;
|
|
@@ -12869,6 +12870,7 @@ function buildLiveReadOnlyV8Body(args) {
|
|
|
12869
12870
|
handlerBlock = "",
|
|
12870
12871
|
rowCommandsLiteral = "",
|
|
12871
12872
|
deleteDialogJsx = "",
|
|
12873
|
+
createDialogJsx = "",
|
|
12872
12874
|
commandBarBlock = ""
|
|
12873
12875
|
} = args;
|
|
12874
12876
|
const rowCommandsAttr = rowCommandsLiteral ? `
|
|
@@ -12897,7 +12899,7 @@ const ${helperName}: React.FC = () => {
|
|
|
12897
12899
|
] as ColumnDef[]}
|
|
12898
12900
|
registry={registry}
|
|
12899
12901
|
getKey={(it) => String((it as { __rowIndex?: number }).__rowIndex ?? '')}${rowCommandsAttr}
|
|
12900
|
-
/>${flatAggregateInner}${flatPaginationInner}${deleteDialogJsx}
|
|
12902
|
+
/>${flatAggregateInner}${flatPaginationInner}${deleteDialogJsx}${createDialogJsx}
|
|
12901
12903
|
</div>
|
|
12902
12904
|
);
|
|
12903
12905
|
};`;
|
|
@@ -12943,6 +12945,7 @@ function buildCardV8LiveBody(args) {
|
|
|
12943
12945
|
handlerBlock = "",
|
|
12944
12946
|
rowCommandsLiteral = "",
|
|
12945
12947
|
deleteDialogJsx = "",
|
|
12948
|
+
createDialogJsx = "",
|
|
12946
12949
|
commandBarBlock = "",
|
|
12947
12950
|
selectionModeAttr = "",
|
|
12948
12951
|
onSelectionChangedAttr = ""
|
|
@@ -12974,7 +12977,7 @@ const ${helperName}: React.FC = () => {
|
|
|
12974
12977
|
registry={registry}
|
|
12975
12978
|
getKey={(it) => String((it as { __rowIndex?: number }).__rowIndex ?? '')}${selectionModeAttr}${onSelectionChangedAttr}
|
|
12976
12979
|
card={{ ${cardConfigLiteral} }}${rowCommandsAttr}
|
|
12977
|
-
/>${flatAggregateInner}${flatPaginationInner}${deleteDialogJsx}
|
|
12980
|
+
/>${flatAggregateInner}${flatPaginationInner}${deleteDialogJsx}${createDialogJsx}
|
|
12978
12981
|
</div>
|
|
12979
12982
|
);
|
|
12980
12983
|
};`;
|
|
@@ -13300,6 +13303,7 @@ function buildNestedSubgridBody(args) {
|
|
|
13300
13303
|
commandBarBlock,
|
|
13301
13304
|
handlerBlock,
|
|
13302
13305
|
deleteDialogJsx,
|
|
13306
|
+
createDialogJsx,
|
|
13303
13307
|
hasSelection,
|
|
13304
13308
|
hasHandler,
|
|
13305
13309
|
dataverseHooks,
|
|
@@ -13355,7 +13359,7 @@ const ${componentName}: React.FC<{ items: Record<string, unknown>[] }> = ({ item
|
|
|
13355
13359
|
calloutMaxRows: ${calloutMaxRows},
|
|
13356
13360
|
hoverDelay: ${hoverDelay},${childEditableLiteral}
|
|
13357
13361
|
}}
|
|
13358
|
-
/>${deleteDialogJsx}${childSaveStatusJsx}
|
|
13362
|
+
/>${deleteDialogJsx}${createDialogJsx}${childSaveStatusJsx}
|
|
13359
13363
|
</div>
|
|
13360
13364
|
);
|
|
13361
13365
|
};`;
|
|
@@ -13398,6 +13402,7 @@ function buildFocusedViewSubgridBody(args) {
|
|
|
13398
13402
|
commandBarBlock,
|
|
13399
13403
|
handlerBlock,
|
|
13400
13404
|
deleteDialogJsx,
|
|
13405
|
+
createDialogJsx,
|
|
13401
13406
|
hasSelection,
|
|
13402
13407
|
hasHandler,
|
|
13403
13408
|
dataverseHooks,
|
|
@@ -13451,7 +13456,7 @@ const ${componentName}: React.FC<{ items: Record<string, unknown>[] }> = ({ item
|
|
|
13451
13456
|
childSelectionMode: ${JSON.stringify(childSelectionMode)},
|
|
13452
13457
|
childSelectionGating: ${JSON.stringify(childSelectionGating)},${childEditableLiteral}
|
|
13453
13458
|
}}
|
|
13454
|
-
/>${deleteDialogJsx}${childSaveStatusJsx}
|
|
13459
|
+
/>${deleteDialogJsx}${createDialogJsx}${childSaveStatusJsx}
|
|
13455
13460
|
</div>
|
|
13456
13461
|
);
|
|
13457
13462
|
};`;
|
|
@@ -14230,14 +14235,182 @@ function effectiveMinSelection(item) {
|
|
|
14230
14235
|
if (typeof item.minSelectionCount === "number") return item.minSelectionCount;
|
|
14231
14236
|
return DEFAULT_MIN_SEL_BY_ACTION[item.actionType ?? "custom"] ?? 0;
|
|
14232
14237
|
}
|
|
14238
|
+
function buildExportColumnsLiteral(cols) {
|
|
14239
|
+
return cols.map(
|
|
14240
|
+
(c) => `{ key: ${JSON.stringify(c.fieldName)}, name: ${JSON.stringify(c.displayName ?? c.fieldName)} }`
|
|
14241
|
+
).join(", ");
|
|
14242
|
+
}
|
|
14243
|
+
var CREATE_FIELD_DENYLIST = /* @__PURE__ */ new Set([
|
|
14244
|
+
"createdon",
|
|
14245
|
+
"createdby",
|
|
14246
|
+
"createdonbehalfby",
|
|
14247
|
+
"modifiedon",
|
|
14248
|
+
"modifiedby",
|
|
14249
|
+
"modifiedonbehalfby",
|
|
14250
|
+
"owningbusinessunit",
|
|
14251
|
+
"owningteam",
|
|
14252
|
+
"owninguser",
|
|
14253
|
+
"ownerid",
|
|
14254
|
+
"versionnumber",
|
|
14255
|
+
"overriddencreatedon",
|
|
14256
|
+
"importsequencenumber",
|
|
14257
|
+
"timezoneruleversionnumber",
|
|
14258
|
+
"utcconversiontimezonecode",
|
|
14259
|
+
"statecode",
|
|
14260
|
+
"statuscode"
|
|
14261
|
+
]);
|
|
14262
|
+
function buildCreateFields(cols, primaryIdAttribute) {
|
|
14263
|
+
const primaryLower = (primaryIdAttribute ?? "").toLowerCase();
|
|
14264
|
+
const entries = [];
|
|
14265
|
+
for (const c of cols) {
|
|
14266
|
+
if (!c.fieldName) continue;
|
|
14267
|
+
const logical = (c.attributeLogicalName ?? c.fieldName).toLowerCase();
|
|
14268
|
+
if (logical === primaryLower) continue;
|
|
14269
|
+
if (c.isLocked) continue;
|
|
14270
|
+
if (c.dataType === "lookup" || c.dataType === "image") continue;
|
|
14271
|
+
if (CREATE_FIELD_DENYLIST.has(logical)) continue;
|
|
14272
|
+
const label = c.displayName ?? c.fieldName;
|
|
14273
|
+
let kind = "text";
|
|
14274
|
+
let inputType;
|
|
14275
|
+
let optionsLit = "";
|
|
14276
|
+
switch (c.dataType) {
|
|
14277
|
+
case "numeric":
|
|
14278
|
+
case "currency":
|
|
14279
|
+
kind = "number";
|
|
14280
|
+
inputType = "number";
|
|
14281
|
+
break;
|
|
14282
|
+
case "boolean":
|
|
14283
|
+
kind = "boolean";
|
|
14284
|
+
break;
|
|
14285
|
+
case "date":
|
|
14286
|
+
kind = "date";
|
|
14287
|
+
inputType = "date";
|
|
14288
|
+
break;
|
|
14289
|
+
case "datetime":
|
|
14290
|
+
kind = "date";
|
|
14291
|
+
inputType = "datetime-local";
|
|
14292
|
+
break;
|
|
14293
|
+
case "optionset": {
|
|
14294
|
+
const opts = c.rendererConfig?.options ?? [];
|
|
14295
|
+
if (opts.length === 0) {
|
|
14296
|
+
kind = "number";
|
|
14297
|
+
inputType = "number";
|
|
14298
|
+
} else {
|
|
14299
|
+
kind = "optionset";
|
|
14300
|
+
optionsLit = opts.map(
|
|
14301
|
+
(o) => `{ key: ${JSON.stringify(o.value)}, text: ${JSON.stringify(o.label)} }`
|
|
14302
|
+
).join(", ");
|
|
14303
|
+
}
|
|
14304
|
+
break;
|
|
14305
|
+
}
|
|
14306
|
+
case "email":
|
|
14307
|
+
kind = "text";
|
|
14308
|
+
inputType = "email";
|
|
14309
|
+
break;
|
|
14310
|
+
case "phone":
|
|
14311
|
+
kind = "text";
|
|
14312
|
+
inputType = "tel";
|
|
14313
|
+
break;
|
|
14314
|
+
case "url":
|
|
14315
|
+
kind = "text";
|
|
14316
|
+
inputType = "url";
|
|
14317
|
+
break;
|
|
14318
|
+
default:
|
|
14319
|
+
kind = "text";
|
|
14320
|
+
break;
|
|
14321
|
+
}
|
|
14322
|
+
const payloadKey = c.attributeLogicalName ?? c.fieldName;
|
|
14323
|
+
const parts = [
|
|
14324
|
+
`key: ${JSON.stringify(payloadKey)}`,
|
|
14325
|
+
`label: ${JSON.stringify(label)}`,
|
|
14326
|
+
`kind: ${JSON.stringify(kind)}`
|
|
14327
|
+
];
|
|
14328
|
+
if (inputType) parts.push(`inputType: ${JSON.stringify(inputType)}`);
|
|
14329
|
+
if (kind === "optionset") parts.push(`options: [${optionsLit}]`);
|
|
14330
|
+
entries.push(`{ ${parts.join(", ")} }`);
|
|
14331
|
+
}
|
|
14332
|
+
return entries.join(", ");
|
|
14333
|
+
}
|
|
14233
14334
|
function buildGridCommandHandlerBlock(args) {
|
|
14234
14335
|
const {
|
|
14235
14336
|
entityName,
|
|
14236
14337
|
entitySetName,
|
|
14237
14338
|
primaryIdAttribute,
|
|
14238
14339
|
itemsExpr,
|
|
14239
|
-
selectionExpr
|
|
14340
|
+
selectionExpr,
|
|
14341
|
+
exportColumnsLiteral,
|
|
14342
|
+
createColumnsLiteral
|
|
14240
14343
|
} = args;
|
|
14344
|
+
const exportCase = exportColumnsLiteral ? `{
|
|
14345
|
+
const exportColumns = [${exportColumnsLiteral}];
|
|
14346
|
+
const rowsToExport = (indices.length ? indices.map((i) => items[i]) : items.slice())
|
|
14347
|
+
.filter((r): r is Record<string, unknown> => Boolean(r))
|
|
14348
|
+
.map((r) => {
|
|
14349
|
+
const formattedRow: Record<string, unknown> = {};
|
|
14350
|
+
for (const col of exportColumns) {
|
|
14351
|
+
formattedRow[col.key] = r[col.key + '@OData.Community.Display.V1.FormattedValue'] ?? r[col.key];
|
|
14352
|
+
}
|
|
14353
|
+
return formattedRow;
|
|
14354
|
+
});
|
|
14355
|
+
exportToFile(rowsToExport, exportColumns, 'csv', generateDefaultFilename(${JSON.stringify(entityName + "-export")}));
|
|
14356
|
+
return;
|
|
14357
|
+
}` : `{
|
|
14358
|
+
console.warn('[grid] Export to Excel is not yet wired in generated forms.');
|
|
14359
|
+
return;
|
|
14360
|
+
}`;
|
|
14361
|
+
const createStateBlock = createColumnsLiteral ? `
|
|
14362
|
+
const gridCreateMutation = useCreateRecord<Record<string, unknown>>('${entitySetName}');
|
|
14363
|
+
const [pendingCreate, setPendingCreate] = React.useState(false);
|
|
14364
|
+
const [createValues, setCreateValues] = React.useState<Record<string, unknown>>({});
|
|
14365
|
+
const [createError, setCreateError] = React.useState<string | null>(null);
|
|
14366
|
+
// Derived from the grid's columns; lookups/images/system fields are excluded by
|
|
14367
|
+
// the emitter. The payload is best-effort and validated server-side (errors show
|
|
14368
|
+
// in \`createError\`). NOTE: against a mock/standalone host (no Dataverse token),
|
|
14369
|
+
// MockApiService.createRecord no-ops and resolves \u2014 the dialog closes as if it
|
|
14370
|
+
// succeeded but nothing is created. Xrm is the primary path in a model-driven host.
|
|
14371
|
+
const createFields = React.useMemo<Array<{ key: string; label: string; kind: 'text' | 'number' | 'boolean' | 'optionset' | 'date'; inputType?: string; options?: Array<{ key: number; text: string }> }>>(
|
|
14372
|
+
() => [${createColumnsLiteral}],
|
|
14373
|
+
[],
|
|
14374
|
+
);
|
|
14375
|
+
const submitCreate = React.useCallback(async () => {
|
|
14376
|
+
setCreateError(null);
|
|
14377
|
+
const payload: Record<string, unknown> = {};
|
|
14378
|
+
for (const f of createFields) {
|
|
14379
|
+
const raw = createValues[f.key];
|
|
14380
|
+
if (raw === undefined || raw === null || raw === '') continue;
|
|
14381
|
+
payload[f.key] = (f.kind === 'number' || f.kind === 'optionset') ? Number(raw) : raw;
|
|
14382
|
+
}
|
|
14383
|
+
if (Object.keys(payload).length === 0) {
|
|
14384
|
+
setCreateError('Enter at least one value before creating.');
|
|
14385
|
+
return;
|
|
14386
|
+
}
|
|
14387
|
+
try {
|
|
14388
|
+
await gridCreateMutation.mutateAsync(payload);
|
|
14389
|
+
setPendingCreate(false);
|
|
14390
|
+
setCreateValues({});
|
|
14391
|
+
} catch (err) {
|
|
14392
|
+
setCreateError((err as Error)?.message ?? 'Create failed.');
|
|
14393
|
+
}
|
|
14394
|
+
}, [createValues, gridCreateMutation, createFields]);` : "";
|
|
14395
|
+
const newCase = createColumnsLiteral ? `{
|
|
14396
|
+
if (xrm?.Navigation?.openForm) {
|
|
14397
|
+
await xrm.Navigation.openForm({ entityName: '${entityName}', useQuickCreateForm: false });
|
|
14398
|
+
await refreshGrid();
|
|
14399
|
+
} else {
|
|
14400
|
+
setCreateError(null);
|
|
14401
|
+
setCreateValues({});
|
|
14402
|
+
setPendingCreate(true);
|
|
14403
|
+
}
|
|
14404
|
+
return;
|
|
14405
|
+
}` : `{
|
|
14406
|
+
if (xrm?.Navigation?.openForm) {
|
|
14407
|
+
await xrm.Navigation.openForm({ entityName: '${entityName}', useQuickCreateForm: false });
|
|
14408
|
+
await refreshGrid();
|
|
14409
|
+
} else {
|
|
14410
|
+
console.warn('[grid] New action requires Xrm.Navigation.openForm; not available in this host.');
|
|
14411
|
+
}
|
|
14412
|
+
return;
|
|
14413
|
+
}`;
|
|
14241
14414
|
return `
|
|
14242
14415
|
const queryClient = useQueryClient();
|
|
14243
14416
|
const gridUpdateMutation = useUpdateRecord<Record<string, unknown>>('${entitySetName}');
|
|
@@ -14261,7 +14434,7 @@ function buildGridCommandHandlerBlock(args) {
|
|
|
14261
14434
|
} catch (err) {
|
|
14262
14435
|
setDeleteError((err as Error)?.message ?? 'Delete failed.');
|
|
14263
14436
|
}
|
|
14264
|
-
}, [pendingDelete, gridDeleteMutation])
|
|
14437
|
+
}, [pendingDelete, gridDeleteMutation]);${createStateBlock}
|
|
14265
14438
|
const handleGridCommand = React.useCallback(async (actionType: string, overrideRowIndex?: number) => {
|
|
14266
14439
|
const xrm = (typeof window !== 'undefined' ? (window as unknown as { Xrm?: any }).Xrm : undefined);
|
|
14267
14440
|
const items = ${itemsExpr} as ReadonlyArray<Record<string, unknown>>;
|
|
@@ -14286,15 +14459,7 @@ function buildGridCommandHandlerBlock(args) {
|
|
|
14286
14459
|
return true;
|
|
14287
14460
|
};
|
|
14288
14461
|
switch (actionType) {
|
|
14289
|
-
case 'new': {
|
|
14290
|
-
if (xrm?.Navigation?.openForm) {
|
|
14291
|
-
await xrm.Navigation.openForm({ entityName: '${entityName}', useQuickCreateForm: false });
|
|
14292
|
-
await refreshGrid();
|
|
14293
|
-
} else {
|
|
14294
|
-
console.warn('[grid] New action requires Xrm.Navigation.openForm; not available in this host.');
|
|
14295
|
-
}
|
|
14296
|
-
return;
|
|
14297
|
-
}
|
|
14462
|
+
case 'new': ${newCase}
|
|
14298
14463
|
case 'addExisting': {
|
|
14299
14464
|
if (xrm?.Navigation?.openForm) {
|
|
14300
14465
|
await xrm.Navigation.openForm({ entityName: '${entityName}' });
|
|
@@ -14339,10 +14504,7 @@ function buildGridCommandHandlerBlock(args) {
|
|
|
14339
14504
|
await refreshGrid();
|
|
14340
14505
|
return;
|
|
14341
14506
|
}
|
|
14342
|
-
case 'export': {
|
|
14343
|
-
console.warn('[grid] Export to Excel is not yet wired in generated forms.');
|
|
14344
|
-
return;
|
|
14345
|
-
}
|
|
14507
|
+
case 'export': ${exportCase}
|
|
14346
14508
|
case 'bulkEdit': {
|
|
14347
14509
|
if (!requireSel('bulk edit')) return;
|
|
14348
14510
|
if (xrm?.Navigation?.openBulkEditForm) {
|
|
@@ -14359,7 +14521,7 @@ function buildGridCommandHandlerBlock(args) {
|
|
|
14359
14521
|
}, [${itemsExpr}, ${selectionExpr}, gridUpdateMutation, refreshGrid]);`;
|
|
14360
14522
|
}
|
|
14361
14523
|
function buildRowCommandsLiteral(contextMenuItems) {
|
|
14362
|
-
return contextMenuItems.filter((ci) => (ci.actionType ?? "custom") !== "custom").map(
|
|
14524
|
+
return contextMenuItems.filter((ci) => (ci.actionType ?? "custom") !== "custom").filter((ci) => ci.actionType !== "new").map(
|
|
14363
14525
|
(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); } }`
|
|
14364
14526
|
).join(",\n ");
|
|
14365
14527
|
}
|
|
@@ -14395,6 +14557,68 @@ function buildGridDeleteDialogJsx(entityName) {
|
|
|
14395
14557
|
</DialogFooter>
|
|
14396
14558
|
</Dialog>`;
|
|
14397
14559
|
}
|
|
14560
|
+
function buildGridCreateDialogJsx(entityName) {
|
|
14561
|
+
return `
|
|
14562
|
+
<Dialog
|
|
14563
|
+
hidden={!pendingCreate}
|
|
14564
|
+
onDismiss={() => { if (!gridCreateMutation.isPending) setPendingCreate(false); }}
|
|
14565
|
+
dialogContentProps={{
|
|
14566
|
+
type: DialogType.normal,
|
|
14567
|
+
title: ${JSON.stringify("Create " + entityName)},
|
|
14568
|
+
}}
|
|
14569
|
+
modalProps={{ isBlocking: true }}
|
|
14570
|
+
>
|
|
14571
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
|
|
14572
|
+
{createFields.map((f) => {
|
|
14573
|
+
if (f.kind === 'boolean') {
|
|
14574
|
+
return (
|
|
14575
|
+
<Checkbox
|
|
14576
|
+
key={f.key}
|
|
14577
|
+
label={f.label}
|
|
14578
|
+
checked={createValues[f.key] === true}
|
|
14579
|
+
onChange={(_ev, checked) => setCreateValues((prev) => ({ ...prev, [f.key]: !!checked }))}
|
|
14580
|
+
/>
|
|
14581
|
+
);
|
|
14582
|
+
}
|
|
14583
|
+
if (f.kind === 'optionset') {
|
|
14584
|
+
return (
|
|
14585
|
+
<Dropdown
|
|
14586
|
+
key={f.key}
|
|
14587
|
+
label={f.label}
|
|
14588
|
+
options={f.options ?? []}
|
|
14589
|
+
selectedKey={(createValues[f.key] as number | undefined) ?? null}
|
|
14590
|
+
onChange={(_ev, opt) => setCreateValues((prev) => ({ ...prev, [f.key]: opt?.key }))}
|
|
14591
|
+
/>
|
|
14592
|
+
);
|
|
14593
|
+
}
|
|
14594
|
+
return (
|
|
14595
|
+
<TextField
|
|
14596
|
+
key={f.key}
|
|
14597
|
+
label={f.label}
|
|
14598
|
+
type={f.inputType ?? 'text'}
|
|
14599
|
+
value={createValues[f.key] == null ? '' : String(createValues[f.key])}
|
|
14600
|
+
onChange={(_ev, val) => setCreateValues((prev) => ({ ...prev, [f.key]: val }))}
|
|
14601
|
+
/>
|
|
14602
|
+
);
|
|
14603
|
+
})}
|
|
14604
|
+
</div>
|
|
14605
|
+
{createError && (
|
|
14606
|
+
<div style={{ color: '#a4262c', fontSize: 12, marginTop: 8 }}>{createError}</div>
|
|
14607
|
+
)}
|
|
14608
|
+
<DialogFooter>
|
|
14609
|
+
<PrimaryButton
|
|
14610
|
+
onClick={submitCreate}
|
|
14611
|
+
text={gridCreateMutation.isPending ? 'Creating\u2026' : 'Create'}
|
|
14612
|
+
disabled={gridCreateMutation.isPending}
|
|
14613
|
+
/>
|
|
14614
|
+
<DefaultButton
|
|
14615
|
+
onClick={() => setPendingCreate(false)}
|
|
14616
|
+
text="Cancel"
|
|
14617
|
+
disabled={gridCreateMutation.isPending}
|
|
14618
|
+
/>
|
|
14619
|
+
</DialogFooter>
|
|
14620
|
+
</Dialog>`;
|
|
14621
|
+
}
|
|
14398
14622
|
function buildV9RenderCell(col) {
|
|
14399
14623
|
const safeFieldName = JSON.stringify(col.fieldName);
|
|
14400
14624
|
switch (col.rendererType) {
|
|
@@ -14772,6 +14996,8 @@ function generateLinkedSubgrid(gridDef, entityName, imports, library = "fluent-v
|
|
|
14772
14996
|
// need the dispatcher in scope even when no wired commandBarItems exist.
|
|
14773
14997
|
!!gridDef.toolbar?.showRefresh)
|
|
14774
14998
|
);
|
|
14999
|
+
const enableExport = _handlerAvailable && (showExport || barItems.some((ci) => ci.actionType === "export") || contextMenuItems.some((ci) => ci.actionType === "export"));
|
|
15000
|
+
const enableCreate = _handlerAvailable && barItems.some((ci) => ci.actionType === "new");
|
|
14775
15001
|
if (gridDef.showCommandBar && (barItems.length > 0 || showSearch || hasToolbarIcons)) {
|
|
14776
15002
|
if (showSearch) imports.add("SearchBox");
|
|
14777
15003
|
imports.add("DefaultButton");
|
|
@@ -14823,10 +15049,12 @@ function generateLinkedSubgrid(gridDef, entityName, imports, library = "fluent-v
|
|
|
14823
15049
|
toolbarIcons.push(
|
|
14824
15050
|
`<IconButton iconProps={{ iconName: 'ColumnOptions' }} title="Column chooser" ariaLabel="Column chooser" />`
|
|
14825
15051
|
);
|
|
14826
|
-
if (showExport)
|
|
15052
|
+
if (showExport) {
|
|
15053
|
+
const exportOnClick = _handlerAvailable ? ` onClick={() => { void handleGridCommand('export'); }}` : "";
|
|
14827
15054
|
toolbarIcons.push(
|
|
14828
|
-
`<IconButton iconProps={{ iconName: 'Download' }} title="Export" ariaLabel="Export" />`
|
|
15055
|
+
`<IconButton iconProps={{ iconName: 'Download' }} title="Export" ariaLabel="Export"${exportOnClick} />`
|
|
14829
15056
|
);
|
|
15057
|
+
}
|
|
14830
15058
|
if (showRefresh) {
|
|
14831
15059
|
const refreshOnClick = _handlerAvailable ? ` onClick={() => { void handleGridCommand('refresh'); }}` : "";
|
|
14832
15060
|
toolbarIcons.push(
|
|
@@ -14850,6 +15078,9 @@ function generateLinkedSubgrid(gridDef, entityName, imports, library = "fluent-v
|
|
|
14850
15078
|
const childGridDef = gridDef.nestedGridId ? _currentGridCustomizers.find((g) => g.id === gridDef.nestedGridId) : void 0;
|
|
14851
15079
|
const isGridKitNested = !!childGridDef;
|
|
14852
15080
|
const visibleCols = [...gridDef.columns].filter((c) => c.isVisible).sort((a, b) => a.order - b.order);
|
|
15081
|
+
const exportColumnsLiteral = enableExport ? buildExportColumnsLiteral(visibleCols) : "";
|
|
15082
|
+
const primaryIdAttr = resolvePrimaryIdAttribute(gridDef);
|
|
15083
|
+
const createColumnsLiteral = enableCreate ? buildCreateFields(visibleCols, primaryIdAttr) : "";
|
|
14853
15084
|
visibleCols.filter((c) => c.rendererType !== "text");
|
|
14854
15085
|
const colEntries = isGridKitNested ? "" : generateColumnEntries(visibleCols, library);
|
|
14855
15086
|
const selectionMode = gridDef.selectionMode === "multiple" ? "SelectionMode.multiple" : gridDef.selectionMode === "single" ? "SelectionMode.single" : "SelectionMode.none";
|
|
@@ -14915,19 +15146,27 @@ ${childEntries.join(",\n")},
|
|
|
14915
15146
|
entitySetName: gridDef.dataSource.entitySetName,
|
|
14916
15147
|
primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
|
|
14917
15148
|
itemsExpr: "keyedItems",
|
|
14918
|
-
selectionExpr: "selectedIndices"
|
|
15149
|
+
selectionExpr: "selectedIndices",
|
|
15150
|
+
exportColumnsLiteral,
|
|
15151
|
+
createColumnsLiteral
|
|
14919
15152
|
}) : "";
|
|
14920
15153
|
const ngHasDelete = (gridDef.commandBarItems ?? []).some(
|
|
14921
15154
|
(ci) => ci.actionType === "delete"
|
|
14922
15155
|
);
|
|
14923
15156
|
const ngDeleteDialogJsx = ngHasDelete && ngHandlerBlock !== "" ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
14924
|
-
|
|
15157
|
+
const ngCreateDialogJsx = ngHandlerBlock !== "" && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15158
|
+
if (ngDeleteDialogJsx || ngCreateDialogJsx) {
|
|
14925
15159
|
imports.add("Dialog");
|
|
14926
15160
|
imports.add("DialogType");
|
|
14927
15161
|
imports.add("DialogFooter");
|
|
14928
15162
|
imports.add("PrimaryButton");
|
|
14929
15163
|
imports.add("DefaultButton");
|
|
14930
15164
|
}
|
|
15165
|
+
if (ngCreateDialogJsx) {
|
|
15166
|
+
imports.add("TextField");
|
|
15167
|
+
imports.add("Dropdown");
|
|
15168
|
+
imports.add("Checkbox");
|
|
15169
|
+
}
|
|
14931
15170
|
const ngRowCommandsLiteral = _handlerAvailable && (ngHasCommandBar || ngHasContextMenu && useLiveNested) ? buildRowCommandsLiteral(contextMenuItems) : "";
|
|
14932
15171
|
const ngToolbarWired = (gridDef.commandBarItems ?? []).some(
|
|
14933
15172
|
(ci) => (ci.actionType ?? "custom") !== "custom"
|
|
@@ -14941,9 +15180,18 @@ ${childEntries.join(",\n")},
|
|
|
14941
15180
|
const ngDataverseHooks = [
|
|
14942
15181
|
...useLiveNested ? ["useDataverseQuery"] : [],
|
|
14943
15182
|
...useLiveNested && ngNeedsUpdate || ngHasHandler ? ["useUpdateRecord"] : [],
|
|
14944
|
-
...useLiveNested && ngNeedsDelete || ngHasHandler ? ["useDeleteRecord"] : []
|
|
15183
|
+
...useLiveNested && ngNeedsDelete || ngHasHandler ? ["useDeleteRecord"] : [],
|
|
15184
|
+
// Create mutation only when the create dialog is wired (the `new` case's
|
|
15185
|
+
// non-Xrm fallback) — gated like the export util import to stay byte-stable
|
|
15186
|
+
// for non-create handler grids.
|
|
15187
|
+
...ngHasHandler && createColumnsLiteral ? ["useCreateRecord"] : []
|
|
15188
|
+
];
|
|
15189
|
+
const ngCommandBarImports = [
|
|
15190
|
+
...useLiveNested && ngNeedsDelete || ngHasHandler ? [`import { useQueryClient } from '@tanstack/react-query';`] : [],
|
|
15191
|
+
// Export case (shared dispatcher → both nested + focused-view) calls
|
|
15192
|
+
// exportToFile/generateDefaultFilename when export is wired.
|
|
15193
|
+
...ngHasHandler && enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
|
|
14945
15194
|
];
|
|
14946
|
-
const ngCommandBarImports = useLiveNested && ngNeedsDelete || ngHasHandler ? [`import { useQueryClient } from '@tanstack/react-query';`] : [];
|
|
14947
15195
|
const ngParentSelectionMode = ngHasCommandBar ? gridDef.selectionMode === "single" ? "single" : "multiple" : gridDef.selectionMode ?? "none";
|
|
14948
15196
|
const ngChildSaveBack = Boolean(
|
|
14949
15197
|
useLiveNested && live && (childGridDef.isEditable ?? false)
|
|
@@ -14992,6 +15240,7 @@ ${childEntries.join(",\n")},
|
|
|
14992
15240
|
commandBarBlock: gridCommandBarBlock,
|
|
14993
15241
|
handlerBlock: ngHandlerBlock,
|
|
14994
15242
|
deleteDialogJsx: ngDeleteDialogJsx,
|
|
15243
|
+
createDialogJsx: ngCreateDialogJsx,
|
|
14995
15244
|
hasSelection: ngHasCommandBar,
|
|
14996
15245
|
hasHandler: ngHandlerBlock !== "",
|
|
14997
15246
|
dataverseHooks: ngDataverseHooks,
|
|
@@ -15047,6 +15296,7 @@ ${childEntries.join(",\n")},
|
|
|
15047
15296
|
commandBarBlock: gridCommandBarBlock,
|
|
15048
15297
|
handlerBlock: ngHandlerBlock,
|
|
15049
15298
|
deleteDialogJsx: ngDeleteDialogJsx,
|
|
15299
|
+
createDialogJsx: ngCreateDialogJsx,
|
|
15050
15300
|
hasSelection: ngHasCommandBar,
|
|
15051
15301
|
dataverseHooks: ngDataverseHooks,
|
|
15052
15302
|
commandBarImports: ngCommandBarImports,
|
|
@@ -15127,11 +15377,14 @@ ${childEntries.join(",\n")},
|
|
|
15127
15377
|
entitySetName: gridDef.dataSource.entitySetName,
|
|
15128
15378
|
primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
|
|
15129
15379
|
itemsExpr: "rows",
|
|
15130
|
-
selectionExpr: "selectedIndices"
|
|
15380
|
+
selectionExpr: "selectedIndices",
|
|
15381
|
+
exportColumnsLiteral,
|
|
15382
|
+
createColumnsLiteral
|
|
15131
15383
|
}) : "";
|
|
15132
15384
|
const cardSelectedIndicesState = cardEmitHandler ? "\n const [selectedIndices, setSelectedIndices] = React.useState<Set<number>>(new Set());" : "";
|
|
15133
15385
|
const cardRowCommandsLiteral = cardEmitRowCommands ? buildRowCommandsLiteral(contextMenuItems) : "";
|
|
15134
15386
|
const cardDeleteDialogJsx = cardEmitRowCommands && flatHasDelete || cardEmitToolbar && flatBarHasDelete ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15387
|
+
const cardCreateDialogJsx = cardEmitHandler && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15135
15388
|
const cardCommandBarBlock = cardEmitToolbar ? gridCommandBarBlock : "";
|
|
15136
15389
|
const cardSelectionModeAttr = cardEmitToolbar ? flatSelectionModeAttr : "";
|
|
15137
15390
|
const cardOnSelectionChangedAttr = cardEmitToolbar ? flatOnSelectionChangedAttr : "";
|
|
@@ -15150,20 +15403,26 @@ ${childEntries.join(",\n")},
|
|
|
15150
15403
|
handlerBlock: cardHandlerBlock,
|
|
15151
15404
|
rowCommandsLiteral: cardRowCommandsLiteral,
|
|
15152
15405
|
deleteDialogJsx: cardDeleteDialogJsx,
|
|
15406
|
+
createDialogJsx: cardCreateDialogJsx,
|
|
15153
15407
|
commandBarBlock: cardCommandBarBlock,
|
|
15154
15408
|
selectionModeAttr: cardSelectionModeAttr,
|
|
15155
15409
|
onSelectionChangedAttr: cardOnSelectionChangedAttr
|
|
15156
15410
|
})
|
|
15157
15411
|
];
|
|
15158
15412
|
_usesGridKit = true;
|
|
15159
|
-
const cardDataverseImport = cardEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
|
|
15160
|
-
if (cardDeleteDialogJsx) {
|
|
15413
|
+
const cardDataverseImport = cardEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord${createColumnsLiteral ? ", useCreateRecord" : ""} } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
|
|
15414
|
+
if (cardDeleteDialogJsx || cardCreateDialogJsx) {
|
|
15161
15415
|
imports.add("Dialog");
|
|
15162
15416
|
imports.add("DialogType");
|
|
15163
15417
|
imports.add("DialogFooter");
|
|
15164
15418
|
imports.add("PrimaryButton");
|
|
15165
15419
|
imports.add("DefaultButton");
|
|
15166
15420
|
}
|
|
15421
|
+
if (cardCreateDialogJsx) {
|
|
15422
|
+
imports.add("TextField");
|
|
15423
|
+
imports.add("Dropdown");
|
|
15424
|
+
imports.add("Checkbox");
|
|
15425
|
+
}
|
|
15167
15426
|
return {
|
|
15168
15427
|
imports,
|
|
15169
15428
|
v9Imports,
|
|
@@ -15171,7 +15430,10 @@ ${childEntries.join(",\n")},
|
|
|
15171
15430
|
extraImports: cardIsLive ? cardEmitHandler ? [
|
|
15172
15431
|
cardDataverseImport,
|
|
15173
15432
|
`import { useQueryClient } from '@tanstack/react-query';`,
|
|
15174
|
-
`import { CardGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit'
|
|
15433
|
+
`import { CardGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`,
|
|
15434
|
+
// Export case calls exportToFile/generateDefaultFilename (cardEmitHandler
|
|
15435
|
+
// here; the export case is only real when enableExport).
|
|
15436
|
+
...enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
|
|
15175
15437
|
] : [
|
|
15176
15438
|
`import { useDataverseQuery } from '../lib/dataverse';`,
|
|
15177
15439
|
`import { CardGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
|
|
@@ -15209,11 +15471,14 @@ ${childEntries.join(",\n")},
|
|
|
15209
15471
|
entitySetName: gridDef.dataSource.entitySetName,
|
|
15210
15472
|
primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
|
|
15211
15473
|
itemsExpr: "rows",
|
|
15212
|
-
selectionExpr: "selectedIndices"
|
|
15474
|
+
selectionExpr: "selectedIndices",
|
|
15475
|
+
exportColumnsLiteral,
|
|
15476
|
+
createColumnsLiteral
|
|
15213
15477
|
}) : "";
|
|
15214
15478
|
const roSelectedIndicesState = roEmitHandler ? "\n const [selectedIndices, setSelectedIndices] = React.useState<Set<number>>(new Set());" : "";
|
|
15215
15479
|
const roRowCommandsLiteral = roEmitRowCommands ? buildRowCommandsLiteral(contextMenuItems) : "";
|
|
15216
15480
|
const roDeleteDialogJsx = roEmitRowCommands && flatHasDelete ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15481
|
+
const roCreateDialogJsx = roEmitHandler && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15217
15482
|
const roCommandBarBlock = roEmitToolbar ? gridCommandBarBlock : "";
|
|
15218
15483
|
const helperComponents = [
|
|
15219
15484
|
buildLiveReadOnlySubgridBody({
|
|
@@ -15232,18 +15497,24 @@ ${childEntries.join(",\n")},
|
|
|
15232
15497
|
handlerBlock: roHandlerBlock,
|
|
15233
15498
|
rowCommandsLiteral: roRowCommandsLiteral,
|
|
15234
15499
|
deleteDialogJsx: roDeleteDialogJsx,
|
|
15500
|
+
createDialogJsx: roCreateDialogJsx,
|
|
15235
15501
|
commandBarBlock: roCommandBarBlock
|
|
15236
15502
|
})
|
|
15237
15503
|
];
|
|
15238
15504
|
if (!isV9) _usesGridKit = true;
|
|
15239
|
-
const roDataverseImport = roEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
|
|
15240
|
-
if (roDeleteDialogJsx) {
|
|
15505
|
+
const roDataverseImport = roEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord${createColumnsLiteral ? ", useCreateRecord" : ""} } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
|
|
15506
|
+
if (roDeleteDialogJsx || roCreateDialogJsx) {
|
|
15241
15507
|
imports.add("Dialog");
|
|
15242
15508
|
imports.add("DialogType");
|
|
15243
15509
|
imports.add("DialogFooter");
|
|
15244
15510
|
imports.add("PrimaryButton");
|
|
15245
15511
|
imports.add("DefaultButton");
|
|
15246
15512
|
}
|
|
15513
|
+
if (roCreateDialogJsx) {
|
|
15514
|
+
imports.add("TextField");
|
|
15515
|
+
imports.add("Dropdown");
|
|
15516
|
+
imports.add("Checkbox");
|
|
15517
|
+
}
|
|
15247
15518
|
return {
|
|
15248
15519
|
imports,
|
|
15249
15520
|
v9Imports,
|
|
@@ -15251,7 +15522,8 @@ ${childEntries.join(",\n")},
|
|
|
15251
15522
|
extraImports: isV9 ? [`import { useDataverseQuery } from '../lib/dataverse';`] : roEmitHandler ? [
|
|
15252
15523
|
roDataverseImport,
|
|
15253
15524
|
`import { useQueryClient } from '@tanstack/react-query';`,
|
|
15254
|
-
`import { ReadOnlyGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit'
|
|
15525
|
+
`import { ReadOnlyGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`,
|
|
15526
|
+
...enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
|
|
15255
15527
|
] : [
|
|
15256
15528
|
`import { useDataverseQuery } from '../lib/dataverse';`,
|
|
15257
15529
|
`import { ReadOnlyGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
|
|
@@ -15311,11 +15583,14 @@ const ${liveWrapperName}: React.FC = () => {
|
|
|
15311
15583
|
entitySetName: gridDef.dataSource.entitySetName,
|
|
15312
15584
|
primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
|
|
15313
15585
|
itemsExpr: "keyedItems",
|
|
15314
|
-
selectionExpr: "selectedIndices"
|
|
15586
|
+
selectionExpr: "selectedIndices",
|
|
15587
|
+
exportColumnsLiteral,
|
|
15588
|
+
createColumnsLiteral
|
|
15315
15589
|
}) : "";
|
|
15316
15590
|
const edSelectedIndicesState = edEmitHandler ? "\n const [selectedIndices, setSelectedIndices] = React.useState<Set<number>>(new Set());" : "";
|
|
15317
15591
|
const edRowCommandsLiteral = edEmitRowCommands ? buildRowCommandsLiteral(contextMenuItems) : "";
|
|
15318
15592
|
const edDeleteDialogJsx = edEmitRowCommands && flatHasDelete || edEmitToolbar && flatBarHasDelete ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15593
|
+
const edCreateDialogJsx = edEmitHandler && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15319
15594
|
const edCommandBarBlock = edEmitToolbar ? gridCommandBarBlock : "";
|
|
15320
15595
|
const edSelectionModeAttr = edEmitToolbar ? flatSelectionModeAttr : "";
|
|
15321
15596
|
const edOnSelectionChangedAttr = edEmitToolbar ? flatOnSelectionChangedAttr : "";
|
|
@@ -15332,6 +15607,7 @@ const ${liveWrapperName}: React.FC = () => {
|
|
|
15332
15607
|
handlerBlock: edHandlerBlock,
|
|
15333
15608
|
rowCommandsLiteral: edRowCommandsLiteral,
|
|
15334
15609
|
deleteDialogJsx: edDeleteDialogJsx,
|
|
15610
|
+
createDialogJsx: edCreateDialogJsx,
|
|
15335
15611
|
commandBarBlock: edCommandBarBlock,
|
|
15336
15612
|
selectionModeAttr: edSelectionModeAttr,
|
|
15337
15613
|
onSelectionChangedAttr: edOnSelectionChangedAttr
|
|
@@ -15343,18 +15619,24 @@ const ${liveWrapperName}: React.FC = () => {
|
|
|
15343
15619
|
const jsx3 = editableUsesLiveData && liveWrapperName ? `{/* Subgrid: ${safeEntityName} \u2014 ${safeGridName} (editable, live) */}${todoComment}
|
|
15344
15620
|
<${liveWrapperName} />` : `{/* Subgrid: ${safeEntityName} \u2014 ${safeGridName} (editable) */}${todoComment}
|
|
15345
15621
|
<${componentName} items={${itemsLiteral}} />`;
|
|
15346
|
-
const edDataverseImport = edEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord } from '../lib/dataverse';` : `import { useDataverseQuery, useUpdateRecord } from '../lib/dataverse';`;
|
|
15347
|
-
if (edDeleteDialogJsx) {
|
|
15622
|
+
const edDataverseImport = edEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord${createColumnsLiteral ? ", useCreateRecord" : ""} } from '../lib/dataverse';` : `import { useDataverseQuery, useUpdateRecord } from '../lib/dataverse';`;
|
|
15623
|
+
if (edDeleteDialogJsx || edCreateDialogJsx) {
|
|
15348
15624
|
imports.add("Dialog");
|
|
15349
15625
|
imports.add("DialogType");
|
|
15350
15626
|
imports.add("DialogFooter");
|
|
15351
15627
|
imports.add("PrimaryButton");
|
|
15352
15628
|
imports.add("DefaultButton");
|
|
15353
15629
|
}
|
|
15630
|
+
if (edCreateDialogJsx) {
|
|
15631
|
+
imports.add("TextField");
|
|
15632
|
+
imports.add("Dropdown");
|
|
15633
|
+
imports.add("Checkbox");
|
|
15634
|
+
}
|
|
15354
15635
|
const extraImports2 = editableUsesLiveData ? edEmitHandler ? [
|
|
15355
15636
|
edDataverseImport,
|
|
15356
15637
|
`import { useQueryClient } from '@tanstack/react-query';`,
|
|
15357
|
-
`import { DataGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit'
|
|
15638
|
+
`import { DataGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`,
|
|
15639
|
+
...enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
|
|
15358
15640
|
] : [
|
|
15359
15641
|
`import { useDataverseQuery, useUpdateRecord } from '../lib/dataverse';`,
|
|
15360
15642
|
`import { DataGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
|