@vc-shell/create-vc-app 2.0.10-pr247.f47cc74 → 2.0.11-pr247.0f1e741

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/index.js CHANGED
@@ -7,7 +7,7 @@ import { fileURLToPath as F } from "node:url";
7
7
  import A from "prompts";
8
8
  import r from "node:fs";
9
9
  import E from "ejs";
10
- const O = "2.0.10", V = {
10
+ const O = "2.0.11-pr247.0f1e741", V = {
11
11
  version: O
12
12
  };
13
13
  function h(e) {
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "devDependencies": {
12
12
  "@types/node": "^20.10.5",
13
- "@vc-shell/ts-config": "^2.0.10",
13
+ "@vc-shell/ts-config": "^2.0.11",
14
14
  "cross-env": "^7.0.3",
15
15
  "sass": "^1.87.0",
16
16
  "typescript": "^5.8.3",
@@ -18,8 +18,8 @@
18
18
  "vue-tsc": "^3.2.5"
19
19
  },
20
20
  "dependencies": {
21
- "@vc-shell/config-generator": "^2.0.10",
22
- "@vc-shell/framework": "^2.0.10",
21
+ "@vc-shell/config-generator": "^2.0.11",
22
+ "@vc-shell/framework": "^2.0.11",
23
23
  "vue": "^3.5.30",
24
24
  "vue-router": "^5.0.3"
25
25
  }
@@ -1,43 +1,42 @@
1
- import { ref, type Ref } from "vue";
2
- import { useAsync, useLoading } from "@vc-shell/framework";
3
-
4
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
- export default function use<%- ModuleNamePascalCase %>List() {
6
- const data: Ref<Record<string, any>[]> = ref([]);
7
- const totalCount = ref(0);
8
- const currentPage = ref(1);
9
- const searchQuery = ref("");
10
-
11
- const { loading: itemsLoading, action: fetchItems } = useAsync(async () => {
12
- // TODO: Replace with real API call
13
- // const result = await apiClient.search({
14
- // keyword: searchQuery.value,
15
- // skip: (currentPage.value - 1) * 20,
16
- // take: 20,
17
- // });
18
- // data.value = result.results ?? [];
19
- // totalCount.value = result.totalCount ?? 0;
20
- });
21
-
22
- const { loading: deleteLoading, action: removeItems } = useAsync(async (ids?: string[]) => {
23
- // TODO: Replace with real API call
24
- // await Promise.all((ids ?? []).map((id) => apiClient.delete(id)));
25
- // await fetchItems();
26
- });
27
-
28
- const loading = useLoading(itemsLoading, deleteLoading);
29
-
30
- async function getItems() {
31
- await fetchItems();
32
- }
33
-
34
- return {
35
- data,
36
- loading,
37
- totalCount,
38
- currentPage,
39
- searchQuery,
40
- getItems,
41
- removeItems,
42
- };
43
- }
1
+ import { ref, type Ref } from "vue";
2
+ import { useAsync, useLoading } from "@vc-shell/framework";
3
+
4
+ export interface <%- ModuleNamePascalCase %>ListQuery {
5
+ keyword?: string;
6
+ sort?: string;
7
+ skip?: number;
8
+ take?: number;
9
+ }
10
+
11
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
+ export default function use<%- ModuleNamePascalCase %>List() {
13
+ const data: Ref<Record<string, any>[]> = ref([]);
14
+ const totalCount = ref(0);
15
+
16
+ const { loading: itemsLoading, action: getItems } = useAsync<<%- ModuleNamePascalCase %>ListQuery>(async (query) => {
17
+ // TODO: Replace with real API call
18
+ // const result = await apiClient.search({
19
+ // keyword: query?.keyword,
20
+ // sort: query?.sort,
21
+ // skip: query?.skip ?? 0,
22
+ // take: query?.take ?? 20,
23
+ // });
24
+ // data.value = result.results ?? [];
25
+ // totalCount.value = result.totalCount ?? 0;
26
+ });
27
+
28
+ const { loading: deleteLoading, action: removeItems } = useAsync(async (ids?: string[]) => {
29
+ // TODO: Replace with real API call
30
+ // await Promise.all((ids ?? []).map((id) => apiClient.delete(id)));
31
+ });
32
+
33
+ const loading = useLoading(itemsLoading, deleteLoading);
34
+
35
+ return {
36
+ data,
37
+ loading,
38
+ totalCount,
39
+ getItems,
40
+ removeItems,
41
+ };
42
+ }
@@ -6,26 +6,28 @@
6
6
  width="50%"
7
7
  >
8
8
  <VcDataTable
9
+ v-model:search-value="searchValue"
10
+ v-model:sort-field="sortField"
11
+ v-model:sort-order="sortOrder"
9
12
  :items="data"
10
13
  :total-count="totalCount"
11
- :current-page="currentPage"
12
- :search-value="searchQuery"
14
+ :pagination="pagination"
15
+ :searchable="true"
13
16
  :state-key="'<%- ModuleNameScreamingSnake %>'"
14
- @search:change="(val: string) => { searchQuery = val; getItems(); }"
15
- @item-click="openDetails"
16
- @pagination-click="(page: number) => { currentPage = page; getItems(); }"
17
+ @row-click="onRowClick"
18
+ @pagination-click="pagination.goToPage"
17
19
  >
18
20
  <!-- Add your columns here -->
19
- <VcColumn id="name" :header="$t('<%- ModuleNameScreamingSnake %>.PAGES.LIST.COLUMNS.NAME')" sortable />
20
- <VcColumn id="createdDate" :header="$t('<%- ModuleNameScreamingSnake %>.PAGES.LIST.COLUMNS.CREATED_DATE')" type="datetime" sortable />
21
+ <VcColumn id="name" :title="$t('<%- ModuleNameScreamingSnake %>.PAGES.LIST.COLUMNS.NAME')" sortable />
22
+ <VcColumn id="createdDate" :title="$t('<%- ModuleNameScreamingSnake %>.PAGES.LIST.COLUMNS.CREATED_DATE')" type="datetime" sortable />
21
23
  </VcDataTable>
22
24
  </VcBlade>
23
25
  </template>
24
26
 
25
27
  <script setup lang="ts">
26
- import { useBlade, type IBladeToolbar } from "@vc-shell/framework";
28
+ import { useBlade, useDataTableSort, useTableSearch, useDataTablePagination, useFunctions, type IBladeToolbar } from "@vc-shell/framework";
27
29
  import { VcBlade, VcDataTable, VcColumn } from "@vc-shell/framework/ui";
28
- import { ref, onMounted } from "vue";
30
+ import { ref, watch } from "vue";
29
31
  import use<%- ModuleNamePascalCase %>List from "../composables/useList";
30
32
  import { useI18n } from "vue-i18n";
31
33
 
@@ -42,22 +44,43 @@ defineBlade({
42
44
 
43
45
  const { t } = useI18n({ useScope: "global" });
44
46
  const { openBlade, exposeToChildren } = useBlade();
47
+ const { debounce } = useFunctions();
45
48
 
46
- const {
47
- data,
48
- loading,
49
- totalCount,
50
- currentPage,
51
- searchQuery,
52
- getItems,
53
- } = use<%- ModuleNamePascalCase %>List();
49
+ const PAGE_SIZE = 20;
50
+
51
+ const { sortField, sortOrder, sortExpression } = useDataTableSort({
52
+ stateKey: "<%- ModuleNameScreamingSnake %>",
53
+ initialField: "createdDate",
54
+ initialDirection: "DESC",
55
+ });
56
+
57
+ const { data, loading, totalCount, getItems } = use<%- ModuleNamePascalCase %>List();
58
+
59
+ const { searchValue } = useTableSearch({ stateKey: "<%- ModuleNameScreamingSnake %>" });
60
+ const pagination = useDataTablePagination({ stateKey: "<%- ModuleNameScreamingSnake %>", pageSize: PAGE_SIZE, totalCount });
61
+
62
+ function load() {
63
+ return getItems({
64
+ sort: sortExpression.value,
65
+ keyword: searchValue.value || undefined,
66
+ skip: pagination.skip,
67
+ take: PAGE_SIZE,
68
+ });
69
+ }
70
+
71
+ // One loader for sort/search/page. Debounced so typing doesn't fetch per keystroke.
72
+ load();
73
+ watch(searchValue, () => pagination.setPage(1));
74
+ watch([sortExpression, searchValue, () => pagination.skip], debounce(load, 300));
75
+
76
+ const reload = () => load();
54
77
 
55
78
  const bladeToolbar = ref<IBladeToolbar[]>([
56
79
  {
57
80
  id: "refresh",
58
81
  title: t("<%- ModuleNameScreamingSnake %>.PAGES.LIST.TOOLBAR.REFRESH"),
59
82
  icon: "lucide-refresh-cw",
60
- clickHandler: () => getItems(),
83
+ clickHandler: () => reload(),
61
84
  },
62
85
  {
63
86
  id: "add",
@@ -72,15 +95,14 @@ function openDetails(item?: { id?: string }) {
72
95
  name: "<%- ModuleNamePascalCase %>Details",
73
96
  param: item?.id,
74
97
  async onClose() {
75
- await getItems();
98
+ await reload();
76
99
  },
77
100
  });
78
101
  }
79
102
 
80
- onMounted(async () => {
81
- await getItems();
82
- })
83
-
103
+ function onRowClick(event: { data: { id?: string } }) {
104
+ openDetails(event.data);
105
+ }
84
106
 
85
- exposeToChildren({ reload: getItems });
107
+ exposeToChildren({ reload });
86
108
  </script>
@@ -7,6 +7,8 @@
7
7
  <!-- Blade contents -->
8
8
  <VcDataTable
9
9
  v-model:search-value="searchValue"
10
+ v-model:sort-field="sortField"
11
+ v-model:sort-order="sortOrder"
10
12
  v-model:active-item-id="selectedItemId"
11
13
  v-model:selection="selectedItems"
12
14
  :loading="loading"
@@ -14,7 +16,7 @@
14
16
  selection-mode="multiple"
15
17
  :items="data ?? []"
16
18
  :row-actions="actionBuilder"
17
- :pagination="{ currentPage, pages }"
19
+ :pagination="pagination"
18
20
  :searchable="true"
19
21
  :search-placeholder="$t('SAMPLE_APP.PAGES.LIST.SEARCH.PLACEHOLDER')"
20
22
  :empty-state="{
@@ -32,9 +34,8 @@
32
34
  :total-label="$t('SAMPLE_APP.PAGES.LIST.TABLE.TOTALS')"
33
35
  :total-count="totalCount"
34
36
  state-key="SAMPLE_APP"
35
- @search="onSearchList"
36
37
  @row-click="onItemClick"
37
- @pagination-click="onPaginationClick"
38
+ @pagination-click="pagination.goToPage"
38
39
  >
39
40
  <VcColumn
40
41
  id="imgSrc"
@@ -73,8 +74,8 @@
73
74
  </template>
74
75
 
75
76
  <script lang="ts" setup>
76
- import { computed, ref, onMounted, watch } from "vue";
77
- import { IBladeToolbar, useBlade, usePopup, useTableSort, useFunctions } from "@vc-shell/framework";
77
+ import { ref, watch, computed } from "vue";
78
+ import { IBladeToolbar, useBlade, usePopup, useDataTableSort, useTableSearch, useDataTablePagination, useFunctions } from "@vc-shell/framework";
78
79
  import type { TableAction } from "@vc-shell/framework";
79
80
  import { VcColumn, VcDataTable, VcBlade } from "@vc-shell/framework/ui";
80
81
  import { useI18n } from "vue-i18n";
@@ -97,17 +98,21 @@ const { param, openBlade, exposeToChildren } = useBlade();
97
98
  const { showConfirmation } = usePopup();
98
99
  const { debounce } = useFunctions();
99
100
 
100
- const { sortExpression } = useTableSort({
101
- initialProperty: "createdDate",
101
+ const PAGE_SIZE = 20;
102
+
103
+ const { sortField, sortOrder, sortExpression } = useDataTableSort({
104
+ stateKey: "SAMPLE_APP",
105
+ initialField: "createdDate",
102
106
  initialDirection: "DESC",
103
107
  });
104
108
 
105
- const { getItems, removeItems, data, loading, totalCount, pages, currentPage, searchQuery } = useList({
109
+ const { getItems, removeItems, data, loading, totalCount } = useList({
106
110
  sort: sortExpression.value,
107
- pageSize: 20,
111
+ pageSize: PAGE_SIZE,
108
112
  });
109
113
 
110
- const searchValue = ref();
114
+ const { searchValue } = useTableSearch({ stateKey: "SAMPLE_APP" });
115
+ const pagination = useDataTablePagination({ stateKey: "SAMPLE_APP", pageSize: PAGE_SIZE, totalCount });
111
116
  const selectedItemId = ref<string>();
112
117
  const selectedItems = ref<MockedItem[]>([]);
113
118
 
@@ -121,47 +126,25 @@ watch(
121
126
  { immediate: true },
122
127
  );
123
128
 
124
- onMounted(async () => {
125
- await getItems({
126
- ...searchQuery.value,
129
+ function load() {
130
+ return getItems({
127
131
  sort: sortExpression.value,
132
+ keyword: searchValue.value || undefined,
133
+ skip: pagination.skip,
128
134
  });
129
- });
130
-
131
- watch(sortExpression, async (value) => {
132
- await getItems({
133
- ...searchQuery.value,
134
- sort: value,
135
- });
136
- });
135
+ }
137
136
 
138
- const onSearchList = debounce(async (keyword: string) => {
139
- searchValue.value = keyword;
140
- await getItems({
141
- ...searchQuery.value,
142
- keyword,
143
- });
144
- }, 1000);
137
+ // One loader for sort/search/page. Debounced so typing doesn't fetch per keystroke.
138
+ load();
139
+ watch(searchValue, () => pagination.setPage(1));
140
+ watch([sortExpression, searchValue, () => pagination.skip], debounce(load, 300));
145
141
 
146
- const clearSearch = async () => {
142
+ const clearSearch = () => {
147
143
  searchValue.value = "";
148
- await getItems({
149
- ...searchQuery.value,
150
- keyword: "",
151
- });
152
144
  };
153
145
 
154
146
  const addItem = () => {
155
- openBlade({
156
- name: "SampleDetails",
157
- });
158
- };
159
-
160
- const onPaginationClick = async (page: number) => {
161
- await getItems({
162
- ...searchQuery.value,
163
- skip: (page - 1) * (searchQuery.value.take ?? 20),
164
- });
147
+ openBlade({ name: "SampleDetails" });
165
148
  };
166
149
 
167
150
  const bladeToolbar = ref<IBladeToolbar[]>([
@@ -188,11 +171,7 @@ const title = computed(() => t("SAMPLE_APP.PAGES.LIST.TITLE"));
188
171
 
189
172
  const reload = async () => {
190
173
  selectedItems.value = [];
191
- await getItems({
192
- ...searchQuery.value,
193
- skip: (currentPage.value - 1) * (searchQuery.value.take ?? 10),
194
- sort: sortExpression.value,
195
- });
174
+ await load();
196
175
  };
197
176
 
198
177
  const onItemClick = (event: { data: MockedItem; index: number; originalEvent: Event }) => {
@@ -22,8 +22,8 @@
22
22
  "@types/node": "^20.10.5",
23
23
  "@typescript-eslint/eslint-plugin": "^8.43.0",
24
24
  "@typescript-eslint/parser": "^8.43.0",
25
- "@vc-shell/api-client-generator": "^2.0.10",
26
- "@vc-shell/ts-config": "^2.0.10",
25
+ "@vc-shell/api-client-generator": "^2.0.11",
26
+ "@vc-shell/ts-config": "^2.0.11",
27
27
  "@vitejs/plugin-vue": "^5.2.3",
28
28
  "@vue/eslint-config-prettier": "^10.2.0",
29
29
  "@vue/eslint-config-typescript": "^14.6.0",
@@ -51,8 +51,8 @@
51
51
  "vue-tsc": "^3.2.5"
52
52
  },
53
53
  "dependencies": {
54
- "@vc-shell/config-generator": "^2.0.10",
55
- "@vc-shell/framework": "^2.0.10",
54
+ "@vc-shell/config-generator": "^2.0.11",
55
+ "@vc-shell/framework": "^2.0.11",
56
56
  "@vueuse/core": "^10.7.1",
57
57
  "@vueuse/integrations": "^10.7.1",
58
58
  "vee-validate": "^4.12.4",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vc-shell/create-vc-app",
3
3
  "description": "Application scaffolding",
4
- "version": "2.0.10-pr247.f47cc74",
4
+ "version": "2.0.11-pr247.0f1e741",
5
5
  "type": "module",
6
6
  "bin": "./dist/index.js",
7
7
  "files": [
@@ -16,7 +16,7 @@
16
16
  "devDependencies": {
17
17
  "@types/ejs": "^3.1.5",
18
18
  "@types/prompts": "^2.4.4",
19
- "@vc-shell/ts-config": "2.0.10-pr247.f47cc74",
19
+ "@vc-shell/ts-config": "2.0.11-pr247.0f1e741",
20
20
  "copyfiles": "^2.4.1",
21
21
  "cross-env": "^7.0.3",
22
22
  "shx": "^0.3.4",