@vc-shell/create-vc-app 1.1.99-alpha.2 → 1.2.1

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 (86) hide show
  1. package/README.md +26 -552
  2. package/dist/index.js +530 -1901
  3. package/dist/templates/base/_package.json +6 -7
  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/_husky/commit-msg +0 -4
  49. package/dist/templates/base/_husky/pre-commit +0 -4
  50. package/dist/templates/base/ai-guides/.cursorrules-vc-shell +0 -529
  51. package/dist/templates/base/ai-guides/README.md +0 -360
  52. package/dist/templates/base/ai-guides/guides/AI_GUIDE.md +0 -195
  53. package/dist/templates/base/ai-guides/guides/blade-patterns.md +0 -384
  54. package/dist/templates/base/ai-guides/guides/complete-workflow.md +0 -781
  55. package/dist/templates/base/ai-guides/guides/composables-reference.md +0 -338
  56. package/dist/templates/base/ai-guides/guides/troubleshooting.md +0 -529
  57. package/dist/templates/base/ai-guides/guides/ui-components-reference.md +0 -903
  58. package/dist/templates/base/ai-guides/prompts/adapt-existing-module.md +0 -1026
  59. package/dist/templates/base/ai-guides/prompts/advanced-scenarios.md +0 -852
  60. package/dist/templates/base/ai-guides/prompts/api-client-generation.md +0 -877
  61. package/dist/templates/base/ai-guides/prompts/cli-usage.md +0 -640
  62. package/dist/templates/base/ai-guides/prompts/quick-start-scenarios.md +0 -773
  63. package/dist/templates/base/ai-guides/prompts/simple-modifications.md +0 -987
  64. package/dist/templates/base/scripts/release.ts +0 -5
  65. package/dist/templates/blades/details/blade.vue +0 -175
  66. package/dist/templates/blades/grid/blade.vue +0 -340
  67. package/dist/templates/composables/details-composable.ts +0 -101
  68. package/dist/templates/composables/grid-composable.ts +0 -244
  69. package/dist/templates/module/components/index.ts +0 -2
  70. package/dist/templates/module/components/widgets/index.ts +0 -2
  71. package/dist/templates/module/composables/index.ts +0 -3
  72. package/dist/templates/module/index.ts +0 -13
  73. package/dist/templates/module/locales/en.json +0 -65
  74. package/dist/templates/module/locales/index.ts +0 -4
  75. package/dist/templates/module/pages/index.ts +0 -3
  76. package/dist/templates/widgets/widget.vue +0 -113
  77. package/dist/utils/form-builder.d.ts +0 -69
  78. package/dist/utils/form-builder.d.ts.map +0 -1
  79. package/dist/utils/format.d.ts +0 -24
  80. package/dist/utils/format.d.ts.map +0 -1
  81. package/dist/utils/naming.d.ts +0 -44
  82. package/dist/utils/naming.d.ts.map +0 -1
  83. package/dist/utils/register-module.d.ts +0 -21
  84. package/dist/utils/register-module.d.ts.map +0 -1
  85. package/dist/workflows/create-app.d.ts +0 -14
  86. package/dist/workflows/create-app.d.ts.map +0 -1
@@ -11,8 +11,7 @@
11
11
  "type-check": "vue-tsc --noEmit",
12
12
  "lint-staged": "lint-staged",
13
13
  "lint": "eslint --fix --cache '**/*.{ts,vue}'",
14
- "generate-api-client": "cross-env api-client-generator --APP_PLATFORM_MODULES='[]' --APP_API_CLIENT_DIRECTORY=./src/api_client/ --APP_OUT_DIR=dist --APP_PACKAGE_NAME=api --APP_PACKAGE_VERSION=$npm_package_version",
15
- "release": "tsx scripts/release.ts --dry"
14
+ "generate-api-client": "cross-env api-client-generator --APP_PLATFORM_MODULES='[]' --APP_API_CLIENT_DIRECTORY=./src/api_client/ --APP_OUT_DIR=dist --APP_PACKAGE_NAME=api --APP_PACKAGE_VERSION=$npm_package_version"
16
15
  },
17
16
  "devDependencies": {
18
17
  "@commitlint/cli": "^18.4.3",
@@ -23,9 +22,9 @@
23
22
  "@types/node": "^20.10.5",
24
23
  "@typescript-eslint/eslint-plugin": "^7.4.0",
25
24
  "@typescript-eslint/parser": "^7.4.0",
26
- "@vc-shell/api-client-generator": "^1.1.99-alpha.2",
27
- "@vc-shell/release-config": "^1.1.99-alpha.2",
28
- "@vc-shell/ts-config": "^1.1.99-alpha.2",
25
+ "@vc-shell/api-client-generator": "^1.2.1",
26
+ "@vc-shell/release-config": "^1.2.1",
27
+ "@vc-shell/ts-config": "^1.2.1",
29
28
  "@vitejs/plugin-vue": "^5.2.3",
30
29
  "@vue/eslint-config-prettier": "^9.0.0",
31
30
  "@vue/eslint-config-typescript": "^13.0.0",
@@ -53,8 +52,8 @@
53
52
  "vue-tsc": "^2.2.10"
54
53
  },
55
54
  "dependencies": {
56
- "@vc-shell/config-generator": "^1.1.99-alpha.2",
57
- "@vc-shell/framework": "^1.1.99-alpha.2",
55
+ "@vc-shell/config-generator": "^1.2.1",
56
+ "@vc-shell/framework": "^1.2.1",
58
57
  "@vueuse/core": "^10.7.1",
59
58
  "@vueuse/integrations": "^10.7.1",
60
59
  "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";