@vc-shell/vc-app-skill 2.0.0-alpha.33 → 2.0.0-alpha.34
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/CHANGELOG.md +78 -54
- package/README.md +42 -12
- package/package.json +4 -4
- package/runtime/VERSION +1 -1
- package/runtime/agents/api-analyzer.md +31 -16
- package/runtime/agents/blade-enhancer.md +15 -9
- package/runtime/agents/details-blade-generator.md +47 -31
- package/runtime/agents/list-blade-generator.md +21 -37
- package/runtime/agents/locales-generator.md +3 -0
- package/runtime/agents/migration-agent.md +18 -7
- package/runtime/agents/module-analyzer.md +2 -0
- package/runtime/agents/module-assembler.md +15 -0
- package/runtime/agents/promote-agent.md +15 -4
- package/runtime/agents/type-checker.md +11 -0
- package/runtime/knowledge/docs/_BUILD_HASH.md +1 -1
- package/runtime/knowledge/docs/core/api/platform.docs.md +30 -30
- package/runtime/knowledge/docs/core/blade-navigation/blade-nav-composables.docs.md +41 -41
- package/runtime/knowledge/docs/core/composables/bladeContext/index.docs.md +12 -10
- package/runtime/knowledge/docs/core/composables/useApiClient/useApiClient.docs.md +11 -14
- package/runtime/knowledge/docs/core/composables/useAppBarMobileButtons/useAppBarMobileButtons.docs.md +35 -35
- package/runtime/knowledge/docs/core/composables/useAppBarWidget/useAppBarWidget.docs.md +35 -35
- package/runtime/knowledge/docs/core/composables/useAppInsights/useAppInsights.docs.md +15 -15
- package/runtime/knowledge/docs/core/composables/useAssets/useAssets.docs.md +21 -18
- package/runtime/knowledge/docs/core/composables/useAssetsManager/useAssetsManager.docs.md +28 -24
- package/runtime/knowledge/docs/core/composables/useAsync/useAsync.docs.md +90 -61
- package/runtime/knowledge/docs/core/composables/useBeforeUnload/useBeforeUnload.docs.md +19 -18
- package/runtime/knowledge/docs/core/composables/useBlade/useBlade.docs.md +89 -68
- package/runtime/knowledge/docs/core/composables/useBladeForm/useBladeForm.docs.md +27 -25
- package/runtime/knowledge/docs/core/composables/useBladeRegistry/useBladeRegistry.docs.md +15 -15
- package/runtime/knowledge/docs/core/composables/useBladeWidgets/index.docs.md +43 -47
- package/runtime/knowledge/docs/core/composables/useBreadcrumbs/useBreadcrumbs.docs.md +11 -11
- package/runtime/knowledge/docs/core/composables/useConnectionStatus/useConnectionStatus.docs.md +27 -15
- package/runtime/knowledge/docs/core/composables/useDashboard/useDashboard.docs.md +30 -30
- package/runtime/knowledge/docs/core/composables/useDynamicProperties/useDynamicProperties.docs.md +34 -36
- package/runtime/knowledge/docs/core/composables/useErrorHandler/useErrorHandler.docs.md +44 -23
- package/runtime/knowledge/docs/core/composables/useFunctions/useFunctions.docs.md +14 -11
- package/runtime/knowledge/docs/core/composables/useKeyboardNavigation/useKeyboardNavigation.docs.md +47 -38
- package/runtime/knowledge/docs/core/composables/useLanguages/useLanguages.docs.md +37 -28
- package/runtime/knowledge/docs/core/composables/useLoading/useLoading.docs.md +23 -17
- package/runtime/knowledge/docs/core/composables/useMenuExpanded/index.docs.md +9 -9
- package/runtime/knowledge/docs/core/composables/useMenuService/useMenuService.docs.md +42 -42
- package/runtime/knowledge/docs/core/composables/useModificationTracker/useModificationTracker.docs.md +22 -12
- package/runtime/knowledge/docs/core/composables/useNotifications/useNotifications.docs.md +33 -41
- package/runtime/knowledge/docs/core/composables/usePermissions/usePermissions.docs.md +16 -16
- package/runtime/knowledge/docs/core/composables/usePlatformLocaleSync/usePlatformLocaleSync.docs.md +28 -0
- package/runtime/knowledge/docs/core/composables/usePopup/usePopup.docs.md +32 -24
- package/runtime/knowledge/docs/core/composables/useResponsive/useResponsive.docs.md +32 -11
- package/runtime/knowledge/docs/core/composables/useSettings/useSettings.docs.md +22 -13
- package/runtime/knowledge/docs/core/composables/useSettingsMenu/useSettingsMenu.docs.md +7 -7
- package/runtime/knowledge/docs/core/composables/useSidebarState/useSidebarState.docs.md +32 -24
- package/runtime/knowledge/docs/core/composables/useSlowNetworkDetection/useSlowNetworkDetection.docs.md +21 -17
- package/runtime/knowledge/docs/core/composables/useTheme/useTheme.docs.md +24 -24
- package/runtime/knowledge/docs/core/composables/useToolbar/useToolbar.docs.md +28 -31
- package/runtime/knowledge/docs/core/composables/useUser/useUser.docs.md +43 -24
- package/runtime/knowledge/docs/core/composables/useUserManagement/useUserManagement.docs.md +68 -48
- package/runtime/knowledge/docs/core/composables/useWebVitals/useWebVitals.docs.md +19 -19
- package/runtime/knowledge/docs/core/composables/useWidgets/useWidgets.docs.md +42 -47
- package/runtime/knowledge/docs/core/directives/autofocus/autofocus.docs.md +10 -4
- package/runtime/knowledge/docs/core/directives/loading/loading.docs.md +35 -20
- package/runtime/knowledge/docs/core/notifications/notifications.docs.md +36 -35
- package/runtime/knowledge/docs/core/plugins/ai-agent/ai-agent.docs.md +38 -38
- package/runtime/knowledge/docs/core/plugins/extension-points/extension-points.docs.md +79 -62
- package/runtime/knowledge/docs/core/plugins/global-error-handler/global-error-handler.docs.md +10 -10
- package/runtime/knowledge/docs/core/plugins/i18n/i18n.docs.md +21 -23
- package/runtime/knowledge/docs/core/plugins/modularity/modularity.docs.md +91 -83
- package/runtime/knowledge/docs/core/plugins/permissions/permissions.docs.md +10 -16
- package/runtime/knowledge/docs/core/plugins/signalR/signalR.docs.md +9 -9
- package/runtime/knowledge/docs/core/plugins/validation/validation.docs.md +65 -22
- package/runtime/knowledge/docs/core/services/services.docs.md +19 -22
- package/runtime/knowledge/docs/core/types/types.docs.md +40 -40
- package/runtime/knowledge/docs/core/utilities/date/date-utilities.docs.md +27 -27
- package/runtime/knowledge/docs/core/utilities/shared-utilities.docs.md +23 -23
- package/runtime/knowledge/docs/core/utilities/thumbnail/thumbnail.docs.md +22 -25
- package/runtime/knowledge/docs/core/utilities/utilities.docs.md +64 -64
- package/runtime/knowledge/docs/injection-keys.docs.md +52 -51
- package/runtime/knowledge/docs/modules/assets-manager/assets-manager.docs.md +9 -9
- package/runtime/knowledge/docs/shell/_internal/popup/common/popup-common.docs.md +23 -43
- package/runtime/knowledge/docs/shell/auth/ChangePasswordPage/change-password-page.docs.md +5 -5
- package/runtime/knowledge/docs/shell/auth/ForgotPasswordPage/forgot-password-page.docs.md +5 -5
- package/runtime/knowledge/docs/shell/auth/InvitePage/invite-page.docs.md +8 -7
- package/runtime/knowledge/docs/shell/auth/LoginPage/login-page.docs.md +7 -7
- package/runtime/knowledge/docs/shell/auth/ResetPasswordPage/reset-password-page.docs.md +8 -7
- package/runtime/knowledge/docs/shell/auth/sign-in/sign-in.docs.md +29 -13
- package/runtime/knowledge/docs/shell/components/change-password/change-password.docs.md +13 -16
- package/runtime/knowledge/docs/shell/components/change-password-button/change-password-button.docs.md +1 -7
- package/runtime/knowledge/docs/shell/components/error-interceptor/error-interceptor.docs.md +5 -5
- package/runtime/knowledge/docs/shell/components/language-selector/language-selector.docs.md +1 -1
- package/runtime/knowledge/docs/shell/components/logout-button/logout-button.docs.md +1 -1
- package/runtime/knowledge/docs/shell/components/notification-template/notification-template.docs.md +17 -9
- package/runtime/knowledge/docs/shell/components/settings-menu/settings-menu.docs.md +11 -17
- package/runtime/knowledge/docs/shell/components/settings-menu-item/settings-menu-item.docs.md +34 -65
- package/runtime/knowledge/docs/shell/components/sidebar/sidebar.docs.md +16 -26
- package/runtime/knowledge/docs/shell/components/theme-selector/theme-selector.docs.md +2 -2
- package/runtime/knowledge/docs/shell/components/user-dropdown-button/user-dropdown-button.docs.md +7 -9
- package/runtime/knowledge/docs/shell/dashboard/dashboard-charts/dashboard-charts.docs.md +30 -40
- package/runtime/knowledge/docs/shell/dashboard/dashboard-widget-card/dashboard-widget-card.docs.md +26 -19
- package/runtime/knowledge/docs/shell/dashboard/draggable-dashboard/draggable-dashboard.docs.md +15 -12
- package/runtime/knowledge/docs/ui/components/atoms/vc-badge/vc-badge.docs.md +15 -26
- package/runtime/knowledge/docs/ui/components/atoms/vc-banner/vc-banner.docs.md +21 -19
- package/runtime/knowledge/docs/ui/components/atoms/vc-button/vc-button.docs.md +83 -67
- package/runtime/knowledge/docs/ui/components/atoms/vc-card/vc-card.docs.md +100 -59
- package/runtime/knowledge/docs/ui/components/atoms/vc-col/vc-col.docs.md +28 -11
- package/runtime/knowledge/docs/ui/components/atoms/vc-container/vc-container.docs.md +20 -17
- package/runtime/knowledge/docs/ui/components/atoms/vc-hint/vc-hint.docs.md +26 -17
- package/runtime/knowledge/docs/ui/components/atoms/vc-icon/vc-icon.docs.md +30 -32
- package/runtime/knowledge/docs/ui/components/atoms/vc-image/vc-image.docs.md +25 -48
- package/runtime/knowledge/docs/ui/components/atoms/vc-label/vc-label.docs.md +29 -24
- package/runtime/knowledge/docs/ui/components/atoms/vc-link/vc-link.docs.md +23 -15
- package/runtime/knowledge/docs/ui/components/atoms/vc-loading/vc-loading.docs.md +22 -13
- package/runtime/knowledge/docs/ui/components/atoms/vc-progress/vc-progress.docs.md +33 -18
- package/runtime/knowledge/docs/ui/components/atoms/vc-row/vc-row.docs.md +56 -15
- package/runtime/knowledge/docs/ui/components/atoms/vc-scrollable-container/vc-scrollable-container.docs.md +28 -15
- package/runtime/knowledge/docs/ui/components/atoms/vc-skeleton/vc-skeleton.docs.md +40 -20
- package/runtime/knowledge/docs/ui/components/atoms/vc-status/vc-status.docs.md +25 -14
- package/runtime/knowledge/docs/ui/components/atoms/vc-status-icon/vc-status-icon.docs.md +40 -14
- package/runtime/knowledge/docs/ui/components/atoms/vc-tooltip/vc-tooltip.docs.md +54 -42
- package/runtime/knowledge/docs/ui/components/atoms/vc-video/vc-video.docs.md +17 -17
- package/runtime/knowledge/docs/ui/components/atoms/vc-widget/vc-widget.docs.md +21 -21
- package/runtime/knowledge/docs/ui/components/molecules/multilanguage-selector/multilanguage-selector.docs.md +23 -10
- package/runtime/knowledge/docs/ui/components/molecules/vc-accordion/vc-accordion.docs.md +55 -44
- package/runtime/knowledge/docs/ui/components/molecules/vc-breadcrumbs/vc-breadcrumbs.docs.md +23 -20
- package/runtime/knowledge/docs/ui/components/molecules/vc-checkbox/vc-checkbox.docs.md +92 -65
- package/runtime/knowledge/docs/ui/components/molecules/vc-checkbox-group/vc-checkbox-group.docs.md +22 -36
- package/runtime/knowledge/docs/ui/components/molecules/vc-color-input/vc-color-input.docs.md +65 -23
- package/runtime/knowledge/docs/ui/components/molecules/vc-date-picker/vc-date-picker.docs.md +52 -73
- package/runtime/knowledge/docs/ui/components/molecules/vc-dropdown/vc-dropdown.docs.md +91 -85
- package/runtime/knowledge/docs/ui/components/molecules/vc-dropdown-panel/vc-dropdown-panel.docs.md +38 -42
- package/runtime/knowledge/docs/ui/components/molecules/vc-editor/vc-editor.docs.md +56 -73
- package/runtime/knowledge/docs/ui/components/molecules/vc-field/vc-field.docs.md +61 -27
- package/runtime/knowledge/docs/ui/components/molecules/vc-file-upload/vc-file-upload.docs.md +42 -50
- package/runtime/knowledge/docs/ui/components/molecules/vc-form/vc-form.docs.md +35 -64
- package/runtime/knowledge/docs/ui/components/molecules/vc-image-tile/vc-image-tile.docs.md +38 -41
- package/runtime/knowledge/docs/ui/components/molecules/vc-input/vc-input.docs.md +109 -131
- package/runtime/knowledge/docs/ui/components/molecules/vc-input-currency/vc-input-currency.docs.md +47 -88
- package/runtime/knowledge/docs/ui/components/molecules/vc-input-dropdown/vc-input-dropdown.docs.md +50 -64
- package/runtime/knowledge/docs/ui/components/molecules/vc-input-group/vc-input-group.docs.md +29 -24
- package/runtime/knowledge/docs/ui/components/molecules/vc-menu/vc-menu.docs.md +32 -28
- package/runtime/knowledge/docs/ui/components/molecules/vc-multivalue/vc-multivalue.docs.md +57 -65
- package/runtime/knowledge/docs/ui/components/molecules/vc-pagination/vc-pagination.docs.md +28 -26
- package/runtime/knowledge/docs/ui/components/molecules/vc-radio-button/vc-radio-button.docs.md +55 -20
- package/runtime/knowledge/docs/ui/components/molecules/vc-radio-group/vc-radio-group.docs.md +21 -35
- package/runtime/knowledge/docs/ui/components/molecules/vc-rating/vc-rating.docs.md +38 -33
- package/runtime/knowledge/docs/ui/components/molecules/vc-select/vc-select.docs.md +72 -83
- package/runtime/knowledge/docs/ui/components/molecules/vc-slider/vc-slider.docs.md +21 -16
- package/runtime/knowledge/docs/ui/components/molecules/vc-switch/vc-switch.docs.md +55 -64
- package/runtime/knowledge/docs/ui/components/molecules/vc-textarea/vc-textarea.docs.md +51 -70
- package/runtime/knowledge/docs/ui/components/molecules/vc-toast/vc-toast.docs.md +58 -57
- package/runtime/knowledge/docs/ui/components/organisms/vc-app/vc-app.docs.md +49 -26
- package/runtime/knowledge/docs/ui/components/organisms/vc-auth-layout/vc-auth-layout.docs.md +82 -28
- package/runtime/knowledge/docs/ui/components/organisms/vc-blade/vc-blade.docs.md +90 -75
- package/runtime/knowledge/docs/ui/components/organisms/vc-data-table/composables/table-composables.docs.md +99 -48
- package/runtime/knowledge/docs/ui/components/organisms/vc-data-table/vc-data-table.docs.md +548 -367
- package/runtime/knowledge/docs/ui/components/organisms/vc-dynamic-property/vc-dynamic-property.docs.md +35 -52
- package/runtime/knowledge/docs/ui/components/organisms/vc-gallery/vc-gallery.docs.md +33 -62
- package/runtime/knowledge/docs/ui/components/organisms/vc-image-upload/vc-image-upload.docs.md +17 -23
- package/runtime/knowledge/docs/ui/components/organisms/vc-popup/vc-popup.docs.md +109 -68
- package/runtime/knowledge/docs/ui/components/organisms/vc-sidebar/vc-sidebar.docs.md +82 -44
- package/runtime/knowledge/docs/ui/composables/ui-composables.docs.md +8 -8
- package/runtime/knowledge/docs/ui/composables/useDataTablePagination.docs.md +164 -0
- package/runtime/knowledge/docs/ui/composables/useDataTableSort.docs.md +34 -26
- package/runtime/knowledge/docs/ui/composables/useTableSelection.docs.md +48 -40
- package/runtime/knowledge/docs/ui/composables/useTableSort.docs.md +30 -17
- package/runtime/knowledge/docs/ui/types/ui-types.docs.md +40 -29
- package/runtime/knowledge/examples/offers-module.md +15 -13
- package/runtime/knowledge/examples/team-module.md +82 -119
- package/runtime/knowledge/examples/videos-module.md +44 -17
- package/runtime/knowledge/index.md +22 -0
- package/runtime/knowledge/migration-prompts/blade-form-migration.md +17 -8
- package/runtime/knowledge/migration-prompts/blade-props-migration.md +1 -2
- package/runtime/knowledge/migration-prompts/datatable-migration.md +801 -0
- package/runtime/knowledge/migration-prompts/icon-migration.md +97 -0
- package/runtime/knowledge/migration-prompts/manual-migration-audit.md +117 -0
- package/runtime/knowledge/migration-prompts/notifications-migration.md +8 -3
- package/runtime/knowledge/migration-prompts/nswag-migration.md +25 -29
- package/runtime/knowledge/migration-prompts/use-assets-migration.md +164 -0
- package/runtime/knowledge/migration-prompts/use-data-table-pagination-migration.md +176 -0
- package/runtime/knowledge/migration-prompts/widgets-migration.md +48 -27
- package/runtime/knowledge/patterns/assets-management.md +20 -20
- package/runtime/knowledge/patterns/blade-navigation.md +7 -14
- package/runtime/knowledge/patterns/blade-widget.md +19 -17
- package/runtime/knowledge/patterns/child-blade-flow.md +19 -7
- package/runtime/knowledge/patterns/composable-details.md +20 -50
- package/runtime/knowledge/patterns/composable-list.md +43 -31
- package/runtime/knowledge/patterns/dashboard-widget.md +14 -16
- package/runtime/knowledge/patterns/datatable-pattern.md +521 -0
- package/runtime/knowledge/patterns/details-blade-pattern.md +78 -116
- package/runtime/knowledge/patterns/extension-points-usage.md +53 -44
- package/runtime/knowledge/patterns/form-validation.md +28 -64
- package/runtime/knowledge/patterns/list-blade-pattern.md +33 -21
- package/runtime/knowledge/patterns/module-structure.md +7 -1
- package/runtime/knowledge/patterns/multilanguage-fields.md +8 -14
- package/runtime/knowledge/patterns/notification-template.md +21 -14
- package/runtime/knowledge/patterns/signalr-notifications.md +30 -32
- package/runtime/knowledge/patterns/toolbar-pattern.md +18 -20
- package/runtime/vc-app.md +241 -62
|
@@ -0,0 +1,801 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: datatable-migration
|
|
3
|
+
description: AI transformation rules for VcTable → VcDataTable migration.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# DataTable Migration: VcTable → VcDataTable
|
|
7
|
+
|
|
8
|
+
Replace the imperative `VcTable` component (columns array, manual sort handlers, filter slots) with the declarative `VcDataTable` component. The new API uses `VcColumn` child components, declarative `v-model` bindings for sort and selection, and a `global-filters` prop instead of a `#filters` template slot.
|
|
9
|
+
|
|
10
|
+
## RULE 1: Component Swap and Props Rewrite
|
|
11
|
+
|
|
12
|
+
Full prop/event mapping:
|
|
13
|
+
|
|
14
|
+
| VcTable prop/event | VcDataTable equivalent |
|
|
15
|
+
| -------------------------- | ------------------------------------------------ |
|
|
16
|
+
| `:columns="columns"` | `<VcColumn>` child components (see RULE 2) |
|
|
17
|
+
| `:selected-item-id="id"` | `v-model:active-item-id="id"` |
|
|
18
|
+
| `:sort="sortExpression"` | `v-model:sort-field` + `v-model:sort-order` |
|
|
19
|
+
| `:multiselect="true"` | `:selection-mode="'multiple'"` |
|
|
20
|
+
| `@selection-changed="fn"` | `v-model:selection="ref"` |
|
|
21
|
+
| `:search-value="val"` | `:searchable="true"` (internal state) |
|
|
22
|
+
| `@search:change="fn"` | `@search="fn"` |
|
|
23
|
+
| `@item-click="fn"` | `@row-click="fn"` (different signature) |
|
|
24
|
+
| `@header-click="fn"` | Remove (sort is declarative via v-model) |
|
|
25
|
+
| `@scroll:ptr="fn"` | `:pull-to-refresh="true"` + `@pull-refresh="fn"` |
|
|
26
|
+
| `:pages` + `:current-page` | `:pagination="{ currentPage, pages }"` |
|
|
27
|
+
| `:active-filter-count` | Remove (managed by global-filters internally) |
|
|
28
|
+
| `<!--@vue-generic {T}-->` | Remove (no longer needed) |
|
|
29
|
+
|
|
30
|
+
**BEFORE:**
|
|
31
|
+
|
|
32
|
+
```vue
|
|
33
|
+
<template>
|
|
34
|
+
<!--@vue-generic {OrderItem}-->
|
|
35
|
+
<VcTable
|
|
36
|
+
:columns="columns"
|
|
37
|
+
:items="items"
|
|
38
|
+
:loading="loading"
|
|
39
|
+
:total-count="totalCount"
|
|
40
|
+
:pages="pages"
|
|
41
|
+
:current-page="currentPage"
|
|
42
|
+
:search-value="searchValue"
|
|
43
|
+
:selected-item-id="selectedItemId"
|
|
44
|
+
:sort="sortExpression"
|
|
45
|
+
:multiselect="true"
|
|
46
|
+
:active-filter-count="activeFilterCount"
|
|
47
|
+
@item-click="onItemClick"
|
|
48
|
+
@header-click="onHeaderClick"
|
|
49
|
+
@selection-changed="onSelectionChanged"
|
|
50
|
+
@search:change="onSearchChange"
|
|
51
|
+
@scroll:ptr="onPullToRefresh"
|
|
52
|
+
@pagination-click="onPageChanged"
|
|
53
|
+
>
|
|
54
|
+
<template #item_name="{ item }">
|
|
55
|
+
<span class="font-bold">{{ item.name }}</span>
|
|
56
|
+
</template>
|
|
57
|
+
<template #item_status="{ item }">
|
|
58
|
+
<StatusBadge :status="item.status" />
|
|
59
|
+
</template>
|
|
60
|
+
<template #filters>
|
|
61
|
+
<!-- filter checkboxes -->
|
|
62
|
+
</template>
|
|
63
|
+
<template #empty>
|
|
64
|
+
<EmptyState @add="onAdd" />
|
|
65
|
+
</template>
|
|
66
|
+
<template #notfound>
|
|
67
|
+
<NotFound @reset="onReset" />
|
|
68
|
+
</template>
|
|
69
|
+
</VcTable>
|
|
70
|
+
</template>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**AFTER:**
|
|
74
|
+
|
|
75
|
+
```vue
|
|
76
|
+
<template>
|
|
77
|
+
<VcDataTable
|
|
78
|
+
:items="items"
|
|
79
|
+
:loading="loading"
|
|
80
|
+
:total-count="totalCount"
|
|
81
|
+
:pagination="{ currentPage, pages }"
|
|
82
|
+
:searchable="true"
|
|
83
|
+
:selection-mode="'multiple'"
|
|
84
|
+
:pull-to-refresh="true"
|
|
85
|
+
:global-filters="computedGlobalFilters"
|
|
86
|
+
:empty-state="{
|
|
87
|
+
icon: 'lucide-package-open',
|
|
88
|
+
title: $t('MY_MODULE.PAGES.LIST.EMPTY'),
|
|
89
|
+
actionLabel: $t('MY_MODULE.PAGES.LIST.ADD'),
|
|
90
|
+
actionHandler: onAdd,
|
|
91
|
+
}"
|
|
92
|
+
v-model:active-item-id="selectedItemId"
|
|
93
|
+
v-model:sort-field="sortField"
|
|
94
|
+
v-model:sort-order="sortOrder"
|
|
95
|
+
v-model:selection="localSelection"
|
|
96
|
+
@row-click="onItemClick"
|
|
97
|
+
@search="onSearchChange"
|
|
98
|
+
@pull-refresh="onPullToRefresh"
|
|
99
|
+
@pagination-click="onPageChanged"
|
|
100
|
+
@filter="onFilter"
|
|
101
|
+
>
|
|
102
|
+
<VcColumn
|
|
103
|
+
id="name"
|
|
104
|
+
:title="$t('MY_MODULE.PAGES.LIST.TABLE.NAME')"
|
|
105
|
+
:sortable="true"
|
|
106
|
+
:always-visible="true"
|
|
107
|
+
field="name"
|
|
108
|
+
>
|
|
109
|
+
<template #body="{ data }">
|
|
110
|
+
<span class="font-bold">{{ data.name }}</span>
|
|
111
|
+
</template>
|
|
112
|
+
</VcColumn>
|
|
113
|
+
<VcColumn
|
|
114
|
+
id="status"
|
|
115
|
+
:title="$t('MY_MODULE.PAGES.LIST.TABLE.STATUS')"
|
|
116
|
+
type="status"
|
|
117
|
+
field="status"
|
|
118
|
+
>
|
|
119
|
+
<template #body="{ data }">
|
|
120
|
+
<StatusBadge :status="data.status" />
|
|
121
|
+
</template>
|
|
122
|
+
</VcColumn>
|
|
123
|
+
</VcDataTable>
|
|
124
|
+
</template>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Key points:
|
|
128
|
+
|
|
129
|
+
- `<!--@vue-generic {T}-->` comment is removed entirely.
|
|
130
|
+
- `@header-click` is removed — sort is now declarative via `v-model:sort-field` and `v-model:sort-order`.
|
|
131
|
+
- `:active-filter-count` is removed — the `global-filters` prop manages filter state and badge count internally.
|
|
132
|
+
- `#filters`, `#empty`, and `#notfound` template slots are removed in favor of props (see RULE 6 and RULE 7).
|
|
133
|
+
|
|
134
|
+
## RULE 2: Columns Array → VcColumn Children
|
|
135
|
+
|
|
136
|
+
Column definition moves from a JavaScript array to `<VcColumn>` child components in the template.
|
|
137
|
+
|
|
138
|
+
Prop mapping:
|
|
139
|
+
|
|
140
|
+
| columns array key | VcColumn prop |
|
|
141
|
+
| ------------------------------------------------------------- | ----------------- |
|
|
142
|
+
| `id` | `id` |
|
|
143
|
+
| `title` | `:title` |
|
|
144
|
+
| `sortable` | `:sortable` |
|
|
145
|
+
| `alwaysVisible` | `:always-visible` |
|
|
146
|
+
| `type` | `type` |
|
|
147
|
+
| `width` | `width` |
|
|
148
|
+
| `visible` | `:visible` |
|
|
149
|
+
| `field` | `:field` |
|
|
150
|
+
| `mobilePosition` (value: `"top-left"`, `"bottom-left"`, etc.) | `mobile-position` |
|
|
151
|
+
| `mobilePosition` (value: `"image"`, `"status"`) | `mobile-role` |
|
|
152
|
+
|
|
153
|
+
**BEFORE:**
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
const columns = ref<ITableColumns[]>([
|
|
157
|
+
{
|
|
158
|
+
id: "imgSrc",
|
|
159
|
+
title: computed(() => t("MY_MODULE.PAGES.LIST.TABLE.IMAGE")),
|
|
160
|
+
width: 60,
|
|
161
|
+
alwaysVisible: true,
|
|
162
|
+
mobilePosition: "image",
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: "name",
|
|
166
|
+
title: computed(() => t("MY_MODULE.PAGES.LIST.TABLE.NAME")),
|
|
167
|
+
sortable: true,
|
|
168
|
+
alwaysVisible: true,
|
|
169
|
+
mobilePosition: "top-left",
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: "status",
|
|
173
|
+
title: computed(() => t("MY_MODULE.PAGES.LIST.TABLE.STATUS")),
|
|
174
|
+
type: "status",
|
|
175
|
+
width: 120,
|
|
176
|
+
mobilePosition: "status",
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: "createdDate",
|
|
180
|
+
title: computed(() => t("MY_MODULE.PAGES.LIST.TABLE.CREATED")),
|
|
181
|
+
sortable: true,
|
|
182
|
+
width: 160,
|
|
183
|
+
type: "date-ago",
|
|
184
|
+
mobilePosition: "bottom-right",
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: "modifiedDate",
|
|
188
|
+
title: computed(() => t("MY_MODULE.PAGES.LIST.TABLE.MODIFIED")),
|
|
189
|
+
sortable: true,
|
|
190
|
+
width: 160,
|
|
191
|
+
type: "date-ago",
|
|
192
|
+
visible: false,
|
|
193
|
+
},
|
|
194
|
+
]);
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**AFTER:**
|
|
198
|
+
|
|
199
|
+
```vue
|
|
200
|
+
<VcDataTable :items="items" :loading="loading" :total-count="totalCount">
|
|
201
|
+
<VcColumn
|
|
202
|
+
id="imgSrc"
|
|
203
|
+
:title="$t('MY_MODULE.PAGES.LIST.TABLE.IMAGE')"
|
|
204
|
+
width="60"
|
|
205
|
+
:always-visible="true"
|
|
206
|
+
mobile-role="image"
|
|
207
|
+
/>
|
|
208
|
+
<VcColumn
|
|
209
|
+
id="name"
|
|
210
|
+
:title="$t('MY_MODULE.PAGES.LIST.TABLE.NAME')"
|
|
211
|
+
:sortable="true"
|
|
212
|
+
:always-visible="true"
|
|
213
|
+
mobile-position="top-left"
|
|
214
|
+
field="name"
|
|
215
|
+
/>
|
|
216
|
+
<VcColumn
|
|
217
|
+
id="status"
|
|
218
|
+
:title="$t('MY_MODULE.PAGES.LIST.TABLE.STATUS')"
|
|
219
|
+
type="status"
|
|
220
|
+
width="120"
|
|
221
|
+
mobile-role="status"
|
|
222
|
+
field="status"
|
|
223
|
+
/>
|
|
224
|
+
<VcColumn
|
|
225
|
+
id="createdDate"
|
|
226
|
+
:title="$t('MY_MODULE.PAGES.LIST.TABLE.CREATED')"
|
|
227
|
+
:sortable="true"
|
|
228
|
+
width="160"
|
|
229
|
+
type="date-ago"
|
|
230
|
+
mobile-position="bottom-right"
|
|
231
|
+
field="createdDate"
|
|
232
|
+
/>
|
|
233
|
+
<VcColumn
|
|
234
|
+
id="modifiedDate"
|
|
235
|
+
:title="$t('MY_MODULE.PAGES.LIST.TABLE.MODIFIED')"
|
|
236
|
+
:sortable="true"
|
|
237
|
+
width="160"
|
|
238
|
+
type="date-ago"
|
|
239
|
+
:visible="false"
|
|
240
|
+
field="modifiedDate"
|
|
241
|
+
/>
|
|
242
|
+
</VcDataTable>
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Custom slot transformation — the slot name changes from `#item_{columnId}` to `#body` inside the `<VcColumn>`, and `item` becomes `data`:
|
|
246
|
+
|
|
247
|
+
```vue
|
|
248
|
+
<!-- BEFORE -->
|
|
249
|
+
<VcTable :columns="columns" :items="items">
|
|
250
|
+
<template #item_name="{ item }">
|
|
251
|
+
<MyRenderer :item="item" />
|
|
252
|
+
</template>
|
|
253
|
+
</VcTable>
|
|
254
|
+
|
|
255
|
+
<!-- AFTER -->
|
|
256
|
+
<VcDataTable :items="items">
|
|
257
|
+
<VcColumn id="name" :title="$t('MY_MODULE.PAGES.LIST.TABLE.NAME')">
|
|
258
|
+
<template #body="{ data }">
|
|
259
|
+
<MyRenderer :item="data" />
|
|
260
|
+
</template>
|
|
261
|
+
</VcColumn>
|
|
262
|
+
</VcDataTable>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Key points:
|
|
266
|
+
|
|
267
|
+
- Remove the entire `columns` ref/reactive from the script section.
|
|
268
|
+
- Remove the `ITableColumns` import if it was only used for the columns array.
|
|
269
|
+
- The `mobilePosition` value `"image"` or `"status"` maps to `mobile-role`, not `mobile-position`. All other position values (`"top-left"`, `"bottom-left"`, `"top-right"`, `"bottom-right"`) map to `mobile-position`.
|
|
270
|
+
- The `field` prop tells VcColumn which property of the row object to display by default. If you have a custom `#body` slot, `field` is optional but still recommended for sort/filter binding.
|
|
271
|
+
- **VcDataTable does NOT have a `:columns` prop.** Never pass columns as a prop — always use `<VcColumn>` children.
|
|
272
|
+
|
|
273
|
+
## RULE 2b: Dynamic Columns (received via props)
|
|
274
|
+
|
|
275
|
+
When a reusable component receives columns as a prop (e.g., `columns: ITableColumns[]`), use `v-for` on `<VcColumn>`:
|
|
276
|
+
|
|
277
|
+
**BEFORE:**
|
|
278
|
+
|
|
279
|
+
```vue
|
|
280
|
+
<VcTable :columns="columns" :items="items" />
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
export interface Props {
|
|
285
|
+
columns: ITableColumns[];
|
|
286
|
+
items: Item[];
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**AFTER:**
|
|
291
|
+
|
|
292
|
+
```vue
|
|
293
|
+
<VcDataTable :items="items">
|
|
294
|
+
<VcColumn
|
|
295
|
+
v-for="col in columns"
|
|
296
|
+
:key="col.id"
|
|
297
|
+
:id="col.id"
|
|
298
|
+
:title="col.title"
|
|
299
|
+
:sortable="col.sortable"
|
|
300
|
+
:always-visible="col.alwaysVisible"
|
|
301
|
+
:type="col.type"
|
|
302
|
+
:width="col.width"
|
|
303
|
+
:visible="col.visible"
|
|
304
|
+
:field="col.field"
|
|
305
|
+
/>
|
|
306
|
+
</VcDataTable>
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
The Props interface keeps `columns` — the parent still passes column definitions. The difference is that they are rendered as `<VcColumn>` children via `v-for`, not as a `:columns` prop.
|
|
310
|
+
|
|
311
|
+
## RULE 3: Sort Composable Replacement
|
|
312
|
+
|
|
313
|
+
Replace `useTableSort` with `useDataTableSort`. The new composable returns `sortField` and `sortOrder` as separate refs that bind directly to `v-model:sort-field` and `v-model:sort-order`. Remove the `onHeaderClick` handler entirely.
|
|
314
|
+
|
|
315
|
+
**BEFORE:**
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
import { useTableSort } from "@vc-shell/framework";
|
|
319
|
+
import type { ITableColumns } from "@vc-shell/framework";
|
|
320
|
+
|
|
321
|
+
const { sortExpression, handleSortChange } = useTableSort({
|
|
322
|
+
initialProperty: "createdDate",
|
|
323
|
+
initialDirection: "DESC",
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
function onHeaderClick(item: ITableColumns) {
|
|
327
|
+
handleSortChange(item.id);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// In the watcher that triggers data reload:
|
|
331
|
+
watch(sortExpression, async () => {
|
|
332
|
+
await load({ sort: sortExpression.value, skip: 0 });
|
|
333
|
+
});
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
**AFTER:**
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
import { useDataTableSort } from "@vc-shell/framework";
|
|
340
|
+
|
|
341
|
+
const { sortField, sortOrder, sortExpression } = useDataTableSort({
|
|
342
|
+
initialField: "createdDate",
|
|
343
|
+
initialDirection: "DESC",
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// Remove onHeaderClick entirely — sort is declarative via v-model.
|
|
347
|
+
// The watcher remains the same, watching the computed sortExpression:
|
|
348
|
+
watch(sortExpression, async () => {
|
|
349
|
+
await load({ sort: sortExpression.value, skip: 0 });
|
|
350
|
+
});
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Key points:
|
|
354
|
+
|
|
355
|
+
- `initialProperty` is renamed to `initialField`.
|
|
356
|
+
- `handleSortChange` is removed — the `v-model:sort-field` and `v-model:sort-order` bindings on VcDataTable handle sort changes automatically.
|
|
357
|
+
- `sortExpression` is still available as a computed string (e.g., `"createdDate:DESC"`) for the API call watcher.
|
|
358
|
+
- Remove `onHeaderClick` and its `@header-click` binding from the template.
|
|
359
|
+
- Remove the `ITableColumns` type import if it was only used for the `onHeaderClick` parameter.
|
|
360
|
+
|
|
361
|
+
## RULE 4: Selection Binding
|
|
362
|
+
|
|
363
|
+
Replace the `@selection-changed` event handler with a `v-model:selection` binding. The selection ref holds the full item objects, not just IDs.
|
|
364
|
+
|
|
365
|
+
**BEFORE:**
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
const selectedIds = ref<string[]>([]);
|
|
369
|
+
|
|
370
|
+
function onSelectionChanged(items: OrderItem[]) {
|
|
371
|
+
selectedIds.value = items.map((i) => i.id!);
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
```vue
|
|
376
|
+
<VcTable :multiselect="true" @selection-changed="onSelectionChanged" />
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
**AFTER:**
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
const localSelection = ref<OrderItem[]>([]);
|
|
383
|
+
|
|
384
|
+
// If you need IDs for an API call, derive them with a watcher or computed:
|
|
385
|
+
const selectedIds = computed(() => localSelection.value.map((i) => i.id!));
|
|
386
|
+
|
|
387
|
+
// Or use a watcher if you need side-effects:
|
|
388
|
+
watch(
|
|
389
|
+
localSelection,
|
|
390
|
+
(newSelection) => {
|
|
391
|
+
// e.g., enable/disable toolbar buttons based on selection
|
|
392
|
+
hasSelection.value = newSelection.length > 0;
|
|
393
|
+
},
|
|
394
|
+
{ deep: true },
|
|
395
|
+
);
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
```vue
|
|
399
|
+
<VcDataTable :selection-mode="'multiple'" v-model:selection="localSelection" />
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Key points:
|
|
403
|
+
|
|
404
|
+
- The `localSelection` ref holds full item objects (typed to your entity), not string IDs.
|
|
405
|
+
- Remove the `onSelectionChanged` function entirely.
|
|
406
|
+
- `:multiselect="true"` becomes `:selection-mode="'multiple'"`.
|
|
407
|
+
- If you only need a boolean "has selection" check, derive it from `localSelection.value.length > 0`.
|
|
408
|
+
|
|
409
|
+
## RULE 5: Row Click Event Signature
|
|
410
|
+
|
|
411
|
+
The `@row-click` event wraps the item in an `{ data }` object, unlike `@item-click` which passed the item directly.
|
|
412
|
+
|
|
413
|
+
**BEFORE:**
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
function onItemClick(item: OrderItem) {
|
|
417
|
+
openBlade({
|
|
418
|
+
name: "OrderDetails",
|
|
419
|
+
param: item.id,
|
|
420
|
+
options: { orderId: item.id },
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
```vue
|
|
426
|
+
<VcTable @item-click="onItemClick" />
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
**AFTER:**
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
function onItemClick(event: { data: OrderItem }) {
|
|
433
|
+
const item = event.data;
|
|
434
|
+
openBlade({
|
|
435
|
+
name: "OrderDetails",
|
|
436
|
+
param: item.id,
|
|
437
|
+
options: { orderId: item.id },
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
```vue
|
|
443
|
+
<VcDataTable @row-click="onItemClick" />
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
Key points:
|
|
447
|
+
|
|
448
|
+
- The parameter changes from `item: T` to `event: { data: T }`.
|
|
449
|
+
- Destructure `event.data` at the top of the handler to minimize changes in the rest of the function body.
|
|
450
|
+
- If the handler was a simple one-liner, you can also inline the destructure: `(event: { data: OrderItem }) => openBlade({ name: "OrderDetails", param: event.data.id })`.
|
|
451
|
+
|
|
452
|
+
## RULE 6: Empty State and Not-Found
|
|
453
|
+
|
|
454
|
+
Replace `#empty` and `#notfound` template slots with the `:empty-state` prop.
|
|
455
|
+
|
|
456
|
+
**BEFORE:**
|
|
457
|
+
|
|
458
|
+
```vue
|
|
459
|
+
<VcTable :columns="columns" :items="items">
|
|
460
|
+
<template #empty>
|
|
461
|
+
<div class="empty-state">
|
|
462
|
+
<VcIcon icon="lucide-package-open" size="xl" />
|
|
463
|
+
<p>{{ $t("MY_MODULE.PAGES.LIST.EMPTY_TITLE") }}</p>
|
|
464
|
+
<VcButton @click="onAdd">
|
|
465
|
+
{{ $t("MY_MODULE.PAGES.LIST.ADD_BUTTON") }}
|
|
466
|
+
</VcButton>
|
|
467
|
+
</div>
|
|
468
|
+
</template>
|
|
469
|
+
<template #notfound>
|
|
470
|
+
<div class="not-found-state">
|
|
471
|
+
<VcIcon icon="lucide-search-x" size="xl" />
|
|
472
|
+
<p>{{ $t("MY_MODULE.PAGES.LIST.NOT_FOUND") }}</p>
|
|
473
|
+
<VcButton @click="onResetSearch">
|
|
474
|
+
{{ $t("MY_MODULE.PAGES.LIST.RESET_SEARCH") }}
|
|
475
|
+
</VcButton>
|
|
476
|
+
</div>
|
|
477
|
+
</template>
|
|
478
|
+
</VcTable>
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
**AFTER:**
|
|
482
|
+
|
|
483
|
+
```vue
|
|
484
|
+
<VcDataTable
|
|
485
|
+
:items="items"
|
|
486
|
+
:empty-state="{
|
|
487
|
+
icon: 'lucide-package-open',
|
|
488
|
+
title: $t('MY_MODULE.PAGES.LIST.EMPTY_TITLE'),
|
|
489
|
+
actionLabel: $t('MY_MODULE.PAGES.LIST.ADD_BUTTON'),
|
|
490
|
+
actionHandler: onAdd,
|
|
491
|
+
}"
|
|
492
|
+
:not-found-state="{
|
|
493
|
+
icon: 'lucide-search-x',
|
|
494
|
+
title: $t('MY_MODULE.PAGES.LIST.NOT_FOUND'),
|
|
495
|
+
actionLabel: $t('MY_MODULE.PAGES.LIST.RESET_SEARCH'),
|
|
496
|
+
actionHandler: onResetSearch,
|
|
497
|
+
}"
|
|
498
|
+
/>
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
Key points:
|
|
502
|
+
|
|
503
|
+
- Remove `#empty` and `#notfound` template slots entirely.
|
|
504
|
+
- Remove dedicated empty/not-found `.vue` components if they only rendered an icon, title, and action button.
|
|
505
|
+
- `:empty-state` shows when there are no items at all (no search active). Props: `icon`, `title`, `actionLabel`, `actionHandler`.
|
|
506
|
+
- `:not-found-state` shows when a search is active but returned no results. Same props as `:empty-state`. If omitted, VcDataTable uses a default not-found message.
|
|
507
|
+
- Both are **separate props** — do NOT combine them into one.
|
|
508
|
+
|
|
509
|
+
## RULE 7: Filters — Template Slot → global-filters Prop
|
|
510
|
+
|
|
511
|
+
This is the most complex transformation. Replace the `#filters` template slot (with staged filter state, apply/reset buttons, and checkbox loops) with a declarative `global-filters` prop and `@filter` event.
|
|
512
|
+
|
|
513
|
+
**DO NOT SKIP THIS RULE even if filter types look exotic.** Real apps often have date-range pickers, multi-select dropdowns, and enum selects alongside checkboxes. Each of these maps to a `GlobalFilterConfig` entry with an appropriate `filter` shape and `options`. If a single filter type does not fit any documented shape, map the other filters and flag the one holdout in your report — **do NOT delete the whole filter panel.** "Removed to get the build green" is not an acceptable resolution: the user loses UX. If in doubt, keep the existing `<template #filters>` slot unchanged and mark the file as needing manual migration in the report, rather than erasing user-visible functionality.
|
|
514
|
+
|
|
515
|
+
### Filter shapes cheat-sheet
|
|
516
|
+
|
|
517
|
+
Use these exact shapes in `GlobalFilterConfig[]` (annotate the `computed<GlobalFilterConfig[]>(...)` to satisfy strict typing):
|
|
518
|
+
|
|
519
|
+
```ts
|
|
520
|
+
// Single-select (checkbox group → dropdown)
|
|
521
|
+
{ id: "type", label: t("..."), filter: { options: [{ value, label }, ...] } }
|
|
522
|
+
|
|
523
|
+
// Multi-select — same shape + multiple: true
|
|
524
|
+
{ id: "tags", label: t("..."), filter: { options: [...], multiple: true } }
|
|
525
|
+
|
|
526
|
+
// Date range — TWO output keys in event.filters (not a nested object)
|
|
527
|
+
{ id: "dueDate", label: t("..."), filter: { range: ["startDate", "endDate"] } }
|
|
528
|
+
|
|
529
|
+
// Free-text (column search already covers this, but for completeness)
|
|
530
|
+
{ id: "keyword", label: t("..."), filter: true }
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### `@filter` handler — no manual Date→string coercion
|
|
534
|
+
|
|
535
|
+
VcDataTable's `GlobalFiltersPanel` internally normalises date-picker values to `"YYYY-MM-DD"` (local components, no TZ shift). Handler receives `event.filters: Record<string, unknown>` with string values:
|
|
536
|
+
|
|
537
|
+
```ts
|
|
538
|
+
async function onFilter(event: { filters: Record<string, unknown> }) {
|
|
539
|
+
currentFilters.value = {
|
|
540
|
+
type: event.filters.type as string | undefined,
|
|
541
|
+
priority: event.filters.priority as string | undefined,
|
|
542
|
+
startDate: event.filters.startDate as string | undefined, // already "YYYY-MM-DD"
|
|
543
|
+
endDate: event.filters.endDate as string | undefined,
|
|
544
|
+
};
|
|
545
|
+
await reload();
|
|
546
|
+
}
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
Do NOT call `.toISOString()` on values from `event.filters` — the panel already hands back plain strings, and `.toISOString()` on a local-midnight Date would introduce an off-by-one-day TZ shift.
|
|
550
|
+
|
|
551
|
+
### Apply / Reset buttons are built-in
|
|
552
|
+
|
|
553
|
+
The old `#filters` slot typically had staged/applied state plus Apply/Reset buttons. VcDataTable's built-in panel ships its own Apply/Clear footer — **delete** `stagedFilters` / `appliedFilters` / `activeFilterCount` / `toggleFilter` / `applyFilters` / `resetFilters`. Keep a single `currentFilters` ref (populated from `@filter`) used by `reload()` / search — that's it.
|
|
554
|
+
|
|
555
|
+
**BEFORE:**
|
|
556
|
+
|
|
557
|
+
```typescript
|
|
558
|
+
// --- Staged filter state ---
|
|
559
|
+
const statuses = ref([
|
|
560
|
+
{ value: "New", displayValue: "New" },
|
|
561
|
+
{ value: "Processing", displayValue: "Processing" },
|
|
562
|
+
{ value: "Completed", displayValue: "Completed" },
|
|
563
|
+
]);
|
|
564
|
+
|
|
565
|
+
const stagedFilters = reactive({
|
|
566
|
+
status: [] as string[],
|
|
567
|
+
});
|
|
568
|
+
const appliedFilters = reactive({
|
|
569
|
+
status: [] as string[],
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
const hasFilterChanges = computed(() => JSON.stringify(stagedFilters) !== JSON.stringify(appliedFilters));
|
|
573
|
+
const hasFiltersApplied = computed(() => appliedFilters.status.length > 0);
|
|
574
|
+
const activeFilterCount = computed(() => (hasFiltersApplied.value ? appliedFilters.status.length : 0));
|
|
575
|
+
|
|
576
|
+
function toggleFilter(group: "status", value: string) {
|
|
577
|
+
const idx = stagedFilters[group].indexOf(value);
|
|
578
|
+
if (idx > -1) {
|
|
579
|
+
stagedFilters[group].splice(idx, 1);
|
|
580
|
+
} else {
|
|
581
|
+
stagedFilters[group].push(value);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
async function applyFilters() {
|
|
586
|
+
Object.assign(appliedFilters, JSON.parse(JSON.stringify(stagedFilters)));
|
|
587
|
+
await load({ statuses: appliedFilters.status, skip: 0 });
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function resetFilters() {
|
|
591
|
+
stagedFilters.status = [];
|
|
592
|
+
appliedFilters.status = [];
|
|
593
|
+
load({ statuses: [], skip: 0 });
|
|
594
|
+
}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
```vue
|
|
598
|
+
<VcTable :columns="columns" :items="items" :active-filter-count="activeFilterCount">
|
|
599
|
+
<template #filters>
|
|
600
|
+
<div class="filters-panel">
|
|
601
|
+
<h4>{{ $t("MY_MODULE.PAGES.LIST.FILTERS.STATUS") }}</h4>
|
|
602
|
+
<VcCheckbox
|
|
603
|
+
v-for="status in statuses"
|
|
604
|
+
:key="status.value"
|
|
605
|
+
:model-value="stagedFilters.status.includes(status.value)"
|
|
606
|
+
@update:model-value="toggleFilter('status', status.value)"
|
|
607
|
+
>
|
|
608
|
+
{{ status.displayValue }}
|
|
609
|
+
</VcCheckbox>
|
|
610
|
+
<div class="filters-actions">
|
|
611
|
+
<VcButton
|
|
612
|
+
variant="primary"
|
|
613
|
+
:disabled="!hasFilterChanges"
|
|
614
|
+
@click="applyFilters"
|
|
615
|
+
>
|
|
616
|
+
{{ $t("MY_MODULE.PAGES.LIST.FILTERS.APPLY") }}
|
|
617
|
+
</VcButton>
|
|
618
|
+
<VcButton
|
|
619
|
+
variant="outline"
|
|
620
|
+
:disabled="!hasFiltersApplied"
|
|
621
|
+
@click="resetFilters"
|
|
622
|
+
>
|
|
623
|
+
{{ $t("MY_MODULE.PAGES.LIST.FILTERS.RESET") }}
|
|
624
|
+
</VcButton>
|
|
625
|
+
</div>
|
|
626
|
+
</div>
|
|
627
|
+
</template>
|
|
628
|
+
</VcTable>
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
**AFTER:**
|
|
632
|
+
|
|
633
|
+
```typescript
|
|
634
|
+
const statuses = ref([
|
|
635
|
+
{ value: "New", displayValue: "New" },
|
|
636
|
+
{ value: "Processing", displayValue: "Processing" },
|
|
637
|
+
{ value: "Completed", displayValue: "Completed" },
|
|
638
|
+
]);
|
|
639
|
+
|
|
640
|
+
const computedGlobalFilters = computed(() => [
|
|
641
|
+
{
|
|
642
|
+
id: "status",
|
|
643
|
+
label: t("MY_MODULE.PAGES.LIST.FILTERS.STATUS"),
|
|
644
|
+
filter: {
|
|
645
|
+
options: statuses.value.map((s) => ({
|
|
646
|
+
value: s.value ?? "",
|
|
647
|
+
label: s.displayValue ?? "",
|
|
648
|
+
})),
|
|
649
|
+
multiple: true,
|
|
650
|
+
},
|
|
651
|
+
},
|
|
652
|
+
]);
|
|
653
|
+
|
|
654
|
+
async function onFilter(event: { filters: Record<string, unknown> }) {
|
|
655
|
+
const statusFilter = event.filters.status as string[] | undefined;
|
|
656
|
+
await load({ statuses: statusFilter ?? [], skip: 0 });
|
|
657
|
+
}
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
```vue
|
|
661
|
+
<VcDataTable :items="items" :global-filters="computedGlobalFilters" @filter="onFilter">
|
|
662
|
+
<!-- VcColumn children here -->
|
|
663
|
+
</VcDataTable>
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
Remove all of the following explicitly:
|
|
667
|
+
|
|
668
|
+
- `stagedFilters` reactive object
|
|
669
|
+
- `appliedFilters` reactive object
|
|
670
|
+
- `hasFilterChanges` computed
|
|
671
|
+
- `hasFiltersApplied` computed
|
|
672
|
+
- `activeFilterCount` computed
|
|
673
|
+
- `toggleFilter` function
|
|
674
|
+
- `applyFilters` function
|
|
675
|
+
- `resetFilters` function
|
|
676
|
+
- `:active-filter-count` prop on the table component
|
|
677
|
+
- `#filters` template slot and all its contents (checkboxes, apply/reset buttons)
|
|
678
|
+
- Any filter-related CSS classes (`.filters-panel`, `.filters-actions`, etc.)
|
|
679
|
+
|
|
680
|
+
Key points:
|
|
681
|
+
|
|
682
|
+
- The `global-filters` prop accepts an array of filter descriptors. Each descriptor has `id`, `label`, and a `filter` object with `options` and `multiple`.
|
|
683
|
+
- VcDataTable renders the filter UI internally — no manual checkbox loops or apply/reset buttons.
|
|
684
|
+
- The `@filter` event fires when the user applies filters. The `event.filters` object is keyed by filter `id`, with values matching the selected option values.
|
|
685
|
+
- The active filter count badge is managed internally by VcDataTable — no need for a manual `activeFilterCount` computed.
|
|
686
|
+
|
|
687
|
+
## RULE 8: Pagination — Manual Calculation → useDataTablePagination
|
|
688
|
+
|
|
689
|
+
Replace manual `pages`/`currentPage` computed properties and `@pagination-click` handlers with the `useDataTablePagination` composable. The composable manages skip/page math internally and returns a reactive pagination object that VcDataTable consumes directly.
|
|
690
|
+
|
|
691
|
+
**BEFORE (composable):**
|
|
692
|
+
|
|
693
|
+
```typescript
|
|
694
|
+
import { computed, ref } from "vue";
|
|
695
|
+
import { useApiClient, useAsync, useLoading } from "@vc-shell/framework";
|
|
696
|
+
|
|
697
|
+
export function useMyList(options?: { pageSize?: number }) {
|
|
698
|
+
const pageSize = options?.pageSize || 20;
|
|
699
|
+
const searchQuery = ref({ take: pageSize, skip: 0, sort: "createdDate:desc" });
|
|
700
|
+
const searchResult = ref();
|
|
701
|
+
|
|
702
|
+
const { action: loadItems, loading } = useAsync(async (query) => {
|
|
703
|
+
searchQuery.value = { ...searchQuery.value, ...query };
|
|
704
|
+
searchResult.value = await (await getApiClient()).search(searchQuery.value);
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
return {
|
|
708
|
+
items: computed(() => searchResult.value?.results || []),
|
|
709
|
+
totalCount: computed(() => searchResult.value?.totalCount || 0),
|
|
710
|
+
pages: computed(() => Math.ceil((searchResult.value?.totalCount || 1) / pageSize)),
|
|
711
|
+
currentPage: computed(() => Math.ceil((searchQuery.value.skip || 0) / pageSize + 1)),
|
|
712
|
+
searchQuery,
|
|
713
|
+
loadItems,
|
|
714
|
+
loading,
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
**AFTER (composable):**
|
|
720
|
+
|
|
721
|
+
```typescript
|
|
722
|
+
import { computed, ref } from "vue";
|
|
723
|
+
import { useApiClient, useAsync, useLoading, useDataTablePagination, type UseDataTablePaginationReturn } from "@vc-shell/framework";
|
|
724
|
+
|
|
725
|
+
export function useMyList(options?: { pageSize?: number }) {
|
|
726
|
+
const pageSize = options?.pageSize || 20;
|
|
727
|
+
const searchQuery = ref({ take: pageSize, skip: 0, sort: "createdDate:desc" });
|
|
728
|
+
const searchResult = ref();
|
|
729
|
+
|
|
730
|
+
const { action: loadItems, loading } = useAsync(async (query) => {
|
|
731
|
+
searchQuery.value = { ...searchQuery.value, ...query };
|
|
732
|
+
searchResult.value = await (await getApiClient()).search(searchQuery.value);
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
const pagination = useDataTablePagination({
|
|
736
|
+
pageSize,
|
|
737
|
+
totalCount: computed(() => searchResult.value?.totalCount ?? 0),
|
|
738
|
+
onPageChange: ({ skip }) => loadItems({ ...searchQuery.value, skip }),
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
return {
|
|
742
|
+
items: computed(() => searchResult.value?.results || []),
|
|
743
|
+
pagination,
|
|
744
|
+
searchQuery,
|
|
745
|
+
loadItems,
|
|
746
|
+
loading,
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
**BEFORE (blade page):**
|
|
752
|
+
|
|
753
|
+
```vue
|
|
754
|
+
<VcDataTable :pagination="{ currentPage, pages }" @pagination-click="onPaginationClick" />
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
```typescript
|
|
758
|
+
const { items, totalCount, pages, currentPage, loadItems, loading } = useMyList();
|
|
759
|
+
|
|
760
|
+
const onPaginationClick = async (page: number) => {
|
|
761
|
+
await loadItems({ ...searchQuery.value, skip: (page - 1) * 20 });
|
|
762
|
+
};
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
**AFTER (blade page):**
|
|
766
|
+
|
|
767
|
+
```vue
|
|
768
|
+
<VcDataTable :pagination="pagination" :total-count="pagination.totalCount" @pagination-click="pagination.goToPage" />
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
```typescript
|
|
772
|
+
const { items, pagination, loadItems, loading } = useMyList();
|
|
773
|
+
// No manual onPaginationClick — pagination.goToPage handles it
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
**What to remove:**
|
|
777
|
+
|
|
778
|
+
- `totalCount`, `pages`, `currentPage` computed from composable return
|
|
779
|
+
- Manual `onPaginationClick` function in blade pages
|
|
780
|
+
- Manual `skip` calculation `(page - 1) * pageSize`
|
|
781
|
+
|
|
782
|
+
**What to add:**
|
|
783
|
+
|
|
784
|
+
- `useDataTablePagination` import in composable
|
|
785
|
+
- `pagination` object in composable return
|
|
786
|
+
- `:pagination="pagination"` and `@pagination-click="pagination.goToPage"` in template
|
|
787
|
+
|
|
788
|
+
## Verification
|
|
789
|
+
|
|
790
|
+
After migration:
|
|
791
|
+
|
|
792
|
+
1. Run `vue-tsc --noEmit` (or `npx tsc --noEmit`) to verify no TypeScript errors
|
|
793
|
+
2. Confirm the table renders with correct columns, data, and layout
|
|
794
|
+
3. Confirm sorting works: click a sortable column header, verify the sort indicator toggles, verify the data reloads with the correct sort expression
|
|
795
|
+
4. Confirm selection works: check rows, verify `localSelection` updates, verify toolbar buttons respond to selection state
|
|
796
|
+
5. Confirm filters work: open the filter panel, select filter options, apply, verify the data reloads with correct filter parameters, verify the active filter badge shows
|
|
797
|
+
6. Confirm the empty state shows when there are no items (correct icon, title, and action button)
|
|
798
|
+
7. Confirm row click navigates correctly: click a row, verify the correct blade opens with the correct parameters (remember the `{ data }` wrapper)
|
|
799
|
+
8. Confirm pull-to-refresh works if enabled
|
|
800
|
+
9. Confirm pagination works: navigate between pages, verify data reloads
|
|
801
|
+
10. Confirm no stale imports remain: `useTableSort`, `ITableColumns`, `columns` ref, staged/applied filter state, `@header-click`, `@selection-changed`, `@item-click`, `#filters`, `#empty`, `#notfound`
|