@vc-shell/create-vc-app 1.1.99-alpha.1 → 1.2.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.
Files changed (83) hide show
  1. package/README.md +26 -552
  2. package/dist/index.js +530 -1900
  3. package/dist/templates/base/_package.json +5 -5
  4. package/dist/templates/base/src/main.ts +4 -0
  5. package/dist/templates/mocks/sample-data/constants.ts +89 -0
  6. package/dist/templates/mocks/sample-data/index.ts +2 -0
  7. package/dist/templates/mocks/sample-data/methods.ts +65 -0
  8. package/dist/templates/modules/classic-module/composables/index.ts +2 -0
  9. package/dist/templates/modules/classic-module/composables/use{{ModuleNamePascalCase}}Details/index.ts +24 -0
  10. package/dist/templates/modules/classic-module/composables/use{{ModuleNamePascalCase}}List/index.ts +47 -0
  11. package/dist/templates/modules/classic-module/index.ts +8 -0
  12. package/dist/templates/modules/classic-module/locales/en.json +37 -0
  13. package/dist/templates/modules/classic-module/locales/index.ts +2 -0
  14. package/dist/templates/modules/classic-module/pages/details.vue +87 -0
  15. package/dist/templates/modules/classic-module/pages/index.ts +2 -0
  16. package/dist/templates/modules/classic-module/pages/list.vue +257 -0
  17. package/dist/templates/sample/classic-module/composables/index.ts +2 -0
  18. package/dist/templates/sample/classic-module/composables/useDetails/index.ts +54 -0
  19. package/dist/templates/sample/classic-module/composables/useList/index.ts +62 -0
  20. package/dist/templates/sample/classic-module/index.ts +8 -0
  21. package/dist/templates/sample/classic-module/locales/en.json +67 -0
  22. package/dist/templates/sample/classic-module/locales/index.ts +2 -0
  23. package/dist/templates/sample/classic-module/pages/details.vue +238 -0
  24. package/dist/templates/sample/classic-module/pages/index.ts +2 -0
  25. package/dist/templates/sample/classic-module/pages/list.vue +300 -0
  26. package/dist/templates/sample/overrides/main.ts +52 -0
  27. package/package.json +7 -12
  28. package/dist/cli/argv.d.ts +0 -4
  29. package/dist/cli/argv.d.ts.map +0 -1
  30. package/dist/cli/constants.d.ts +0 -4
  31. package/dist/cli/constants.d.ts.map +0 -1
  32. package/dist/cli/errors.d.ts +0 -12
  33. package/dist/cli/errors.d.ts.map +0 -1
  34. package/dist/cli/help.d.ts +0 -3
  35. package/dist/cli/help.d.ts.map +0 -1
  36. package/dist/cli/run.d.ts +0 -2
  37. package/dist/cli/run.d.ts.map +0 -1
  38. package/dist/cli/runtime.d.ts +0 -7
  39. package/dist/cli/runtime.d.ts.map +0 -1
  40. package/dist/cli/types.d.ts +0 -30
  41. package/dist/cli/types.d.ts.map +0 -1
  42. package/dist/cli/utils.d.ts +0 -4
  43. package/dist/cli/utils.d.ts.map +0 -1
  44. package/dist/cli/validation.d.ts +0 -5
  45. package/dist/cli/validation.d.ts.map +0 -1
  46. package/dist/commands/generate-blade.d.ts +0 -16
  47. package/dist/commands/generate-blade.d.ts.map +0 -1
  48. package/dist/templates/base/ai-guides/.cursorrules-vc-shell +0 -529
  49. package/dist/templates/base/ai-guides/README.md +0 -360
  50. package/dist/templates/base/ai-guides/guides/AI_GUIDE.md +0 -195
  51. package/dist/templates/base/ai-guides/guides/blade-patterns.md +0 -384
  52. package/dist/templates/base/ai-guides/guides/complete-workflow.md +0 -781
  53. package/dist/templates/base/ai-guides/guides/composables-reference.md +0 -338
  54. package/dist/templates/base/ai-guides/guides/troubleshooting.md +0 -529
  55. package/dist/templates/base/ai-guides/guides/ui-components-reference.md +0 -903
  56. package/dist/templates/base/ai-guides/prompts/adapt-existing-module.md +0 -1026
  57. package/dist/templates/base/ai-guides/prompts/advanced-scenarios.md +0 -852
  58. package/dist/templates/base/ai-guides/prompts/api-client-generation.md +0 -877
  59. package/dist/templates/base/ai-guides/prompts/cli-usage.md +0 -640
  60. package/dist/templates/base/ai-guides/prompts/quick-start-scenarios.md +0 -773
  61. package/dist/templates/base/ai-guides/prompts/simple-modifications.md +0 -987
  62. package/dist/templates/blades/details/blade.vue +0 -175
  63. package/dist/templates/blades/grid/blade.vue +0 -340
  64. package/dist/templates/composables/details-composable.ts +0 -101
  65. package/dist/templates/composables/grid-composable.ts +0 -244
  66. package/dist/templates/module/components/index.ts +0 -2
  67. package/dist/templates/module/components/widgets/index.ts +0 -2
  68. package/dist/templates/module/composables/index.ts +0 -3
  69. package/dist/templates/module/index.ts +0 -13
  70. package/dist/templates/module/locales/en.json +0 -65
  71. package/dist/templates/module/locales/index.ts +0 -4
  72. package/dist/templates/module/pages/index.ts +0 -3
  73. package/dist/templates/widgets/widget.vue +0 -113
  74. package/dist/utils/form-builder.d.ts +0 -69
  75. package/dist/utils/form-builder.d.ts.map +0 -1
  76. package/dist/utils/format.d.ts +0 -24
  77. package/dist/utils/format.d.ts.map +0 -1
  78. package/dist/utils/naming.d.ts +0 -44
  79. package/dist/utils/naming.d.ts.map +0 -1
  80. package/dist/utils/register-module.d.ts +0 -21
  81. package/dist/utils/register-module.d.ts.map +0 -1
  82. package/dist/workflows/create-app.d.ts +0 -14
  83. package/dist/workflows/create-app.d.ts.map +0 -1
