@htlkg/components 0.0.11 → 0.0.13
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/{AdminWrapper.vue_vue_type_script_setup_true_lang-B32IylcT.js → AdminWrapper.vue_vue_type_script_setup_true_lang-BhnWQ-b0.js} +26 -29
- package/dist/AdminWrapper.vue_vue_type_script_setup_true_lang-BhnWQ-b0.js.map +1 -0
- package/dist/Alert.vue_vue_type_script_setup_true_lang-DxPCS-Hx.js.map +1 -1
- package/dist/DateRange.vue_vue_type_script_setup_true_lang-BLVg1Hah.js.map +1 -1
- package/dist/ProductBadge.vue_vue_type_script_setup_true_lang-Cmr2f4Cy.js.map +1 -1
- package/dist/components.css +4 -4
- package/dist/composables/index.js +23 -22
- package/dist/data/index.js +10 -10
- package/dist/{filterHelpers-DgRyoYSa.js → filterHelpers-DpHSlTuh.js} +11 -11
- package/dist/filterHelpers-DpHSlTuh.js.map +1 -0
- package/dist/index-QK97OdqQ.js.map +1 -1
- package/dist/index.js +34 -33
- package/dist/navigation/index.js +1 -1
- package/dist/{useAdminPage-GhgXp0x8.js → useAdminPage-AgWRvw6o.js} +150 -26
- package/dist/useAdminPage-AgWRvw6o.js.map +1 -0
- package/package.json +3 -3
- package/src/composables/index.ts +1 -0
- package/src/composables/useJsonForm.test.ts +272 -0
- package/src/composables/useJsonForm.ts +261 -0
- package/src/composables/useModal.test.ts +264 -0
- package/src/composables/useModal.ts +54 -8
- package/src/data/Chart/index.ts +2 -0
- package/src/data/DataList/index.ts +1 -0
- package/src/data/{DataTable.vue → DataTable/DataTable.vue} +2 -2
- package/src/data/DataTable/index.ts +8 -0
- package/src/data/SearchableSelect/index.ts +1 -0
- package/src/data/Table/index.ts +1 -0
- package/src/data/index.ts +5 -15
- package/src/domain/BrandCard/index.ts +1 -0
- package/src/domain/BrandSelector/index.ts +1 -0
- package/src/domain/ProductBadge/index.ts +1 -0
- package/src/domain/UserAvatar/index.ts +1 -0
- package/src/domain/index.ts +4 -4
- package/src/forms/DateRange/index.ts +2 -0
- package/src/forms/JsonSchemaForm/index.ts +1 -0
- package/src/forms/index.ts +2 -3
- package/src/navigation/{AdminWrapper.vue → AdminWrapper/AdminWrapper.vue} +41 -30
- package/src/navigation/AdminWrapper/index.ts +1 -0
- package/src/navigation/Breadcrumbs/index.ts +1 -0
- package/src/navigation/Stepper/index.ts +2 -0
- package/src/navigation/Tabs/index.ts +2 -0
- package/src/navigation/index.ts +4 -6
- package/src/overlays/Alert/index.ts +1 -0
- package/src/overlays/Drawer/index.ts +1 -0
- package/src/overlays/Modal/index.ts +1 -0
- package/src/overlays/Notification/index.ts +1 -0
- package/src/overlays/index.ts +4 -4
- package/src/patterns/DASHBOARD_PAGE.md +642 -0
- package/src/patterns/DETAIL_PAGE.md +446 -0
- package/src/patterns/FORM_PAGE.md +439 -0
- package/src/patterns/LIST_PAGE.md +340 -0
- package/src/patterns/PAGE_PATTERNS.md +110 -0
- package/src/patterns/WIZARD_PAGE.md +733 -0
- package/dist/AdminWrapper.vue_vue_type_script_setup_true_lang-B32IylcT.js.map +0 -1
- package/dist/filterHelpers-DgRyoYSa.js.map +0 -1
- package/dist/useAdminPage-GhgXp0x8.js.map +0 -1
- package/src/data/Table.vue +0 -295
- /package/src/data/{Chart.demo.vue → Chart/Chart.demo.vue} +0 -0
- /package/src/data/{Chart.md → Chart/Chart.md} +0 -0
- /package/src/data/{Chart.vue → Chart/Chart.vue} +0 -0
- /package/src/data/{DataList.md → DataList/DataList.md} +0 -0
- /package/src/data/{DataList.test.ts → DataList/DataList.test.ts} +0 -0
- /package/src/data/{DataList.vue → DataList/DataList.vue} +0 -0
- /package/src/data/{SearchableSelect.md → SearchableSelect/SearchableSelect.md} +0 -0
- /package/src/data/{SearchableSelect.vue → SearchableSelect/SearchableSelect.vue} +0 -0
- /package/src/data/{Table.demo.vue → Table/Table.demo.vue} +0 -0
- /package/src/data/{Table.md → Table/Table.md} +0 -0
- /package/src/data/{Table.property.test.ts → Table/Table.property.test.ts} +0 -0
- /package/src/data/{Table.test.ts → Table/Table.test.ts} +0 -0
- /package/src/data/{Table.unit.test.ts → Table/Table.unit.test.ts} +0 -0
- /package/src/domain/{BrandCard.md → BrandCard/BrandCard.md} +0 -0
- /package/src/domain/{BrandCard.vue → BrandCard/BrandCard.vue} +0 -0
- /package/src/domain/{BrandSelector.md → BrandSelector/BrandSelector.md} +0 -0
- /package/src/domain/{BrandSelector.vue → BrandSelector/BrandSelector.vue} +0 -0
- /package/src/domain/{ProductBadge.md → ProductBadge/ProductBadge.md} +0 -0
- /package/src/domain/{ProductBadge.vue → ProductBadge/ProductBadge.vue} +0 -0
- /package/src/domain/{UserAvatar.md → UserAvatar/UserAvatar.md} +0 -0
- /package/src/domain/{UserAvatar.vue → UserAvatar/UserAvatar.vue} +0 -0
- /package/src/forms/{DateRange.demo.vue → DateRange/DateRange.demo.vue} +0 -0
- /package/src/forms/{DateRange.md → DateRange/DateRange.md} +0 -0
- /package/src/forms/{DateRange.vue → DateRange/DateRange.vue} +0 -0
- /package/src/forms/{JsonSchemaForm.demo.vue → JsonSchemaForm/JsonSchemaForm.demo.vue} +0 -0
- /package/src/forms/{JsonSchemaForm.md → JsonSchemaForm/JsonSchemaForm.md} +0 -0
- /package/src/forms/{JsonSchemaForm.property.test.ts → JsonSchemaForm/JsonSchemaForm.property.test.ts} +0 -0
- /package/src/forms/{JsonSchemaForm.test.ts → JsonSchemaForm/JsonSchemaForm.test.ts} +0 -0
- /package/src/forms/{JsonSchemaForm.unit.test.ts → JsonSchemaForm/JsonSchemaForm.unit.test.ts} +0 -0
- /package/src/forms/{JsonSchemaForm.vue → JsonSchemaForm/JsonSchemaForm.vue} +0 -0
- /package/src/navigation/{Breadcrumbs.demo.vue → Breadcrumbs/Breadcrumbs.demo.vue} +0 -0
- /package/src/navigation/{Breadcrumbs.md → Breadcrumbs/Breadcrumbs.md} +0 -0
- /package/src/navigation/{Breadcrumbs.test.ts → Breadcrumbs/Breadcrumbs.test.ts} +0 -0
- /package/src/navigation/{Breadcrumbs.vue → Breadcrumbs/Breadcrumbs.vue} +0 -0
- /package/src/navigation/{Stepper.demo.vue → Stepper/Stepper.demo.vue} +0 -0
- /package/src/navigation/{Stepper.md → Stepper/Stepper.md} +0 -0
- /package/src/navigation/{Stepper.vue → Stepper/Stepper.vue} +0 -0
- /package/src/navigation/{Tabs.demo.vue → Tabs/Tabs.demo.vue} +0 -0
- /package/src/navigation/{Tabs.md → Tabs/Tabs.md} +0 -0
- /package/src/navigation/{Tabs.test.ts → Tabs/Tabs.test.ts} +0 -0
- /package/src/navigation/{Tabs.vue → Tabs/Tabs.vue} +0 -0
- /package/src/overlays/{Alert.demo.vue → Alert/Alert.demo.vue} +0 -0
- /package/src/overlays/{Alert.md → Alert/Alert.md} +0 -0
- /package/src/overlays/{Alert.test.ts → Alert/Alert.test.ts} +0 -0
- /package/src/overlays/{Alert.vue → Alert/Alert.vue} +0 -0
- /package/src/overlays/{Drawer.md → Drawer/Drawer.md} +0 -0
- /package/src/overlays/{Drawer.test.ts → Drawer/Drawer.test.ts} +0 -0
- /package/src/overlays/{Drawer.vue → Drawer/Drawer.vue} +0 -0
- /package/src/overlays/{Modal.demo.vue → Modal/Modal.demo.vue} +0 -0
- /package/src/overlays/{Modal.md → Modal/Modal.md} +0 -0
- /package/src/overlays/{Modal.test.ts → Modal/Modal.test.ts} +0 -0
- /package/src/overlays/{Modal.vue → Modal/Modal.vue} +0 -0
- /package/src/overlays/{Notification.md → Notification/Notification.md} +0 -0
- /package/src/overlays/{Notification.test.ts → Notification/Notification.test.ts} +0 -0
- /package/src/overlays/{Notification.vue → Notification/Notification.vue} +0 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# List Page Pattern
|
|
2
|
+
|
|
3
|
+
Display collections of data with filtering, sorting, pagination, and bulk actions.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
9
|
+
│ AdminLayout │
|
|
10
|
+
├─────────────────────────────────────────────────────────────┤
|
|
11
|
+
│ Page Header (title + breadcrumbs) │
|
|
12
|
+
├─────────────────────────────────────────────────────────────┤
|
|
13
|
+
│ Optional: Tabs (for multiple views) │
|
|
14
|
+
├─────────────────────────────────────────────────────────────┤
|
|
15
|
+
│ Optional: Stats Grid (summary metrics) │
|
|
16
|
+
├─────────────────────────────────────────────────────────────┤
|
|
17
|
+
│ DataTable │
|
|
18
|
+
│ ├── Smart Filters │
|
|
19
|
+
│ ├── Column Headers (sortable) │
|
|
20
|
+
│ ├── Data Rows (selectable) │
|
|
21
|
+
│ ├── Row Actions │
|
|
22
|
+
│ └── Pagination │
|
|
23
|
+
├─────────────────────────────────────────────────────────────┤
|
|
24
|
+
│ Optional: Modal (for row actions) │
|
|
25
|
+
└─────────────────────────────────────────────────────────────┘
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## CRM Template Reference
|
|
29
|
+
|
|
30
|
+
Based on: `ui/src/components/Templates/CRM/ContactsAll.vue`
|
|
31
|
+
|
|
32
|
+
```vue
|
|
33
|
+
<!-- CRM Template Structure (raw @hotelinking/ui) -->
|
|
34
|
+
<uiWrapper :sidebar :topbar>
|
|
35
|
+
<uiViewHeader :pages="pages" title="Contacts" />
|
|
36
|
+
<uiTabs :tabs="tabs" @tabClicked="handleTabClick" />
|
|
37
|
+
|
|
38
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
|
|
39
|
+
<uiStats v-for="stat in stats" :item="stat" />
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<uiTable
|
|
43
|
+
:header="tableHeader"
|
|
44
|
+
:items="tableItems"
|
|
45
|
+
:smartFilterCategories="smartFilterCategories"
|
|
46
|
+
:paginationCurrent="paginationCurrent"
|
|
47
|
+
@smartFiltersSent="handleSmartFilters"
|
|
48
|
+
@changePage="handlePageChange"
|
|
49
|
+
/>
|
|
50
|
+
</uiWrapper>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Enhanced Pattern (with @htlkg/*)
|
|
54
|
+
|
|
55
|
+
### Astro Page
|
|
56
|
+
|
|
57
|
+
```astro
|
|
58
|
+
---
|
|
59
|
+
// src/pages/admin/contacts.astro
|
|
60
|
+
import Layout from '@/layouts/Layout.astro';
|
|
61
|
+
import ContactsView from '@/components/admin/ContactsView.vue';
|
|
62
|
+
import { createListPage } from '@htlkg/astro';
|
|
63
|
+
import { $contacts } from '@/stores/contacts';
|
|
64
|
+
|
|
65
|
+
export const prerender = false;
|
|
66
|
+
|
|
67
|
+
const { layoutProps, initialState, items } = await createListPage(Astro, {
|
|
68
|
+
title: 'Contacts',
|
|
69
|
+
pageId: 'contacts',
|
|
70
|
+
fetchFn: async () => {
|
|
71
|
+
// Fetch contacts from API or database
|
|
72
|
+
return { data: await getContacts() };
|
|
73
|
+
},
|
|
74
|
+
store: $contacts,
|
|
75
|
+
filterableFields: [
|
|
76
|
+
{ key: 'name', type: 'text', graphql: false },
|
|
77
|
+
{ key: 'email', type: 'text', graphql: false },
|
|
78
|
+
{ key: 'status', type: 'select', graphql: false, options: [
|
|
79
|
+
{ id: 'active', name: 'Active' },
|
|
80
|
+
{ id: 'inactive', name: 'Inactive' },
|
|
81
|
+
]},
|
|
82
|
+
],
|
|
83
|
+
searchableFields: ['name', 'email'],
|
|
84
|
+
defaultSort: { key: 'name', order: 'asc' },
|
|
85
|
+
defaultPageSize: 25,
|
|
86
|
+
});
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
<Layout {...layoutProps}>
|
|
90
|
+
<ContactsView client:load initialState={initialState} />
|
|
91
|
+
</Layout>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Vue Component
|
|
95
|
+
|
|
96
|
+
```vue
|
|
97
|
+
<!-- src/components/admin/ContactsView.vue -->
|
|
98
|
+
<script setup lang="ts">
|
|
99
|
+
import { computed, ref } from 'vue';
|
|
100
|
+
import { useStore } from '@nanostores/vue';
|
|
101
|
+
import { DataTable, columns } from '@htlkg/components/data';
|
|
102
|
+
import { Modal } from '@htlkg/components/overlays';
|
|
103
|
+
import { useTable, useModal, useTabs } from '@htlkg/components/composables';
|
|
104
|
+
import { $contacts } from '@/stores/contacts';
|
|
105
|
+
|
|
106
|
+
interface Props {
|
|
107
|
+
initialState: {
|
|
108
|
+
pageSize: number;
|
|
109
|
+
sortKey: string;
|
|
110
|
+
sortOrder: 'asc' | 'desc';
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const props = defineProps<Props>();
|
|
115
|
+
|
|
116
|
+
// Data from store
|
|
117
|
+
const contacts = useStore($contacts);
|
|
118
|
+
|
|
119
|
+
// Tab state
|
|
120
|
+
const { activeTab, tabs, setActiveTab } = useTabs({
|
|
121
|
+
tabs: [
|
|
122
|
+
{ id: 'all', label: 'All Contacts' },
|
|
123
|
+
{ id: 'tags', label: 'Tags' },
|
|
124
|
+
{ id: 'segments', label: 'Segments' },
|
|
125
|
+
],
|
|
126
|
+
defaultTab: 'all',
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Table state
|
|
130
|
+
const {
|
|
131
|
+
items,
|
|
132
|
+
currentPage,
|
|
133
|
+
pageSize,
|
|
134
|
+
totalPages,
|
|
135
|
+
sortKey,
|
|
136
|
+
sortOrder,
|
|
137
|
+
goToPage,
|
|
138
|
+
setPageSize,
|
|
139
|
+
setSortBy,
|
|
140
|
+
setFilter,
|
|
141
|
+
} = useTable({
|
|
142
|
+
items: contacts,
|
|
143
|
+
pageSize: props.initialState.pageSize,
|
|
144
|
+
defaultSort: {
|
|
145
|
+
key: props.initialState.sortKey,
|
|
146
|
+
order: props.initialState.sortOrder,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Modal for contact details
|
|
151
|
+
const contactModal = useModal<Contact>();
|
|
152
|
+
|
|
153
|
+
// Table columns
|
|
154
|
+
const tableColumns = computed(() => [
|
|
155
|
+
columns.link('name', 'Name', {
|
|
156
|
+
onClick: (row) => contactModal.open(row),
|
|
157
|
+
}),
|
|
158
|
+
columns.text('email', 'Email'),
|
|
159
|
+
columns.text('phone', 'Phone'),
|
|
160
|
+
columns.text('country', 'Country'),
|
|
161
|
+
columns.tag('status', 'Status', {
|
|
162
|
+
colorMap: {
|
|
163
|
+
active: 'green',
|
|
164
|
+
inactive: 'gray',
|
|
165
|
+
vip: 'yellow',
|
|
166
|
+
},
|
|
167
|
+
}),
|
|
168
|
+
columns.tag('loyaltyTier', 'Loyalty Tier'),
|
|
169
|
+
columns.text('tags', 'Tags'),
|
|
170
|
+
]);
|
|
171
|
+
|
|
172
|
+
// Stats for header
|
|
173
|
+
const stats = computed(() => [
|
|
174
|
+
{
|
|
175
|
+
id: 'total',
|
|
176
|
+
name: 'Total Contacts',
|
|
177
|
+
stat: contacts.value.length.toLocaleString(),
|
|
178
|
+
icon: 'UsersIcon',
|
|
179
|
+
color: 'green',
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: 'new',
|
|
183
|
+
name: 'New This Month',
|
|
184
|
+
stat: '1.2K',
|
|
185
|
+
icon: 'PlusIcon',
|
|
186
|
+
color: 'green',
|
|
187
|
+
},
|
|
188
|
+
]);
|
|
189
|
+
|
|
190
|
+
// Row actions
|
|
191
|
+
const rowActions = [
|
|
192
|
+
{ name: 'Edit', id: 'edit' },
|
|
193
|
+
{ name: 'Delete', id: 'delete' },
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
function handleRowAction(action: string, items: number[]) {
|
|
197
|
+
if (action === 'delete') {
|
|
198
|
+
// Handle delete
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
</script>
|
|
202
|
+
|
|
203
|
+
<template>
|
|
204
|
+
<!-- Tabs -->
|
|
205
|
+
<div class="mb-6">
|
|
206
|
+
<nav class="flex space-x-4">
|
|
207
|
+
<button
|
|
208
|
+
v-for="tab in tabs"
|
|
209
|
+
:key="tab.id"
|
|
210
|
+
:class="[
|
|
211
|
+
'px-3 py-2 text-sm font-medium rounded-md',
|
|
212
|
+
activeTab === tab.id
|
|
213
|
+
? 'bg-gray-100 text-gray-900'
|
|
214
|
+
: 'text-gray-500 hover:text-gray-700'
|
|
215
|
+
]"
|
|
216
|
+
@click="setActiveTab(tab.id)"
|
|
217
|
+
>
|
|
218
|
+
{{ tab.label }}
|
|
219
|
+
</button>
|
|
220
|
+
</nav>
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
<!-- Stats Grid -->
|
|
224
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
|
|
225
|
+
<div
|
|
226
|
+
v-for="stat in stats"
|
|
227
|
+
:key="stat.id"
|
|
228
|
+
class="bg-white rounded-lg border border-gray-200 p-6"
|
|
229
|
+
>
|
|
230
|
+
<p class="text-sm font-medium text-gray-500">{{ stat.name }}</p>
|
|
231
|
+
<p class="mt-1 text-3xl font-semibold text-gray-900">{{ stat.stat }}</p>
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
<!-- Data Table -->
|
|
236
|
+
<DataTable
|
|
237
|
+
:columns="tableColumns"
|
|
238
|
+
:items="items"
|
|
239
|
+
:current-page="currentPage"
|
|
240
|
+
:page-size="pageSize"
|
|
241
|
+
:total-pages="totalPages"
|
|
242
|
+
:sort-key="sortKey"
|
|
243
|
+
:sort-order="sortOrder"
|
|
244
|
+
:actions="rowActions"
|
|
245
|
+
@page-change="goToPage"
|
|
246
|
+
@page-size-change="setPageSize"
|
|
247
|
+
@sort-change="setSortBy"
|
|
248
|
+
@action="handleRowAction"
|
|
249
|
+
/>
|
|
250
|
+
|
|
251
|
+
<!-- Contact Detail Modal -->
|
|
252
|
+
<Modal
|
|
253
|
+
:open="contactModal.isOpen.value"
|
|
254
|
+
:title="contactModal.data.value?.name || 'Contact Details'"
|
|
255
|
+
@close="contactModal.close"
|
|
256
|
+
>
|
|
257
|
+
<div v-if="contactModal.data.value" class="space-y-4">
|
|
258
|
+
<p><strong>Email:</strong> {{ contactModal.data.value.email }}</p>
|
|
259
|
+
<p><strong>Phone:</strong> {{ contactModal.data.value.phone }}</p>
|
|
260
|
+
<p><strong>Status:</strong> {{ contactModal.data.value.status }}</p>
|
|
261
|
+
</div>
|
|
262
|
+
</Modal>
|
|
263
|
+
</template>
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Key Components
|
|
267
|
+
|
|
268
|
+
### DataTable
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
import { DataTable, columns } from '@htlkg/components/data';
|
|
272
|
+
|
|
273
|
+
// Column helpers
|
|
274
|
+
columns.text('field', 'Label') // Plain text
|
|
275
|
+
columns.link('field', 'Label', options) // Clickable link
|
|
276
|
+
columns.tag('field', 'Label', options) // Tag/badge
|
|
277
|
+
columns.date('field', 'Label', format) // Formatted date
|
|
278
|
+
columns.currency('field', 'Label') // Currency formatting
|
|
279
|
+
columns.custom('field', 'Label', render) // Custom render function
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### useTable Composable
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
import { useTable } from '@htlkg/components/composables';
|
|
286
|
+
|
|
287
|
+
const {
|
|
288
|
+
items, // Paginated, sorted, filtered items
|
|
289
|
+
currentPage, // Current page number
|
|
290
|
+
pageSize, // Items per page
|
|
291
|
+
totalPages, // Total page count
|
|
292
|
+
totalItems, // Total item count
|
|
293
|
+
sortKey, // Current sort field
|
|
294
|
+
sortOrder, // 'asc' | 'desc'
|
|
295
|
+
filters, // Active filters
|
|
296
|
+
goToPage, // Navigate to page
|
|
297
|
+
setPageSize, // Change page size
|
|
298
|
+
setSortBy, // Change sort
|
|
299
|
+
setFilter, // Apply filter
|
|
300
|
+
clearFilters, // Clear all filters
|
|
301
|
+
} = useTable(options);
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Smart Filters
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
const filterableFields = [
|
|
308
|
+
{
|
|
309
|
+
key: 'name',
|
|
310
|
+
type: 'text', // Text input filter
|
|
311
|
+
placeholder: 'Search by name...',
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
key: 'status',
|
|
315
|
+
type: 'select', // Dropdown filter
|
|
316
|
+
options: [
|
|
317
|
+
{ id: 'active', name: 'Active' },
|
|
318
|
+
{ id: 'inactive', name: 'Inactive' },
|
|
319
|
+
],
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
key: 'createdAt',
|
|
323
|
+
type: 'date', // Date range filter
|
|
324
|
+
},
|
|
325
|
+
];
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Checklist
|
|
329
|
+
|
|
330
|
+
- [ ] Astro page with `createListPage` factory
|
|
331
|
+
- [ ] Vue component with `DataTable`
|
|
332
|
+
- [ ] `useTable` composable for state
|
|
333
|
+
- [ ] Column definitions with helpers
|
|
334
|
+
- [ ] Smart filters for searchable fields
|
|
335
|
+
- [ ] Pagination controls
|
|
336
|
+
- [ ] Sort by column headers
|
|
337
|
+
- [ ] Row actions (edit, delete, etc.)
|
|
338
|
+
- [ ] Optional: Tabs for multiple views
|
|
339
|
+
- [ ] Optional: Stats grid at top
|
|
340
|
+
- [ ] Optional: Modal for row details
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Page Patterns Overview
|
|
2
|
+
|
|
3
|
+
This documentation describes the standard page patterns used in Hotelinking applications. These patterns are based on the CRM template examples in `@hotelinking/ui` but enhanced to use `@htlkg/*` shared packages.
|
|
4
|
+
|
|
5
|
+
## Pattern Selection Guide
|
|
6
|
+
|
|
7
|
+
| Pattern | Use When | Example |
|
|
8
|
+
|---------|----------|---------|
|
|
9
|
+
| **List Page** | Displaying collections of data with filtering, sorting, pagination | Contacts, Campaigns, Bookings |
|
|
10
|
+
| **Detail Page** | Showing comprehensive info about a single entity | Booking Details, Contact Profile |
|
|
11
|
+
| **Form Page** | Creating or editing entity data | Settings, Brand Configuration |
|
|
12
|
+
| **Wizard Page** | Multi-step data entry with progress indicator | Campaign Creation, Onboarding |
|
|
13
|
+
| **Dashboard Page** | Displaying metrics, KPIs, and analytics | Overview, Analytics |
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
19
|
+
│ Astro Page (.astro) │
|
|
20
|
+
│ - Server-side data fetching │
|
|
21
|
+
│ - Pass data to Vue islands │
|
|
22
|
+
└─────────────────────────────────────────────────────────────┘
|
|
23
|
+
│
|
|
24
|
+
▼
|
|
25
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
26
|
+
│ @htlkg/astro Layout (AdminLayout, BrandLayout) │
|
|
27
|
+
│ - Sidebar navigation │
|
|
28
|
+
│ - Topbar with user menu │
|
|
29
|
+
│ - Page structure │
|
|
30
|
+
└─────────────────────────────────────────────────────────────┘
|
|
31
|
+
│
|
|
32
|
+
▼
|
|
33
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
34
|
+
│ Vue Component (client:load) │
|
|
35
|
+
│ - Interactive UI │
|
|
36
|
+
│ - Uses @htlkg/components │
|
|
37
|
+
│ - Uses composables for state │
|
|
38
|
+
└─────────────────────────────────────────────────────────────┘
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## CRM to @htlkg/* Mapping
|
|
42
|
+
|
|
43
|
+
| CRM Template Component | @htlkg/* Replacement |
|
|
44
|
+
|-----------------------|----------------------|
|
|
45
|
+
| `uiWrapper` | `AdminLayout` from `@htlkg/astro/layouts` |
|
|
46
|
+
| `uiViewHeader` | Built into layout or `PageHeader` component |
|
|
47
|
+
| `uiTable` | `DataTable` from `@htlkg/components/data` + `useTable` |
|
|
48
|
+
| `uiModal` | `Modal` from `@htlkg/components/overlays` + `useModal` |
|
|
49
|
+
| `uiTabs` | `Tabs` from `@htlkg/components/navigation` + `useTabs` |
|
|
50
|
+
| `uiStats` | Stats pattern with grid layout |
|
|
51
|
+
| Manual form state | `JsonSchemaForm` + `useJsonForm` composable |
|
|
52
|
+
| `uiStepsV4` | `Stepper` from `@htlkg/components/navigation` + `useWizard` |
|
|
53
|
+
|
|
54
|
+
## Key Composables
|
|
55
|
+
|
|
56
|
+
| Composable | Purpose | Import |
|
|
57
|
+
|------------|---------|--------|
|
|
58
|
+
| `useTable` | Table state (sort, filter, pagination) | `@htlkg/components/composables` |
|
|
59
|
+
| `useModal` | Modal visibility and data context | `@htlkg/components/composables` |
|
|
60
|
+
| `useJsonForm` | Form state with isDirty, confirmOnLeave | `@htlkg/components/composables` |
|
|
61
|
+
| `useConfirmation` | Delete/reset confirmation dialogs | `@htlkg/components/composables` |
|
|
62
|
+
| `useTabs` | Tab navigation state | `@htlkg/components/composables` |
|
|
63
|
+
| `useWizard` | Multi-step wizard navigation | `@htlkg/components/composables` |
|
|
64
|
+
| `useNotifications` | Toast notifications | `@htlkg/components/composables` |
|
|
65
|
+
|
|
66
|
+
## Pattern Documentation
|
|
67
|
+
|
|
68
|
+
- [LIST_PAGE.md](./LIST_PAGE.md) - List/table page pattern
|
|
69
|
+
- [DETAIL_PAGE.md](./DETAIL_PAGE.md) - Detail page pattern
|
|
70
|
+
- [FORM_PAGE.md](./FORM_PAGE.md) - Form page pattern
|
|
71
|
+
- [WIZARD_PAGE.md](./WIZARD_PAGE.md) - Multi-step wizard pattern
|
|
72
|
+
- [DASHBOARD_PAGE.md](./DASHBOARD_PAGE.md) - Dashboard page pattern
|
|
73
|
+
|
|
74
|
+
## Common Structure
|
|
75
|
+
|
|
76
|
+
All patterns follow this basic structure:
|
|
77
|
+
|
|
78
|
+
```astro
|
|
79
|
+
---
|
|
80
|
+
// 1. Imports
|
|
81
|
+
import Layout from '@/layouts/Layout.astro';
|
|
82
|
+
import MyComponent from '@/components/MyComponent.vue';
|
|
83
|
+
|
|
84
|
+
// 2. Server-side data fetching
|
|
85
|
+
const data = await fetchData();
|
|
86
|
+
|
|
87
|
+
// 3. Page metadata
|
|
88
|
+
const title = 'Page Title';
|
|
89
|
+
const breadcrumbs = [
|
|
90
|
+
{ name: 'Home', href: '/' },
|
|
91
|
+
{ name: 'Current', current: true }
|
|
92
|
+
];
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
<!-- 4. Layout wrapper -->
|
|
96
|
+
<Layout title={title} breadcrumbs={breadcrumbs}>
|
|
97
|
+
<!-- 5. Vue island for interactivity -->
|
|
98
|
+
<MyComponent client:load initialData={data} />
|
|
99
|
+
</Layout>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Best Practices
|
|
103
|
+
|
|
104
|
+
1. **Data Fetching**: Always fetch data server-side in Astro frontmatter
|
|
105
|
+
2. **Hydration**: Use `client:load` for interactive components
|
|
106
|
+
3. **State**: Use composables from `@htlkg/components/composables`
|
|
107
|
+
4. **Forms**: Use `JsonSchemaForm` for all forms except very simple ones
|
|
108
|
+
5. **Modals**: Use `useModal` composable with data context
|
|
109
|
+
6. **Tables**: Use `DataTable` with `useTable` for consistent behavior
|
|
110
|
+
7. **Validation**: Let JsonSchemaForm handle validation via JSON Schema
|