@carlonicora/nextjs-jsonapi 1.1.1 → 1.3.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/{BlockNoteEditor-6NVBI7XL.js → BlockNoteEditor-CNXQ3WL3.js} +7 -7
- package/dist/{BlockNoteEditor-6NVBI7XL.js.map → BlockNoteEditor-CNXQ3WL3.js.map} +1 -1
- package/dist/{BlockNoteEditor-4VOBTXWC.mjs → BlockNoteEditor-DC33K4IF.mjs} +3 -3
- package/dist/{chunk-KPRHIJO6.js → chunk-KDFDGXCX.js} +53 -53
- package/dist/{chunk-KPRHIJO6.js.map → chunk-KDFDGXCX.js.map} +1 -1
- package/dist/{chunk-FYRFMABS.js → chunk-L6LH7WA4.js} +21 -2
- package/dist/chunk-L6LH7WA4.js.map +1 -0
- package/dist/{chunk-MA2L2PL2.mjs → chunk-MHVXFWZY.mjs} +20 -1
- package/dist/chunk-MHVXFWZY.mjs.map +1 -0
- package/dist/{chunk-HP4Q5ZVG.mjs → chunk-Y4XI3AZR.mjs} +3 -3
- package/dist/client/index.js +3 -3
- package/dist/client/index.mjs +2 -2
- package/dist/components/index.js +3 -3
- package/dist/components/index.mjs +2 -2
- package/dist/contexts/index.js +3 -3
- package/dist/contexts/index.mjs +2 -2
- package/dist/features/index.d.mts +3 -1
- package/dist/features/index.d.ts +3 -1
- package/dist/features/index.js +4 -2
- package/dist/features/index.js.map +1 -1
- package/dist/features/index.mjs +3 -1
- package/dist/hooks/index.js +3 -3
- package/dist/hooks/index.mjs +2 -2
- package/dist/scripts/generate-web-module/generator.d.ts +15 -0
- package/dist/scripts/generate-web-module/generator.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/generator.js +320 -0
- package/dist/scripts/generate-web-module/generator.js.map +1 -0
- package/dist/scripts/generate-web-module/index.d.ts +16 -0
- package/dist/scripts/generate-web-module/index.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/index.js +80 -0
- package/dist/scripts/generate-web-module/index.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/container.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/components/container.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/container.template.js +124 -0
- package/dist/scripts/generate-web-module/templates/components/container.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/content.template.d.ts +15 -0
- package/dist/scripts/generate-web-module/templates/components/content.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/content.template.js +39 -0
- package/dist/scripts/generate-web-module/templates/components/content.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/deleter.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/components/deleter.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/deleter.template.js +51 -0
- package/dist/scripts/generate-web-module/templates/components/deleter.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/details.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/components/details.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/details.template.js +109 -0
- package/dist/scripts/generate-web-module/templates/components/details.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/editor.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/components/editor.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/editor.template.js +459 -0
- package/dist/scripts/generate-web-module/templates/components/editor.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/index.d.ts +15 -0
- package/dist/scripts/generate-web-module/templates/components/index.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/index.js +29 -0
- package/dist/scripts/generate-web-module/templates/components/index.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/list-container.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/components/list-container.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/list-container.template.js +30 -0
- package/dist/scripts/generate-web-module/templates/components/list-container.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/list.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/components/list.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/list.template.js +84 -0
- package/dist/scripts/generate-web-module/templates/components/list.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/multi-selector.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/components/multi-selector.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/multi-selector.template.js +184 -0
- package/dist/scripts/generate-web-module/templates/components/multi-selector.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/selector.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/components/selector.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/components/selector.template.js +199 -0
- package/dist/scripts/generate-web-module/templates/components/selector.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/context.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/context.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/context.template.js +121 -0
- package/dist/scripts/generate-web-module/templates/context.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/data/fields.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/data/fields.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/data/fields.template.js +55 -0
- package/dist/scripts/generate-web-module/templates/data/fields.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/data/index.d.ts +10 -0
- package/dist/scripts/generate-web-module/templates/data/index.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/data/index.js +26 -0
- package/dist/scripts/generate-web-module/templates/data/index.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/data/interface.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/data/interface.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/data/interface.template.js +116 -0
- package/dist/scripts/generate-web-module/templates/data/interface.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/data/model.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/data/model.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/data/model.template.js +274 -0
- package/dist/scripts/generate-web-module/templates/data/model.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/data/service.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/data/service.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/data/service.template.js +168 -0
- package/dist/scripts/generate-web-module/templates/data/service.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/index.d.ts +12 -0
- package/dist/scripts/generate-web-module/templates/index.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/index.js +37 -0
- package/dist/scripts/generate-web-module/templates/index.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/module.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/module.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/module.template.js +64 -0
- package/dist/scripts/generate-web-module/templates/module.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/pages/detail-page.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/pages/detail-page.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/pages/detail-page.template.js +65 -0
- package/dist/scripts/generate-web-module/templates/pages/detail-page.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/pages/index.d.ts +8 -0
- package/dist/scripts/generate-web-module/templates/pages/index.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/pages/index.js +13 -0
- package/dist/scripts/generate-web-module/templates/pages/index.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/pages/list-page.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/pages/list-page.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/pages/list-page.template.js +37 -0
- package/dist/scripts/generate-web-module/templates/pages/list-page.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/table-hook.template.d.ts +14 -0
- package/dist/scripts/generate-web-module/templates/table-hook.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/table-hook.template.js +174 -0
- package/dist/scripts/generate-web-module/templates/table-hook.template.js.map +1 -0
- package/dist/scripts/generate-web-module/transformers/field-mapper.d.ts +55 -0
- package/dist/scripts/generate-web-module/transformers/field-mapper.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/transformers/field-mapper.js +179 -0
- package/dist/scripts/generate-web-module/transformers/field-mapper.js.map +1 -0
- package/dist/scripts/generate-web-module/transformers/i18n-generator.d.ts +78 -0
- package/dist/scripts/generate-web-module/transformers/i18n-generator.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/transformers/i18n-generator.js +182 -0
- package/dist/scripts/generate-web-module/transformers/i18n-generator.js.map +1 -0
- package/dist/scripts/generate-web-module/transformers/import-resolver.d.ts +106 -0
- package/dist/scripts/generate-web-module/transformers/import-resolver.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/transformers/import-resolver.js +193 -0
- package/dist/scripts/generate-web-module/transformers/import-resolver.js.map +1 -0
- package/dist/scripts/generate-web-module/transformers/index.d.ts +12 -0
- package/dist/scripts/generate-web-module/transformers/index.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/transformers/index.js +28 -0
- package/dist/scripts/generate-web-module/transformers/index.js.map +1 -0
- package/dist/scripts/generate-web-module/transformers/name-transformer.d.ts +60 -0
- package/dist/scripts/generate-web-module/transformers/name-transformer.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/transformers/name-transformer.js +115 -0
- package/dist/scripts/generate-web-module/transformers/name-transformer.js.map +1 -0
- package/dist/scripts/generate-web-module/transformers/parent-detector.d.ts +57 -0
- package/dist/scripts/generate-web-module/transformers/parent-detector.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/transformers/parent-detector.js +88 -0
- package/dist/scripts/generate-web-module/transformers/parent-detector.js.map +1 -0
- package/dist/scripts/generate-web-module/transformers/relationship-resolver.d.ts +68 -0
- package/dist/scripts/generate-web-module/transformers/relationship-resolver.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/transformers/relationship-resolver.js +219 -0
- package/dist/scripts/generate-web-module/transformers/relationship-resolver.js.map +1 -0
- package/dist/scripts/generate-web-module/types/field-mapping.types.d.ts +68 -0
- package/dist/scripts/generate-web-module/types/field-mapping.types.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/types/field-mapping.types.js +129 -0
- package/dist/scripts/generate-web-module/types/field-mapping.types.js.map +1 -0
- package/dist/scripts/generate-web-module/types/index.d.ts +9 -0
- package/dist/scripts/generate-web-module/types/index.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/types/index.js +25 -0
- package/dist/scripts/generate-web-module/types/index.js.map +1 -0
- package/dist/scripts/generate-web-module/types/json-schema.interface.d.ts +40 -0
- package/dist/scripts/generate-web-module/types/json-schema.interface.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/types/json-schema.interface.js +10 -0
- package/dist/scripts/generate-web-module/types/json-schema.interface.js.map +1 -0
- package/dist/scripts/generate-web-module/types/template-data.interface.d.ts +128 -0
- package/dist/scripts/generate-web-module/types/template-data.interface.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/types/template-data.interface.js +9 -0
- package/dist/scripts/generate-web-module/types/template-data.interface.js.map +1 -0
- package/dist/scripts/generate-web-module/utils/bootstrapper-updater.d.ts +29 -0
- package/dist/scripts/generate-web-module/utils/bootstrapper-updater.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/utils/bootstrapper-updater.js +153 -0
- package/dist/scripts/generate-web-module/utils/bootstrapper-updater.js.map +1 -0
- package/dist/scripts/generate-web-module/utils/file-writer.d.ts +38 -0
- package/dist/scripts/generate-web-module/utils/file-writer.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/utils/file-writer.js +126 -0
- package/dist/scripts/generate-web-module/utils/file-writer.js.map +1 -0
- package/dist/scripts/generate-web-module/utils/i18n-updater.d.ts +28 -0
- package/dist/scripts/generate-web-module/utils/i18n-updater.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/utils/i18n-updater.js +122 -0
- package/dist/scripts/generate-web-module/utils/i18n-updater.js.map +1 -0
- package/dist/scripts/generate-web-module/utils/index.d.ts +9 -0
- package/dist/scripts/generate-web-module/utils/index.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/utils/index.js +20 -0
- package/dist/scripts/generate-web-module/utils/index.js.map +1 -0
- package/dist/scripts/generate-web-module/validators/json-schema-validator.d.ts +46 -0
- package/dist/scripts/generate-web-module/validators/json-schema-validator.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/validators/json-schema-validator.js +265 -0
- package/dist/scripts/generate-web-module/validators/json-schema-validator.js.map +1 -0
- package/package.json +27 -21
- package/scripts/generate-web-module/generator.ts +363 -0
- package/scripts/generate-web-module/index.ts +49 -0
- package/scripts/generate-web-module/templates/components/container.template.ts +129 -0
- package/scripts/generate-web-module/templates/components/content.template.ts +40 -0
- package/scripts/generate-web-module/templates/components/deleter.template.ts +51 -0
- package/scripts/generate-web-module/templates/components/details.template.ts +115 -0
- package/scripts/generate-web-module/templates/components/editor.template.ts +495 -0
- package/scripts/generate-web-module/templates/components/index.ts +18 -0
- package/scripts/generate-web-module/templates/components/list-container.template.ts +30 -0
- package/scripts/generate-web-module/templates/components/list.template.ts +91 -0
- package/scripts/generate-web-module/templates/components/multi-selector.template.ts +184 -0
- package/scripts/generate-web-module/templates/components/selector.template.ts +199 -0
- package/scripts/generate-web-module/templates/context.template.ts +121 -0
- package/scripts/generate-web-module/templates/data/fields.template.ts +64 -0
- package/scripts/generate-web-module/templates/data/index.ts +10 -0
- package/scripts/generate-web-module/templates/data/interface.template.ts +136 -0
- package/scripts/generate-web-module/templates/data/model.template.ts +327 -0
- package/scripts/generate-web-module/templates/data/service.template.ts +185 -0
- package/scripts/generate-web-module/templates/index.ts +34 -0
- package/scripts/generate-web-module/templates/module.template.ts +69 -0
- package/scripts/generate-web-module/templates/pages/detail-page.template.ts +65 -0
- package/scripts/generate-web-module/templates/pages/index.ts +8 -0
- package/scripts/generate-web-module/templates/pages/list-page.template.ts +37 -0
- package/scripts/generate-web-module/templates/table-hook.template.ts +182 -0
- package/scripts/generate-web-module/transformers/field-mapper.ts +201 -0
- package/scripts/generate-web-module/transformers/i18n-generator.ts +199 -0
- package/scripts/generate-web-module/transformers/import-resolver.ts +250 -0
- package/scripts/generate-web-module/transformers/index.ts +12 -0
- package/scripts/generate-web-module/transformers/name-transformer.ts +115 -0
- package/scripts/generate-web-module/transformers/parent-detector.ts +87 -0
- package/scripts/generate-web-module/transformers/relationship-resolver.ts +221 -0
- package/scripts/generate-web-module/tsconfig.json +24 -0
- package/scripts/generate-web-module/types/field-mapping.types.ts +141 -0
- package/scripts/generate-web-module/types/index.ts +9 -0
- package/scripts/generate-web-module/types/json-schema.interface.ts +42 -0
- package/scripts/generate-web-module/types/template-data.interface.ts +164 -0
- package/scripts/generate-web-module/utils/bootstrapper-updater.ts +145 -0
- package/scripts/generate-web-module/utils/file-writer.ts +115 -0
- package/scripts/generate-web-module/utils/i18n-updater.ts +108 -0
- package/scripts/generate-web-module/utils/index.ts +9 -0
- package/scripts/generate-web-module/validators/json-schema-validator.ts +306 -0
- package/src/features/user/index.ts +1 -1
- package/dist/chunk-FYRFMABS.js.map +0 -1
- package/dist/chunk-MA2L2PL2.mjs.map +0 -1
- /package/dist/{BlockNoteEditor-4VOBTXWC.mjs.map → BlockNoteEditor-DC33K4IF.mjs.map} +0 -0
- /package/dist/{chunk-HP4Q5ZVG.mjs.map → chunk-Y4XI3AZR.mjs.map} +0 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ListContainer Template
|
|
3
|
+
*
|
|
4
|
+
* Generates {Module}ListContainer.tsx component for the list page.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { FrontendTemplateData } from "../../types/template-data.interface";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate the list container component file content
|
|
11
|
+
*
|
|
12
|
+
* @param data - Frontend template data
|
|
13
|
+
* @returns Generated file content
|
|
14
|
+
*/
|
|
15
|
+
export function generateListContainerTemplate(data: FrontendTemplateData): string {
|
|
16
|
+
const { names } = data;
|
|
17
|
+
|
|
18
|
+
return `"use client";
|
|
19
|
+
|
|
20
|
+
import ${names.pascalCase}List from "@/features/${data.targetDir}/${names.kebabCase}/components/lists/${names.pascalCase}List";
|
|
21
|
+
|
|
22
|
+
function ${names.pascalCase}ListContainerInternal() {
|
|
23
|
+
return <${names.pascalCase}List />;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default function ${names.pascalCase}ListContainer() {
|
|
27
|
+
return <${names.pascalCase}ListContainerInternal />;
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List Template
|
|
3
|
+
*
|
|
4
|
+
* Generates {Module}List.tsx component for displaying a list of items.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { FrontendTemplateData } from "../../types/template-data.interface";
|
|
8
|
+
import { toCamelCase, pluralize } from "../../transformers/name-transformer";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate the list component file content
|
|
12
|
+
*
|
|
13
|
+
* @param data - Frontend template data
|
|
14
|
+
* @returns Generated file content
|
|
15
|
+
*/
|
|
16
|
+
export function generateListTemplate(data: FrontendTemplateData): string {
|
|
17
|
+
const { names, fields, relationships, extendsContent, tableFieldNames } = data;
|
|
18
|
+
|
|
19
|
+
// Build field list for the table - use name + first few fields + updatedAt
|
|
20
|
+
const displayFields = buildDisplayFields(data);
|
|
21
|
+
|
|
22
|
+
return `"use client";
|
|
23
|
+
|
|
24
|
+
import ${names.pascalCase}Editor from "@/features/${data.targetDir}/${names.kebabCase}/components/forms/${names.pascalCase}Editor";
|
|
25
|
+
import { ${names.pascalCase}Fields } from "@/features/${data.targetDir}/${names.kebabCase}/data/${names.pascalCase}Fields";
|
|
26
|
+
import { ${names.pascalCase}Interface } from "@/features/${data.targetDir}/${names.kebabCase}/data/${names.pascalCase}Interface";
|
|
27
|
+
import { ${names.pascalCase}Service } from "@/features/${data.targetDir}/${names.kebabCase}/data/${names.pascalCase}Service";
|
|
28
|
+
import "@/features/${data.targetDir}/${names.kebabCase}/hooks/use${names.pascalCase}TableStructure";
|
|
29
|
+
import { ContentListTable } from "@carlonicora/nextjs-jsonapi/components";
|
|
30
|
+
import { Modules } from "@carlonicora/nextjs-jsonapi/core";
|
|
31
|
+
import { DataListRetriever, useDataListRetriever } from "@carlonicora/nextjs-jsonapi/hooks";
|
|
32
|
+
import { useTranslations } from "next-intl";
|
|
33
|
+
import { ReactNode } from "react";
|
|
34
|
+
|
|
35
|
+
export default function ${names.pascalCase}List() {
|
|
36
|
+
const t = useTranslations();
|
|
37
|
+
|
|
38
|
+
const data: DataListRetriever<${names.pascalCase}Interface> = useDataListRetriever({
|
|
39
|
+
module: Modules.${names.pascalCase},
|
|
40
|
+
retriever: (params) => ${names.pascalCase}Service.findMany(params),
|
|
41
|
+
retrieverParams: {},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const functions: ReactNode[] = [<${names.pascalCase}Editor key="create-${names.kebabCase}" />];
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<ContentListTable
|
|
48
|
+
data={data}
|
|
49
|
+
fields={[${displayFields}]}
|
|
50
|
+
tableGeneratorType={Modules.${names.pascalCase}}
|
|
51
|
+
functions={functions}
|
|
52
|
+
title={t(\`types.${names.pluralKebab}\`, { count: 2 })}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Build display fields array for the table
|
|
61
|
+
*/
|
|
62
|
+
function buildDisplayFields(data: FrontendTemplateData): string {
|
|
63
|
+
const { names, fields, relationships, extendsContent } = data;
|
|
64
|
+
const displayFields: string[] = [];
|
|
65
|
+
|
|
66
|
+
// Always include name if it exists
|
|
67
|
+
if (extendsContent || fields.some((f) => f.name === "name")) {
|
|
68
|
+
displayFields.push(`${names.pascalCase}Fields.name`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Add author if present
|
|
72
|
+
const authorRel = relationships.find((r) => r.variant === "Author");
|
|
73
|
+
if (authorRel) {
|
|
74
|
+
const authorFieldName = toCamelCase(authorRel.variant!);
|
|
75
|
+
displayFields.push(`${names.pascalCase}Fields.${authorFieldName}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Add first non-author relationship
|
|
79
|
+
const otherRel = relationships.find((r) => r.variant !== "Author");
|
|
80
|
+
if (otherRel) {
|
|
81
|
+
const relFieldName = otherRel.single
|
|
82
|
+
? toCamelCase(otherRel.variant || otherRel.name)
|
|
83
|
+
: pluralize(toCamelCase(otherRel.name));
|
|
84
|
+
displayFields.push(`${names.pascalCase}Fields.${relFieldName}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Add updatedAt
|
|
88
|
+
displayFields.push(`${names.pascalCase}Fields.updatedAt`);
|
|
89
|
+
|
|
90
|
+
return displayFields.join(", ");
|
|
91
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MultiSelector Template
|
|
3
|
+
*
|
|
4
|
+
* Generates {Module}MultiSelector.tsx multi-select component.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { FrontendTemplateData } from "../../types/template-data.interface";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate the multi-selector component file content
|
|
11
|
+
*
|
|
12
|
+
* @param data - Frontend template data
|
|
13
|
+
* @returns Generated file content
|
|
14
|
+
*/
|
|
15
|
+
export function generateMultiSelectorTemplate(data: FrontendTemplateData): string {
|
|
16
|
+
const { names } = data;
|
|
17
|
+
|
|
18
|
+
return `"use client";
|
|
19
|
+
|
|
20
|
+
import { FormControl, FormField, FormItem, FormLabel, FormMessage, MultiSelect } from "@carlonicora/nextjs-jsonapi/shadcnui";
|
|
21
|
+
import { ${names.pascalCase}Interface } from "@/features/${data.targetDir}/${names.kebabCase}/data/${names.pascalCase}Interface";
|
|
22
|
+
import { ${names.pascalCase}Service } from "@/features/${data.targetDir}/${names.kebabCase}/data/${names.pascalCase}Service";
|
|
23
|
+
import { DataListRetriever, useDataListRetriever } from "@carlonicora/nextjs-jsonapi/hooks";
|
|
24
|
+
import { useDebounce } from "@carlonicora/nextjs-jsonapi/hooks";
|
|
25
|
+
import { Modules } from "@carlonicora/nextjs-jsonapi/core";
|
|
26
|
+
import { useTranslations } from "next-intl";
|
|
27
|
+
import { useCallback, useEffect, useState } from "react";
|
|
28
|
+
import { useWatch } from "react-hook-form";
|
|
29
|
+
|
|
30
|
+
type ${names.pascalCase}MultiSelectType = {
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type ${names.pascalCase}MultiSelectorProps = {
|
|
36
|
+
id: string;
|
|
37
|
+
form: any;
|
|
38
|
+
current${names.pascalCase}?: ${names.pascalCase}Interface;
|
|
39
|
+
label?: string;
|
|
40
|
+
placeholder?: string;
|
|
41
|
+
onChange?: (${names.pluralCamel}?: ${names.pascalCase}Interface[]) => void;
|
|
42
|
+
maxCount?: number;
|
|
43
|
+
isRequired?: boolean;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default function ${names.pascalCase}MultiSelector({
|
|
47
|
+
id,
|
|
48
|
+
form,
|
|
49
|
+
current${names.pascalCase},
|
|
50
|
+
label,
|
|
51
|
+
placeholder,
|
|
52
|
+
onChange,
|
|
53
|
+
maxCount = 3,
|
|
54
|
+
isRequired = false,
|
|
55
|
+
}: ${names.pascalCase}MultiSelectorProps) {
|
|
56
|
+
const t = useTranslations("features.${names.camelCase}");
|
|
57
|
+
const [${names.camelCase}Options, set${names.pascalCase}Options] = useState<any[]>([]);
|
|
58
|
+
const [searchTerm, setSearchTerm] = useState<string>("");
|
|
59
|
+
|
|
60
|
+
const selected${names.pluralPascal}: ${names.pascalCase}MultiSelectType[] = useWatch({ control: form.control, name: id }) || [];
|
|
61
|
+
|
|
62
|
+
const data: DataListRetriever<${names.pascalCase}Interface> = useDataListRetriever({
|
|
63
|
+
retriever: (params) => ${names.pascalCase}Service.findMany(params),
|
|
64
|
+
retrieverParams: {},
|
|
65
|
+
ready: true,
|
|
66
|
+
module: Modules.${names.pascalCase},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const updateSearch = useCallback(
|
|
70
|
+
(searchedTerm: string) => {
|
|
71
|
+
if (searchedTerm.trim()) {
|
|
72
|
+
data.addAdditionalParameter("search", searchedTerm.trim());
|
|
73
|
+
} else {
|
|
74
|
+
data.removeAdditionalParameter("search");
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
[data],
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const debouncedUpdateSearch = useDebounce(updateSearch, 500);
|
|
81
|
+
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
debouncedUpdateSearch(searchTerm);
|
|
84
|
+
}, [debouncedUpdateSearch, searchTerm]);
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (data.data && data.data.length > 0) {
|
|
88
|
+
const ${names.pluralCamel} = data.data as ${names.pascalCase}Interface[];
|
|
89
|
+
const filtered${names.pluralPascal} = ${names.pluralCamel}.filter((${names.camelCase}) => ${names.camelCase}.id !== current${names.pascalCase}?.id);
|
|
90
|
+
|
|
91
|
+
const options = filtered${names.pluralPascal}.map((${names.camelCase}) => ({
|
|
92
|
+
label: ${names.camelCase}.name,
|
|
93
|
+
value: ${names.camelCase}.id,
|
|
94
|
+
${names.camelCase}Data: ${names.camelCase},
|
|
95
|
+
}));
|
|
96
|
+
|
|
97
|
+
set${names.pascalCase}Options(options);
|
|
98
|
+
}
|
|
99
|
+
}, [data.data, current${names.pascalCase}]);
|
|
100
|
+
|
|
101
|
+
// Add options for any already selected ${names.pluralCamel} that aren't in search results
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (selected${names.pluralPascal}.length > 0) {
|
|
104
|
+
// Create a map of existing option IDs for quick lookup
|
|
105
|
+
const existingOptionIds = new Set(${names.camelCase}Options.map((option) => option.value));
|
|
106
|
+
|
|
107
|
+
// Find selected ${names.pluralCamel} that don't have an option yet
|
|
108
|
+
const missingOptions = selected${names.pluralPascal}
|
|
109
|
+
.filter((${names.camelCase}) => !existingOptionIds.has(${names.camelCase}.id))
|
|
110
|
+
.map((${names.camelCase}) => ({
|
|
111
|
+
label: ${names.camelCase}.name,
|
|
112
|
+
value: ${names.camelCase}.id,
|
|
113
|
+
${names.camelCase}Data: ${names.camelCase},
|
|
114
|
+
}));
|
|
115
|
+
|
|
116
|
+
if (missingOptions.length > 0) {
|
|
117
|
+
set${names.pascalCase}Options((prev) => [...prev, ...missingOptions]);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}, [selected${names.pluralPascal}, ${names.camelCase}Options]);
|
|
121
|
+
|
|
122
|
+
const handleValueChange = (selectedIds: string[]) => {
|
|
123
|
+
const updatedSelected${names.pluralPascal} = selectedIds.map((id) => {
|
|
124
|
+
const existing${names.pascalCase} = selected${names.pluralPascal}.find((${names.camelCase}) => ${names.camelCase}.id === id);
|
|
125
|
+
if (existing${names.pascalCase}) {
|
|
126
|
+
return existing${names.pascalCase};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const option = ${names.camelCase}Options.find((option) => option.value === id);
|
|
130
|
+
if (option?.${names.camelCase}Data) {
|
|
131
|
+
return {
|
|
132
|
+
id: option.${names.camelCase}Data.id,
|
|
133
|
+
name: option.${names.camelCase}Data.name,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { id, name: id };
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
form.setValue(id, updatedSelected${names.pluralPascal});
|
|
141
|
+
|
|
142
|
+
if (onChange) {
|
|
143
|
+
const fullSelected${names.pluralPascal} = selectedIds
|
|
144
|
+
.map((id) => ${names.camelCase}Options.find((option) => option.value === id)?.${names.camelCase}Data)
|
|
145
|
+
.filter(Boolean) as ${names.pascalCase}Interface[];
|
|
146
|
+
onChange(fullSelected${names.pluralPascal});
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const selected${names.pascalCase}Ids = selected${names.pluralPascal}.map((${names.camelCase}: ${names.pascalCase}MultiSelectType) => ${names.camelCase}.id);
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<div className="flex w-full flex-col">
|
|
154
|
+
<FormField
|
|
155
|
+
control={form.control}
|
|
156
|
+
name={id}
|
|
157
|
+
render={({ field }) => (
|
|
158
|
+
<FormItem className={\`\${label ? "mb-5" : "mb-1"}\`}>
|
|
159
|
+
{label && (
|
|
160
|
+
<FormLabel className="flex items-center">
|
|
161
|
+
{label}
|
|
162
|
+
{isRequired && <span className="text-destructive ml-2 font-semibold">*</span>}
|
|
163
|
+
</FormLabel>
|
|
164
|
+
)}
|
|
165
|
+
<FormControl>
|
|
166
|
+
<MultiSelect
|
|
167
|
+
options={${names.camelCase}Options}
|
|
168
|
+
onValueChange={handleValueChange}
|
|
169
|
+
defaultValue={selected${names.pascalCase}Ids}
|
|
170
|
+
placeholder={placeholder}
|
|
171
|
+
maxCount={maxCount}
|
|
172
|
+
animation={0}
|
|
173
|
+
onSearchChange={setSearchTerm}
|
|
174
|
+
/>
|
|
175
|
+
</FormControl>
|
|
176
|
+
<FormMessage />
|
|
177
|
+
</FormItem>
|
|
178
|
+
)}
|
|
179
|
+
/>
|
|
180
|
+
</div>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
`;
|
|
184
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Selector Template
|
|
3
|
+
*
|
|
4
|
+
* Generates {Module}Selector.tsx searchable combobox component.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { FrontendTemplateData } from "../../types/template-data.interface";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate the selector component file content
|
|
11
|
+
*
|
|
12
|
+
* @param data - Frontend template data
|
|
13
|
+
* @returns Generated file content
|
|
14
|
+
*/
|
|
15
|
+
export function generateSelectorTemplate(data: FrontendTemplateData): string {
|
|
16
|
+
const { names } = data;
|
|
17
|
+
|
|
18
|
+
return `"use client";
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
Command,
|
|
22
|
+
CommandItem,
|
|
23
|
+
CommandList,
|
|
24
|
+
FormControl,
|
|
25
|
+
FormField,
|
|
26
|
+
FormItem,
|
|
27
|
+
FormLabel,
|
|
28
|
+
FormMessage,
|
|
29
|
+
Input,
|
|
30
|
+
Popover,
|
|
31
|
+
PopoverContent,
|
|
32
|
+
PopoverTrigger,
|
|
33
|
+
} from "@carlonicora/nextjs-jsonapi/shadcnui";
|
|
34
|
+
import { ${names.pascalCase}Interface } from "@/features/${data.targetDir}/${names.kebabCase}/data/${names.pascalCase}Interface";
|
|
35
|
+
import { ${names.pascalCase}Service } from "@/features/${data.targetDir}/${names.kebabCase}/data/${names.pascalCase}Service";
|
|
36
|
+
import { DataListRetriever, useDataListRetriever } from "@carlonicora/nextjs-jsonapi/hooks";
|
|
37
|
+
import { useDebounce } from "@carlonicora/nextjs-jsonapi/hooks";
|
|
38
|
+
import { Modules } from "@carlonicora/nextjs-jsonapi/core";
|
|
39
|
+
|
|
40
|
+
import { CircleX, RefreshCwIcon, SearchIcon, XIcon } from "lucide-react";
|
|
41
|
+
import { useTranslations } from "next-intl";
|
|
42
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
43
|
+
|
|
44
|
+
type ${names.pascalCase}SelectorProps = {
|
|
45
|
+
id: string;
|
|
46
|
+
form: any;
|
|
47
|
+
label?: string;
|
|
48
|
+
placeholder?: string;
|
|
49
|
+
onChange?: (${names.camelCase}?: ${names.pascalCase}Interface) => void;
|
|
50
|
+
isRequired?: boolean;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export default function ${names.pascalCase}Selector({
|
|
54
|
+
id,
|
|
55
|
+
form,
|
|
56
|
+
label,
|
|
57
|
+
placeholder,
|
|
58
|
+
onChange,
|
|
59
|
+
isRequired = false,
|
|
60
|
+
}: ${names.pascalCase}SelectorProps) {
|
|
61
|
+
const t = useTranslations();
|
|
62
|
+
|
|
63
|
+
const [open, setOpen] = useState<boolean>(false);
|
|
64
|
+
|
|
65
|
+
const searchTermRef = useRef<string>("");
|
|
66
|
+
const [searchTerm, setSearchTerm] = useState<string>("");
|
|
67
|
+
|
|
68
|
+
const [isSearching, setIsSearching] = useState<boolean>(false);
|
|
69
|
+
|
|
70
|
+
const data: DataListRetriever<${names.pascalCase}Interface> = useDataListRetriever({
|
|
71
|
+
retriever: (params) => {
|
|
72
|
+
return ${names.pascalCase}Service.findMany(params);
|
|
73
|
+
},
|
|
74
|
+
retrieverParams: {},
|
|
75
|
+
module: Modules.${names.pascalCase},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const search = useCallback(
|
|
79
|
+
async (searchedTerm: string) => {
|
|
80
|
+
try {
|
|
81
|
+
if (searchedTerm === searchTermRef.current) return;
|
|
82
|
+
setIsSearching(true);
|
|
83
|
+
searchTermRef.current = searchedTerm;
|
|
84
|
+
await data.search(searchedTerm);
|
|
85
|
+
} finally {
|
|
86
|
+
setIsSearching(false);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
[searchTermRef, data],
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const updateSearchTerm = useDebounce(search, 500);
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
setIsSearching(true);
|
|
96
|
+
updateSearchTerm(searchTerm);
|
|
97
|
+
}, [updateSearchTerm, searchTerm]);
|
|
98
|
+
|
|
99
|
+
const set${names.pascalCase} = (${names.camelCase}?: ${names.pascalCase}Interface) => {
|
|
100
|
+
if (onChange) onChange(${names.camelCase});
|
|
101
|
+
if (!${names.camelCase}) {
|
|
102
|
+
form.setValue(id, undefined);
|
|
103
|
+
setOpen(false);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
form.setValue(id, { id: ${names.camelCase}.id, name: ${names.camelCase}.name });
|
|
108
|
+
setOpen(false);
|
|
109
|
+
|
|
110
|
+
setTimeout(() => {
|
|
111
|
+
setOpen(false);
|
|
112
|
+
}, 0);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div className="flex w-full flex-col">
|
|
117
|
+
<FormField
|
|
118
|
+
control={form.control}
|
|
119
|
+
name={id}
|
|
120
|
+
render={({ field }) => (
|
|
121
|
+
<FormItem className={\`\${label ? "mb-5" : "mb-1"}\`}>
|
|
122
|
+
{label && (
|
|
123
|
+
<FormLabel className="flex items-center">
|
|
124
|
+
{label}
|
|
125
|
+
{isRequired && <span className="text-destructive ml-2 font-semibold">*</span>}
|
|
126
|
+
</FormLabel>
|
|
127
|
+
)}
|
|
128
|
+
<FormControl>
|
|
129
|
+
<Popover open={open} onOpenChange={setOpen} modal={true}>
|
|
130
|
+
<div className="flex w-full flex-row items-center justify-between">
|
|
131
|
+
<PopoverTrigger className="w-full">
|
|
132
|
+
<div className="flex w-full flex-row items-center justify-start rounded-md text-sm">
|
|
133
|
+
{field.value ? (
|
|
134
|
+
<>
|
|
135
|
+
<div className="flex w-full flex-row items-center justify-start rounded-md border p-2">
|
|
136
|
+
<span className="">{field.value?.name ?? ""}</span>
|
|
137
|
+
</div>
|
|
138
|
+
</>
|
|
139
|
+
) : (
|
|
140
|
+
<div className="text-muted-foreground mr-7 flex h-10 w-full flex-row items-center justify-start rounded-md border p-2 text-sm">
|
|
141
|
+
{placeholder ?? t(\`generic.search.placeholder\`, { type: t(\`types.${names.pluralKebab}\`, { count: 1 }) })}
|
|
142
|
+
</div>
|
|
143
|
+
)}
|
|
144
|
+
</div>
|
|
145
|
+
</PopoverTrigger>
|
|
146
|
+
{field.value && (
|
|
147
|
+
<CircleX
|
|
148
|
+
className="text-muted hover:text-destructive ml-2 h-6 w-6 cursor-pointer"
|
|
149
|
+
onClick={() => set${names.pascalCase}()}
|
|
150
|
+
/>
|
|
151
|
+
)}
|
|
152
|
+
</div>
|
|
153
|
+
<PopoverContent>
|
|
154
|
+
<Command shouldFilter={false}>
|
|
155
|
+
<div className="relative mb-2 w-full">
|
|
156
|
+
<SearchIcon className="text-muted-foreground absolute left-2.5 top-2.5 h-4 w-4" />
|
|
157
|
+
<Input
|
|
158
|
+
placeholder={t(\`generic.search.placeholder\`, { type: t(\`types.${names.pluralKebab}\`, { count: 1 }) })}
|
|
159
|
+
type="text"
|
|
160
|
+
className="w-full pl-8 pr-8"
|
|
161
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
162
|
+
value={searchTerm}
|
|
163
|
+
/>
|
|
164
|
+
{isSearching ? (
|
|
165
|
+
<RefreshCwIcon className="text-muted-foreground absolute right-2.5 top-2.5 h-4 w-4 animate-spin" />
|
|
166
|
+
) : searchTermRef.current ? (
|
|
167
|
+
<XIcon
|
|
168
|
+
className={\`absolute right-2.5 top-2.5 h-4 w-4 \${searchTermRef.current ? "cursor-pointer" : "text-muted-foreground"}\`}
|
|
169
|
+
onClick={() => {
|
|
170
|
+
setSearchTerm("");
|
|
171
|
+
search("");
|
|
172
|
+
}}
|
|
173
|
+
/>
|
|
174
|
+
) : (
|
|
175
|
+
<></>
|
|
176
|
+
)}
|
|
177
|
+
</div>
|
|
178
|
+
<CommandList>
|
|
179
|
+
{data.data &&
|
|
180
|
+
data.data.length > 0 &&
|
|
181
|
+
(data.data as ${names.pascalCase}Interface[]).map((${names.camelCase}: ${names.pascalCase}Interface) => (
|
|
182
|
+
<CommandItem className="cursor-pointer" key={${names.camelCase}.id} onSelect={() => set${names.pascalCase}(${names.camelCase})}>
|
|
183
|
+
<span className="">{${names.camelCase}.name}</span>
|
|
184
|
+
</CommandItem>
|
|
185
|
+
))}
|
|
186
|
+
</CommandList>
|
|
187
|
+
</Command>
|
|
188
|
+
</PopoverContent>
|
|
189
|
+
</Popover>
|
|
190
|
+
</FormControl>
|
|
191
|
+
<FormMessage />
|
|
192
|
+
</FormItem>
|
|
193
|
+
)}
|
|
194
|
+
/>
|
|
195
|
+
</div>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
`;
|
|
199
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Template
|
|
3
|
+
*
|
|
4
|
+
* Generates {Module}Context.tsx for module state management.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { FrontendTemplateData } from "../types/template-data.interface";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate the context file content
|
|
11
|
+
*
|
|
12
|
+
* @param data - Frontend template data
|
|
13
|
+
* @returns Generated file content
|
|
14
|
+
*/
|
|
15
|
+
export function generateContextTemplate(data: FrontendTemplateData): string {
|
|
16
|
+
const { names, extendsContent } = data;
|
|
17
|
+
|
|
18
|
+
return `"use client";
|
|
19
|
+
|
|
20
|
+
import ${names.pascalCase}Deleter from "@/features/${data.targetDir}/${names.kebabCase}/components/forms/${names.pascalCase}Deleter";
|
|
21
|
+
import ${names.pascalCase}Editor from "@/features/${data.targetDir}/${names.kebabCase}/components/forms/${names.pascalCase}Editor";
|
|
22
|
+
import { ${names.pascalCase}Interface } from "@/features/${data.targetDir}/${names.kebabCase}/data/${names.pascalCase}Interface";
|
|
23
|
+
import { ${names.pascalCase}Service } from "@/features/${data.targetDir}/${names.kebabCase}/data/${names.pascalCase}Service";
|
|
24
|
+
import { SharedProvider } from "@carlonicora/nextjs-jsonapi/contexts";
|
|
25
|
+
import { usePageUrlGenerator } from "@carlonicora/nextjs-jsonapi/hooks";
|
|
26
|
+
import { BreadcrumbItemData } from "@carlonicora/nextjs-jsonapi/interfaces";
|
|
27
|
+
|
|
28
|
+
import { JsonApiHydratedDataInterface, Modules, rehydrate } from "@carlonicora/nextjs-jsonapi/core";
|
|
29
|
+
import { useTranslations } from "next-intl";
|
|
30
|
+
import { createContext, ReactNode, useContext, useState } from "react";
|
|
31
|
+
|
|
32
|
+
interface ${names.pascalCase}ContextType {
|
|
33
|
+
${names.camelCase}: ${names.pascalCase}Interface | undefined;
|
|
34
|
+
set${names.pascalCase}: (value: ${names.pascalCase}Interface | undefined) => void;
|
|
35
|
+
reload${names.pascalCase}: () => Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const ${names.pascalCase}Context = createContext<${names.pascalCase}ContextType | undefined>(undefined);
|
|
39
|
+
|
|
40
|
+
type ${names.pascalCase}ProviderProps = {
|
|
41
|
+
children: ReactNode;
|
|
42
|
+
dehydrated${names.pascalCase}?: JsonApiHydratedDataInterface;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const ${names.pascalCase}Provider = ({ children, dehydrated${names.pascalCase} }: ${names.pascalCase}ProviderProps) => {
|
|
46
|
+
const generateUrl = usePageUrlGenerator();
|
|
47
|
+
const t = useTranslations();
|
|
48
|
+
|
|
49
|
+
const [${names.camelCase}, set${names.pascalCase}] = useState<${names.pascalCase}Interface | undefined>(
|
|
50
|
+
dehydrated${names.pascalCase} ? rehydrate<${names.pascalCase}Interface>(Modules.${names.pascalCase}, dehydrated${names.pascalCase}) : undefined,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const reload${names.pascalCase} = async () => {
|
|
54
|
+
if (!${names.camelCase}) return;
|
|
55
|
+
|
|
56
|
+
const fresh${names.pascalCase} = await ${names.pascalCase}Service.findOne({ id: ${names.camelCase}.id });
|
|
57
|
+
set${names.pascalCase}(fresh${names.pascalCase});
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const breadcrumb = () => {
|
|
61
|
+
const response: BreadcrumbItemData[] = [];
|
|
62
|
+
|
|
63
|
+
response.push({
|
|
64
|
+
name: t(\`types.${names.pluralCamel}\`, { count: 2 }),
|
|
65
|
+
href: generateUrl({ page: Modules.${names.pascalCase} }),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (${names.camelCase})
|
|
69
|
+
response.push({
|
|
70
|
+
name: ${names.camelCase}.name,
|
|
71
|
+
href: generateUrl({ page: Modules.${names.pascalCase}, id: ${names.camelCase}.id }),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return response;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const title = () => {
|
|
78
|
+
const response: any = {
|
|
79
|
+
type: t(\`types.${names.pluralCamel}\`, { count: ${names.camelCase} ? 1 : 2 }),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const functions: ReactNode[] = [];
|
|
83
|
+
|
|
84
|
+
if (${names.camelCase}) {
|
|
85
|
+
response.element = ${names.camelCase}.name;
|
|
86
|
+
|
|
87
|
+
functions.push(<${names.pascalCase}Deleter key={\`${names.pascalCase}Deleter\`} ${names.camelCase}={${names.camelCase}} />);
|
|
88
|
+
functions.push(<${names.pascalCase}Editor key={\`${names.pascalCase}Editor\`} ${names.camelCase}={${names.camelCase}} propagateChanges={set${names.pascalCase}} />);
|
|
89
|
+
} else {
|
|
90
|
+
functions.push(<${names.pascalCase}Editor key={\`${names.pascalCase}Editor\`} />);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (functions.length > 0) response.functions = functions;
|
|
94
|
+
|
|
95
|
+
return response;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<SharedProvider value={{ breadcrumbs: breadcrumb(), title: title() }}>
|
|
100
|
+
<${names.pascalCase}Context.Provider
|
|
101
|
+
value={{
|
|
102
|
+
${names.camelCase}: ${names.camelCase},
|
|
103
|
+
set${names.pascalCase}: set${names.pascalCase},
|
|
104
|
+
reload${names.pascalCase}: reload${names.pascalCase},
|
|
105
|
+
}}
|
|
106
|
+
>
|
|
107
|
+
{children}
|
|
108
|
+
</${names.pascalCase}Context.Provider>
|
|
109
|
+
</SharedProvider>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const use${names.pascalCase}Context = (): ${names.pascalCase}ContextType => {
|
|
114
|
+
const context = useContext(${names.pascalCase}Context);
|
|
115
|
+
if (context === undefined) {
|
|
116
|
+
throw new Error("use${names.pascalCase}Context must be used within a ${names.pascalCase}Provider");
|
|
117
|
+
}
|
|
118
|
+
return context;
|
|
119
|
+
};
|
|
120
|
+
`;
|
|
121
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fields Template
|
|
3
|
+
*
|
|
4
|
+
* Generates {Module}Fields.ts enum file for type-safe field references.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { FrontendTemplateData } from "../../types/template-data.interface";
|
|
8
|
+
import { toCamelCase, pluralize } from "../../transformers/name-transformer";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate the fields enum file content
|
|
12
|
+
*
|
|
13
|
+
* @param data - Frontend template data
|
|
14
|
+
* @returns Generated file content
|
|
15
|
+
*/
|
|
16
|
+
export function generateFieldsTemplate(data: FrontendTemplateData): string {
|
|
17
|
+
const { names, fields, relationships, tableFieldNames } = data;
|
|
18
|
+
|
|
19
|
+
const enumEntries = generateEnumEntries(data);
|
|
20
|
+
|
|
21
|
+
return `export enum ${names.pascalCase}Fields {
|
|
22
|
+
${enumEntries}
|
|
23
|
+
}
|
|
24
|
+
`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generate enum entries
|
|
29
|
+
*/
|
|
30
|
+
function generateEnumEntries(data: FrontendTemplateData): string {
|
|
31
|
+
const { names, fields, relationships } = data;
|
|
32
|
+
const entries: string[] = [];
|
|
33
|
+
|
|
34
|
+
// Add module ID field
|
|
35
|
+
entries.push(` ${names.camelCase}Id = "${names.camelCase}Id",`);
|
|
36
|
+
entries.push(``);
|
|
37
|
+
|
|
38
|
+
// Add primitive fields
|
|
39
|
+
const fieldNames = fields.map((f) => f.name);
|
|
40
|
+
fieldNames.forEach((name) => {
|
|
41
|
+
entries.push(` ${name} = "${name}",`);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (relationships.length > 0) {
|
|
45
|
+
entries.push(``);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Add relationship fields
|
|
49
|
+
relationships.forEach((rel) => {
|
|
50
|
+
const effectiveName = rel.variant || rel.name;
|
|
51
|
+
const key = rel.single
|
|
52
|
+
? toCamelCase(effectiveName)
|
|
53
|
+
: pluralize(toCamelCase(rel.name));
|
|
54
|
+
entries.push(` ${key} = "${key}",`);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
entries.push(``);
|
|
58
|
+
|
|
59
|
+
// Add standard timestamp fields
|
|
60
|
+
entries.push(` createdAt = "createdAt",`);
|
|
61
|
+
entries.push(` updatedAt = "updatedAt",`);
|
|
62
|
+
|
|
63
|
+
return entries.join("\n");
|
|
64
|
+
}
|