@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,446 @@
|
|
|
1
|
+
# Detail Page Pattern
|
|
2
|
+
|
|
3
|
+
Display comprehensive information about a single entity with related data, cards, and navigation.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
9
|
+
│ AdminLayout │
|
|
10
|
+
├─────────────────────────────────────────────────────────────┤
|
|
11
|
+
│ Page Header (entity title + breadcrumbs) │
|
|
12
|
+
├─────────────────────────────────────────────────────────────┤
|
|
13
|
+
│ Back Button + Action Dropdown │
|
|
14
|
+
├─────────────────────────────────────────────────────────────┤
|
|
15
|
+
│ Card Grid (1-3 columns) │
|
|
16
|
+
│ ├── Info Card 1 (Guest Info, Primary Data) │
|
|
17
|
+
│ ├── Info Card 2 (Summary, Secondary Data) │
|
|
18
|
+
│ └── Info Card 3 (Status, Payment, etc.) │
|
|
19
|
+
├─────────────────────────────────────────────────────────────┤
|
|
20
|
+
│ Related Data Section │
|
|
21
|
+
│ ├── Principal/Owner Card │
|
|
22
|
+
│ └── Related Items (companions, reservations) │
|
|
23
|
+
├─────────────────────────────────────────────────────────────┤
|
|
24
|
+
│ Timeline / Activity Log │
|
|
25
|
+
└─────────────────────────────────────────────────────────────┘
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## CRM Template Reference
|
|
29
|
+
|
|
30
|
+
Based on: `ui/src/components/Templates/CRM/BookingDetails.vue`
|
|
31
|
+
|
|
32
|
+
```vue
|
|
33
|
+
<!-- CRM Template Structure (raw @hotelinking/ui) -->
|
|
34
|
+
<uiWrapper :sidebar :topbar>
|
|
35
|
+
<uiViewHeader :pages="pages" :title="`Booking ID: ${bookingData.bookingId}`" />
|
|
36
|
+
|
|
37
|
+
<!-- Back + Actions -->
|
|
38
|
+
<div class="flex justify-between items-center mb-4">
|
|
39
|
+
<uiButton :icon="ArrowLeftIcon" @click="handleBack">Back to Bookings</uiButton>
|
|
40
|
+
<uiDropdown :items="contactOptions" @optionSelected="handleOption" />
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<!-- Card Grid -->
|
|
44
|
+
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
|
|
45
|
+
<!-- Guest Information Card -->
|
|
46
|
+
<div class="bg-white rounded-lg border border-gray-200 shadow">
|
|
47
|
+
<div class="px-6 py-5 border-b border-gray-200">
|
|
48
|
+
<h3 class="text-lg font-bold">Guest Information</h3>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="px-6 py-5 space-y-8">
|
|
51
|
+
<div class="flex items-start gap-3">
|
|
52
|
+
<EnvelopeIcon class="h-4 w-4 text-gray-400" />
|
|
53
|
+
<div>
|
|
54
|
+
<div class="text-sm font-medium text-gray-500">Email</div>
|
|
55
|
+
<div class="text-sm text-gray-900">{{ data.email }}</div>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
<!-- More fields... -->
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
<!-- More cards... -->
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<!-- Timeline -->
|
|
65
|
+
<uiTimeline :timeline="timelineData" />
|
|
66
|
+
</uiWrapper>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Enhanced Pattern (with @htlkg/*)
|
|
70
|
+
|
|
71
|
+
### Astro Page
|
|
72
|
+
|
|
73
|
+
```astro
|
|
74
|
+
---
|
|
75
|
+
// src/pages/[brandId]/admin/booking/[bookingId].astro
|
|
76
|
+
import Layout from '@/layouts/Layout.astro';
|
|
77
|
+
import BookingDetails from '@/components/admin/BookingDetails.vue';
|
|
78
|
+
import { getBooking } from '@/services/booking';
|
|
79
|
+
|
|
80
|
+
export const prerender = false;
|
|
81
|
+
|
|
82
|
+
const { brandId, bookingId } = Astro.params;
|
|
83
|
+
const booking = await getBooking(bookingId);
|
|
84
|
+
|
|
85
|
+
if (!booking) {
|
|
86
|
+
return Astro.redirect('/404');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const layoutProps = {
|
|
90
|
+
title: `Booking ${booking.id}`,
|
|
91
|
+
breadcrumbs: [
|
|
92
|
+
{ name: 'Bookings', href: `/${brandId}/admin/bookings` },
|
|
93
|
+
{ name: booking.id, current: true },
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
<Layout {...layoutProps}>
|
|
99
|
+
<BookingDetails
|
|
100
|
+
client:load
|
|
101
|
+
booking={booking}
|
|
102
|
+
brandId={brandId}
|
|
103
|
+
/>
|
|
104
|
+
</Layout>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Vue Component
|
|
108
|
+
|
|
109
|
+
```vue
|
|
110
|
+
<!-- src/components/admin/BookingDetails.vue -->
|
|
111
|
+
<script setup lang="ts">
|
|
112
|
+
import { computed } from 'vue';
|
|
113
|
+
import { Modal } from '@htlkg/components/overlays';
|
|
114
|
+
import { useModal, useConfirmation, useNotifications } from '@htlkg/components/composables';
|
|
115
|
+
import { uiButton, uiDropdown, uiTag, uiTimeline } from '@hotelinking/ui';
|
|
116
|
+
import {
|
|
117
|
+
ArrowLeftIcon,
|
|
118
|
+
EnvelopeIcon,
|
|
119
|
+
PhoneIcon,
|
|
120
|
+
MapPinIcon,
|
|
121
|
+
CalendarIcon,
|
|
122
|
+
CreditCardIcon,
|
|
123
|
+
UserIcon,
|
|
124
|
+
UsersIcon,
|
|
125
|
+
} from '@heroicons/vue/24/outline';
|
|
126
|
+
|
|
127
|
+
interface Props {
|
|
128
|
+
booking: Booking;
|
|
129
|
+
brandId: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const props = defineProps<Props>();
|
|
133
|
+
const emit = defineEmits<{
|
|
134
|
+
back: [];
|
|
135
|
+
edit: [booking: Booking];
|
|
136
|
+
}>();
|
|
137
|
+
|
|
138
|
+
// Modal for editing guest
|
|
139
|
+
const editModal = useModal<Guest>();
|
|
140
|
+
|
|
141
|
+
// Confirmation for cancel booking
|
|
142
|
+
const confirmation = useConfirmation();
|
|
143
|
+
const { notify } = useNotifications();
|
|
144
|
+
|
|
145
|
+
// Action dropdown items
|
|
146
|
+
const actionItems = [
|
|
147
|
+
{ name: 'Contact guest via:', value: '0', active: true },
|
|
148
|
+
{ name: 'Email', value: 'email' },
|
|
149
|
+
{ name: 'Phone', value: 'phone' },
|
|
150
|
+
{ name: 'SMS', value: 'sms' },
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
// Timeline data
|
|
154
|
+
const timelineData = computed(() => ({
|
|
155
|
+
id: 'booking-timeline',
|
|
156
|
+
footerText: 'View all events',
|
|
157
|
+
items: props.booking.timeline.map((event, index) => ({
|
|
158
|
+
id: index + 1,
|
|
159
|
+
content: event.description,
|
|
160
|
+
date: formatDate(event.date),
|
|
161
|
+
datetime: event.date,
|
|
162
|
+
icon: event.icon,
|
|
163
|
+
iconBackground: 'default',
|
|
164
|
+
})),
|
|
165
|
+
}));
|
|
166
|
+
|
|
167
|
+
// Handlers
|
|
168
|
+
function handleBack() {
|
|
169
|
+
emit('back');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function handleAction(item: { value: string }) {
|
|
173
|
+
if (item.value === 'email') {
|
|
174
|
+
window.location.href = `mailto:${props.booking.guest.email}`;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function handleCancelBooking() {
|
|
179
|
+
const confirmed = await confirmation.confirmDestructive('Cancel Booking');
|
|
180
|
+
if (confirmed) {
|
|
181
|
+
try {
|
|
182
|
+
await cancelBooking(props.booking.id);
|
|
183
|
+
notify({ type: 'success', message: 'Booking cancelled' });
|
|
184
|
+
} catch (error) {
|
|
185
|
+
notify({ type: 'error', message: 'Failed to cancel booking' });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function formatDate(dateString: string): string {
|
|
191
|
+
return new Date(dateString).toLocaleDateString('en-US', {
|
|
192
|
+
year: 'numeric',
|
|
193
|
+
month: 'short',
|
|
194
|
+
day: 'numeric',
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function getInitials(name: string): string {
|
|
199
|
+
return name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
|
|
200
|
+
}
|
|
201
|
+
</script>
|
|
202
|
+
|
|
203
|
+
<template>
|
|
204
|
+
<!-- Back Button + Actions -->
|
|
205
|
+
<div class="flex justify-between items-center mb-4">
|
|
206
|
+
<uiButton :icon="ArrowLeftIcon" @click="handleBack">
|
|
207
|
+
Back to Bookings
|
|
208
|
+
</uiButton>
|
|
209
|
+
<uiDropdown
|
|
210
|
+
:items="actionItems"
|
|
211
|
+
position="right"
|
|
212
|
+
@optionSelected="handleAction"
|
|
213
|
+
/>
|
|
214
|
+
</div>
|
|
215
|
+
|
|
216
|
+
<!-- Card Grid -->
|
|
217
|
+
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
|
|
218
|
+
<!-- Guest Information Card -->
|
|
219
|
+
<div class="bg-white rounded-lg border border-gray-200 shadow overflow-hidden">
|
|
220
|
+
<div class="px-6 py-5 border-b border-gray-200">
|
|
221
|
+
<div class="flex items-center gap-2 mb-2">
|
|
222
|
+
<UserIcon class="h-5 w-5 text-gray-400" />
|
|
223
|
+
<h3 class="text-lg font-bold text-gray-900">Guest Information</h3>
|
|
224
|
+
</div>
|
|
225
|
+
<p class="text-sm text-gray-500">
|
|
226
|
+
{{ booking.guest.name }} • {{ booking.guest.memberStatus }}
|
|
227
|
+
</p>
|
|
228
|
+
</div>
|
|
229
|
+
<div class="px-6 py-5 space-y-6">
|
|
230
|
+
<!-- Email -->
|
|
231
|
+
<div class="flex items-start gap-3">
|
|
232
|
+
<EnvelopeIcon class="h-4 w-4 text-gray-400 mt-0.5" />
|
|
233
|
+
<div>
|
|
234
|
+
<div class="text-sm font-medium text-gray-500">Email</div>
|
|
235
|
+
<div class="text-sm text-gray-900">{{ booking.guest.email }}</div>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
<!-- Phone -->
|
|
239
|
+
<div class="flex items-start gap-3">
|
|
240
|
+
<PhoneIcon class="h-4 w-4 text-gray-400 mt-0.5" />
|
|
241
|
+
<div>
|
|
242
|
+
<div class="text-sm font-medium text-gray-500">Phone</div>
|
|
243
|
+
<div class="text-sm text-gray-900">{{ booking.guest.phone }}</div>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
<!-- Address -->
|
|
247
|
+
<div class="flex items-start gap-3">
|
|
248
|
+
<MapPinIcon class="h-4 w-4 text-gray-400 mt-0.5" />
|
|
249
|
+
<div>
|
|
250
|
+
<div class="text-sm font-medium text-gray-500">Address</div>
|
|
251
|
+
<div class="text-sm text-gray-900">{{ booking.guest.address }}</div>
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
<!-- Preferences Tags -->
|
|
255
|
+
<div class="pt-4 border-t border-gray-200">
|
|
256
|
+
<div class="text-sm font-medium text-gray-500 mb-3">Preferences</div>
|
|
257
|
+
<div class="flex flex-wrap gap-2">
|
|
258
|
+
<uiTag
|
|
259
|
+
v-for="pref in booking.guest.preferences"
|
|
260
|
+
:key="pref"
|
|
261
|
+
color="gray"
|
|
262
|
+
size="small"
|
|
263
|
+
>
|
|
264
|
+
{{ pref }}
|
|
265
|
+
</uiTag>
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<!-- Booking Summary Card -->
|
|
272
|
+
<div class="bg-white rounded-lg border border-gray-200 shadow overflow-hidden">
|
|
273
|
+
<div class="px-6 py-5 border-b border-gray-200">
|
|
274
|
+
<div class="flex items-center gap-2">
|
|
275
|
+
<CalendarIcon class="h-5 w-5 text-gray-400" />
|
|
276
|
+
<h3 class="text-lg font-bold text-gray-900">Booking Summary</h3>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
<div class="px-6 py-5 space-y-4">
|
|
280
|
+
<div class="flex items-start gap-3">
|
|
281
|
+
<CalendarIcon class="h-4 w-4 text-gray-400 mt-0.5" />
|
|
282
|
+
<div>
|
|
283
|
+
<div class="text-sm font-medium text-gray-500">Check-in</div>
|
|
284
|
+
<div class="text-sm text-gray-900">{{ formatDate(booking.checkin) }}</div>
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
<div class="flex items-start gap-3">
|
|
288
|
+
<CalendarIcon class="h-4 w-4 text-gray-400 mt-0.5" />
|
|
289
|
+
<div>
|
|
290
|
+
<div class="text-sm font-medium text-gray-500">Check-out</div>
|
|
291
|
+
<div class="text-sm text-gray-900">{{ formatDate(booking.checkout) }}</div>
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
<div class="flex items-start gap-3">
|
|
295
|
+
<CalendarIcon class="h-4 w-4 text-gray-400 mt-0.5" />
|
|
296
|
+
<div>
|
|
297
|
+
<div class="text-sm font-medium text-gray-500">Booking Date</div>
|
|
298
|
+
<div class="text-sm text-gray-900">{{ formatDate(booking.createdAt) }}</div>
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
|
|
304
|
+
<!-- Payment Details Card -->
|
|
305
|
+
<div class="bg-white rounded-lg border border-gray-200 shadow overflow-hidden">
|
|
306
|
+
<div class="px-6 py-5 border-b border-gray-200">
|
|
307
|
+
<div class="flex items-center gap-2">
|
|
308
|
+
<CreditCardIcon class="h-5 w-5 text-gray-400" />
|
|
309
|
+
<h3 class="text-lg font-bold text-gray-900">Payment Details</h3>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
<div class="px-6 py-5 space-y-4">
|
|
313
|
+
<div class="flex items-start gap-3">
|
|
314
|
+
<CreditCardIcon class="h-4 w-4 text-gray-400 mt-0.5" />
|
|
315
|
+
<div>
|
|
316
|
+
<div class="text-sm font-medium text-gray-500">Amount</div>
|
|
317
|
+
<div class="text-sm text-gray-900">{{ booking.payment.amount }}</div>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
<div class="flex items-start gap-3">
|
|
321
|
+
<div class="h-4 w-4" /> <!-- Spacer -->
|
|
322
|
+
<div>
|
|
323
|
+
<div class="text-sm font-medium text-gray-500">Status</div>
|
|
324
|
+
<div class="mt-1">
|
|
325
|
+
<uiTag :color="booking.payment.status === 'Paid' ? 'green' : 'yellow'" size="small">
|
|
326
|
+
{{ booking.payment.status }}
|
|
327
|
+
</uiTag>
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
|
|
335
|
+
<!-- Companions Section -->
|
|
336
|
+
<div class="bg-white rounded-lg border border-gray-200 shadow overflow-hidden mb-8">
|
|
337
|
+
<div class="px-6 py-5 border-b border-gray-200">
|
|
338
|
+
<div class="flex items-center gap-2">
|
|
339
|
+
<UsersIcon class="h-5 w-5 text-gray-400" />
|
|
340
|
+
<h3 class="text-lg font-bold text-gray-900">Booking Companions</h3>
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
<div class="px-6 py-5 space-y-4">
|
|
344
|
+
<div
|
|
345
|
+
v-for="companion in booking.companions"
|
|
346
|
+
:key="companion.email"
|
|
347
|
+
class="flex items-center justify-between"
|
|
348
|
+
>
|
|
349
|
+
<div class="flex items-center gap-4">
|
|
350
|
+
<div class="w-10 h-10 rounded-full bg-gray-200 flex items-center justify-center">
|
|
351
|
+
<span class="text-sm font-medium text-gray-600">
|
|
352
|
+
{{ getInitials(companion.name) }}
|
|
353
|
+
</span>
|
|
354
|
+
</div>
|
|
355
|
+
<div>
|
|
356
|
+
<div class="font-semibold text-gray-900">{{ companion.name }}</div>
|
|
357
|
+
<div class="text-sm text-gray-500">{{ companion.email }}</div>
|
|
358
|
+
</div>
|
|
359
|
+
</div>
|
|
360
|
+
<uiTag color="gray" size="small">{{ companion.relationship }}</uiTag>
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|
|
364
|
+
|
|
365
|
+
<!-- Timeline -->
|
|
366
|
+
<uiTimeline
|
|
367
|
+
name="Timeline"
|
|
368
|
+
:timeline="timelineData"
|
|
369
|
+
@timeline-event-clicked="(id) => console.log('Event clicked:', id)"
|
|
370
|
+
@timeline-footer-clicked="() => console.log('View all events')"
|
|
371
|
+
/>
|
|
372
|
+
|
|
373
|
+
<!-- Edit Guest Modal -->
|
|
374
|
+
<Modal
|
|
375
|
+
:open="editModal.isOpen.value"
|
|
376
|
+
:title="`Edit ${editModal.data.value?.name || 'Guest'}`"
|
|
377
|
+
@close="editModal.close"
|
|
378
|
+
>
|
|
379
|
+
<!-- Edit form here -->
|
|
380
|
+
</Modal>
|
|
381
|
+
</template>
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## Key Patterns
|
|
385
|
+
|
|
386
|
+
### Info Card Structure
|
|
387
|
+
|
|
388
|
+
```vue
|
|
389
|
+
<div class="bg-white rounded-lg border border-gray-200 shadow overflow-hidden">
|
|
390
|
+
<!-- Header with icon and title -->
|
|
391
|
+
<div class="px-6 py-5 border-b border-gray-200">
|
|
392
|
+
<div class="flex items-center gap-2 mb-2">
|
|
393
|
+
<IconComponent class="h-5 w-5 text-gray-400" />
|
|
394
|
+
<h3 class="text-lg font-bold text-gray-900">Card Title</h3>
|
|
395
|
+
</div>
|
|
396
|
+
<p class="text-sm text-gray-500">Subtitle or description</p>
|
|
397
|
+
</div>
|
|
398
|
+
|
|
399
|
+
<!-- Content with icon-labeled fields -->
|
|
400
|
+
<div class="px-6 py-5 space-y-6">
|
|
401
|
+
<div class="flex items-start gap-3">
|
|
402
|
+
<FieldIcon class="h-4 w-4 text-gray-400 mt-0.5" />
|
|
403
|
+
<div>
|
|
404
|
+
<div class="text-sm font-medium text-gray-500">Label</div>
|
|
405
|
+
<div class="text-sm text-gray-900">Value</div>
|
|
406
|
+
</div>
|
|
407
|
+
</div>
|
|
408
|
+
<!-- More fields... -->
|
|
409
|
+
</div>
|
|
410
|
+
</div>
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Grid Layouts
|
|
414
|
+
|
|
415
|
+
```vue
|
|
416
|
+
<!-- 3-column on desktop, 1 on mobile -->
|
|
417
|
+
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
418
|
+
|
|
419
|
+
<!-- 2-column on desktop -->
|
|
420
|
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Back Navigation
|
|
424
|
+
|
|
425
|
+
```vue
|
|
426
|
+
<div class="flex justify-between items-center mb-4">
|
|
427
|
+
<uiButton :icon="ArrowLeftIcon" @click="emit('back')">
|
|
428
|
+
Back to List
|
|
429
|
+
</uiButton>
|
|
430
|
+
<uiDropdown :items="actions" @optionSelected="handleAction" />
|
|
431
|
+
</div>
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## Checklist
|
|
435
|
+
|
|
436
|
+
- [ ] Astro page with entity data fetch
|
|
437
|
+
- [ ] Vue component with card-based layout
|
|
438
|
+
- [ ] Back button navigation
|
|
439
|
+
- [ ] Action dropdown for entity operations
|
|
440
|
+
- [ ] Info cards with icon-labeled fields (3-column grid)
|
|
441
|
+
- [ ] Tags for status, preferences, etc.
|
|
442
|
+
- [ ] Related data sections (companions, related items)
|
|
443
|
+
- [ ] Timeline/activity log
|
|
444
|
+
- [ ] `useModal` for edit dialogs
|
|
445
|
+
- [ ] `useConfirmation` for destructive actions
|
|
446
|
+
- [ ] Loading/skeleton states
|