@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.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,213 @@ 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,
|
|
14343
|
+
addExisting
|
|
14240
14344
|
} = args;
|
|
14345
|
+
const exportCase = exportColumnsLiteral ? `{
|
|
14346
|
+
const exportColumns = [${exportColumnsLiteral}];
|
|
14347
|
+
const rowsToExport = (indices.length ? indices.map((i) => items[i]) : items.slice())
|
|
14348
|
+
.filter((r): r is Record<string, unknown> => Boolean(r))
|
|
14349
|
+
.map((r) => {
|
|
14350
|
+
const formattedRow: Record<string, unknown> = {};
|
|
14351
|
+
for (const col of exportColumns) {
|
|
14352
|
+
formattedRow[col.key] = r[col.key + '@OData.Community.Display.V1.FormattedValue'] ?? r[col.key];
|
|
14353
|
+
}
|
|
14354
|
+
return formattedRow;
|
|
14355
|
+
});
|
|
14356
|
+
exportToFile(rowsToExport, exportColumns, 'csv', generateDefaultFilename(${JSON.stringify(entityName + "-export")}));
|
|
14357
|
+
return;
|
|
14358
|
+
}` : `{
|
|
14359
|
+
console.warn('[grid] Export to Excel is not yet wired in generated forms.');
|
|
14360
|
+
return;
|
|
14361
|
+
}`;
|
|
14362
|
+
const createStateBlock = createColumnsLiteral ? `
|
|
14363
|
+
const gridCreateMutation = useCreateRecord<Record<string, unknown>>('${entitySetName}');
|
|
14364
|
+
const [pendingCreate, setPendingCreate] = React.useState(false);
|
|
14365
|
+
const [createValues, setCreateValues] = React.useState<Record<string, unknown>>({});
|
|
14366
|
+
const [createError, setCreateError] = React.useState<string | null>(null);
|
|
14367
|
+
// Derived from the grid's columns; lookups/images/system fields are excluded by
|
|
14368
|
+
// the emitter. The payload is best-effort and validated server-side (errors show
|
|
14369
|
+
// in \`createError\`). NOTE: against a mock/standalone host (no Dataverse token),
|
|
14370
|
+
// MockApiService.createRecord no-ops and resolves \u2014 the dialog closes as if it
|
|
14371
|
+
// succeeded but nothing is created. Xrm is the primary path in a model-driven host.
|
|
14372
|
+
const createFields = React.useMemo<Array<{ key: string; label: string; kind: 'text' | 'number' | 'boolean' | 'optionset' | 'date'; inputType?: string; options?: Array<{ key: number; text: string }> }>>(
|
|
14373
|
+
() => [${createColumnsLiteral}],
|
|
14374
|
+
[],
|
|
14375
|
+
);
|
|
14376
|
+
const submitCreate = React.useCallback(async () => {
|
|
14377
|
+
setCreateError(null);
|
|
14378
|
+
const payload: Record<string, unknown> = {};
|
|
14379
|
+
for (const f of createFields) {
|
|
14380
|
+
const raw = createValues[f.key];
|
|
14381
|
+
if (raw === undefined || raw === null || raw === '') continue;
|
|
14382
|
+
payload[f.key] = (f.kind === 'number' || f.kind === 'optionset') ? Number(raw) : raw;
|
|
14383
|
+
}
|
|
14384
|
+
if (Object.keys(payload).length === 0) {
|
|
14385
|
+
setCreateError('Enter at least one value before creating.');
|
|
14386
|
+
return;
|
|
14387
|
+
}
|
|
14388
|
+
try {
|
|
14389
|
+
await gridCreateMutation.mutateAsync(payload);
|
|
14390
|
+
setPendingCreate(false);
|
|
14391
|
+
setCreateValues({});
|
|
14392
|
+
} catch (err) {
|
|
14393
|
+
setCreateError((err as Error)?.message ?? 'Create failed.');
|
|
14394
|
+
}
|
|
14395
|
+
}, [createValues, gridCreateMutation, createFields]);` : "";
|
|
14396
|
+
const newCase = createColumnsLiteral ? `{
|
|
14397
|
+
if (xrm?.Navigation?.openForm) {
|
|
14398
|
+
await xrm.Navigation.openForm({ entityName: '${entityName}', useQuickCreateForm: false });
|
|
14399
|
+
await refreshGrid();
|
|
14400
|
+
} else {
|
|
14401
|
+
setCreateError(null);
|
|
14402
|
+
setCreateValues({});
|
|
14403
|
+
setPendingCreate(true);
|
|
14404
|
+
}
|
|
14405
|
+
return;
|
|
14406
|
+
}` : `{
|
|
14407
|
+
if (xrm?.Navigation?.openForm) {
|
|
14408
|
+
await xrm.Navigation.openForm({ entityName: '${entityName}', useQuickCreateForm: false });
|
|
14409
|
+
await refreshGrid();
|
|
14410
|
+
} else {
|
|
14411
|
+
console.warn('[grid] New action requires Xrm.Navigation.openForm; not available in this host.');
|
|
14412
|
+
}
|
|
14413
|
+
return;
|
|
14414
|
+
}`;
|
|
14415
|
+
const addExistingStateBlock = addExisting ? `
|
|
14416
|
+
const gridAddExistingMutation = useUpdateRecord<Record<string, unknown>>('${addExisting.childEntitySet}');` : "";
|
|
14417
|
+
const addExistingCase = addExisting ? `{
|
|
14418
|
+
const parentId = selectedIds[0];
|
|
14419
|
+
if (!parentId) {
|
|
14420
|
+
console.warn('[grid] Select or open a parent record before adding an existing ${addExisting.childEntityLogical}.');
|
|
14421
|
+
return;
|
|
14422
|
+
}
|
|
14423
|
+
const lookup = (xrm as { Utility?: { lookupObjects?: (opts: unknown) => Promise<Array<{ id: string }>> } } | undefined)?.Utility;
|
|
14424
|
+
if (lookup?.lookupObjects) {
|
|
14425
|
+
const picked = await lookup.lookupObjects({ entityTypes: ['${addExisting.childEntityLogical}'], allowMultiSelect: true });
|
|
14426
|
+
if (!picked || picked.length === 0) return;
|
|
14427
|
+
for (const rec of picked) {
|
|
14428
|
+
const childId = String(rec.id).replace(/[{}]/g, '');
|
|
14429
|
+
await gridAddExistingMutation.mutateAsync({ id: childId, data: { '${addExisting.childField}@odata.bind': '/${addExisting.parentEntitySet}(' + parentId + ')' } as Record<string, unknown> });
|
|
14430
|
+
}
|
|
14431
|
+
await refreshGrid();
|
|
14432
|
+
} else {
|
|
14433
|
+
console.warn('[grid] Add Existing requires Xrm.Utility.lookupObjects; not available in this host.');
|
|
14434
|
+
}
|
|
14435
|
+
return;
|
|
14436
|
+
}` : `{
|
|
14437
|
+
if (xrm?.Navigation?.openForm) {
|
|
14438
|
+
await xrm.Navigation.openForm({ entityName: '${entityName}' });
|
|
14439
|
+
await refreshGrid();
|
|
14440
|
+
} else {
|
|
14441
|
+
console.warn('[grid] Add Existing requires Xrm.Navigation; not available in this host.');
|
|
14442
|
+
}
|
|
14443
|
+
return;
|
|
14444
|
+
}`;
|
|
14241
14445
|
return `
|
|
14242
14446
|
const queryClient = useQueryClient();
|
|
14243
14447
|
const gridUpdateMutation = useUpdateRecord<Record<string, unknown>>('${entitySetName}');
|
|
@@ -14261,7 +14465,7 @@ function buildGridCommandHandlerBlock(args) {
|
|
|
14261
14465
|
} catch (err) {
|
|
14262
14466
|
setDeleteError((err as Error)?.message ?? 'Delete failed.');
|
|
14263
14467
|
}
|
|
14264
|
-
}, [pendingDelete, gridDeleteMutation])
|
|
14468
|
+
}, [pendingDelete, gridDeleteMutation]);${createStateBlock}${addExistingStateBlock}
|
|
14265
14469
|
const handleGridCommand = React.useCallback(async (actionType: string, overrideRowIndex?: number) => {
|
|
14266
14470
|
const xrm = (typeof window !== 'undefined' ? (window as unknown as { Xrm?: any }).Xrm : undefined);
|
|
14267
14471
|
const items = ${itemsExpr} as ReadonlyArray<Record<string, unknown>>;
|
|
@@ -14286,24 +14490,8 @@ function buildGridCommandHandlerBlock(args) {
|
|
|
14286
14490
|
return true;
|
|
14287
14491
|
};
|
|
14288
14492
|
switch (actionType) {
|
|
14289
|
-
case 'new': {
|
|
14290
|
-
|
|
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
|
-
}
|
|
14298
|
-
case 'addExisting': {
|
|
14299
|
-
if (xrm?.Navigation?.openForm) {
|
|
14300
|
-
await xrm.Navigation.openForm({ entityName: '${entityName}' });
|
|
14301
|
-
await refreshGrid();
|
|
14302
|
-
} else {
|
|
14303
|
-
console.warn('[grid] Add Existing requires Xrm.Navigation; not available in this host.');
|
|
14304
|
-
}
|
|
14305
|
-
return;
|
|
14306
|
-
}
|
|
14493
|
+
case 'new': ${newCase}
|
|
14494
|
+
case 'addExisting': ${addExistingCase}
|
|
14307
14495
|
case 'edit': {
|
|
14308
14496
|
if (!requireSel('edit')) return;
|
|
14309
14497
|
if (selectedIds.length > 1 && xrm?.Navigation?.openBulkEditForm) {
|
|
@@ -14339,10 +14527,7 @@ function buildGridCommandHandlerBlock(args) {
|
|
|
14339
14527
|
await refreshGrid();
|
|
14340
14528
|
return;
|
|
14341
14529
|
}
|
|
14342
|
-
case 'export': {
|
|
14343
|
-
console.warn('[grid] Export to Excel is not yet wired in generated forms.');
|
|
14344
|
-
return;
|
|
14345
|
-
}
|
|
14530
|
+
case 'export': ${exportCase}
|
|
14346
14531
|
case 'bulkEdit': {
|
|
14347
14532
|
if (!requireSel('bulk edit')) return;
|
|
14348
14533
|
if (xrm?.Navigation?.openBulkEditForm) {
|
|
@@ -14356,10 +14541,10 @@ function buildGridCommandHandlerBlock(args) {
|
|
|
14356
14541
|
default:
|
|
14357
14542
|
return;
|
|
14358
14543
|
}
|
|
14359
|
-
}, [${itemsExpr}, ${selectionExpr}, gridUpdateMutation, refreshGrid]);`;
|
|
14544
|
+
}, [${itemsExpr}, ${selectionExpr}, gridUpdateMutation, refreshGrid${addExisting ? ", gridAddExistingMutation" : ""}]);`;
|
|
14360
14545
|
}
|
|
14361
14546
|
function buildRowCommandsLiteral(contextMenuItems) {
|
|
14362
|
-
return contextMenuItems.filter((ci) => (ci.actionType ?? "custom") !== "custom").map(
|
|
14547
|
+
return contextMenuItems.filter((ci) => (ci.actionType ?? "custom") !== "custom").filter((ci) => ci.actionType !== "new").map(
|
|
14363
14548
|
(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
14549
|
).join(",\n ");
|
|
14365
14550
|
}
|
|
@@ -14395,6 +14580,68 @@ function buildGridDeleteDialogJsx(entityName) {
|
|
|
14395
14580
|
</DialogFooter>
|
|
14396
14581
|
</Dialog>`;
|
|
14397
14582
|
}
|
|
14583
|
+
function buildGridCreateDialogJsx(entityName) {
|
|
14584
|
+
return `
|
|
14585
|
+
<Dialog
|
|
14586
|
+
hidden={!pendingCreate}
|
|
14587
|
+
onDismiss={() => { if (!gridCreateMutation.isPending) setPendingCreate(false); }}
|
|
14588
|
+
dialogContentProps={{
|
|
14589
|
+
type: DialogType.normal,
|
|
14590
|
+
title: ${JSON.stringify("Create " + entityName)},
|
|
14591
|
+
}}
|
|
14592
|
+
modalProps={{ isBlocking: true }}
|
|
14593
|
+
>
|
|
14594
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
|
|
14595
|
+
{createFields.map((f) => {
|
|
14596
|
+
if (f.kind === 'boolean') {
|
|
14597
|
+
return (
|
|
14598
|
+
<Checkbox
|
|
14599
|
+
key={f.key}
|
|
14600
|
+
label={f.label}
|
|
14601
|
+
checked={createValues[f.key] === true}
|
|
14602
|
+
onChange={(_ev, checked) => setCreateValues((prev) => ({ ...prev, [f.key]: !!checked }))}
|
|
14603
|
+
/>
|
|
14604
|
+
);
|
|
14605
|
+
}
|
|
14606
|
+
if (f.kind === 'optionset') {
|
|
14607
|
+
return (
|
|
14608
|
+
<Dropdown
|
|
14609
|
+
key={f.key}
|
|
14610
|
+
label={f.label}
|
|
14611
|
+
options={f.options ?? []}
|
|
14612
|
+
selectedKey={(createValues[f.key] as number | undefined) ?? null}
|
|
14613
|
+
onChange={(_ev, opt) => setCreateValues((prev) => ({ ...prev, [f.key]: opt?.key }))}
|
|
14614
|
+
/>
|
|
14615
|
+
);
|
|
14616
|
+
}
|
|
14617
|
+
return (
|
|
14618
|
+
<TextField
|
|
14619
|
+
key={f.key}
|
|
14620
|
+
label={f.label}
|
|
14621
|
+
type={f.inputType ?? 'text'}
|
|
14622
|
+
value={createValues[f.key] == null ? '' : String(createValues[f.key])}
|
|
14623
|
+
onChange={(_ev, val) => setCreateValues((prev) => ({ ...prev, [f.key]: val }))}
|
|
14624
|
+
/>
|
|
14625
|
+
);
|
|
14626
|
+
})}
|
|
14627
|
+
</div>
|
|
14628
|
+
{createError && (
|
|
14629
|
+
<div style={{ color: '#a4262c', fontSize: 12, marginTop: 8 }}>{createError}</div>
|
|
14630
|
+
)}
|
|
14631
|
+
<DialogFooter>
|
|
14632
|
+
<PrimaryButton
|
|
14633
|
+
onClick={submitCreate}
|
|
14634
|
+
text={gridCreateMutation.isPending ? 'Creating\u2026' : 'Create'}
|
|
14635
|
+
disabled={gridCreateMutation.isPending}
|
|
14636
|
+
/>
|
|
14637
|
+
<DefaultButton
|
|
14638
|
+
onClick={() => setPendingCreate(false)}
|
|
14639
|
+
text="Cancel"
|
|
14640
|
+
disabled={gridCreateMutation.isPending}
|
|
14641
|
+
/>
|
|
14642
|
+
</DialogFooter>
|
|
14643
|
+
</Dialog>`;
|
|
14644
|
+
}
|
|
14398
14645
|
function buildV9RenderCell(col) {
|
|
14399
14646
|
const safeFieldName = JSON.stringify(col.fieldName);
|
|
14400
14647
|
switch (col.rendererType) {
|
|
@@ -14757,12 +15004,18 @@ function generateLinkedSubgrid(gridDef, entityName, imports, library = "fluent-v
|
|
|
14757
15004
|
const showRefresh = gridDef.toolbar?.showRefresh ?? false;
|
|
14758
15005
|
const showColumnChooser = gridDef.toolbar?.showColumnChooser ?? false;
|
|
14759
15006
|
const hasToolbarIcons = showFilters || showViewToggle || showExport || showRefresh || showColumnChooser;
|
|
15007
|
+
const childGridDef = gridDef.nestedGridId ? _currentGridCustomizers.find((g) => g.id === gridDef.nestedGridId) : void 0;
|
|
15008
|
+
const _aeRel = gridDef.nestedRelationship;
|
|
15009
|
+
const addExistingFeasible = Boolean(
|
|
15010
|
+
childGridDef && _includeDataAccessLayer && _aeRel?.relationshipType === "OneToMany" && _aeRel?.parentField && _aeRel?.childField && gridDef.dataSource?.entitySetName && gridDef.dataSource?.fetchXml && childGridDef.dataSource?.entitySetName && childGridDef.dataSource?.entityName && childGridDef.dataSource?.fetchXml
|
|
15011
|
+
);
|
|
14760
15012
|
const barItems = (gridDef.commandBarItems ?? []).filter(
|
|
14761
15013
|
(ci) => ci.visibility === "commandBar" || ci.visibility === "both" || !ci.visibility
|
|
14762
|
-
);
|
|
15014
|
+
).filter((ci) => ci.actionType !== "addExisting" || addExistingFeasible);
|
|
14763
15015
|
const contextMenuItems = (gridDef.commandBarItems ?? []).filter(
|
|
14764
15016
|
(ci) => ci.visibility === "contextMenu" || ci.visibility === "both" || !ci.visibility
|
|
14765
|
-
);
|
|
15017
|
+
).filter((ci) => ci.actionType !== "addExisting" || addExistingFeasible);
|
|
15018
|
+
const enableAddExisting = addExistingFeasible && (barItems.some((ci) => ci.actionType === "addExisting") || contextMenuItems.some((ci) => ci.actionType === "addExisting"));
|
|
14766
15019
|
const _handlerAvailable = (
|
|
14767
15020
|
// The dispatcher calls useUpdateRecord/useDeleteRecord/useQueryClient — which only
|
|
14768
15021
|
// exist (module + provider + dep) when the data-access layer is included. Without it,
|
|
@@ -14772,6 +15025,8 @@ function generateLinkedSubgrid(gridDef, entityName, imports, library = "fluent-v
|
|
|
14772
15025
|
// need the dispatcher in scope even when no wired commandBarItems exist.
|
|
14773
15026
|
!!gridDef.toolbar?.showRefresh)
|
|
14774
15027
|
);
|
|
15028
|
+
const enableExport = _handlerAvailable && (showExport || barItems.some((ci) => ci.actionType === "export") || contextMenuItems.some((ci) => ci.actionType === "export"));
|
|
15029
|
+
const enableCreate = _handlerAvailable && barItems.some((ci) => ci.actionType === "new");
|
|
14775
15030
|
if (gridDef.showCommandBar && (barItems.length > 0 || showSearch || hasToolbarIcons)) {
|
|
14776
15031
|
if (showSearch) imports.add("SearchBox");
|
|
14777
15032
|
imports.add("DefaultButton");
|
|
@@ -14823,10 +15078,12 @@ function generateLinkedSubgrid(gridDef, entityName, imports, library = "fluent-v
|
|
|
14823
15078
|
toolbarIcons.push(
|
|
14824
15079
|
`<IconButton iconProps={{ iconName: 'ColumnOptions' }} title="Column chooser" ariaLabel="Column chooser" />`
|
|
14825
15080
|
);
|
|
14826
|
-
if (showExport)
|
|
15081
|
+
if (showExport) {
|
|
15082
|
+
const exportOnClick = _handlerAvailable ? ` onClick={() => { void handleGridCommand('export'); }}` : "";
|
|
14827
15083
|
toolbarIcons.push(
|
|
14828
|
-
`<IconButton iconProps={{ iconName: 'Download' }} title="Export" ariaLabel="Export" />`
|
|
15084
|
+
`<IconButton iconProps={{ iconName: 'Download' }} title="Export" ariaLabel="Export"${exportOnClick} />`
|
|
14829
15085
|
);
|
|
15086
|
+
}
|
|
14830
15087
|
if (showRefresh) {
|
|
14831
15088
|
const refreshOnClick = _handlerAvailable ? ` onClick={() => { void handleGridCommand('refresh'); }}` : "";
|
|
14832
15089
|
toolbarIcons.push(
|
|
@@ -14847,9 +15104,11 @@ function generateLinkedSubgrid(gridDef, entityName, imports, library = "fluent-v
|
|
|
14847
15104
|
</div>
|
|
14848
15105
|
</div>`;
|
|
14849
15106
|
}
|
|
14850
|
-
const childGridDef = gridDef.nestedGridId ? _currentGridCustomizers.find((g) => g.id === gridDef.nestedGridId) : void 0;
|
|
14851
15107
|
const isGridKitNested = !!childGridDef;
|
|
14852
15108
|
const visibleCols = [...gridDef.columns].filter((c) => c.isVisible).sort((a, b) => a.order - b.order);
|
|
15109
|
+
const exportColumnsLiteral = enableExport ? buildExportColumnsLiteral(visibleCols) : "";
|
|
15110
|
+
const primaryIdAttr = resolvePrimaryIdAttribute(gridDef);
|
|
15111
|
+
const createColumnsLiteral = enableCreate ? buildCreateFields(visibleCols, primaryIdAttr) : "";
|
|
14853
15112
|
visibleCols.filter((c) => c.rendererType !== "text");
|
|
14854
15113
|
const colEntries = isGridKitNested ? "" : generateColumnEntries(visibleCols, library);
|
|
14855
15114
|
const selectionMode = gridDef.selectionMode === "multiple" ? "SelectionMode.multiple" : gridDef.selectionMode === "single" ? "SelectionMode.single" : "SelectionMode.none";
|
|
@@ -14915,19 +15174,38 @@ ${childEntries.join(",\n")},
|
|
|
14915
15174
|
entitySetName: gridDef.dataSource.entitySetName,
|
|
14916
15175
|
primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
|
|
14917
15176
|
itemsExpr: "keyedItems",
|
|
14918
|
-
selectionExpr: "selectedIndices"
|
|
15177
|
+
selectionExpr: "selectedIndices",
|
|
15178
|
+
exportColumnsLiteral,
|
|
15179
|
+
createColumnsLiteral,
|
|
15180
|
+
// Associate an existing child to the clicked/active parent (the ONLY call site
|
|
15181
|
+
// that wires it). Shared by the nested AND focused-view emitters below — both
|
|
15182
|
+
// reuse this ngHandlerBlock. Gated on enableAddExisting (feasible AND an
|
|
15183
|
+
// addExisting item present); the && chain narrows the optional entity-set strings
|
|
15184
|
+
// to `string` (enableAddExisting ⟹ addExistingFeasible guarantees they're present).
|
|
15185
|
+
addExisting: enableAddExisting && childEntitySet && childEntityLogical && parentEntitySet && nestedRel?.childField ? {
|
|
15186
|
+
childEntitySet,
|
|
15187
|
+
childEntityLogical,
|
|
15188
|
+
childField: nestedRel.childField,
|
|
15189
|
+
parentEntitySet
|
|
15190
|
+
} : null
|
|
14919
15191
|
}) : "";
|
|
14920
15192
|
const ngHasDelete = (gridDef.commandBarItems ?? []).some(
|
|
14921
15193
|
(ci) => ci.actionType === "delete"
|
|
14922
15194
|
);
|
|
14923
15195
|
const ngDeleteDialogJsx = ngHasDelete && ngHandlerBlock !== "" ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
14924
|
-
|
|
15196
|
+
const ngCreateDialogJsx = ngHandlerBlock !== "" && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15197
|
+
if (ngDeleteDialogJsx || ngCreateDialogJsx) {
|
|
14925
15198
|
imports.add("Dialog");
|
|
14926
15199
|
imports.add("DialogType");
|
|
14927
15200
|
imports.add("DialogFooter");
|
|
14928
15201
|
imports.add("PrimaryButton");
|
|
14929
15202
|
imports.add("DefaultButton");
|
|
14930
15203
|
}
|
|
15204
|
+
if (ngCreateDialogJsx) {
|
|
15205
|
+
imports.add("TextField");
|
|
15206
|
+
imports.add("Dropdown");
|
|
15207
|
+
imports.add("Checkbox");
|
|
15208
|
+
}
|
|
14931
15209
|
const ngRowCommandsLiteral = _handlerAvailable && (ngHasCommandBar || ngHasContextMenu && useLiveNested) ? buildRowCommandsLiteral(contextMenuItems) : "";
|
|
14932
15210
|
const ngToolbarWired = (gridDef.commandBarItems ?? []).some(
|
|
14933
15211
|
(ci) => (ci.actionType ?? "custom") !== "custom"
|
|
@@ -14941,9 +15219,18 @@ ${childEntries.join(",\n")},
|
|
|
14941
15219
|
const ngDataverseHooks = [
|
|
14942
15220
|
...useLiveNested ? ["useDataverseQuery"] : [],
|
|
14943
15221
|
...useLiveNested && ngNeedsUpdate || ngHasHandler ? ["useUpdateRecord"] : [],
|
|
14944
|
-
...useLiveNested && ngNeedsDelete || ngHasHandler ? ["useDeleteRecord"] : []
|
|
15222
|
+
...useLiveNested && ngNeedsDelete || ngHasHandler ? ["useDeleteRecord"] : [],
|
|
15223
|
+
// Create mutation only when the create dialog is wired (the `new` case's
|
|
15224
|
+
// non-Xrm fallback) — gated like the export util import to stay byte-stable
|
|
15225
|
+
// for non-create handler grids.
|
|
15226
|
+
...ngHasHandler && createColumnsLiteral ? ["useCreateRecord"] : []
|
|
15227
|
+
];
|
|
15228
|
+
const ngCommandBarImports = [
|
|
15229
|
+
...useLiveNested && ngNeedsDelete || ngHasHandler ? [`import { useQueryClient } from '@tanstack/react-query';`] : [],
|
|
15230
|
+
// Export case (shared dispatcher → both nested + focused-view) calls
|
|
15231
|
+
// exportToFile/generateDefaultFilename when export is wired.
|
|
15232
|
+
...ngHasHandler && enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
|
|
14945
15233
|
];
|
|
14946
|
-
const ngCommandBarImports = useLiveNested && ngNeedsDelete || ngHasHandler ? [`import { useQueryClient } from '@tanstack/react-query';`] : [];
|
|
14947
15234
|
const ngParentSelectionMode = ngHasCommandBar ? gridDef.selectionMode === "single" ? "single" : "multiple" : gridDef.selectionMode ?? "none";
|
|
14948
15235
|
const ngChildSaveBack = Boolean(
|
|
14949
15236
|
useLiveNested && live && (childGridDef.isEditable ?? false)
|
|
@@ -14992,6 +15279,7 @@ ${childEntries.join(",\n")},
|
|
|
14992
15279
|
commandBarBlock: gridCommandBarBlock,
|
|
14993
15280
|
handlerBlock: ngHandlerBlock,
|
|
14994
15281
|
deleteDialogJsx: ngDeleteDialogJsx,
|
|
15282
|
+
createDialogJsx: ngCreateDialogJsx,
|
|
14995
15283
|
hasSelection: ngHasCommandBar,
|
|
14996
15284
|
hasHandler: ngHandlerBlock !== "",
|
|
14997
15285
|
dataverseHooks: ngDataverseHooks,
|
|
@@ -15047,6 +15335,7 @@ ${childEntries.join(",\n")},
|
|
|
15047
15335
|
commandBarBlock: gridCommandBarBlock,
|
|
15048
15336
|
handlerBlock: ngHandlerBlock,
|
|
15049
15337
|
deleteDialogJsx: ngDeleteDialogJsx,
|
|
15338
|
+
createDialogJsx: ngCreateDialogJsx,
|
|
15050
15339
|
hasSelection: ngHasCommandBar,
|
|
15051
15340
|
dataverseHooks: ngDataverseHooks,
|
|
15052
15341
|
commandBarImports: ngCommandBarImports,
|
|
@@ -15127,11 +15416,16 @@ ${childEntries.join(",\n")},
|
|
|
15127
15416
|
entitySetName: gridDef.dataSource.entitySetName,
|
|
15128
15417
|
primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
|
|
15129
15418
|
itemsExpr: "rows",
|
|
15130
|
-
selectionExpr: "selectedIndices"
|
|
15419
|
+
selectionExpr: "selectedIndices",
|
|
15420
|
+
exportColumnsLiteral,
|
|
15421
|
+
createColumnsLiteral,
|
|
15422
|
+
addExisting: null
|
|
15423
|
+
// flat (no parent relationship) → addExisting filtered out above
|
|
15131
15424
|
}) : "";
|
|
15132
15425
|
const cardSelectedIndicesState = cardEmitHandler ? "\n const [selectedIndices, setSelectedIndices] = React.useState<Set<number>>(new Set());" : "";
|
|
15133
15426
|
const cardRowCommandsLiteral = cardEmitRowCommands ? buildRowCommandsLiteral(contextMenuItems) : "";
|
|
15134
15427
|
const cardDeleteDialogJsx = cardEmitRowCommands && flatHasDelete || cardEmitToolbar && flatBarHasDelete ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15428
|
+
const cardCreateDialogJsx = cardEmitHandler && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15135
15429
|
const cardCommandBarBlock = cardEmitToolbar ? gridCommandBarBlock : "";
|
|
15136
15430
|
const cardSelectionModeAttr = cardEmitToolbar ? flatSelectionModeAttr : "";
|
|
15137
15431
|
const cardOnSelectionChangedAttr = cardEmitToolbar ? flatOnSelectionChangedAttr : "";
|
|
@@ -15150,20 +15444,26 @@ ${childEntries.join(",\n")},
|
|
|
15150
15444
|
handlerBlock: cardHandlerBlock,
|
|
15151
15445
|
rowCommandsLiteral: cardRowCommandsLiteral,
|
|
15152
15446
|
deleteDialogJsx: cardDeleteDialogJsx,
|
|
15447
|
+
createDialogJsx: cardCreateDialogJsx,
|
|
15153
15448
|
commandBarBlock: cardCommandBarBlock,
|
|
15154
15449
|
selectionModeAttr: cardSelectionModeAttr,
|
|
15155
15450
|
onSelectionChangedAttr: cardOnSelectionChangedAttr
|
|
15156
15451
|
})
|
|
15157
15452
|
];
|
|
15158
15453
|
_usesGridKit = true;
|
|
15159
|
-
const cardDataverseImport = cardEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
|
|
15160
|
-
if (cardDeleteDialogJsx) {
|
|
15454
|
+
const cardDataverseImport = cardEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord${createColumnsLiteral ? ", useCreateRecord" : ""} } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
|
|
15455
|
+
if (cardDeleteDialogJsx || cardCreateDialogJsx) {
|
|
15161
15456
|
imports.add("Dialog");
|
|
15162
15457
|
imports.add("DialogType");
|
|
15163
15458
|
imports.add("DialogFooter");
|
|
15164
15459
|
imports.add("PrimaryButton");
|
|
15165
15460
|
imports.add("DefaultButton");
|
|
15166
15461
|
}
|
|
15462
|
+
if (cardCreateDialogJsx) {
|
|
15463
|
+
imports.add("TextField");
|
|
15464
|
+
imports.add("Dropdown");
|
|
15465
|
+
imports.add("Checkbox");
|
|
15466
|
+
}
|
|
15167
15467
|
return {
|
|
15168
15468
|
imports,
|
|
15169
15469
|
v9Imports,
|
|
@@ -15171,7 +15471,10 @@ ${childEntries.join(",\n")},
|
|
|
15171
15471
|
extraImports: cardIsLive ? cardEmitHandler ? [
|
|
15172
15472
|
cardDataverseImport,
|
|
15173
15473
|
`import { useQueryClient } from '@tanstack/react-query';`,
|
|
15174
|
-
`import { CardGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit'
|
|
15474
|
+
`import { CardGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`,
|
|
15475
|
+
// Export case calls exportToFile/generateDefaultFilename (cardEmitHandler
|
|
15476
|
+
// here; the export case is only real when enableExport).
|
|
15477
|
+
...enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
|
|
15175
15478
|
] : [
|
|
15176
15479
|
`import { useDataverseQuery } from '../lib/dataverse';`,
|
|
15177
15480
|
`import { CardGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
|
|
@@ -15209,11 +15512,16 @@ ${childEntries.join(",\n")},
|
|
|
15209
15512
|
entitySetName: gridDef.dataSource.entitySetName,
|
|
15210
15513
|
primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
|
|
15211
15514
|
itemsExpr: "rows",
|
|
15212
|
-
selectionExpr: "selectedIndices"
|
|
15515
|
+
selectionExpr: "selectedIndices",
|
|
15516
|
+
exportColumnsLiteral,
|
|
15517
|
+
createColumnsLiteral,
|
|
15518
|
+
addExisting: null
|
|
15519
|
+
// flat (no parent relationship) → addExisting filtered out above
|
|
15213
15520
|
}) : "";
|
|
15214
15521
|
const roSelectedIndicesState = roEmitHandler ? "\n const [selectedIndices, setSelectedIndices] = React.useState<Set<number>>(new Set());" : "";
|
|
15215
15522
|
const roRowCommandsLiteral = roEmitRowCommands ? buildRowCommandsLiteral(contextMenuItems) : "";
|
|
15216
15523
|
const roDeleteDialogJsx = roEmitRowCommands && flatHasDelete ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15524
|
+
const roCreateDialogJsx = roEmitHandler && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15217
15525
|
const roCommandBarBlock = roEmitToolbar ? gridCommandBarBlock : "";
|
|
15218
15526
|
const helperComponents = [
|
|
15219
15527
|
buildLiveReadOnlySubgridBody({
|
|
@@ -15232,18 +15540,24 @@ ${childEntries.join(",\n")},
|
|
|
15232
15540
|
handlerBlock: roHandlerBlock,
|
|
15233
15541
|
rowCommandsLiteral: roRowCommandsLiteral,
|
|
15234
15542
|
deleteDialogJsx: roDeleteDialogJsx,
|
|
15543
|
+
createDialogJsx: roCreateDialogJsx,
|
|
15235
15544
|
commandBarBlock: roCommandBarBlock
|
|
15236
15545
|
})
|
|
15237
15546
|
];
|
|
15238
15547
|
if (!isV9) _usesGridKit = true;
|
|
15239
|
-
const roDataverseImport = roEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
|
|
15240
|
-
if (roDeleteDialogJsx) {
|
|
15548
|
+
const roDataverseImport = roEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord${createColumnsLiteral ? ", useCreateRecord" : ""} } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
|
|
15549
|
+
if (roDeleteDialogJsx || roCreateDialogJsx) {
|
|
15241
15550
|
imports.add("Dialog");
|
|
15242
15551
|
imports.add("DialogType");
|
|
15243
15552
|
imports.add("DialogFooter");
|
|
15244
15553
|
imports.add("PrimaryButton");
|
|
15245
15554
|
imports.add("DefaultButton");
|
|
15246
15555
|
}
|
|
15556
|
+
if (roCreateDialogJsx) {
|
|
15557
|
+
imports.add("TextField");
|
|
15558
|
+
imports.add("Dropdown");
|
|
15559
|
+
imports.add("Checkbox");
|
|
15560
|
+
}
|
|
15247
15561
|
return {
|
|
15248
15562
|
imports,
|
|
15249
15563
|
v9Imports,
|
|
@@ -15251,7 +15565,8 @@ ${childEntries.join(",\n")},
|
|
|
15251
15565
|
extraImports: isV9 ? [`import { useDataverseQuery } from '../lib/dataverse';`] : roEmitHandler ? [
|
|
15252
15566
|
roDataverseImport,
|
|
15253
15567
|
`import { useQueryClient } from '@tanstack/react-query';`,
|
|
15254
|
-
`import { ReadOnlyGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit'
|
|
15568
|
+
`import { ReadOnlyGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`,
|
|
15569
|
+
...enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
|
|
15255
15570
|
] : [
|
|
15256
15571
|
`import { useDataverseQuery } from '../lib/dataverse';`,
|
|
15257
15572
|
`import { ReadOnlyGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
|
|
@@ -15311,11 +15626,16 @@ const ${liveWrapperName}: React.FC = () => {
|
|
|
15311
15626
|
entitySetName: gridDef.dataSource.entitySetName,
|
|
15312
15627
|
primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
|
|
15313
15628
|
itemsExpr: "keyedItems",
|
|
15314
|
-
selectionExpr: "selectedIndices"
|
|
15629
|
+
selectionExpr: "selectedIndices",
|
|
15630
|
+
exportColumnsLiteral,
|
|
15631
|
+
createColumnsLiteral,
|
|
15632
|
+
addExisting: null
|
|
15633
|
+
// flat (no parent relationship) → addExisting filtered out above
|
|
15315
15634
|
}) : "";
|
|
15316
15635
|
const edSelectedIndicesState = edEmitHandler ? "\n const [selectedIndices, setSelectedIndices] = React.useState<Set<number>>(new Set());" : "";
|
|
15317
15636
|
const edRowCommandsLiteral = edEmitRowCommands ? buildRowCommandsLiteral(contextMenuItems) : "";
|
|
15318
15637
|
const edDeleteDialogJsx = edEmitRowCommands && flatHasDelete || edEmitToolbar && flatBarHasDelete ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15638
|
+
const edCreateDialogJsx = edEmitHandler && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
|
|
15319
15639
|
const edCommandBarBlock = edEmitToolbar ? gridCommandBarBlock : "";
|
|
15320
15640
|
const edSelectionModeAttr = edEmitToolbar ? flatSelectionModeAttr : "";
|
|
15321
15641
|
const edOnSelectionChangedAttr = edEmitToolbar ? flatOnSelectionChangedAttr : "";
|
|
@@ -15332,6 +15652,7 @@ const ${liveWrapperName}: React.FC = () => {
|
|
|
15332
15652
|
handlerBlock: edHandlerBlock,
|
|
15333
15653
|
rowCommandsLiteral: edRowCommandsLiteral,
|
|
15334
15654
|
deleteDialogJsx: edDeleteDialogJsx,
|
|
15655
|
+
createDialogJsx: edCreateDialogJsx,
|
|
15335
15656
|
commandBarBlock: edCommandBarBlock,
|
|
15336
15657
|
selectionModeAttr: edSelectionModeAttr,
|
|
15337
15658
|
onSelectionChangedAttr: edOnSelectionChangedAttr
|
|
@@ -15343,18 +15664,24 @@ const ${liveWrapperName}: React.FC = () => {
|
|
|
15343
15664
|
const jsx3 = editableUsesLiveData && liveWrapperName ? `{/* Subgrid: ${safeEntityName} \u2014 ${safeGridName} (editable, live) */}${todoComment}
|
|
15344
15665
|
<${liveWrapperName} />` : `{/* Subgrid: ${safeEntityName} \u2014 ${safeGridName} (editable) */}${todoComment}
|
|
15345
15666
|
<${componentName} items={${itemsLiteral}} />`;
|
|
15346
|
-
const edDataverseImport = edEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord } from '../lib/dataverse';` : `import { useDataverseQuery, useUpdateRecord } from '../lib/dataverse';`;
|
|
15347
|
-
if (edDeleteDialogJsx) {
|
|
15667
|
+
const edDataverseImport = edEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord${createColumnsLiteral ? ", useCreateRecord" : ""} } from '../lib/dataverse';` : `import { useDataverseQuery, useUpdateRecord } from '../lib/dataverse';`;
|
|
15668
|
+
if (edDeleteDialogJsx || edCreateDialogJsx) {
|
|
15348
15669
|
imports.add("Dialog");
|
|
15349
15670
|
imports.add("DialogType");
|
|
15350
15671
|
imports.add("DialogFooter");
|
|
15351
15672
|
imports.add("PrimaryButton");
|
|
15352
15673
|
imports.add("DefaultButton");
|
|
15353
15674
|
}
|
|
15675
|
+
if (edCreateDialogJsx) {
|
|
15676
|
+
imports.add("TextField");
|
|
15677
|
+
imports.add("Dropdown");
|
|
15678
|
+
imports.add("Checkbox");
|
|
15679
|
+
}
|
|
15354
15680
|
const extraImports2 = editableUsesLiveData ? edEmitHandler ? [
|
|
15355
15681
|
edDataverseImport,
|
|
15356
15682
|
`import { useQueryClient } from '@tanstack/react-query';`,
|
|
15357
|
-
`import { DataGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit'
|
|
15683
|
+
`import { DataGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`,
|
|
15684
|
+
...enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
|
|
15358
15685
|
] : [
|
|
15359
15686
|
`import { useDataverseQuery, useUpdateRecord } from '../lib/dataverse';`,
|
|
15360
15687
|
`import { DataGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
|
|
@@ -18163,6 +18490,19 @@ ${code}
|
|
|
18163
18490
|
};
|
|
18164
18491
|
`;
|
|
18165
18492
|
}
|
|
18493
|
+
function dedupeImportStatements(content) {
|
|
18494
|
+
const isImportLine = (l) => /^import\b[^\n]*\bfrom\b[^\n]*;[ \t]*$/.test(l) || /^import\s+['"][^'"]+['"];[ \t]*$/.test(l);
|
|
18495
|
+
const seen = /* @__PURE__ */ new Set();
|
|
18496
|
+
const out = [];
|
|
18497
|
+
for (const line of content.split("\n")) {
|
|
18498
|
+
if (isImportLine(line)) {
|
|
18499
|
+
if (seen.has(line)) continue;
|
|
18500
|
+
seen.add(line);
|
|
18501
|
+
}
|
|
18502
|
+
out.push(line);
|
|
18503
|
+
}
|
|
18504
|
+
return out.join("\n");
|
|
18505
|
+
}
|
|
18166
18506
|
function generateFormCode(form, options) {
|
|
18167
18507
|
_currentGridCustomizers = options?.gridCustomizers ?? [];
|
|
18168
18508
|
_includeSampleData = options?.includeSampleData ?? false;
|
|
@@ -18193,6 +18533,7 @@ function generateFormCode(form, options) {
|
|
|
18193
18533
|
default:
|
|
18194
18534
|
content = generateMainForm(form, hasRules, library);
|
|
18195
18535
|
}
|
|
18536
|
+
content = dedupeImportStatements(content);
|
|
18196
18537
|
const files = [
|
|
18197
18538
|
{ path: `src/components/${fileName}`, content }
|
|
18198
18539
|
];
|