@@ -1,175 +0,0 @@
1
- <template>
2
- <VcBlade
3
- v-loading="loading"
4
- :title="bladeTitle"
5
- :toolbar-items="bladeToolbar"
6
- :closable="closable"
7
- :expanded="expanded"
8
- :modified="isModified"
9
- width="70%"
10
- @close="$emit('close:blade')"
11
- @expand="$emit('expand:blade')"
12
- @collapse="$emit('collapse:blade')"
13
- >
14
- <VcContainer class="tw-p-2">
15
- <VcRow class="tw-space-x-4">
16
- <VcCol :size="6">
17
- <!-- Main Form -->
18
- <div class="tw-space-y-4">
19
- {{FORM_FIELDS}}
20
- </div>
21
- </VcCol>
22
- </VcRow>
23
- </VcContainer>
24
- </VcBlade>
25
- </template>
26
-
27
- <script setup lang="ts">
28
- import { computed, onMounted, watch } from "vue";
29
- import { useI18n } from "vue-i18n";
30
- import {
31
- IBladeToolbar,
32
- IParentCallArgs,
33
- useBladeNavigation,
34
- useBeforeUnload,
35
- usePopup,{{GALLERY_IMPORTS}}
36
- } from "@vc-shell/framework";
37
- import { use{{EntityName}}Details } from "../composables";
38
- import { Field, useForm } from "vee-validate";
39
- import moment from "moment";
40
-
41
- // TODO: Replace with your actual types
42
- // Example: import { IProduct } from "@your-app/api/products";
43
-
44
- export interface Props {
45
- expanded?: boolean;
46
- closable?: boolean;
47
- param?: string;
48
- options?: Record<string, unknown>;
49
- }
50
-
51
- export interface Emits {
52
- (event: "parent:call", args: IParentCallArgs): void;
53
- (event: "close:blade"): void;
54
- (event: "expand:blade"): void;
55
- (event: "collapse:blade"): void;
56
- }
57
-
58
- const props = withDefaults(defineProps<Props>(), {
59
- expanded: true,
60
- closable: true,
61
- });
62
-
63
- const emit = defineEmits<Emits>();
64
-
65
- defineOptions({
66
- name: "{{EntityName}}Details",
67
- url: "/{{entity-name}}",
68
- });
69
-
70
- const { t } = useI18n({ useScope: "global" });
71
- const { onBeforeClose } = useBladeNavigation();
72
- const { showConfirmation, showInfo } = usePopup();
73
- const { meta } = useForm({ validateOnMount: false });
74
-
75
- const {
76
- item,
77
- loading,
78
- load{{EntityName}},
79
- save{{EntityName}},
80
- delete{{EntityName}},
81
- isModified,
82
- resetModificationState,
83
- } = use{{EntityName}}Details();
84
-
85
- {{GALLERY_SCRIPT_ADDITIONS}}
86
-
87
- const bladeTitle = computed(() => {
88
- return t("{{MODULE_NAME_UPPERCASE}}.PAGES.DETAILS.TITLE");
89
- });
90
-
91
- const createdDate = computed(() => {
92
- const date = new Date(item.value?.createdDate ?? "");
93
- return moment(date).format("L LT");
94
- });
95
-
96
- const bladeToolbar = computed((): IBladeToolbar[] => [
97
- {
98
- id: "save",
99
- title: t("{{MODULE_NAME_UPPERCASE}}.PAGES.DETAILS.TOOLBAR.SAVE"),
100
- icon: "material-save",
101
- async clickHandler() {
102
- if (meta.value.valid) {
103
- const saved = await save{{EntityName}}(item.value);
104
-
105
- if (!props.param && saved?.id) {
106
- emit("parent:call", {
107
- method: "onItemClick",
108
- args: { id: saved.id },
109
- });
110
- }
111
-
112
- resetModificationState();
113
- emit("parent:call", { method: "reload" });
114
-
115
- if (!props.param) {
116
- emit("close:blade");
117
- }
118
- } else {
119
- showInfo(t("{{MODULE_NAME_UPPERCASE}}.PAGES.ALERTS.NOT_VALID"));
120
- }
121
- },
122
- disabled: computed(() => !(meta.value.valid && isModified.value)),
123
- },
124
- {
125
- id: "delete",
126
- title: t("{{MODULE_NAME_UPPERCASE}}.PAGES.DETAILS.TOOLBAR.DELETE"),
127
- icon: "material-delete",
128
- async clickHandler() {
129
- if (
130
- props.param &&
131
- (await showConfirmation(t("{{MODULE_NAME_UPPERCASE}}.PAGES.ALERTS.DELETE")))
132
- ) {
133
- await delete{{EntityName}}(props.param);
134
- emit("parent:call", { method: "reload" });
135
- emit("close:blade");
136
- }
137
- },
138
- isVisible: computed(() => !!props.param),
139
- },
140
- ]);
141
-
142
- watch(
143
- () => props.param,
144
- async (newParam) => {
145
- if (newParam) {
146
- await load{{EntityName}}(newParam);
147
- }
148
- },
149
- { immediate: true, deep: true }
150
- );
151
-
152
- onMounted(async () => {
153
- if (props.param) {
154
- await load{{EntityName}}(props.param);
155
- } else if (props.options?.item) {
156
- item.value = props.options.item;
157
- resetModificationState();
158
- }
159
- });
160
-
161
- onBeforeClose(async () => {
162
- if (isModified.value) {
163
- return await showConfirmation(
164
- t("{{MODULE_NAME_UPPERCASE}}.PAGES.ALERTS.CLOSE_CONFIRMATION")
165
- );
166
- }
167
- return true;
168
- });
169
-
170
- useBeforeUnload(computed(() => isModified.value));
171
-
172
- defineExpose({
173
- title: bladeTitle,
174
- });
175
- </script>
@@ -1,340 +0,0 @@
1
- <template>
2
- <VcBlade
3
- v-loading="loading"
4
- :title="bladeTitle"
5
- width="50%"
6
- :expanded="expanded"
7
- :closable="closable"
8
- :toolbar-items="bladeToolbar"
9
- @close="$emit('close:blade')"
10
- @expand="$emit('expand:blade')"
11
- @collapse="$emit('collapse:blade')"
12
- >
13
- <!-- @vue-generic {I{{EntityName}}} -->
14
- <VcTable
15
- :total-label="$t('{{MODULE_NAME_UPPERCASE}}.PAGES.LIST.TABLE.TOTALS')"
16
- :items="items"
17
- :selected-item-id="selectedItemId"
18
- :search-value="searchValue"
19
- :columns="tableColumns"
20
- :sort="sortExpression"
21
- :pages="pages"
22
- :current-page="currentPage"
23
- :total-count="totalCount"
24
- :expanded="expanded"
25
- :active-filter-count="activeFilterCount"
26
- :empty="empty"
27
- :notfound="notfound"
28
- state-key="{{entity_name_plural}}_list"
29
- :multiselect="false"
30
- class="tw-grow tw-basis-0"
31
- @item-click="onItemClick"
32
- @header-click="onHeaderClick"
33
- @pagination-click="onPaginationClick"
34
- @search:change="onSearchList"
35
- @scroll:ptr="reload"
36
- >
37
- <template #filters>
38
- <div class="tw-p-4">
39
- <VcRow class="tw-gap-16">
40
- <div class="tw-flex tw-flex-col">
41
- <!-- Status Filter -->
42
- <h3 class="tw-text-sm tw-font-medium tw-mb-3">
43
- {{ $t("{{MODULE_NAME_UPPERCASE}}.PAGES.LIST.TABLE.FILTER.STATUS.TITLE") }}
44
- </h3>
45
- <div class="tw-space-y-2">
46
- <VcRadioButton
47
- v-for="status in statuses"
48
- :key="status.value"
49
- :model-value="stagedFilters.status[0] || ''"
50
- :value="status.value"
51
- :label="status.displayValue"
52
- @update:model-value="(value) => toggleFilter('status', String(value), true)"
53
- >
54
- </VcRadioButton>
55
- </div>
56
- </div>
57
-
58
- <!-- Date Range Filter -->
59
- <div class="tw-flex tw-flex-col">
60
- <h3 class="tw-text-sm tw-font-medium tw-mb-3">
61
- {{ $t("{{MODULE_NAME_UPPERCASE}}.PAGES.LIST.TABLE.FILTER.DATE.TITLE") }}
62
- </h3>
63
- <div class="tw-space-y-3">
64
- <VcInput
65
- v-model="stagedFilters.startDate"
66
- type="date"
67
- :label="$t('{{MODULE_NAME_UPPERCASE}}.PAGES.LIST.TABLE.FILTER.DATE.START_DATE')"
68
- @update:model-value="(value) => toggleFilter('startDate', String(value || ''), true)"
69
- />
70
- <VcInput
71
- v-model="stagedFilters.endDate"
72
- type="date"
73
- :label="$t('{{MODULE_NAME_UPPERCASE}}.PAGES.LIST.TABLE.FILTER.DATE.END_DATE')"
74
- @update:model-value="(value) => toggleFilter('endDate', String(value || ''), true)"
75
- />
76
- </div>
77
- </div>
78
- </VcRow>
79
-
80
- <!-- Filter Controls -->
81
- <div class="tw-flex tw-gap-2 tw-mt-4">
82
- <VcButton
83
- variant="primary"
84
- :disabled="!hasFilterChanges"
85
- @click="applyFilters"
86
- >
87
- {{ $t("{{MODULE_NAME_UPPERCASE}}.PAGES.LIST.TABLE.FILTER.APPLY") }}
88
- </VcButton>
89
-
90
- <VcButton
91
- variant="secondary"
92
- :disabled="!hasFiltersApplied"
93
- @click="resetFilters"
94
- >
95
- {{ $t("{{MODULE_NAME_UPPERCASE}}.PAGES.LIST.TABLE.FILTER.RESET") }}
96
- </VcButton>
97
- </div>
98
- </div>
99
- </template>
100
-
101
- <!-- Override column template example -->
102
- <template #item_name="itemData">
103
- <div class="tw-truncate">
104
- {{ itemData.item.name }}
105
- </div>
106
- </template>
107
-
108
- <!-- Status column template example -->
109
- <template #item_isActive="itemData">
110
- <VcStatusIcon :status="itemData.item.isActive" />
111
- </template>
112
- </VcTable>
113
- </VcBlade>
114
- </template>
115
-
116
- <script lang="ts" setup>
117
- import { computed, inject, onMounted, ref, watch, markRaw, Ref } from "vue";
118
- import {
119
- IBladeToolbar,
120
- IParentCallArgs,
121
- useFunctions,
122
- IActionBuilderResult,
123
- ITableColumns,
124
- useNotifications,
125
- notification,
126
- useBladeNavigation,
127
- usePopup,
128
- useTableSort,
129
- useBlade,
130
- } from "@vc-shell/framework";
131
- import { use{{EntityName}}List } from "../composables";
132
- import {{EntityName}}Details from "./{{entity-name}}-details.vue";
133
- import { useI18n } from "vue-i18n";
134
-
135
- // TODO: Replace with your actual types
136
- // Example: import { IProduct } from "@your-app/api/products";
137
-
138
- export interface Props {
139
- expanded?: boolean;
140
- closable?: boolean;
141
- param?: string;
142
- options?: Record<string, unknown>;
143
- }
144
-
145
- export interface Emits {
146
- (event: "parent:call", args: IParentCallArgs): void;
147
- (event: "close:blade"): void;
148
- (event: "collapse:blade"): void;
149
- (event: "expand:blade"): void;
150
- }
151
-
152
- defineOptions({
153
- name: "{{EntityName}}List",
154
- url: "/{{entity-name-plural}}",
155
- notifyType: "{{EntityName}}DeletedDomainEvent",
156
- isWorkspace: {{isWorkspace}},
157
- menuItem: {{menuItem}},
158
- });
159
-
160
- const props = withDefaults(defineProps<Props>(), {
161
- expanded: true,
162
- closable: true,
163
- });
164
-
165
- const emit = defineEmits<Emits>();
166
- const { openBlade, closeBlade } = useBladeNavigation();
167
- const { showConfirmation } = usePopup();
168
- const { t } = useI18n({ useScope: "global" });
169
- const { debounce } = useFunctions();
170
-
171
- const {
172
- searchQuery,
173
- items,
174
- totalCount,
175
- pages,
176
- currentPage,
177
- load{{EntityName}}s,
178
- loading,
179
- statuses,
180
- // Filters
181
- stagedFilters,
182
- appliedFilters,
183
- hasFilterChanges,
184
- hasFiltersApplied,
185
- activeFilterCount,
186
- toggleFilter,
187
- applyFilters,
188
- resetFilters,
189
- resetSearch,
190
- } = use{{EntityName}}List();
191
-
192
- const { markAsRead, setNotificationHandler } = useNotifications("{{EntityName}}DeletedDomainEvent");
193
- const { sortExpression, handleSortChange } = useTableSort({
194
- initialProperty: "createdDate",
195
- initialDirection: "DESC",
196
- });
197
- const blade = useBlade();
198
-
199
- const searchValue = ref();
200
- const selectedItemId = ref<string>();
201
- const isDesktop = inject<Ref<boolean>>("isDesktop");
202
- const bladeTitle = computed(() => t("{{MODULE_NAME_UPPERCASE}}.PAGES.LIST.TITLE"));
203
-
204
- setNotificationHandler((message) => {
205
- if (message.title) {
206
- notification.success(message.title, {
207
- onClose() {
208
- markAsRead(message);
209
- },
210
- });
211
- }
212
- });
213
-
214
- watch(sortExpression, async (value) => {
215
- await load{{EntityName}}s({ ...searchQuery.value, sort: value });
216
- });
217
-
218
- watch(
219
- () => props.param,
220
- (newVal) => {
221
- if (newVal) {
222
- selectedItemId.value = newVal;
223
- }
224
- },
225
- { immediate: true, deep: true },
226
- );
227
-
228
- onMounted(async () => {
229
- await load{{EntityName}}s({ ...searchQuery.value, sort: sortExpression.value });
230
- });
231
-
232
- const reload = async () => {
233
- await load{{EntityName}}s({
234
- ...searchQuery.value,
235
- skip: (currentPage.value - 1) * (searchQuery.value.take ?? 20),
236
- sort: sortExpression.value,
237
- });
238
- emit("parent:call", { method: "reload" });
239
- };
240
-
241
- const onSearchList = debounce(async (keyword: string) => {
242
- searchValue.value = keyword;
243
- await load{{EntityName}}s({
244
- ...searchQuery.value,
245
- keyword,
246
- });
247
- }, 1000);
248
-
249
- const bladeToolbar = ref<IBladeToolbar[]>([
250
- {
251
- id: "refresh",
252
- title: computed(() => t("{{MODULE_NAME_UPPERCASE}}.PAGES.LIST.TOOLBAR.REFRESH")),
253
- icon: "material-refresh",
254
- async clickHandler() {
255
- await reload();
256
- },
257
- },
258
- {
259
- id: "add",
260
- title: computed(() => t("{{MODULE_NAME_UPPERCASE}}.PAGES.LIST.TOOLBAR.ADD")),
261
- icon: "material-add",
262
- clickHandler() {
263
- add{{EntityName}}();
264
- },
265
- },
266
- ]);
267
-
268
- const tableColumns = ref<ITableColumns[]>([
269
- {
270
- id: "name",
271
- field: "name",
272
- title: computed(() => t("{{MODULE_NAME_UPPERCASE}}.PAGES.LIST.TABLE.HEADER.NAME")),
273
- sortable: true,
274
- alwaysVisible: true,
275
- mobilePosition: "top-left",
276
- },
277
- {
278
- id: "createdDate",
279
- title: computed(() => t("{{MODULE_NAME_UPPERCASE}}.PAGES.LIST.TABLE.HEADER.CREATED_DATE")),
280
- sortable: true,
281
- type: "date-ago",
282
- mobilePosition: "bottom-right",
283
- },
284
- ]);
285
-
286
- const empty = {
287
- icon: "material-list",
288
- text: computed(() => t("{{MODULE_NAME_UPPERCASE}}.PAGES.LIST.EMPTY.NO_ITEMS")),
289
- action: computed(() => t("{{MODULE_NAME_UPPERCASE}}.PAGES.LIST.EMPTY.ADD")),
290
- clickHandler: () => {
291
- add{{EntityName}}();
292
- },
293
- };
294
-
295
- const notfound = {
296
- icon: "material-list",
297
- text: computed(() => t("{{MODULE_NAME_UPPERCASE}}.PAGES.LIST.NOT_FOUND.EMPTY")),
298
- action: computed(() => t("{{MODULE_NAME_UPPERCASE}}.PAGES.LIST.NOT_FOUND.RESET")),
299
- clickHandler: async () => {
300
- searchValue.value = "";
301
- await resetSearch();
302
- },
303
- };
304
-
305
- const onItemClick = (item: { id?: string }) => {
306
- openBlade({
307
- blade: markRaw({{EntityName}}Details),
308
- param: item.id,
309
- onOpen() {
310
- selectedItemId.value = item.id;
311
- },
312
- onClose() {
313
- selectedItemId.value = undefined;
314
- },
315
- });
316
- };
317
-
318
- const onHeaderClick = (item: ITableColumns) => {
319
- handleSortChange(item.id);
320
- };
321
-
322
- const add{{EntityName}} = () => {
323
- openBlade({
324
- blade: markRaw({{EntityName}}Details),
325
- });
326
- };
327
-
328
- const onPaginationClick = async (page: number) => {
329
- await load{{EntityName}}s({
330
- ...searchQuery.value,
331
- skip: (page - 1) * (searchQuery.value.take ?? 20),
332
- });
333
- };
334
-
335
- defineExpose({
336
- title: bladeTitle,
337
- reload,
338
- onItemClick,
339
- });
340
- </script>
@@ -1,101 +0,0 @@
1
- import { computed, ref, ComputedRef, Ref, reactive } from "vue";
2
- import { useAsync, useApiClient, useModificationTracker, useLoading } from "@vc-shell/framework";
3
-
4
- // TODO: Replace with your actual API client imports
5
- // Example: import { ProductsClient, IProduct } from "@your-app/api/products";
6
-
7
- // @ts-expect-error - Replace with your API types
8
- interface I{{EntityName}} {
9
- id?: string;
10
- name?: string;
11
- createdDate?: string;
12
- [key: string]: any;
13
- }
14
-
15
- // @ts-expect-error - Replace with your API client
16
- class {{EntityName}}Client {
17
- // TODO: Replace these mock methods with your actual API client methods
18
- async get{{EntityName}}ById(id: string): Promise<I{{EntityName}}> {
19
- throw new Error("Method not implemented. Replace with your actual API method.");
20
- }
21
-
22
- async update{{EntityName}}(data: I{{EntityName}}): Promise<I{{EntityName}}> {
23
- throw new Error("Method not implemented. Replace with your actual API method.");
24
- }
25
-
26
- async create{{EntityName}}(data: I{{EntityName}}): Promise<I{{EntityName}}> {
27
- throw new Error("Method not implemented. Replace with your actual API method.");
28
- }
29
-
30
- async delete{{EntityName}}(id: string): Promise<void> {
31
- throw new Error("Method not implemented. Replace with your actual API method.");
32
- }
33
- }
34
-
35
- export interface IUse{{EntityName}}Details {
36
- item: Ref<I{{EntityName}}>;
37
- loading: ComputedRef<boolean>;
38
- load{{EntityName}}: (id: string) => Promise<void>;
39
- save{{EntityName}}: (data?: I{{EntityName}}) => Promise<I{{EntityName}} | undefined>;
40
- delete{{EntityName}}: (id: string) => Promise<void>;
41
-
42
- // Modification tracking
43
- isModified: Readonly<Ref<boolean>>;
44
- resetModificationState: () => void;
45
- }
46
-
47
- export function use{{EntityName}}Details(): IUse{{EntityName}}Details {
48
- const { getApiClient } = useApiClient({{EntityName}}Client);
49
-
50
- const item = ref<I{{EntityName}}>(reactive({} as I{{EntityName}}));
51
-
52
- // Use modification tracker - КАК В useOrderDetailsNew.ts
53
- const { currentValue, isModified, resetModificationState } = useModificationTracker(item);
54
-
55
- const { action: load{{EntityName}}, loading: loading{{EntityName}} } = useAsync<string>(async (id) => {
56
- if (id) {
57
- const apiClient = await getApiClient();
58
- const data = await apiClient.get{{EntityName}}ById(id);
59
-
60
- currentValue.value = reactive(data);
61
- resetModificationState();
62
- }
63
- });
64
-
65
- const { action: save{{EntityName}}, loading: saving{{EntityName}} } = useAsync<I{{EntityName}} | undefined, I{{EntityName}} | undefined>(
66
- async (data) => {
67
- if (!data) return;
68
-
69
- const apiClient = await getApiClient();
70
- let result: I{{EntityName}};
71
-
72
- if (data.id) {
73
- result = await apiClient.update{{EntityName}}(data);
74
- } else {
75
- result = await apiClient.create{{EntityName}}(data);
76
- }
77
-
78
- if (result) {
79
- currentValue.value = reactive(result);
80
- resetModificationState();
81
- }
82
-
83
- return result;
84
- }
85
- );
86
-
87
- const { action: delete{{EntityName}}, loading: deleting{{EntityName}} } = useAsync<string>(async (id) => {
88
- const apiClient = await getApiClient();
89
- await apiClient.delete{{EntityName}}(id);
90
- });
91
-
92
- return {
93
- item: currentValue, // ВАЖНО: возвращаем currentValue, а не item
94
- loading: useLoading(loading{{EntityName}}, saving{{EntityName}}, deleting{{EntityName}}),
95
- load{{EntityName}},
96
- save{{EntityName}},
97
- delete{{EntityName}},
98
- isModified,
99
- resetModificationState,
100
- };
101
- }