@vc-shell/create-vc-app 2.0.10-pr242.7d8e7c3 → 2.0.10-pr243.88977c0

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.
@@ -1,42 +1,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
- }
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
+ }
@@ -6,29 +6,26 @@
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"
12
9
  :items="data"
13
10
  :total-count="totalCount"
14
- :pagination="{ currentPage, pages }"
15
- :searchable="true"
11
+ :current-page="currentPage"
12
+ :search-value="searchQuery"
16
13
  :state-key="'<%- ModuleNameScreamingSnake %>'"
17
- @search="onSearch"
18
- @row-click="onRowClick"
19
- @pagination-click="onPaginationClick"
14
+ @search:change="(val: string) => { searchQuery = val; getItems(); }"
15
+ @item-click="openDetails"
16
+ @pagination-click="(page: number) => { currentPage = page; getItems(); }"
20
17
  >
21
18
  <!-- Add your columns here -->
22
- <VcColumn id="name" :title="$t('<%- ModuleNameScreamingSnake %>.PAGES.LIST.COLUMNS.NAME')" sortable />
23
- <VcColumn id="createdDate" :title="$t('<%- ModuleNameScreamingSnake %>.PAGES.LIST.COLUMNS.CREATED_DATE')" type="datetime" sortable />
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 />
24
21
  </VcDataTable>
25
22
  </VcBlade>
26
23
  </template>
27
24
 
28
25
  <script setup lang="ts">
29
- import { useBlade, useDataTableSort, useTableQueryState, type IBladeToolbar } from "@vc-shell/framework";
26
+ import { useBlade, type IBladeToolbar } from "@vc-shell/framework";
30
27
  import { VcBlade, VcDataTable, VcColumn } from "@vc-shell/framework/ui";
31
- import { computed, ref, watch } from "vue";
28
+ import { ref, onMounted } from "vue";
32
29
  import use<%- ModuleNamePascalCase %>List from "../composables/useList";
33
30
  import { useI18n } from "vue-i18n";
34
31
 
