@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
@@ -23,9 +23,9 @@
23
23
  "@types/node": "^20.10.5",
24
24
  "@typescript-eslint/eslint-plugin": "^7.4.0",
25
25
  "@typescript-eslint/parser": "^7.4.0",
26
- "@vc-shell/api-client-generator": "^1.1.99-alpha.1",
27
- "@vc-shell/release-config": "^1.1.99-alpha.1",
28
- "@vc-shell/ts-config": "^1.1.99-alpha.1",
26
+ "@vc-shell/api-client-generator": "^1.2.0",
27
+ "@vc-shell/release-config": "^1.2.0",
28
+ "@vc-shell/ts-config": "^1.2.0",
29
29
  "@vitejs/plugin-vue": "^5.2.3",
30
30
  "@vue/eslint-config-prettier": "^9.0.0",
31
31
  "@vue/eslint-config-typescript": "^13.0.0",
@@ -53,8 +53,8 @@
53
53
  "vue-tsc": "^2.2.10"
54
54
  },
55
55
  "dependencies": {
56
- "@vc-shell/config-generator": "^1.1.99-alpha.1",
57
- "@vc-shell/framework": "^1.1.99-alpha.1",
56
+ "@vc-shell/config-generator": "^1.2.0",
57
+ "@vc-shell/framework": "^1.2.0",
58
58
  "@vueuse/core": "^10.7.1",
59
59
  "@vueuse/integrations": "^10.7.1",
60
60
  "cross-spawn": "^7.0.3",
@@ -3,6 +3,7 @@ import { createApp } from "vue";
3
3
  import { router } from "./router";
4
4
  import * as locales from "./locales";
5
5
  import { RouterView } from "vue-router";
6
+ import {{ModuleNamePascalCase}} from "./modules/{{ModuleName}}";
6
7
  import { bootstrap } from "./bootstrap";
7
8
 
8
9
  // Load required CSS
@@ -11,6 +12,7 @@ import "@vc-shell/framework/dist/index.css";
11
12
  async function startApp() {
12
13
  const { loadUser } = useUser();
13
14
 
15
+
14
16
  try {
15
17
  await loadUser();
16
18
  } catch (e) {
@@ -27,6 +29,8 @@ async function startApp() {
27
29
  fallbackLocale: import.meta.env.APP_I18N_FALLBACK_LOCALE,
28
30
  },
29
31
  })
32
+ // {{ModuleNamePascalCase}} module initialization
33
+ .use({{ModuleNamePascalCase}}, { router })
30
34
  .use(router);
31
35
 
32
36
  bootstrap(app);
@@ -0,0 +1,89 @@
1
+ interface MockedItem {
2
+ imgSrc: string;
3
+ name: string;
4
+ id: string;
5
+ description: string;
6
+ price: number;
7
+ salePrice: number;
8
+ guid: string;
9
+ currency: string;
10
+ }
11
+
12
+ interface MockedQuery {
13
+ keyword?: string;
14
+ take?: number;
15
+ skip?: number;
16
+ sort?: string;
17
+ }
18
+
19
+ const mockedItems: MockedItem[] = [
20
+ {
21
+ id: "beb211ea-1379-5a79-832c-9577ad387feb",
22
+ name: "Product 1",
23
+ imgSrc: "https://via.placeholder.com/150",
24
+ description: "Product 1 description",
25
+ price: 100,
26
+ salePrice: 90,
27
+ guid: "a1e26bcd-2704-5a3f-b97c-3e32e3d77a38",
28
+ currency: "USD",
29
+ },
30
+ {
31
+ id: "44b96758-792a-5449-b649-f2a0cd614c4b",
32
+ name: "Product 2",
33
+ imgSrc: "https://via.placeholder.com/150",
34
+ description: "Product 2 description",
35
+ price: 200,
36
+ salePrice: 90,
37
+ guid: "0ab995c0-3798-5011-a4ae-498e4c683cfd",
38
+ currency: "USD",
39
+ },
40
+ {
41
+ id: "605b2bfb-ba13-5687-91d5-0261b4b60524",
42
+ name: "Product 3",
43
+ imgSrc: "https://via.placeholder.com/150",
44
+ description: "Product 3 description",
45
+ price: 300,
46
+ salePrice: 90,
47
+ guid: "8a9b6954-7bf7-58b2-a146-c68e06fa7bb4",
48
+ currency: "USD",
49
+ },
50
+ {
51
+ id: "5ca8185c-0c28-59bf-ab40-991f134e6db3",
52
+ name: "Product 4",
53
+ imgSrc: "https://via.placeholder.com/150",
54
+ description: "Product 4 description",
55
+ price: 400,
56
+ salePrice: 90,
57
+ guid: "261364c5-f976-5c86-a92c-b0f0ea14efc7",
58
+ currency: "USD",
59
+ },
60
+ {
61
+ id: "077930cb-0874-5f85-9a5b-daafdd0bf860",
62
+ name: "Product 5",
63
+ imgSrc: "https://via.placeholder.com/150",
64
+ description: "Product 5 description",
65
+ price: 500,
66
+ salePrice: 90,
67
+ guid: "74d302ef-31e6-5432-91f1-d92c87ee2d2a",
68
+ currency: "USD",
69
+ },
70
+ ];
71
+
72
+ const currencyOptions = [
73
+ {
74
+ value: "USD",
75
+ label: "USD",
76
+ },
77
+ {
78
+ value: "EUR",
79
+ label: "EUR",
80
+ },
81
+ {
82
+ value: "GBP",
83
+ label: "GBP",
84
+ },
85
+ ];
86
+
87
+ export { mockedItems, currencyOptions };
88
+
89
+ export type { MockedItem, MockedQuery };
@@ -0,0 +1,2 @@
1
+ export * from './constants'
2
+ export * from './methods'
@@ -0,0 +1,65 @@
1
+ import { type MockedItem, type MockedQuery, mockedItems } from ".";
2
+
3
+ export function loadMockItemsList(query: MockedQuery) {
4
+ return new Promise((resolve: (value: MockedItem[]) => void) => {
5
+ setTimeout(() => resolve(mockedItems), 1000);
6
+ }).then((res) => {
7
+ res = res.filter((x) => {
8
+ if (query.keyword) {
9
+ return x.name.toLowerCase().includes(query.keyword.toLowerCase());
10
+ }
11
+ return true;
12
+ });
13
+
14
+ return { results: res, totalCount: res.length };
15
+ });
16
+ }
17
+
18
+ export async function loadMockItem(args?: { id: string }) {
19
+ return new Promise((resolve: (value: MockedItem) => void) => {
20
+ setTimeout(() => {
21
+ const findMockedItem = mockedItems.find((x) => x.id === args?.id);
22
+
23
+ if (findMockedItem) resolve({ ...findMockedItem });
24
+ }, 1000);
25
+ });
26
+ }
27
+
28
+ export async function removeMockItem(args: { id: string }) {
29
+ new Promise((resolve: (value: boolean) => void) => {
30
+ setTimeout(() => {
31
+ const index = mockedItems.findIndex((x) => x.id === args.id);
32
+
33
+ if (index > -1) {
34
+ mockedItems.splice(index, 1);
35
+ resolve(true);
36
+ } else {
37
+ resolve(false);
38
+ }
39
+ }, 1000);
40
+ });
41
+ }
42
+
43
+ export async function addNewMockItem(args: MockedItem) {
44
+ return new Promise((resolve: (value: MockedItem) => void) => {
45
+ setTimeout(() => {
46
+ mockedItems.push(args);
47
+ resolve(args);
48
+ }, 1000);
49
+ });
50
+ }
51
+
52
+ export async function updateMockItem(args: MockedItem) {
53
+ return new Promise((resolve: (value: MockedItem) => void) => {
54
+ setTimeout(() => {
55
+ const index = mockedItems.findIndex((x) => x.id === args.id);
56
+
57
+ if (index > -1) {
58
+ mockedItems[index] = args;
59
+ resolve(args);
60
+ } else {
61
+ resolve(args);
62
+ }
63
+ }, 1000);
64
+ });
65
+ }
@@ -0,0 +1,2 @@
1
+ export { default as use{{ModuleNamePascalCase}}List } from "./use{{ModuleNamePascalCase}}List";
2
+ export { default as use{{ModuleNamePascalCase}}Details } from "./use{{ModuleNamePascalCase}}Details";
@@ -0,0 +1,24 @@
1
+ import { computed, ref } from "vue";
2
+ import { useAsync, useLoading, useModificationTracker } from "@vc-shell/framework";
3
+
4
+ export default () => {
5
+ const item = ref({});
6
+
7
+ const { isModified, currentValue, resetModificationState } = useModificationTracker(item);
8
+
9
+ // Implement your own load function
10
+ const { loading: itemLoading, action: getItem } = useAsync<{ id: string }>(async (payload) => {
11
+ item.value = {};
12
+
13
+ resetModificationState();
14
+ });
15
+
16
+ const loading = useLoading(itemLoading);
17
+
18
+ return {
19
+ item: currentValue,
20
+ loading: computed(() => loading.value),
21
+ getItem,
22
+ isModified,
23
+ };
24
+ };
@@ -0,0 +1,47 @@
1
+ import { computed, ref } from "vue";
2
+ import { useAsync, useLoading } from "@vc-shell/framework";
3
+
4
+ // Replace with the actual search query interface from the API client
5
+ interface SearchQuery {
6
+ take?: number;
7
+ skip?: number;
8
+ sort?: string;
9
+ keyword?: string;
10
+ }
11
+
12
+ export default (options?: { pageSize?: number, sort?: string }) => {
13
+ const pageSize = options?.pageSize || 20;
14
+ const searchResult = ref();
15
+ const searchQuery = ref<SearchQuery>({
16
+ take: pageSize,
17
+ skip: 0,
18
+ sort: options?.sort || "createdDate:DESC",
19
+ });
20
+
21
+ // Implement your own load function
22
+ const { loading: itemLoading, action: getItems } = useAsync<SearchQuery>(async (payload) => {
23
+ searchQuery.value = { ...searchQuery.value, ...payload };
24
+ searchResult.value = {
25
+ totalCount: 0,
26
+ items: [],
27
+ };
28
+ });
29
+
30
+ // Implement your own remove function
31
+ const { loading: removeLoading, action: removeItems } = useAsync<{ ids: string[] }>(async (payload) => {
32
+ return;
33
+ });
34
+
35
+ const loading = useLoading(itemLoading, removeLoading);
36
+
37
+ return {
38
+ data: computed(() => searchResult.value?.items),
39
+ loading: computed(() => loading.value),
40
+ totalCount: computed(() => searchResult.value?.totalCount || 0),
41
+ pages: computed(() => Math.ceil((searchResult.value?.totalCount || 1) / pageSize)),
42
+ currentPage: computed(() => Math.ceil((searchQuery.value?.skip || 0) / Math.max(1, pageSize) + 1)),
43
+ getItems,
44
+ removeItems,
45
+ searchQuery,
46
+ };
47
+ };
@@ -0,0 +1,8 @@
1
+ import * as pages from "./pages";
2
+ import * as locales from "./locales";
3
+ import { createAppModule } from "@vc-shell/framework";
4
+
5
+ export default createAppModule(pages, locales);
6
+
7
+ export * from "./pages";
8
+ export * from "./composables";
@@ -0,0 +1,37 @@
1
+ {
2
+ "{{ModuleNameUppercaseSnakeCase}}": {
3
+ "MENU": {
4
+ "TITLE": "{{ModuleNameSentenceCase}}"
5
+ },
6
+ "PAGES": {
7
+ "LIST": {
8
+ "TITLE": "{{ModuleNameSentenceCase}} list",
9
+ "TOOLBAR": {
10
+ "REFRESH": "Refresh",
11
+ "ADD": "Add"
12
+ },
13
+ "SEARCH": {
14
+ "PLACEHOLDER": "Search keywords"
15
+ },
16
+ "TABLE": {
17
+ "TOTALS": "Count:",
18
+ "HEADER": {}
19
+ },
20
+ "EMPTY": {
21
+ "NO_ITEMS": "No items found",
22
+ "ADD": "Add items"
23
+ },
24
+ "NOT_FOUND": {
25
+ "EMPTY": "No items found",
26
+ "RESET": "Reset"
27
+ }
28
+ },
29
+ "DETAILS": {
30
+ "TITLE": "{{ModuleNameSentenceCase}} details"
31
+ },
32
+ "ALERTS": {
33
+ "CLOSE_CONFIRMATION": "You have unsaved changes. Close anyway?"
34
+ }
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,2 @@
1
+ import * as en from "./en.json";
2
+ export { en };
@@ -0,0 +1,87 @@
1
+ <template>
2
+ <VcBlade
3
+ v-loading="loading"
4
+ :title="title"
5
+ :expanded="expanded"
6
+ :closable="closable"
7
+ width="70%"
8
+ :toolbar-items="bladeToolbar"
9
+ @close="$emit('close:blade')"
10
+ @expand="$emit('expand:blade')"
11
+ @collapse="$emit('collapse:blade')"
12
+ >
13
+ <VcContainer class="tw-p-2">
14
+ <VcForm>
15
+ <!-- You can add form fields here -->
16
+ </VcForm>
17
+ </VcContainer>
18
+ </VcBlade>
19
+ </template>
20
+
21
+ <script lang="ts" setup>
22
+ import { IBladeToolbar, IParentCallArgs, useBladeNavigation, usePopup, useBeforeUnload } from "@vc-shell/framework";
23
+ import { use{{ModuleNamePascalCase}}Details } from "./../composables";
24
+ import { computed, onMounted, ref } from "vue";
25
+ import { useI18n } from "vue-i18n";
26
+ import { useForm } from "vee-validate";
27
+
28
+ export interface Props {
29
+ expanded?: boolean;
30
+ closable?: boolean;
31
+ param?: string;
32
+ }
33
+
34
+ export interface Emits {
35
+ (event: "parent:call", args: IParentCallArgs): void;
36
+ (event: "collapse:blade"): void;
37
+ (event: "expand:blade"): void;
38
+ (event: "close:blade"): void;
39
+ }
40
+
41
+ defineOptions({
42
+ url: "/{{ModuleName}}-details",
43
+ name: "{{ModuleNamePascalCase}}Details",
44
+ });
45
+
46
+ const props = withDefaults(defineProps<Props>(), {
47
+ expanded: true,
48
+ closable: true,
49
+ param: undefined,
50
+ });
51
+
52
+ defineEmits<Emits>();
53
+
54
+ const { loading, getItem, isModified } = use{{ModuleNamePascalCase}}Details();
55
+ const { onBeforeClose } = useBladeNavigation();
56
+ const { showConfirmation } = usePopup();
57
+ const { t } = useI18n({ useScope: "global" });
58
+
59
+ const { meta } = useForm({
60
+ validateOnMount: false,
61
+ });
62
+
63
+ const isDisabled = computed(() => {
64
+ return !meta.value.dirty || !meta.value.valid;
65
+ });
66
+
67
+ const bladeToolbar = ref<IBladeToolbar[]>([]);
68
+ const title = computed(() => t("{{ModuleNameUppercaseSnakeCase}}.PAGES.DETAILS.TITLE"));
69
+
70
+ onMounted(async () => {
71
+ if (props.param) {
72
+ await getItem({ id: props.param });
73
+ }
74
+ });
75
+
76
+ onBeforeClose(async () => {
77
+ if (!isDisabled.value && isModified.value) {
78
+ return await showConfirmation(t("SAMPLE_APP.PAGES.ALERTS.CLOSE_CONFIRMATION"));
79
+ }
80
+ });
81
+
82
+ useBeforeUnload(computed(() => !isDisabled.value && isModified.value));
83
+
84
+ defineExpose({
85
+ title,
86
+ });
87
+ </script>
@@ -0,0 +1,2 @@
1
+ export { default as List } from "./list.vue";
2
+ export {default as Details } from './details.vue'
@@ -0,0 +1,257 @@
1
+ <template>
2
+ <VcBlade
3
+ :title="title"
4
+ width="50%"
5
+ :expanded="expanded"
6
+ :closable="closable"
7
+ :toolbar-items="bladeToolbar"
8
+ @close="$emit('close:blade')"
9
+ @expand="$emit('expand:blade')"
10
+ @collapse="$emit('collapse:blade')"
11
+ >
12
+ <!-- Blade contents -->
13
+ <!-- @vue-generic {never} -->
14
+ <VcTable
15
+ :expanded="expanded"
16
+ class="tw-grow tw-basis-0"
17
+ multiselect
18
+ :loading="loading"
19
+ :columns="columns"
20
+ :sort="sortExpression"
21
+ :current-page="currentPage"
22
+ :search-value="searchValue"
23
+ enable-item-actions
24
+ :item-action-builder="actionBuilder"
25
+ :pages="pages"
26
+ :empty="empty"
27
+ :notfound="notfound"
28
+ :total-count="totalCount"
29
+ :selected-item-id="selectedItemId"
30
+ :search-placeholder="$t('{{ModuleNameUppercaseSnakeCase}}.PAGES.LIST.SEARCH.PLACEHOLDER')"
31
+ :total-label="$t('{{ModuleNameUppercaseSnakeCase}}.PAGES.LIST.TABLE.TOTALS')"
32
+ state-key="{{ModuleNameUppercaseSnakeCase}}"
33
+ :items="data"
34
+ @item-click="onItemClick"
35
+ @header-click="onHeaderClick"
36
+ @search:change="onSearchList"
37
+ @pagination-click="onPaginationClick"
38
+ @selection-changed="onSelectionChanged"
39
+ >
40
+ </VcTable>
41
+ </VcBlade>
42
+ </template>
43
+
44
+ <script lang="ts" setup>
45
+ import { computed, ref, markRaw, onMounted, watch } from "vue";
46
+ import { IBladeToolbar, IParentCallArgs, ITableColumns, useBladeNavigation, useTableSort, IActionBuilderResult, useFunctions, usePopup } from "@vc-shell/framework";
47
+ import { useI18n } from "vue-i18n";
48
+ import { use{{ModuleNamePascalCase}}List } from "./../composables";
49
+ import Details from "./details.vue";
50
+
51
+ export interface Props {
52
+ expanded?: boolean;
53
+ closable?: boolean;
54
+ param?: string;
55
+ options?: Record<string, unknown>;
56
+ }
57
+
58
+ export interface Emits {
59
+ (event: "parent:call", args: IParentCallArgs): void;
60
+ (event: "collapse:blade"): void;
61
+ (event: "expand:blade"): void;
62
+ (event: "close:blade"): void;
63
+ }
64
+
65
+ defineOptions({
66
+ url: "/{{ModuleName}}",
67
+ name: "{{ModuleNamePascalCase}}List",
68
+ isWorkspace: true,
69
+ menuItem: {
70
+ title: "{{ModuleNameUppercaseSnakeCase}}.MENU.TITLE",
71
+ icon: "lucide-file",
72
+ priority: 1,
73
+ },
74
+ });
75
+
76
+ const props = withDefaults(defineProps<Props>(), {
77
+ expanded: true,
78
+ closable: true,
79
+ });
80
+
81
+ defineEmits<Emits>();
82
+
83
+ const { t } = useI18n({ useScope: "global" });
84
+ const { openBlade } = useBladeNavigation();
85
+ const { debounce } = useFunctions();
86
+ const { showConfirmation } = usePopup();
87
+ const { sortExpression, handleSortChange: tableSortHandler } = useTableSort({
88
+ initialDirection: "DESC",
89
+ initialProperty: "createdDate",
90
+ });
91
+
92
+ const { getItems, data, loading, totalCount, pages, currentPage, searchQuery, removeItems } = use{{ModuleNamePascalCase}}List({
93
+ sort: sortExpression.value,
94
+ pageSize: 20,
95
+ });
96
+
97
+ const searchValue = ref();
98
+ const selectedItemId = ref<string>();
99
+ const selectedItemsIds = ref<string[]>([]);
100
+
101
+ const empty = {
102
+ icon: "lucide-file",
103
+ text: computed(() => t("{{ModuleNameUppercaseSnakeCase}}.PAGES.LIST.EMPTY.NO_ITEMS")),
104
+ action: computed(() => t("{{ModuleNameUppercaseSnakeCase}}.PAGES.LIST.EMPTY.ADD")),
105
+ clickHandler: () => {
106
+ addItem();
107
+ },
108
+ };
109
+
110
+ const notfound = {
111
+ icon: "lucide-file",
112
+ text: computed(() => t("{{ModuleNameUppercaseSnakeCase}}.PAGES.LIST.NOT_FOUND.EMPTY")),
113
+ action: computed(() => t("{{ModuleNameUppercaseSnakeCase}}.PAGES.LIST.NOT_FOUND.RESET")),
114
+ clickHandler: async () => {
115
+ searchValue.value = "";
116
+ await getItems({
117
+ ...searchQuery.value,
118
+ keyword: "",
119
+ });
120
+ },
121
+ };
122
+
123
+ watch(
124
+ () => props.param,
125
+ (newVal) => {
126
+ selectedItemId.value = newVal;
127
+ },
128
+ { immediate: true },
129
+ );
130
+
131
+ onMounted(async () => {
132
+ await getItems({
133
+ ...searchQuery.value,
134
+ sort: sortExpression.value,
135
+ });
136
+ });
137
+
138
+ const bladeToolbar = ref<IBladeToolbar[]>([
139
+ {
140
+ id: "refresh",
141
+ title: computed(() => t("{{ModuleNameUppercaseSnakeCase}}.PAGES.LIST.TOOLBAR.REFRESH")),
142
+ icon: "material-refresh",
143
+ async clickHandler() {
144
+ await reload();
145
+ },
146
+ },
147
+ {
148
+ id: "add",
149
+ icon: "material-add",
150
+ title: computed(() => t("{{ModuleNameUppercaseSnakeCase}}.PAGES.LIST.TOOLBAR.ADD")),
151
+ clickHandler: () => {
152
+ addItem();
153
+ },
154
+ },
155
+ ]);
156
+
157
+ const columns = ref<ITableColumns[]>([
158
+ // You can add columns here
159
+ ]);
160
+
161
+ const title = computed(() => t("{{ModuleNameUppercaseSnakeCase}}.PAGES.LIST.TITLE"));
162
+
163
+ const onSearchList = debounce(async (keyword: string) => {
164
+ searchValue.value = keyword;
165
+ await getItems({
166
+ ...searchQuery.value,
167
+ keyword,
168
+ });
169
+ }, 1000);
170
+
171
+ const reload = async () => {
172
+ await getItems({
173
+ ...searchQuery.value,
174
+ skip: (currentPage.value - 1) * (searchQuery.value.take ?? 10),
175
+ sort: sortExpression.value,
176
+ });
177
+ };
178
+
179
+ const addItem = () => {
180
+ openBlade({
181
+ blade: markRaw(Details),
182
+ });
183
+ };
184
+
185
+ const onItemClick = (item: { id: string }) => {
186
+ openBlade({
187
+ blade: markRaw(Details),
188
+ param: item.id,
189
+ onOpen() {
190
+ selectedItemId.value = item.id;
191
+ },
192
+ onClose() {
193
+ selectedItemId.value = undefined;
194
+ },
195
+ });
196
+ };
197
+
198
+ const onHeaderClick = (item: ITableColumns) => {
199
+ tableSortHandler(item.id);
200
+ };
201
+
202
+ const actionBuilder = (): IActionBuilderResult[] => {
203
+ const result: IActionBuilderResult[] = [];
204
+ result.push({
205
+ icon: "material-delete",
206
+ title: "Delete",
207
+ type: "danger",
208
+ async clickHandler(_item: { id: string }) {
209
+ if (_item.id && !selectedItemsIds.value.includes(_item.id)) {
210
+ selectedItemsIds.value.push(_item.id);
211
+ }
212
+ await remove(selectedItemsIds.value);
213
+ selectedItemsIds.value = [];
214
+ },
215
+ });
216
+
217
+ return result;
218
+ };
219
+
220
+ const onPaginationClick = async (page: number) => {
221
+ await getItems({
222
+ ...searchQuery.value,
223
+ skip: (page - 1) * (searchQuery.value.take ?? 20),
224
+ });
225
+ };
226
+
227
+ const onSelectionChanged = (items: Record<string, any>[]) => {
228
+ selectedItemsIds.value = items.flatMap((item) => (item.id ? [item.id] : []));
229
+ };
230
+
231
+ async function remove(ids: string[]) {
232
+ if (
233
+ await showConfirmation(
234
+ t(`{{ModuleNameUppercaseSnakeCase}}.PAGES.ALERTS.DELETE_SELECTED_CONFIRMATION.MESSAGE`, {
235
+ count: selectedItemsIds.value.length,
236
+ }),
237
+ )
238
+ ) {
239
+ await removeItems({ ids });
240
+ await reload();
241
+ }
242
+ }
243
+
244
+ watch(
245
+ () => sortExpression.value,
246
+ async (newVal) => {
247
+ await getItems({
248
+ sort: newVal,
249
+ });
250
+ },
251
+ );
252
+
253
+ defineExpose({
254
+ title,
255
+ reload,
256
+ });
257
+ </script>
@@ -0,0 +1,2 @@
1
+ export { default as useList } from "./useList";
2
+ export { default as useDetails } from "./useDetails";