@postxl/generators 1.12.3 → 1.14.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/backend-ai/ai.generator.d.ts +18 -0
- package/dist/backend-ai/ai.generator.js +174 -0
- package/dist/backend-ai/ai.generator.js.map +1 -0
- package/dist/backend-ai/generators/ai-agent-service.generator.d.ts +4 -0
- package/dist/backend-ai/generators/ai-agent-service.generator.js +264 -0
- package/dist/backend-ai/generators/ai-agent-service.generator.js.map +1 -0
- package/dist/backend-ai/generators/ai-cache-service.generator.d.ts +1 -0
- package/dist/backend-ai/generators/ai-cache-service.generator.js +110 -0
- package/dist/backend-ai/generators/ai-cache-service.generator.js.map +1 -0
- package/dist/backend-ai/generators/ai-config.generator.d.ts +1 -0
- package/dist/backend-ai/generators/ai-config.generator.js +27 -0
- package/dist/backend-ai/generators/ai-config.generator.js.map +1 -0
- package/dist/backend-ai/generators/ai-module.generator.d.ts +2 -0
- package/dist/backend-ai/generators/ai-module.generator.js +89 -0
- package/dist/backend-ai/generators/ai-module.generator.js.map +1 -0
- package/dist/backend-ai/generators/ai-route.generator.d.ts +1 -0
- package/dist/backend-ai/generators/ai-route.generator.js +29 -0
- package/dist/backend-ai/generators/ai-route.generator.js.map +1 -0
- package/dist/backend-ai/generators/ai-tools-service.generator.d.ts +4 -0
- package/dist/backend-ai/generators/ai-tools-service.generator.js +222 -0
- package/dist/backend-ai/generators/ai-tools-service.generator.js.map +1 -0
- package/dist/backend-ai/generators/model-provider-interface.generator.d.ts +1 -0
- package/dist/backend-ai/generators/model-provider-interface.generator.js +48 -0
- package/dist/backend-ai/generators/model-provider-interface.generator.js.map +1 -0
- package/dist/backend-ai/generators/openai-model-provider-service.generator.d.ts +1 -0
- package/dist/backend-ai/generators/openai-model-provider-service.generator.js +128 -0
- package/dist/backend-ai/generators/openai-model-provider-service.generator.js.map +1 -0
- package/dist/backend-ai/index.d.ts +4 -0
- package/dist/backend-ai/index.js +40 -0
- package/dist/backend-ai/index.js.map +1 -0
- package/dist/backend-authentication/generators/authentication-service.generator.js +1 -1
- package/dist/backend-core/backend.generator.js +0 -4
- package/dist/backend-core/backend.generator.js.map +1 -1
- package/dist/backend-core/generators/main.generator.js +4 -3
- package/dist/backend-core/generators/main.generator.js.map +1 -1
- package/dist/backend-core/modules/backend-module-xlport.generator.js +1 -1
- package/dist/backend-e2e/backend-e2e.generator.js +4 -4
- package/dist/backend-e2e/backend-e2e.generator.js.map +1 -1
- package/dist/backend-excel-io/excel-io.generator.d.ts +19 -0
- package/dist/backend-excel-io/excel-io.generator.js +106 -0
- package/dist/backend-excel-io/excel-io.generator.js.map +1 -0
- package/dist/backend-excel-io/generators/excel-io-service.generator.d.ts +9 -0
- package/dist/backend-excel-io/generators/excel-io-service.generator.js +680 -0
- package/dist/backend-excel-io/generators/excel-io-service.generator.js.map +1 -0
- package/dist/backend-excel-io/index.d.ts +2 -0
- package/dist/backend-excel-io/index.js +22 -0
- package/dist/backend-excel-io/index.js.map +1 -0
- package/dist/backend-excel-io/template/README.md +24 -0
- package/dist/backend-excel-io/template/excel-io.controller.ts +195 -0
- package/dist/backend-excel-io/template/excel-io.module.ts +17 -0
- package/dist/backend-import/generators/detect-delta/detect-delta-functions.generator.js +149 -14
- package/dist/backend-import/generators/detect-delta/detect-delta-functions.generator.js.map +1 -1
- package/dist/backend-import/generators/filter-error-rows.generator.d.ts +2 -0
- package/dist/backend-import/generators/filter-error-rows.generator.js +28 -0
- package/dist/backend-import/generators/filter-error-rows.generator.js.map +1 -0
- package/dist/backend-import/generators/import-service.generator.js +126 -2
- package/dist/backend-import/generators/import-service.generator.js.map +1 -1
- package/dist/backend-import/import.generator.js +2 -0
- package/dist/backend-import/import.generator.js.map +1 -1
- package/dist/backend-repositories/generators/model-repository.generator.js +17 -2
- package/dist/backend-repositories/generators/model-repository.generator.js.map +1 -1
- package/dist/backend-router-trpc/generators/app-routes.generator.js +8 -1
- package/dist/backend-router-trpc/generators/app-routes.generator.js.map +1 -1
- package/dist/backend-router-trpc/generators/excel-io-route.generator.d.ts +4 -0
- package/dist/backend-router-trpc/generators/excel-io-route.generator.js +35 -0
- package/dist/backend-router-trpc/generators/excel-io-route.generator.js.map +1 -0
- package/dist/backend-router-trpc/generators/trpc-plugin.generator.js +9 -0
- package/dist/backend-router-trpc/generators/trpc-plugin.generator.js.map +1 -1
- package/dist/backend-router-trpc/generators/trpc-router-module.generator.js +9 -1
- package/dist/backend-router-trpc/generators/trpc-router-module.generator.js.map +1 -1
- package/dist/backend-router-trpc/generators/trpc-shared.generator.js +16 -1
- package/dist/backend-router-trpc/generators/trpc-shared.generator.js.map +1 -1
- package/dist/backend-router-trpc/router-trpc.generator.d.ts +3 -1
- package/dist/backend-router-trpc/router-trpc.generator.js +6 -0
- package/dist/backend-router-trpc/router-trpc.generator.js.map +1 -1
- package/dist/backend-seed/seed.generator.js +10 -1
- package/dist/backend-seed/seed.generator.js.map +1 -1
- package/dist/base/base.generator.js +0 -4
- package/dist/base/base.generator.js.map +1 -1
- package/dist/base/template/scripts/setup.sh +9 -4
- package/dist/base/template/sonar-project.properties +9 -1
- package/dist/decoders/datamodel-decoder.generator.js +91 -1
- package/dist/decoders/datamodel-decoder.generator.js.map +1 -1
- package/dist/decoders/decoders.generator.d.ts +9 -0
- package/dist/decoders/decoders.generator.js +15 -0
- package/dist/decoders/decoders.generator.js.map +1 -1
- package/dist/devops/devops.generator.d.ts +5 -1
- package/dist/devops/devops.generator.js +5 -4
- package/dist/devops/devops.generator.js.map +1 -1
- package/dist/devops/generators/bitbucket-pipelines-yml.generator.js +1 -0
- package/dist/devops/generators/bitbucket-pipelines-yml.generator.js.map +1 -1
- package/dist/devops/generators/docker-compose-yml.generator.d.ts +1 -1
- package/dist/devops/generators/docker-compose-yml.generator.js +16 -1
- package/dist/devops/generators/docker-compose-yml.generator.js.map +1 -1
- package/dist/devops/generators/e2e-yml.generator.js +35 -10
- package/dist/devops/generators/e2e-yml.generator.js.map +1 -1
- package/dist/devops/generators/jenkinsfile.generator.js +25 -1
- package/dist/devops/generators/jenkinsfile.generator.js.map +1 -1
- package/dist/e2e/template/e2e/specs/example.spec.ts-snapshots/Navigate-to-homepage-and-take-snapshot-1-chromium-linux.png +0 -0
- package/dist/frontend-actions/actions.generator.d.ts +9 -0
- package/dist/frontend-actions/actions.generator.js +111 -0
- package/dist/frontend-actions/actions.generator.js.map +1 -0
- package/dist/frontend-actions/generators/ai-action-text.utils.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/ai-action-text.utils.generator.js +52 -0
- package/dist/frontend-actions/generators/ai-action-text.utils.generator.js.map +1 -0
- package/dist/frontend-actions/generators/ai-assistant-store.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/ai-assistant-store.generator.js +230 -0
- package/dist/frontend-actions/generators/ai-assistant-store.generator.js.map +1 -0
- package/dist/frontend-actions/generators/ai-sidebar-content.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/ai-sidebar-content.generator.js +139 -0
- package/dist/frontend-actions/generators/ai-sidebar-content.generator.js.map +1 -0
- package/dist/frontend-actions/generators/ai-sidepane.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/ai-sidepane.generator.js +98 -0
- package/dist/frontend-actions/generators/ai-sidepane.generator.js.map +1 -0
- package/dist/frontend-actions/generators/base-global-actions.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/base-global-actions.generator.js +405 -0
- package/dist/frontend-actions/generators/base-global-actions.generator.js.map +1 -0
- package/dist/frontend-actions/generators/command-palette-action.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/command-palette-action.generator.js +87 -0
- package/dist/frontend-actions/generators/command-palette-action.generator.js.map +1 -0
- package/dist/frontend-actions/generators/command-palette-store.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/command-palette-store.generator.js +288 -0
- package/dist/frontend-actions/generators/command-palette-store.generator.js.map +1 -0
- package/dist/frontend-actions/generators/command-palette.generator.d.ts +5 -0
- package/dist/frontend-actions/generators/command-palette.generator.js +332 -0
- package/dist/frontend-actions/generators/command-palette.generator.js.map +1 -0
- package/dist/frontend-actions/generators/filter-utils.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/filter-utils.generator.js +50 -0
- package/dist/frontend-actions/generators/filter-utils.generator.js.map +1 -0
- package/dist/frontend-actions/generators/sidepanel-toggle.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/sidepanel-toggle.generator.js +37 -0
- package/dist/frontend-actions/generators/sidepanel-toggle.generator.js.map +1 -0
- package/dist/frontend-actions/index.d.ts +4 -0
- package/dist/frontend-actions/index.js +40 -0
- package/dist/frontend-actions/index.js.map +1 -0
- package/dist/frontend-admin/admin.generator.d.ts +5 -1
- package/dist/frontend-admin/admin.generator.js +36 -1
- package/dist/frontend-admin/admin.generator.js.map +1 -1
- package/dist/frontend-admin/generators/admin-global-actions.generator.d.ts +4 -0
- package/dist/frontend-admin/generators/admin-global-actions.generator.js +230 -0
- package/dist/frontend-admin/generators/admin-global-actions.generator.js.map +1 -0
- package/dist/frontend-admin/generators/admin-overview-page.generator.js +21 -1
- package/dist/frontend-admin/generators/admin-overview-page.generator.js.map +1 -1
- package/dist/frontend-admin/generators/admin-sidebar.generator.js +20 -0
- package/dist/frontend-admin/generators/admin-sidebar.generator.js.map +1 -1
- package/dist/frontend-admin/generators/detail-sidebar.generator.js +44 -32
- package/dist/frontend-admin/generators/detail-sidebar.generator.js.map +1 -1
- package/dist/frontend-admin/generators/excel-io-page.generator.d.ts +4 -0
- package/dist/frontend-admin/generators/excel-io-page.generator.js +258 -0
- package/dist/frontend-admin/generators/excel-io-page.generator.js.map +1 -0
- package/dist/frontend-admin/generators/import-review-page-result-stage.generator.d.ts +1 -0
- package/dist/frontend-admin/generators/import-review-page-result-stage.generator.js +104 -0
- package/dist/frontend-admin/generators/import-review-page-result-stage.generator.js.map +1 -0
- package/dist/frontend-admin/generators/import-review-page-review-stage.generator.d.ts +1 -0
- package/dist/frontend-admin/generators/import-review-page-review-stage.generator.js +1031 -0
- package/dist/frontend-admin/generators/import-review-page-review-stage.generator.js.map +1 -0
- package/dist/frontend-admin/generators/import-review-page-upload-stage.generator.d.ts +1 -0
- package/dist/frontend-admin/generators/import-review-page-upload-stage.generator.js +77 -0
- package/dist/frontend-admin/generators/import-review-page-upload-stage.generator.js.map +1 -0
- package/dist/frontend-admin/generators/import-review-page.generator.d.ts +7 -0
- package/dist/frontend-admin/generators/import-review-page.generator.js +180 -0
- package/dist/frontend-admin/generators/import-review-page.generator.js.map +1 -0
- package/dist/frontend-admin/generators/model-admin-page.generator.js +198 -60
- package/dist/frontend-admin/generators/model-admin-page.generator.js.map +1 -1
- package/dist/frontend-admin/utils.d.ts +1 -0
- package/dist/frontend-admin/utils.js +26 -33
- package/dist/frontend-admin/utils.js.map +1 -1
- package/dist/frontend-core/frontend.generator.js +1 -2
- package/dist/frontend-core/frontend.generator.js.map +1 -1
- package/dist/frontend-core/generators/tsconfig.generator.js +1 -0
- package/dist/frontend-core/generators/tsconfig.generator.js.map +1 -1
- package/dist/frontend-core/template/.env.example +3 -0
- package/dist/frontend-core/template/src/components/admin/excel-io-actions.tsx +64 -0
- package/dist/frontend-core/template/src/components/admin/table-view-panel.tsx +41 -3
- package/dist/frontend-core/template/src/components/ui/color-mode-toggle/color-mode-toggle.tsx +1 -1
- package/dist/frontend-core/template/src/hooks/use-excel-io.ts +137 -0
- package/dist/frontend-core/template/src/hooks/use-import-review.ts +143 -0
- package/dist/frontend-core/template/src/lib/color.ts +6 -3
- package/dist/frontend-core/template/src/lib/config.ts +3 -1
- package/dist/frontend-core/template/src/lib/excel-download.ts +28 -0
- package/dist/frontend-core/types/hook.d.ts +1 -1
- package/dist/frontend-tables/generators/model-table.generator.js +21 -13
- package/dist/frontend-tables/generators/model-table.generator.js.map +1 -1
- package/dist/generators.js +6 -0
- package/dist/generators.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/dist/seed-data/seed-data.generator.d.ts +3 -0
- package/dist/seed-data/seed-data.generator.js +45 -1
- package/dist/seed-data/seed-data.generator.js.map +1 -1
- package/dist/types/template/ai.types.ts +34 -0
- package/dist/types/types.generator.js +1 -0
- package/dist/types/types.generator.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import-review-page-review-stage.generator.js","sourceRoot":"","sources":["../../../src/frontend-admin/generators/import-review-page-review-stage.generator.ts"],"names":[],"mappings":";;AAAA,0EAkgCC;AAlgCD,SAAgB,+BAA+B;IAC7C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAggCR,CAAA;AACD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateImportReviewUploadStage(): string;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateImportReviewUploadStage = generateImportReviewUploadStage;
|
|
4
|
+
function generateImportReviewUploadStage() {
|
|
5
|
+
return `
|
|
6
|
+
function UploadStage({
|
|
7
|
+
isLoading,
|
|
8
|
+
onUpload,
|
|
9
|
+
}: Readonly<{
|
|
10
|
+
isLoading: boolean
|
|
11
|
+
onUpload: (file: File, options: { includeDetailedUnchangedRecords: boolean }) => Promise<void>
|
|
12
|
+
}>) {
|
|
13
|
+
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
|
14
|
+
const [includeDetailedUnchangedRecords, setIncludeDetailedUnchangedRecords] = useState(false)
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<Card>
|
|
18
|
+
<CardHeader>
|
|
19
|
+
<CardTitle>Upload Excel File</CardTitle>
|
|
20
|
+
</CardHeader>
|
|
21
|
+
<CardContent className="space-y-4">
|
|
22
|
+
<div className="space-y-2">
|
|
23
|
+
<Label htmlFor="import-review-file">Select an .xlsx or .xls file to preview before importing</Label>
|
|
24
|
+
<Input
|
|
25
|
+
id="import-review-file"
|
|
26
|
+
type="file"
|
|
27
|
+
accept=".xlsx,.xls"
|
|
28
|
+
disabled={isLoading}
|
|
29
|
+
onChange={(event) => setSelectedFile(event.target.files?.[0] ?? null)}
|
|
30
|
+
/>
|
|
31
|
+
{selectedFile && <p className="text-sm text-muted-foreground">Selected file: {selectedFile.name}</p>}
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div className="flex items-center gap-2">
|
|
35
|
+
<Checkbox
|
|
36
|
+
id="detailed-unchanged"
|
|
37
|
+
checked={includeDetailedUnchangedRecords}
|
|
38
|
+
checkIcon="check"
|
|
39
|
+
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
|
40
|
+
setIncludeDetailedUnchangedRecords(event.target.checked)
|
|
41
|
+
}
|
|
42
|
+
/>
|
|
43
|
+
<Label htmlFor="detailed-unchanged">Include detailed unchanged records</Label>
|
|
44
|
+
<TooltipProvider delayDuration={150}>
|
|
45
|
+
<Tooltip>
|
|
46
|
+
<TooltipTrigger asChild>
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
aria-label="Info about detailed unchanged records"
|
|
50
|
+
className="inline-flex h-5 w-5 items-center justify-center rounded-full border text-xs text-muted-foreground"
|
|
51
|
+
>
|
|
52
|
+
i
|
|
53
|
+
</button>
|
|
54
|
+
</TooltipTrigger>
|
|
55
|
+
<TooltipContent>
|
|
56
|
+
<p>On large datasets, including unchanged row details may take longer.</p>
|
|
57
|
+
</TooltipContent>
|
|
58
|
+
</Tooltip>
|
|
59
|
+
</TooltipProvider>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div className="flex gap-3">
|
|
63
|
+
<Button
|
|
64
|
+
disabled={isLoading || !selectedFile}
|
|
65
|
+
onClick={() => selectedFile && void onUpload(selectedFile, { includeDetailedUnchangedRecords })}
|
|
66
|
+
>
|
|
67
|
+
{isLoading ? 'Building preview...' : 'Create Preview'}
|
|
68
|
+
</Button>
|
|
69
|
+
</div>
|
|
70
|
+
</CardContent>
|
|
71
|
+
</Card>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=import-review-page-upload-stage.generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import-review-page-upload-stage.generator.js","sourceRoot":"","sources":["../../../src/frontend-admin/generators/import-review-page-upload-stage.generator.ts"],"names":[],"mappings":";;AAAA,0EAwEC;AAxED,SAAgB,+BAA+B;IAC7C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsER,CAAA;AACD,CAAC"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateImportReviewPage = generateImportReviewPage;
|
|
4
|
+
const import_review_page_result_stage_generator_1 = require("./import-review-page-result-stage.generator");
|
|
5
|
+
const import_review_page_review_stage_generator_1 = require("./import-review-page-review-stage.generator");
|
|
6
|
+
const import_review_page_upload_stage_generator_1 = require("./import-review-page-upload-stage.generator");
|
|
7
|
+
/**
|
|
8
|
+
* Generator function that generates the Import Review admin page.
|
|
9
|
+
*/
|
|
10
|
+
function generateImportReviewPage({ context }) {
|
|
11
|
+
const models = [...context.models.values()].sort((a, b) => a.userFriendlyName.localeCompare(b.userFriendlyName));
|
|
12
|
+
const modelSchemaMapEntries = models
|
|
13
|
+
.map((model) => ` ${model._conjugated.camelCasePlural}: '${model.databaseSchema}',`)
|
|
14
|
+
.join('\n');
|
|
15
|
+
const modelLabelMapEntries = models
|
|
16
|
+
.map((model) => ` ${model._conjugated.camelCasePlural}: '${model.userFriendlyName.replaceAll("'", "\\'")}',`)
|
|
17
|
+
.join('\n');
|
|
18
|
+
const modelFieldOrderEntries = models
|
|
19
|
+
.map((model) => {
|
|
20
|
+
const fields = Array.from(model.fields.values())
|
|
21
|
+
.map((field) => `'${field.name}'`)
|
|
22
|
+
.join(', ');
|
|
23
|
+
return ` ${model._conjugated.camelCasePlural}: [${fields}],`;
|
|
24
|
+
})
|
|
25
|
+
.join('\n');
|
|
26
|
+
const schemaOrderEntries = [...new Set(models.map((model) => model.databaseSchema))]
|
|
27
|
+
.sort((a, b) => a.localeCompare(b))
|
|
28
|
+
.map((schema) => `'${schema}'`)
|
|
29
|
+
.join(', ');
|
|
30
|
+
return `
|
|
31
|
+
import { AdminSidebar } from '@admin/admin-sidebar'
|
|
32
|
+
|
|
33
|
+
import type { ColumnDef } from '@tanstack/react-table'
|
|
34
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
35
|
+
import { AlertTriangleIcon, MinusIcon, PencilIcon, PlusIcon, Trash2Icon } from 'lucide-react'
|
|
36
|
+
|
|
37
|
+
import type {
|
|
38
|
+
ImportExecutionSummary,
|
|
39
|
+
ImportPreviewItem,
|
|
40
|
+
ImportPreviewModelSummary,
|
|
41
|
+
ImportPreviewResponse,
|
|
42
|
+
SelectedDeltaIndicesByModel,
|
|
43
|
+
} from '@hooks/use-import-review'
|
|
44
|
+
import { useImportReview } from '@hooks/use-import-review'
|
|
45
|
+
import {
|
|
46
|
+
Alert,
|
|
47
|
+
AlertDialog,
|
|
48
|
+
AlertDialogAction,
|
|
49
|
+
AlertDialogCancel,
|
|
50
|
+
AlertDialogContent,
|
|
51
|
+
AlertDialogDescription,
|
|
52
|
+
AlertDialogFooter,
|
|
53
|
+
AlertDialogHeader,
|
|
54
|
+
AlertDialogTitle,
|
|
55
|
+
AlertDescription,
|
|
56
|
+
Badge,
|
|
57
|
+
Button,
|
|
58
|
+
Card,
|
|
59
|
+
CardContent,
|
|
60
|
+
CardHeader,
|
|
61
|
+
CardTitle,
|
|
62
|
+
Checkbox,
|
|
63
|
+
DataGrid,
|
|
64
|
+
Input,
|
|
65
|
+
Label,
|
|
66
|
+
SidebarInset,
|
|
67
|
+
SidebarProvider,
|
|
68
|
+
Tooltip,
|
|
69
|
+
TooltipContent,
|
|
70
|
+
TooltipProvider,
|
|
71
|
+
TooltipTrigger,
|
|
72
|
+
cn,
|
|
73
|
+
useDataGrid,
|
|
74
|
+
} from '@postxl/ui-components'
|
|
75
|
+
|
|
76
|
+
const MODEL_SCHEMA_MAP: Record<string, string> = {
|
|
77
|
+
${modelSchemaMapEntries}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const MODEL_LABEL_MAP: Record<string, string> = {
|
|
81
|
+
${modelLabelMapEntries}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const MODEL_FIELD_ORDER: Record<string, string[]> = {
|
|
85
|
+
${modelFieldOrderEntries}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const SCHEMA_ORDER: string[] = [${schemaOrderEntries}]
|
|
89
|
+
|
|
90
|
+
const TYPE_ORDER: ImportPreviewItem['type'][] = ['errors', 'delete', 'update', 'create', 'unchanged']
|
|
91
|
+
|
|
92
|
+
const TYPE_BADGE_CLASSES: Record<ImportPreviewItem['type'], string> = {
|
|
93
|
+
create: 'text-green-700 border-green-300 dark:text-green-400 dark:border-green-700',
|
|
94
|
+
update: 'text-blue-700 border-blue-300 dark:text-blue-400 dark:border-blue-700',
|
|
95
|
+
delete: 'text-red-700 border-red-300 dark:text-red-400 dark:border-red-700',
|
|
96
|
+
unchanged: 'text-muted-foreground border-border',
|
|
97
|
+
errors: 'text-red-700 border-red-300 dark:text-red-400 dark:border-red-700',
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const EXECUTABLE_TYPES = new Set<ImportPreviewItem['type']>(['create', 'update', 'delete'])
|
|
101
|
+
|
|
102
|
+
export default function ImportReviewPage() {
|
|
103
|
+
const { stage, preview, executionResult, error, isLoading, uploadAndPreview, execute, reset } = useImportReview()
|
|
104
|
+
|
|
105
|
+
const showAdminSidebar = stage === 'upload' || stage === 'previewing' || stage === 'result'
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<SidebarProvider defaultOpen>
|
|
109
|
+
{showAdminSidebar && <AdminSidebar />}
|
|
110
|
+
|
|
111
|
+
<SidebarInset className="p-4">
|
|
112
|
+
<div className="mx-auto flex w-full max-w-[1400px] flex-col gap-4">
|
|
113
|
+
<PageHeader stage={stage} />
|
|
114
|
+
|
|
115
|
+
{error && (
|
|
116
|
+
<Alert variant="destructive">
|
|
117
|
+
<AlertDescription>{error}</AlertDescription>
|
|
118
|
+
</Alert>
|
|
119
|
+
)}
|
|
120
|
+
|
|
121
|
+
{(stage === 'upload' || stage === 'previewing') && (
|
|
122
|
+
<UploadStage isLoading={isLoading} onUpload={uploadAndPreview} />
|
|
123
|
+
)}
|
|
124
|
+
|
|
125
|
+
{stage === 'review' && preview && (
|
|
126
|
+
<ReviewStage preview={preview} isLoading={isLoading} onExecute={execute} onReset={reset} />
|
|
127
|
+
)}
|
|
128
|
+
|
|
129
|
+
{stage === 'executing' && (
|
|
130
|
+
<Card>
|
|
131
|
+
<CardContent className="py-8 text-center">
|
|
132
|
+
<p className="text-lg font-medium">Executing selected changes...</p>
|
|
133
|
+
<p className="mt-2 text-sm text-muted-foreground">Applying selected rows to the database.</p>
|
|
134
|
+
</CardContent>
|
|
135
|
+
</Card>
|
|
136
|
+
)}
|
|
137
|
+
|
|
138
|
+
{stage === 'result' && executionResult && <ResultStage result={executionResult} onReset={reset} />}
|
|
139
|
+
</div>
|
|
140
|
+
</SidebarInset>
|
|
141
|
+
</SidebarProvider>
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function PageHeader({ stage }: Readonly<{ stage: 'upload' | 'previewing' | 'review' | 'executing' | 'result' }>) {
|
|
146
|
+
const currentStep = stage === 'upload' || stage === 'previewing' ? 1 : stage === 'review' || stage === 'executing' ? 2 : 3
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<div className="space-y-3">
|
|
150
|
+
<h1 className="text-3xl font-bold">Import Review</h1>
|
|
151
|
+
<p className="text-sm text-muted-foreground">
|
|
152
|
+
Upload an Excel file, review and select changes, then execute a controlled import.
|
|
153
|
+
</p>
|
|
154
|
+
<div className="grid grid-cols-3 gap-2">
|
|
155
|
+
<StepChip label="1. Upload" active={currentStep === 1} done={currentStep > 1} />
|
|
156
|
+
<StepChip label="2. Preview & Select" active={currentStep === 2} done={currentStep > 2} />
|
|
157
|
+
<StepChip label="3. Results" active={currentStep === 3} done={false} />
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function StepChip({ label, active, done }: Readonly<{ label: string; active: boolean; done: boolean }>) {
|
|
164
|
+
const stateClass = done
|
|
165
|
+
? 'border-green-300 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-950/40 dark:text-green-300'
|
|
166
|
+
: active
|
|
167
|
+
? 'border-blue-300 bg-blue-50 text-blue-700 dark:border-blue-800 dark:bg-blue-950/40 dark:text-blue-300'
|
|
168
|
+
: 'border-border bg-background text-muted-foreground'
|
|
169
|
+
|
|
170
|
+
return <div className={cn('rounded border px-3 py-2 text-sm font-medium', stateClass)}>{label}</div>
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
${(0, import_review_page_upload_stage_generator_1.generateImportReviewUploadStage)()}
|
|
174
|
+
|
|
175
|
+
${(0, import_review_page_review_stage_generator_1.generateImportReviewReviewStage)()}
|
|
176
|
+
|
|
177
|
+
${(0, import_review_page_result_stage_generator_1.generateImportReviewResultStage)()}
|
|
178
|
+
`;
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=import-review-page.generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import-review-page.generator.js","sourceRoot":"","sources":["../../../src/frontend-admin/generators/import-review-page.generator.ts"],"names":[],"mappings":";;AASA,4DA8KC;AArLD,2GAA6F;AAC7F,2GAA6F;AAC7F,2GAA6F;AAE7F;;GAEG;AACH,SAAgB,wBAAwB,CAAC,EAAE,OAAO,EAA8B;IAC9E,MAAM,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAA;IAEhH,MAAM,qBAAqB,GAAG,MAAM;SACjC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,CAAC,WAAW,CAAC,eAAe,MAAM,KAAK,CAAC,cAAc,IAAI,CAAC;SACpF,IAAI,CAAC,IAAI,CAAC,CAAA;IAEb,MAAM,oBAAoB,GAAG,MAAM;SAChC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,CAAC,WAAW,CAAC,eAAe,MAAM,KAAK,CAAC,gBAAgB,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC;SAC7G,IAAI,CAAC,IAAI,CAAC,CAAA;IAEb,MAAM,sBAAsB,GAAG,MAAM;SAClC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACb,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;aAC7C,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC,IAAI,GAAG,CAAC;aACjC,IAAI,CAAC,IAAI,CAAC,CAAA;QACb,OAAO,KAAK,KAAK,CAAC,WAAW,CAAC,eAAe,MAAM,MAAM,IAAI,CAAA;IAC/D,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAA;IAEb,MAAM,kBAAkB,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;SACjF,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;SAClC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,MAAM,GAAG,CAAC;SAC9B,IAAI,CAAC,IAAI,CAAC,CAAA;IAEb,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+CP,qBAAqB;;;;EAIrB,oBAAoB;;;;EAIpB,sBAAsB;;;kCAGU,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqFlD,IAAA,2EAA+B,GAAE;;EAEjC,IAAA,2EAA+B,GAAE;;EAEjC,IAAA,2EAA+B,GAAE;CAClC,CAAA;AACD,CAAC"}
|
|
@@ -52,6 +52,7 @@ function generateModelAdminPage({ model, context, }) {
|
|
|
52
52
|
Generator.toFunctionName('useState'),
|
|
53
53
|
Generator.toFunctionName('useCallback'),
|
|
54
54
|
Generator.toFunctionName('useMemo'),
|
|
55
|
+
Generator.toFunctionName('useEffect'),
|
|
55
56
|
],
|
|
56
57
|
})
|
|
57
58
|
.addImport({
|
|
@@ -69,7 +70,34 @@ function generateModelAdminPage({ model, context, }) {
|
|
|
69
70
|
})
|
|
70
71
|
.addImport({
|
|
71
72
|
from: Generator.toPackageName('@tanstack/react-router'),
|
|
72
|
-
items: [Generator.toFunctionName('useNavigate')],
|
|
73
|
+
items: [Generator.toFunctionName('useNavigate'), Generator.toFunctionName('useSearch')],
|
|
74
|
+
})
|
|
75
|
+
.addImport({
|
|
76
|
+
from: Generator.toBackendModuleLocation('@app-actions/command-palette-action'),
|
|
77
|
+
items: [
|
|
78
|
+
Generator.toFunctionName('command'),
|
|
79
|
+
Generator.toFunctionName('input'),
|
|
80
|
+
Generator.toFunctionName('waitForNextFrame'),
|
|
81
|
+
],
|
|
82
|
+
})
|
|
83
|
+
.addImport({
|
|
84
|
+
from: Generator.toBackendModuleLocation('@app-actions/command-palette-store'),
|
|
85
|
+
items: [Generator.toFunctionName('useCommandPaletteActions')],
|
|
86
|
+
})
|
|
87
|
+
.addImport({
|
|
88
|
+
from: Generator.toBackendModuleLocation('@app-actions/ai-assistant-store'),
|
|
89
|
+
items: [Generator.toFunctionName('useAiAssistantActions'), Generator.toFunctionName('useAiAssistantState')],
|
|
90
|
+
})
|
|
91
|
+
.addImport({
|
|
92
|
+
from: Generator.toBackendModuleLocation('@lib/command-palette-filter.utils'),
|
|
93
|
+
items: [
|
|
94
|
+
Generator.toConstantName('stringFilterInputSchema'),
|
|
95
|
+
Generator.toConstantName('numberFilterInputSchema'),
|
|
96
|
+
Generator.toConstantName('dateFilterInputSchema'),
|
|
97
|
+
Generator.toConstantName('booleanFilterInputSchema'),
|
|
98
|
+
Generator.toFunctionName('parseBooleanQuery'),
|
|
99
|
+
Generator.toFunctionName('toFilterActionLabel'),
|
|
100
|
+
],
|
|
73
101
|
});
|
|
74
102
|
const showComments = model.name !== 'Comment' && !!context.admin.components.commentSidebar;
|
|
75
103
|
const relatedModels = [];
|
|
@@ -83,6 +111,7 @@ function generateModelAdminPage({ model, context, }) {
|
|
|
83
111
|
});
|
|
84
112
|
}
|
|
85
113
|
let createModalHook = '';
|
|
114
|
+
let createModalIntentHook = '';
|
|
86
115
|
let createModal = '';
|
|
87
116
|
if (!model.isReadonly) {
|
|
88
117
|
imports
|
|
@@ -96,6 +125,33 @@ function generateModelAdminPage({ model, context, }) {
|
|
|
96
125
|
const variableName = hasLabelField ? `created${model.name}` : '_';
|
|
97
126
|
const label = hasLabelField ? ` "\${${variableName}.${model.labelField.name}}"` : '';
|
|
98
127
|
createModalHook = ` const [isCreateModalOpen, setIsCreateModalOpen] = useState(false)`;
|
|
128
|
+
createModalIntentHook = `
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
if (typeof globalThis === 'undefined') {
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const storageKey = 'pxl:create-intent-model'
|
|
135
|
+
const targetModel = '${model.name}'
|
|
136
|
+
const openFromIntent = (intentModel: unknown) => {
|
|
137
|
+
if (intentModel === targetModel) {
|
|
138
|
+
setIsCreateModalOpen(true)
|
|
139
|
+
globalThis.sessionStorage.removeItem(storageKey)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
openFromIntent(globalThis.sessionStorage.getItem(storageKey))
|
|
144
|
+
|
|
145
|
+
const onIntent = (event: Event) => {
|
|
146
|
+
const detail = (event as CustomEvent<{ model?: string }>).detail
|
|
147
|
+
openFromIntent(detail?.model)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
globalThis.addEventListener('pxl:create-intent', onIntent)
|
|
151
|
+
return () => {
|
|
152
|
+
globalThis.removeEventListener('pxl:create-intent', onIntent)
|
|
153
|
+
}
|
|
154
|
+
}, [])`;
|
|
99
155
|
createModal = `
|
|
100
156
|
<${model.forms.createModal.name}
|
|
101
157
|
open={isCreateModalOpen}
|
|
@@ -110,44 +166,34 @@ function generateModelAdminPage({ model, context, }) {
|
|
|
110
166
|
/>`;
|
|
111
167
|
}
|
|
112
168
|
return `
|
|
113
|
-
import { SidebarInset, SidebarProvider,
|
|
169
|
+
import { SidebarInset, SidebarProvider, useDebouncedCallback } from '@postxl/ui-components'
|
|
114
170
|
import useLocalStorageState from 'use-local-storage-state'
|
|
115
|
-
import useSessionStorageState from 'use-session-storage-state'
|
|
116
171
|
import { useAuth } from '@context-providers/auth-context-provider'
|
|
117
172
|
${imports.generate()}
|
|
118
173
|
|
|
119
|
-
type CellSelection = {
|
|
120
|
-
rowIndex: number
|
|
121
|
-
columnId: string
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const STORAGE_KEY = 'audit-log-collapse-state'
|
|
125
174
|
const SIDEBAR_WIDTH_KEY = 'audit-log-sidebar-width'
|
|
126
175
|
|
|
127
|
-
|
|
128
|
-
selectedKey?: string
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export default function ${model.admin.page.pageComponent.name}({ selectedKey }: ${model.admin.page.pageComponent.name}Props) {
|
|
176
|
+
export default function ${model.admin.page.pageComponent.name}() {
|
|
132
177
|
const navigate = useNavigate()
|
|
133
178
|
const { viewerData } = useAuth()
|
|
134
179
|
const currentUserId = viewerData?.user?.id
|
|
135
180
|
const isAdmin = viewerData?.userRoles?.includes('admin') ?? false
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
defaultValue: false,
|
|
181
|
+
|
|
182
|
+
const { selectedKey, focusedCell } = useSearch({
|
|
183
|
+
from: '/_auth-routes${model.admin.page.route}',
|
|
184
|
+
select: (search) => ({ selectedKey: search.selectedKey, focusedCell: search.focusedCell }),
|
|
141
185
|
})
|
|
186
|
+
|
|
187
|
+
${createModalHook}
|
|
188
|
+
${createModalIntentHook}
|
|
189
|
+
const sidepanelOpen = useAiAssistantState((state) => state.open)
|
|
190
|
+
const { setOpen: setSidepanelOpen } = useAiAssistantActions()
|
|
142
191
|
|
|
143
192
|
// audit log sidebar width
|
|
144
193
|
const [sidebarWidth, setSidebarWidth] = useLocalStorageState(SIDEBAR_WIDTH_KEY, {
|
|
145
194
|
defaultValue: 350,
|
|
146
195
|
})
|
|
147
196
|
|
|
148
|
-
// Track the currently focused cell for audit log filtering
|
|
149
|
-
const [focusedCell, setFocusedCell] = useState<CellSelection | null>(null)
|
|
150
|
-
|
|
151
197
|
// Track selected row IDs from checkbox selection
|
|
152
198
|
const [selectedRowIds, setSelectedRowIds] = useState<string[]>([])
|
|
153
199
|
|
|
@@ -158,6 +204,7 @@ export default function ${model.admin.page.pageComponent.name}({ selectedKey }:
|
|
|
158
204
|
|
|
159
205
|
// Global search input state (debounced filter update happens via handleSearchInputChange)
|
|
160
206
|
const [searchInput, setSearchInput] = useState('')
|
|
207
|
+
const { registerComponentActions, deregisterComponentActions } = useCommandPaletteActions()
|
|
161
208
|
|
|
162
209
|
const {
|
|
163
210
|
${model.itemsHook.filteredList.name}: filteredDataList,
|
|
@@ -171,21 +218,15 @@ export default function ${model.admin.page.pageComponent.name}({ selectedKey }:
|
|
|
171
218
|
${relatedModels.map((rm) => rm.mapDefinition).join('\n ')}
|
|
172
219
|
|
|
173
220
|
|
|
174
|
-
//
|
|
175
|
-
const selectedEntityId =
|
|
176
|
-
if (!filteredDataList || !focusedCell || focusedCell.rowIndex < 0 || focusedCell.rowIndex >= filteredDataList.length) {
|
|
177
|
-
return null
|
|
178
|
-
}
|
|
179
|
-
const row = filteredDataList[focusedCell.rowIndex]
|
|
180
|
-
return row?.id ?? null
|
|
181
|
-
}, [focusedCell, filteredDataList])
|
|
221
|
+
// The selected entity ID comes from the URL selectedKey (which is the entity's id)
|
|
222
|
+
const selectedEntityId = selectedKey ?? null
|
|
182
223
|
|
|
183
|
-
// Get the selected field name based on focused cell
|
|
224
|
+
// Get the selected field name based on focused cell (columnId from search params)
|
|
184
225
|
const selectedField = useMemo(() => {
|
|
185
|
-
if (!focusedCell || focusedCell
|
|
226
|
+
if (!focusedCell || focusedCell === 'select' || focusedCell === 'actions') {
|
|
186
227
|
return null
|
|
187
228
|
}
|
|
188
|
-
return focusedCell
|
|
229
|
+
return focusedCell
|
|
189
230
|
}, [focusedCell])
|
|
190
231
|
|
|
191
232
|
${showComments
|
|
@@ -200,35 +241,25 @@ export default function ${model.admin.page.pageComponent.name}({ selectedKey }:
|
|
|
200
241
|
)
|
|
201
242
|
`
|
|
202
243
|
: ''}
|
|
203
|
-
// Callback when cell focus changes in the table
|
|
204
|
-
const handleCellChange = useCallback((args: { rowIndex: number; columnId: string }) => {
|
|
205
|
-
setFocusedCell({ rowIndex: args.rowIndex, columnId: args.columnId })
|
|
206
|
-
// Clear checkbox selection when focusing a cell
|
|
207
|
-
setSelectedRowIds([])
|
|
208
|
-
}, [])
|
|
209
|
-
|
|
210
244
|
// Callback when row selection changes (checkbox)
|
|
211
245
|
const handleRowSelectionChange = useCallback((rowIds: string[]) => {
|
|
212
246
|
setSelectedRowIds(rowIds)
|
|
213
247
|
// Clear cell selection when using checkbox selection
|
|
214
248
|
if (rowIds.length > 0) {
|
|
215
|
-
|
|
249
|
+
void navigate({ to: '${model.admin.page.route}', search: { selectedKey: undefined, focusedCell: undefined } })
|
|
216
250
|
}
|
|
217
|
-
}, [])
|
|
251
|
+
}, [navigate])
|
|
218
252
|
|
|
219
253
|
// Clear field filter (go up to entity level)
|
|
220
254
|
const handleClearField = useCallback(() => {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
setFocusedCell({ rowIndex: focusedCell.rowIndex, columnId: '' })
|
|
224
|
-
}
|
|
225
|
-
}, [focusedCell])
|
|
255
|
+
void navigate({ to: '${model.admin.page.route}', search: { selectedKey, focusedCell: undefined } })
|
|
256
|
+
}, [navigate, selectedKey])
|
|
226
257
|
|
|
227
258
|
// Clear entity filter (go back to model overview)
|
|
228
259
|
const handleClearEntity = useCallback(() => {
|
|
229
|
-
|
|
260
|
+
void navigate({ to: '${model.admin.page.route}', search: { selectedKey: undefined, focusedCell: undefined } })
|
|
230
261
|
setSelectedRowIds([])
|
|
231
|
-
}, [])
|
|
262
|
+
}, [navigate])
|
|
232
263
|
|
|
233
264
|
const handleFilterChange = useCallback((key: ${model.types.filter.fieldType.name} | 'globalSearch', val: any) => {
|
|
234
265
|
setFilters((prev) => {
|
|
@@ -249,14 +280,124 @@ export default function ${model.admin.page.pageComponent.name}({ selectedKey }:
|
|
|
249
280
|
debouncedFilterChange('globalSearch', value)
|
|
250
281
|
}, [debouncedFilterChange])
|
|
251
282
|
|
|
252
|
-
//
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
283
|
+
// Callback when cell focus changes in the table — also sets selectedKey from the focused row
|
|
284
|
+
const handleCellChange = useCallback((args: { rowIndex: number; columnId: string }) => {
|
|
285
|
+
const row = filteredDataList?.[args.rowIndex]
|
|
286
|
+
const key = row?.${model.keyField.name} != null ? String(row.${model.keyField.name}) : undefined
|
|
287
|
+
void navigate({ to: '${model.admin.page.route}', search: { selectedKey: key, focusedCell: args.columnId || undefined } })
|
|
288
|
+
// Clear checkbox selection when focusing a cell
|
|
289
|
+
setSelectedRowIds([])
|
|
290
|
+
}, [navigate, filteredDataList])
|
|
291
|
+
|
|
292
|
+
useEffect(() => {
|
|
293
|
+
const prefix = 'admin-${model._conjugated.kebabCase}-filter'
|
|
294
|
+
|
|
295
|
+
const filterActions = Object.entries(${model.types.filter.config.name}).map(([field, fieldConfig]) =>
|
|
296
|
+
input({
|
|
297
|
+
key: prefix + '-' + field,
|
|
298
|
+
visibility: 'visible',
|
|
299
|
+
group: 'Filter records by...',
|
|
300
|
+
label: toFilterActionLabel(field),
|
|
301
|
+
inputSchema:
|
|
302
|
+
fieldConfig.valueType === 'number'
|
|
303
|
+
? numberFilterInputSchema
|
|
304
|
+
: fieldConfig.valueType === 'date'
|
|
305
|
+
? dateFilterInputSchema
|
|
306
|
+
: fieldConfig.valueType === 'boolean'
|
|
307
|
+
? booleanFilterInputSchema
|
|
308
|
+
: stringFilterInputSchema,
|
|
309
|
+
run: async (query, inputValue) => {
|
|
310
|
+
if (fieldConfig.valueType === 'number') {
|
|
311
|
+
const parsed = numberFilterInputSchema.safeParse(inputValue)
|
|
312
|
+
if (parsed.success && Object.values(parsed.data).some((value) => value !== undefined)) {
|
|
313
|
+
handleFilterChange(fieldConfig.filterKey as ${model.types.filter.fieldType.name}, parsed.data)
|
|
314
|
+
} else {
|
|
315
|
+
const numericValue = Number(query.trim())
|
|
316
|
+
if (!Number.isNaN(numericValue)) {
|
|
317
|
+
handleFilterChange(fieldConfig.filterKey as ${model.types.filter.fieldType.name}, { values: [numericValue] })
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
} else if (fieldConfig.valueType === 'date') {
|
|
321
|
+
const parsed = dateFilterInputSchema.safeParse(inputValue)
|
|
322
|
+
if (parsed.success && Object.values(parsed.data).some((value) => value !== undefined)) {
|
|
323
|
+
handleFilterChange(fieldConfig.filterKey as ${model.types.filter.fieldType.name}, parsed.data)
|
|
324
|
+
} else if (query.trim()) {
|
|
325
|
+
handleFilterChange(fieldConfig.filterKey as ${model.types.filter.fieldType.name}, { values: [query.trim()] })
|
|
326
|
+
}
|
|
327
|
+
} else if (fieldConfig.valueType === 'boolean') {
|
|
328
|
+
const parsed = booleanFilterInputSchema.safeParse(inputValue)
|
|
329
|
+
if (parsed.success && parsed.data.values && parsed.data.values.length > 0) {
|
|
330
|
+
handleFilterChange(fieldConfig.filterKey as ${model.types.filter.fieldType.name}, parsed.data.values)
|
|
331
|
+
} else {
|
|
332
|
+
const parsedFromQuery = parseBooleanQuery(query)
|
|
333
|
+
if (parsedFromQuery) {
|
|
334
|
+
handleFilterChange(fieldConfig.filterKey as ${model.types.filter.fieldType.name}, parsedFromQuery)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
} else {
|
|
338
|
+
const parsed = stringFilterInputSchema.safeParse(inputValue)
|
|
339
|
+
if (parsed.success && Object.values(parsed.data).some((value) => value !== undefined && value !== '')) {
|
|
340
|
+
handleFilterChange(fieldConfig.filterKey as ${model.types.filter.fieldType.name}, parsed.data)
|
|
341
|
+
} else if (query.trim()) {
|
|
342
|
+
handleFilterChange(fieldConfig.filterKey as ${model.types.filter.fieldType.name}, { contains: query.trim() })
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
await waitForNextFrame()
|
|
347
|
+
return { kind: 'close' }
|
|
348
|
+
},
|
|
349
|
+
}),
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
const actions = [
|
|
353
|
+
command({
|
|
354
|
+
key: 'admin-${model._conjugated.kebabCase}-clear-all-filters',
|
|
355
|
+
visibility: 'visible',
|
|
356
|
+
group: 'Filter records by...',
|
|
357
|
+
label: 'Clear all filters',
|
|
358
|
+
run: async () => {
|
|
359
|
+
setFilters({})
|
|
360
|
+
setSearchInput('')
|
|
361
|
+
await waitForNextFrame()
|
|
362
|
+
return { kind: 'close' }
|
|
363
|
+
},
|
|
364
|
+
}),
|
|
365
|
+
command({
|
|
366
|
+
key: 'admin-${model._conjugated.kebabCase}-query-filters',
|
|
367
|
+
visibility: 'llm',
|
|
368
|
+
group: 'Filter records by...',
|
|
369
|
+
label: 'Query active filters',
|
|
370
|
+
run: () => Promise.resolve({ kind: 'output', output: { filters, searchInput } }),
|
|
371
|
+
}),
|
|
372
|
+
input({
|
|
373
|
+
key: 'admin-${model._conjugated.kebabCase}-search',
|
|
374
|
+
visibility: 'visible',
|
|
375
|
+
group: 'Filter records by...',
|
|
376
|
+
label: 'Global search',
|
|
377
|
+
run: async (query) => {
|
|
378
|
+
handleSearchInputChange(query)
|
|
379
|
+
await waitForNextFrame()
|
|
380
|
+
return { kind: 'close' }
|
|
381
|
+
},
|
|
382
|
+
}),
|
|
383
|
+
...filterActions,
|
|
384
|
+
]
|
|
385
|
+
|
|
386
|
+
registerComponentActions(actions)
|
|
387
|
+
|
|
388
|
+
return () => {
|
|
389
|
+
deregisterComponentActions(actions)
|
|
258
390
|
}
|
|
259
|
-
}, [
|
|
391
|
+
}, [
|
|
392
|
+
deregisterComponentActions,
|
|
393
|
+
filters,
|
|
394
|
+
handleFilterChange,
|
|
395
|
+
handleSearchInputChange,
|
|
396
|
+
registerComponentActions,
|
|
397
|
+
searchInput,
|
|
398
|
+
setFilters,
|
|
399
|
+
setSearchInput,
|
|
400
|
+
])
|
|
260
401
|
|
|
261
402
|
${model.isReadonly
|
|
262
403
|
? ''
|
|
@@ -276,7 +417,7 @@ export default function ${model.admin.page.pageComponent.name}({ selectedKey }:
|
|
|
276
417
|
|
|
277
418
|
return (
|
|
278
419
|
${model.isReadonly ? '' : '<>'}
|
|
279
|
-
<SidebarProvider open={
|
|
420
|
+
<SidebarProvider open={sidepanelOpen} onOpenChange={setSidepanelOpen} width={sidebarWidth} onWidthChange={setSidebarWidth}>
|
|
280
421
|
<AdminSidebar collapsible="none" className="w-64" />
|
|
281
422
|
|
|
282
423
|
<SidebarInset className="p-4 overflow-hidden gap-4">
|
|
@@ -307,7 +448,6 @@ export default function ${model.admin.page.pageComponent.name}({ selectedKey }:
|
|
|
307
448
|
onCellChange={handleCellChange}
|
|
308
449
|
onRowSelectionChange={handleRowSelectionChange}
|
|
309
450
|
selectedKey={selectedKey}
|
|
310
|
-
onRowClick={handleRowClick}
|
|
311
451
|
keyField="${model.keyField.name}"
|
|
312
452
|
hasNextPage={hasNextPage}
|
|
313
453
|
fetchNextPage={fetchNextPage}
|
|
@@ -321,8 +461,6 @@ export default function ${model.admin.page.pageComponent.name}({ selectedKey }:
|
|
|
321
461
|
/>
|
|
322
462
|
</SidebarInset>
|
|
323
463
|
|
|
324
|
-
<SidebarTrigger />
|
|
325
|
-
|
|
326
464
|
<AdminDetailSidebar
|
|
327
465
|
side="right"
|
|
328
466
|
collapsible="offcanvas"
|