@@ -46,75 +43,21 @@ defineBlade({
46
43
  const { t } = useI18n({ useScope: "global" });
47
44
  const { openBlade, exposeToChildren } = useBlade();
48
45
 
49
- const PAGE_SIZE = 20;
50
-
51
- const { sortField, sortOrder, sortExpression } = useDataTableSort({
52
- initialField: "createdDate",
53
- initialDirection: "DESC",
54
- });
55
-
56
- const { data, loading, totalCount, getItems } = use<%- ModuleNamePascalCase %>List();
57
-
58
- const searchValue = ref<string>(); // live search box (v-model)
59
- const appliedKeyword = ref<string>(); // applied filter — drives the loader
60
- const currentPage = ref(1);
61
-
62
- const pages = computed(() => Math.ceil(totalCount.value / PAGE_SIZE) || 0);
63
-
64
- // Pull-restore: read the URL-persisted view state and seed our refs synchronously,
65
- // BEFORE the loader watch below — so a reload costs exactly one request.
66
- const restored = useTableQueryState("<%- ModuleNameScreamingSnake %>").read();
67
- if (restored.sort) {
68
- const [field, direction] = restored.sort.split(":");
69
- sortField.value = field;
70
- sortOrder.value = direction === "DESC" ? -1 : 1;
71
- }
72
- if (restored.search !== undefined) {
73
- searchValue.value = restored.search;
74
- appliedKeyword.value = restored.search;
75
- }
76
- if (restored.page) {
77
- currentPage.value = restored.page;
78
- }
79
-
80
- // Single loader — the ONLY trigger that calls the API. Vue batches synchronous
81
- // changes to several criteria into one flush → one request, even on restore.
82
- watch(
83
- () => ({
84
- sort: sortExpression.value,
85
- keyword: appliedKeyword.value || undefined,
86
- skip: (currentPage.value - 1) * PAGE_SIZE,
87
- take: PAGE_SIZE,
88
- }),
89
- (query) => getItems(query),
90
- { immediate: true },
91
- );
92
-
93
- // Handlers only update state — they never load. VcDataTable debounces @search, so
94
- // reacting to appliedKeyword coalesces fast typing into a single request.
95
- const onSearch = (keyword: string) => {
96
- appliedKeyword.value = keyword;
97
- currentPage.value = 1;
98
- };
99
-
100
- const onPaginationClick = (page: number) => {
101
- currentPage.value = page;
102
- };
103
-
104
- const reload = () =>
105
- getItems({
106
- sort: sortExpression.value,
107
- keyword: appliedKeyword.value || undefined,
108
- skip: (currentPage.value - 1) * PAGE_SIZE,
109
- take: PAGE_SIZE,
110
- });
46
+ const {
47
+ data,
48
+ loading,
49
+ totalCount,
50
+ currentPage,
51
+ searchQuery,
52
+ getItems,
53
+ } = use<%- ModuleNamePascalCase %>List();
111
54
 
112
55
  const bladeToolbar = ref<IBladeToolbar[]>([
113
56
  {
114
57
  id: "refresh",
115
58
  title: t("<%- ModuleNameScreamingSnake %>.PAGES.LIST.TOOLBAR.REFRESH"),
116
59
  icon: "lucide-refresh-cw",
117
- clickHandler: () => reload(),
60
+ clickHandler: () => getItems(),
118
61
  },
119
62
  {
120
63
  id: "add",
@@ -129,14 +72,15 @@ function openDetails(item?: { id?: string }) {
129
72
  name: "<%- ModuleNamePascalCase %>Details",
130
73
  param: item?.id,
131
74
  async onClose() {
132
- await reload();
75
+ await getItems();
133
76
  },
134
77
  });
135
78
  }
136
79
 
137
- function onRowClick(event: { data: { id?: string } }) {
138
- openDetails(event.data);
139
- }
80
+ onMounted(async () => {
81
+ await getItems();
82
+ })
83
+
140
84
 
141
- exposeToChildren({ reload });
85
+ exposeToChildren({ reload: getItems });
142
86
  </script>
@@ -7,8 +7,6 @@
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"
12
10
  v-model:active-item-id="selectedItemId"
13
11
  v-model:selection="selectedItems"
14
12
  :loading="loading"
@@ -75,8 +73,8 @@
75
73
  </template>
76
74
 
77
75
  <script lang="ts" setup>
78
- import { computed, ref, watch } from "vue";
79
- import { IBladeToolbar, useBlade, usePopup, useDataTableSort, useTableQueryState } from "@vc-shell/framework";
76
+ import { computed, ref, onMounted, watch } from "vue";
77
+ import { IBladeToolbar, useBlade, usePopup, useTableSort, useFunctions } from "@vc-shell/framework";
80
78
  import type { TableAction } from "@vc-shell/framework";
81
79
  import { VcColumn, VcDataTable, VcBlade } from "@vc-shell/framework/ui";
82
80
  import { useI18n } from "vue-i18n";
@@ -97,44 +95,24 @@ defineBlade({
97
95
  const { t } = useI18n({ useScope: "global" });
98
96
  const { param, openBlade, exposeToChildren } = useBlade();
99
97
  const { showConfirmation } = usePopup();
98
+ const { debounce } = useFunctions();
100
99
 
101
- const PAGE_SIZE = 20;
102
-
103
- const { sortField, sortOrder, sortExpression } = useDataTableSort({
104
- initialField: "createdDate",
100
+ const { sortExpression } = useTableSort({
101
+ initialProperty: "createdDate",
105
102
  initialDirection: "DESC",
106
103
  });
107
104
 
108
- const { getItems, removeItems, data, loading, totalCount, pages } = useList({
105
+ const { getItems, removeItems, data, loading, totalCount, pages, currentPage, searchQuery } = useList({
109
106
  sort: sortExpression.value,
110
- pageSize: PAGE_SIZE,
107
+ pageSize: 20,
111
108
  });
112
109
 
113
- const searchValue = ref<string>(); // live search box (v-model)
114
- const appliedKeyword = ref<string>(); // applied filter — drives the loader
115
- const currentPage = ref(1);
110
+ const searchValue = ref();
116
111
  const selectedItemId = ref<string>();
117
112
  const selectedItems = ref<MockedItem[]>([]);
118
113
 
119
114
  const selectedIds = computed(() => selectedItems.value.map((item) => item.id).filter(Boolean) as string[]);
120
115
 
121
- // Pull-restore: read the URL-persisted view state and seed our refs synchronously,
122
- // BEFORE the loader watch below — so a page reload costs exactly one request with
123
- // the full criteria, instead of several uncoordinated loads.
124
- const restored = useTableQueryState("SAMPLE_APP").read();
125
- if (restored.sort) {
126
- const [field, direction] = restored.sort.split(":");
127
- sortField.value = field;
128
- sortOrder.value = direction === "DESC" ? -1 : 1;
129
- }
130
- if (restored.search !== undefined) {
131
- searchValue.value = restored.search;
132
- appliedKeyword.value = restored.search;
133
- }
134
- if (restored.page) {
135
- currentPage.value = restored.page;
136
- }
137
-
138
116
  watch(
139
117
  param,
140
118
  (newVal) => {
@@ -143,30 +121,34 @@ watch(
143
121
  { immediate: true },
144
122
  );
145
123
 
146
- // Single loader — the ONLY place that calls the API. Vue batches synchronous changes
147
- // to several criteria into one flush, so sort + search + page changing together still
148
- // yields one request.
149
- watch(
150
- () => ({
124
+ onMounted(async () => {
125
+ await getItems({
126
+ ...searchQuery.value,
151
127
  sort: sortExpression.value,
152
- keyword: appliedKeyword.value || undefined,
153
- skip: (currentPage.value - 1) * PAGE_SIZE,
154
- }),
155
- (query) => getItems(query),
156
- { immediate: true },
157
- );
128
+ });
129
+ });
158
130
 
159
- // Handlers only update state — they never load. VcDataTable debounces @search, so
160
- // reacting to appliedKeyword coalesces fast typing into a single request.
161
- const onSearchList = (keyword: string) => {
162
- appliedKeyword.value = keyword;
163
- currentPage.value = 1;
164
- };
131
+ watch(sortExpression, async (value) => {
132
+ await getItems({
133
+ ...searchQuery.value,
134
+ sort: value,
135
+ });
136
+ });
165
137
 
166
- const clearSearch = () => {
138
+ const onSearchList = debounce(async (keyword: string) => {
139
+ searchValue.value = keyword;
140
+ await getItems({
141
+ ...searchQuery.value,
142
+ keyword,
143
+ });
144
+ }, 1000);
145
+
146
+ const clearSearch = async () => {
167
147
  searchValue.value = "";
168
- appliedKeyword.value = "";
169
- currentPage.value = 1;
148
+ await getItems({
149
+ ...searchQuery.value,
150
+ keyword: "",
151
+ });
170
152
  };
171
153
 
172
154
  const addItem = () => {
@@ -175,8 +157,11 @@ const addItem = () => {
175
157
  });
176
158
  };
177
159
 
178
- const onPaginationClick = (page: number) => {
179
- currentPage.value = page;
160
+ const onPaginationClick = async (page: number) => {
161
+ await getItems({
162
+ ...searchQuery.value,
163
+ skip: (page - 1) * (searchQuery.value.take ?? 20),
164
+ });
180
165
  };
181
166
 
182
167
  const bladeToolbar = ref<IBladeToolbar[]>([
@@ -203,12 +188,10 @@ const title = computed(() => t("SAMPLE_APP.PAGES.LIST.TITLE"));
203
188
 
204
189
  const reload = async () => {
205
190
  selectedItems.value = [];
206
- // Explicit user refresh — the criteria are unchanged, so the loader watch would
207
- // not fire; reload calls the API directly with the current combined criteria.
208
191
  await getItems({
192
+ ...searchQuery.value,
193
+ skip: (currentPage.value - 1) * (searchQuery.value.take ?? 10),
209
194
  sort: sortExpression.value,
210
- keyword: appliedKeyword.value || undefined,
211
- skip: (currentPage.value - 1) * PAGE_SIZE,
212
195
  });
213
196
  };
214
197
 
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-pr242.7d8e7c3",
4
+ "version": "2.0.10-pr243.88977c0",
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-pr242.7d8e7c3",
19
+ "@vc-shell/ts-config": "2.0.10-pr243.88977c0",
20
20
  "copyfiles": "^2.4.1",
21
21
  "cross-env": "^7.0.3",
22
22
  "shx": "^0.3.4",