includio-cms 0.36.8 → 0.37.0

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.
Files changed (259) hide show
  1. package/API.md +179 -3
  2. package/CHANGELOG.md +64 -0
  3. package/DOCS.md +28 -3
  4. package/ROADMAP.md +18 -0
  5. package/dist/admin/client/booking/booking-dashboard.svelte +40 -0
  6. package/dist/admin/client/booking/booking-dashboard.svelte.d.ts +3 -0
  7. package/dist/admin/client/booking/customer-panel.svelte +102 -0
  8. package/dist/admin/client/booking/customer-panel.svelte.d.ts +11 -0
  9. package/dist/admin/client/booking/customer-view.d.ts +52 -0
  10. package/dist/admin/client/booking/customer-view.js +134 -0
  11. package/dist/admin/client/booking/participant-data-panel.svelte +330 -0
  12. package/dist/admin/client/booking/participant-data-panel.svelte.d.ts +22 -0
  13. package/dist/admin/client/booking/reservation-detail-page.svelte +637 -0
  14. package/dist/admin/client/booking/reservation-detail-page.svelte.d.ts +3 -0
  15. package/dist/admin/client/booking/reservations-page.svelte +221 -0
  16. package/dist/admin/client/booking/reservations-page.svelte.d.ts +3 -0
  17. package/dist/admin/client/collection/collection-entries.svelte +21 -2
  18. package/dist/admin/client/collection/entry-link.svelte +6 -4
  19. package/dist/admin/client/collection/entry-link.svelte.d.ts +3 -1
  20. package/dist/admin/client/collection/row-actions.svelte +12 -4
  21. package/dist/admin/client/collection/row-actions.svelte.d.ts +1 -0
  22. package/dist/admin/client/collection/status-badge.svelte +39 -3
  23. package/dist/admin/client/collection/status-badge.svelte.d.ts +5 -3
  24. package/dist/admin/client/entry/entry.svelte +26 -9
  25. package/dist/admin/client/entry/hybrid/hybrid-preview.svelte +73 -10
  26. package/dist/admin/client/entry/hybrid/hybrid-preview.svelte.d.ts +6 -5
  27. package/dist/admin/client/entry/preview-views-context.svelte.d.ts +22 -0
  28. package/dist/admin/client/entry/preview-views-context.svelte.js +58 -0
  29. package/dist/admin/client/entry/preview-views.d.ts +27 -0
  30. package/dist/admin/client/entry/preview-views.js +33 -0
  31. package/dist/admin/client/index.d.ts +3 -0
  32. package/dist/admin/client/index.js +3 -0
  33. package/dist/admin/client/modules/dashboard-registry.d.ts +7 -0
  34. package/dist/admin/client/modules/dashboard-registry.js +11 -0
  35. package/dist/admin/client/modules/module-dashboard-page.svelte +26 -0
  36. package/dist/admin/client/modules/module-dashboard-page.svelte.d.ts +6 -0
  37. package/dist/admin/client/modules/stat-card.svelte +22 -0
  38. package/dist/admin/client/modules/stat-card.svelte.d.ts +10 -0
  39. package/dist/admin/client/shop/shop-dashboard.svelte +29 -0
  40. package/dist/admin/client/shop/shop-dashboard.svelte.d.ts +3 -0
  41. package/dist/admin/components/fields/relation-field.svelte +11 -2
  42. package/dist/admin/components/icons/icon-map.js +8 -0
  43. package/dist/admin/components/layout/app-sidebar.svelte +45 -14
  44. package/dist/admin/components/layout/context-switcher.svelte +90 -0
  45. package/dist/admin/components/layout/context-switcher.svelte.d.ts +3 -0
  46. package/dist/admin/components/layout/lang.d.ts +4 -0
  47. package/dist/admin/components/layout/lang.js +8 -0
  48. package/dist/admin/components/layout/layout-renderer.svelte +2 -1
  49. package/dist/admin/components/layout/layout-tabs.svelte +14 -0
  50. package/dist/admin/components/layout/layout-tabs.svelte.d.ts +2 -0
  51. package/dist/admin/components/layout/nav-collections.svelte +16 -9
  52. package/dist/admin/components/layout/nav-collections.svelte.d.ts +5 -1
  53. package/dist/admin/components/layout/nav-global.svelte +45 -0
  54. package/dist/admin/components/layout/nav-global.svelte.d.ts +3 -0
  55. package/dist/admin/components/layout/nav-main.svelte +8 -32
  56. package/dist/admin/components/layout/nav-main.svelte.d.ts +6 -1
  57. package/dist/admin/components/layout/nav-module.svelte +57 -0
  58. package/dist/admin/components/layout/nav-module.svelte.d.ts +7 -0
  59. package/dist/admin/components/layout/nav-section.svelte +1 -1
  60. package/dist/admin/components/layout/nav-section.svelte.d.ts +1 -1
  61. package/dist/admin/components/layout/nav-singletons.svelte +14 -7
  62. package/dist/admin/components/layout/nav-singletons.svelte.d.ts +5 -1
  63. package/dist/admin/remote/booking.remote.d.ts +244 -0
  64. package/dist/admin/remote/booking.remote.js +283 -0
  65. package/dist/admin/remote/entry.remote.d.ts +3 -0
  66. package/dist/admin/remote/entry.remote.js +14 -3
  67. package/dist/admin/remote/index.d.ts +2 -0
  68. package/dist/admin/remote/index.js +2 -0
  69. package/dist/admin/remote/modules.remote.d.ts +10 -0
  70. package/dist/admin/remote/modules.remote.js +14 -0
  71. package/dist/admin/state/active-context-resolve.d.ts +37 -0
  72. package/dist/admin/state/active-context-resolve.js +66 -0
  73. package/dist/admin/state/active-context.svelte.d.ts +8 -0
  74. package/dist/admin/state/active-context.svelte.js +27 -0
  75. package/dist/booking/adjustments.d.ts +33 -0
  76. package/dist/booking/adjustments.js +26 -0
  77. package/dist/booking/capacity.d.ts +12 -0
  78. package/dist/booking/capacity.js +10 -0
  79. package/dist/booking/client/index.d.ts +39 -0
  80. package/dist/booking/client/index.js +44 -0
  81. package/dist/booking/client/use-booking.svelte.d.ts +25 -0
  82. package/dist/booking/client/use-booking.svelte.js +56 -0
  83. package/dist/booking/config.d.ts +156 -0
  84. package/dist/booking/config.js +36 -0
  85. package/dist/booking/draft.d.ts +77 -0
  86. package/dist/booking/draft.js +67 -0
  87. package/dist/booking/expiry-logic.d.ts +30 -0
  88. package/dist/booking/expiry-logic.js +33 -0
  89. package/dist/booking/index.d.ts +18 -0
  90. package/dist/booking/index.js +52 -0
  91. package/dist/booking/ledger.d.ts +46 -0
  92. package/dist/booking/ledger.js +60 -0
  93. package/dist/booking/number.d.ts +2 -0
  94. package/dist/booking/number.js +11 -0
  95. package/dist/booking/participant-fields/access.d.ts +10 -0
  96. package/dist/booking/participant-fields/access.js +14 -0
  97. package/dist/booking/participant-fields/age.d.ts +12 -0
  98. package/dist/booking/participant-fields/age.js +28 -0
  99. package/dist/booking/participant-fields/completeness.d.ts +8 -0
  100. package/dist/booking/participant-fields/completeness.js +13 -0
  101. package/dist/booking/participant-fields/group.d.ts +6 -0
  102. package/dist/booking/participant-fields/group.js +15 -0
  103. package/dist/booking/participant-fields/index.d.ts +6 -0
  104. package/dist/booking/participant-fields/index.js +6 -0
  105. package/dist/booking/participant-fields/types.d.ts +50 -0
  106. package/dist/booking/participant-fields/types.js +5 -0
  107. package/dist/booking/participant-fields/validate.d.ts +7 -0
  108. package/dist/booking/participant-fields/validate.js +44 -0
  109. package/dist/booking/participant-pricing.d.ts +93 -0
  110. package/dist/booking/participant-pricing.js +91 -0
  111. package/dist/booking/participants-view.d.ts +32 -0
  112. package/dist/booking/participants-view.js +22 -0
  113. package/dist/booking/payment-context.d.ts +19 -0
  114. package/dist/booking/payment-context.js +29 -0
  115. package/dist/booking/pricing-inputs.d.ts +28 -0
  116. package/dist/booking/pricing-inputs.js +35 -0
  117. package/dist/booking/pricing.d.ts +86 -0
  118. package/dist/booking/pricing.js +125 -0
  119. package/dist/booking/recompute.d.ts +22 -0
  120. package/dist/booking/recompute.js +31 -0
  121. package/dist/booking/reminders-schedule.d.ts +11 -0
  122. package/dist/booking/reminders-schedule.js +28 -0
  123. package/dist/booking/server/adjustments.d.ts +27 -0
  124. package/dist/booking/server/adjustments.js +136 -0
  125. package/dist/booking/server/bookings.d.ts +45 -0
  126. package/dist/booking/server/bookings.js +288 -0
  127. package/dist/booking/server/catalog.d.ts +9 -0
  128. package/dist/booking/server/catalog.js +20 -0
  129. package/dist/booking/server/db.d.ts +7 -0
  130. package/dist/booking/server/db.js +18 -0
  131. package/dist/booking/server/email.d.ts +23 -0
  132. package/dist/booking/server/email.js +59 -0
  133. package/dist/booking/server/emailTemplateRegistry.d.ts +29 -0
  134. package/dist/booking/server/emailTemplateRegistry.js +125 -0
  135. package/dist/booking/server/http/create-handler.d.ts +5 -0
  136. package/dist/booking/server/http/create-handler.js +68 -0
  137. package/dist/booking/server/http/index.d.ts +4 -0
  138. package/dist/booking/server/http/index.js +4 -0
  139. package/dist/booking/server/http/maintenance-handler.d.ts +9 -0
  140. package/dist/booking/server/http/maintenance-handler.js +36 -0
  141. package/dist/booking/server/http/portal-handler.d.ts +13 -0
  142. package/dist/booking/server/http/portal-handler.js +99 -0
  143. package/dist/booking/server/http/webhook-handler.d.ts +9 -0
  144. package/dist/booking/server/http/webhook-handler.js +39 -0
  145. package/dist/booking/server/index.d.ts +12 -0
  146. package/dist/booking/server/index.js +14 -0
  147. package/dist/booking/server/maintenance.d.ts +28 -0
  148. package/dist/booking/server/maintenance.js +134 -0
  149. package/dist/booking/server/participants.d.ts +26 -0
  150. package/dist/booking/server/participants.js +92 -0
  151. package/dist/booking/server/payments.d.ts +34 -0
  152. package/dist/booking/server/payments.js +207 -0
  153. package/dist/booking/server/portal-session.d.ts +16 -0
  154. package/dist/booking/server/portal-session.js +42 -0
  155. package/dist/booking/server/portal.d.ts +207 -0
  156. package/dist/booking/server/portal.js +234 -0
  157. package/dist/booking/server/refunds.d.ts +7 -0
  158. package/dist/booking/server/refunds.js +58 -0
  159. package/dist/booking/server/reminders.d.ts +9 -0
  160. package/dist/booking/server/reminders.js +24 -0
  161. package/dist/booking/server/validation.d.ts +17 -0
  162. package/dist/booking/server/validation.js +55 -0
  163. package/dist/booking/state-machine.d.ts +41 -0
  164. package/dist/booking/state-machine.js +73 -0
  165. package/dist/booking/templates/_partials/footer.en.html +4 -0
  166. package/dist/booking/templates/_partials/footer.pl.html +4 -0
  167. package/dist/booking/templates/_partials/header.en.html +4 -0
  168. package/dist/booking/templates/_partials/header.pl.html +4 -0
  169. package/dist/booking/templates/balance-due.en.html +10 -0
  170. package/dist/booking/templates/balance-due.pl.html +10 -0
  171. package/dist/booking/templates/balance-overdue-cancelled.en.html +5 -0
  172. package/dist/booking/templates/balance-overdue-cancelled.pl.html +5 -0
  173. package/dist/booking/templates/booking-received.en.html +9 -0
  174. package/dist/booking/templates/booking-received.pl.html +9 -0
  175. package/dist/booking/templates/deposit-paid.en.html +6 -0
  176. package/dist/booking/templates/deposit-paid.pl.html +6 -0
  177. package/dist/booking/templates/payment-link.en.html +5 -0
  178. package/dist/booking/templates/payment-link.pl.html +5 -0
  179. package/dist/booking/templates/payment-received.en.html +10 -0
  180. package/dist/booking/templates/payment-received.pl.html +10 -0
  181. package/dist/booking/totals.d.ts +38 -0
  182. package/dist/booking/totals.js +61 -0
  183. package/dist/booking/webhook-logic.d.ts +28 -0
  184. package/dist/booking/webhook-logic.js +50 -0
  185. package/dist/cli/create-user.d.ts +7 -1
  186. package/dist/cli/create-user.js +23 -15
  187. package/dist/cli/index.js +55 -7
  188. package/dist/cli/scaffold/admin.d.ts +6 -0
  189. package/dist/cli/scaffold/admin.js +5 -3
  190. package/dist/cli/scaffold/booking.d.ts +12 -0
  191. package/dist/cli/scaffold/booking.js +103 -0
  192. package/dist/core/cms.d.ts +24 -9
  193. package/dist/core/cms.js +72 -34
  194. package/dist/core/config/validatePreviews.d.ts +12 -0
  195. package/dist/core/config/validatePreviews.js +90 -0
  196. package/dist/core/fields/dotPath.d.ts +4 -0
  197. package/dist/core/fields/dotPath.js +18 -0
  198. package/dist/core/fields/slugPath.d.ts +9 -1
  199. package/dist/core/fields/slugPath.js +16 -2
  200. package/dist/core/fields/slugify.d.ts +3 -0
  201. package/dist/core/fields/slugify.js +4 -0
  202. package/dist/core/server/entries/operations/duplicate.d.ts +10 -0
  203. package/dist/core/server/entries/operations/duplicate.js +73 -0
  204. package/dist/core/server/entries/operations/slugNormalize.d.ts +8 -0
  205. package/dist/core/server/entries/operations/slugNormalize.js +20 -0
  206. package/dist/core/server/entries/operations/slugUniqueness.d.ts +17 -0
  207. package/dist/core/server/entries/operations/slugUniqueness.js +39 -0
  208. package/dist/core/server/fields/resolveRelationFields.d.ts +11 -2
  209. package/dist/core/server/fields/resolveRelationFields.js +12 -3
  210. package/dist/core/server/generator/generator.js +7 -2
  211. package/dist/db-postgres/schema/booking/bookingAdjustments.d.ts +186 -0
  212. package/dist/db-postgres/schema/booking/bookingAdjustments.js +22 -0
  213. package/dist/db-postgres/schema/booking/bookingItemPersons.d.ts +117 -0
  214. package/dist/db-postgres/schema/booking/bookingItemPersons.js +22 -0
  215. package/dist/db-postgres/schema/booking/bookingItems.d.ts +181 -0
  216. package/dist/db-postgres/schema/booking/bookingItems.js +22 -0
  217. package/dist/db-postgres/schema/booking/bookingPayments.d.ts +204 -0
  218. package/dist/db-postgres/schema/booking/bookingPayments.js +25 -0
  219. package/dist/db-postgres/schema/booking/bookingPersons.d.ts +99 -0
  220. package/dist/db-postgres/schema/booking/bookingPersons.js +17 -0
  221. package/dist/db-postgres/schema/booking/bookingReminders.d.ts +131 -0
  222. package/dist/db-postgres/schema/booking/bookingReminders.js +18 -0
  223. package/dist/db-postgres/schema/booking/bookings.d.ts +307 -0
  224. package/dist/db-postgres/schema/booking/bookings.js +40 -0
  225. package/dist/db-postgres/schema/booking/index.d.ts +7 -0
  226. package/dist/db-postgres/schema/booking/index.js +7 -0
  227. package/dist/db-postgres/schema/index.d.ts +1 -0
  228. package/dist/db-postgres/schema/index.js +1 -0
  229. package/dist/db-postgres/schema-booking.d.ts +1 -0
  230. package/dist/db-postgres/schema-booking.js +1 -0
  231. package/dist/server/security/rate-limit-handler.d.ts +19 -0
  232. package/dist/server/security/rate-limit-handler.js +34 -0
  233. package/dist/shop/adapters/payu/client.js +3 -1
  234. package/dist/shop/adapters/payu/index.js +37 -17
  235. package/dist/shop/adapters/payu/payload.d.ts +6 -0
  236. package/dist/shop/adapters/payu/payload.js +1 -1
  237. package/dist/shop/adapters/stripe/index.js +36 -21
  238. package/dist/shop/index.d.ts +11 -2
  239. package/dist/shop/index.js +43 -1
  240. package/dist/shop/rate-limit.d.ts +1 -9
  241. package/dist/shop/rate-limit.js +3 -28
  242. package/dist/shop/types.d.ts +19 -0
  243. package/dist/sveltekit/config.d.ts +4 -0
  244. package/dist/sveltekit/config.js +6 -0
  245. package/dist/types/cms.d.ts +15 -1
  246. package/dist/types/cms.schema.d.ts +96 -2
  247. package/dist/types/cms.schema.js +84 -1
  248. package/dist/types/config.d.ts +33 -0
  249. package/dist/types/layout.d.ts +6 -0
  250. package/dist/types/modules.d.ts +133 -0
  251. package/dist/types/modules.js +27 -0
  252. package/dist/updates/0.36.9/index.d.ts +2 -0
  253. package/dist/updates/0.36.9/index.js +12 -0
  254. package/dist/updates/0.37.0/index.d.ts +2 -0
  255. package/dist/updates/0.37.0/index.js +52 -0
  256. package/dist/updates/index.js +5 -1
  257. package/package.json +19 -1
  258. package/dist/admin/components/layout/nav-shop.svelte +0 -33
  259. package/dist/admin/components/layout/nav-shop.svelte.d.ts +0 -3
@@ -0,0 +1,637 @@
1
+ <script lang="ts">
2
+ import { page } from '$app/state';
3
+ import { getRemotes } from '../../../sveltekit/index.js';
4
+ import { Button } from '../../../components/ui/button/index.js';
5
+ import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
6
+ import PageHeader from '../../components/layout/page-header.svelte';
7
+ import { formatCentsPrice, formatDateTime } from '../../utils/formatters.js';
8
+ import { BOOKING_STATUSES } from '../../../booking/state-machine.js';
9
+ import ParticipantDataPanel from './participant-data-panel.svelte';
10
+ import CustomerPanel from './customer-panel.svelte';
11
+ import StatusBadge from '../collection/status-badge.svelte';
12
+
13
+ const remotes = getRemotes();
14
+ const interfaceLanguage = useInterfaceLanguage();
15
+
16
+ const id = $derived(page.params.id ?? '');
17
+ const detailQuery = $derived(remotes.getBooking(id));
18
+ const detail = $derived(detailQuery.current ?? null);
19
+
20
+ type TabKey = 'overview' | 'participants' | 'payments' | 'adjustments' | 'history' | 'items-edit';
21
+ let activeTab = $state<TabKey>('overview');
22
+
23
+ let saving = $state(false);
24
+ let error = $state<string | null>(null);
25
+ let linkAmount = $state(0);
26
+ let cashAmount = $state(0);
27
+ let newStatus = $state('');
28
+ let emailKind = $state('payment-link');
29
+ let adjKind = $state<'manual_surcharge' | 'manual_discount'>('manual_surcharge');
30
+ let adjAmount = $state(0);
31
+ let adjLabelInput = $state('');
32
+ let adjReasonInput = $state('');
33
+
34
+ const EMAIL_KINDS = [
35
+ 'booking-received',
36
+ 'deposit-paid',
37
+ 'payment-received',
38
+ 'payment-link',
39
+ 'balance-due',
40
+ 'balance-overdue-cancelled'
41
+ ];
42
+
43
+ const t = $derived(
44
+ interfaceLanguage.current === 'pl'
45
+ ? {
46
+ loading: 'Ładowanie…',
47
+ notFound: 'Nie znaleziono rezerwacji',
48
+ gross: 'Wartość',
49
+ paid: 'Wpłacono',
50
+ balance: 'Saldo',
51
+ discount: 'Rabat',
52
+ perks: 'Prezenty',
53
+ items: 'Pozycje',
54
+ payments: 'Księga płatności',
55
+ reminders: 'Przypomnienia',
56
+ kind: 'Rodzaj',
57
+ amount: 'Kwota',
58
+ pstatus: 'Status',
59
+ when: 'Termin',
60
+ trip: 'Wycieczka',
61
+ count: 'Osób',
62
+ unit: 'Cena jedn.',
63
+ participant: 'Uczestnik',
64
+ noData: 'Brak danych uczestnika',
65
+ none: 'Brak',
66
+ actions: 'Akcje',
67
+ amountPln: 'Kwota (PLN)',
68
+ sendLink: 'Wyślij link płatności',
69
+ recordCash: 'Zapisz gotówkę',
70
+ changeStatus: 'Zmień status',
71
+ cancel: 'Anuluj rezerwację',
72
+ sendEmail: 'Wyślij e-mail',
73
+ refund: 'Zwrot',
74
+ tabs: 'Sekcje rezerwacji',
75
+ tabOverview: 'Przegląd',
76
+ tabParticipants: 'Dane uczestników',
77
+ tabPayments: 'Płatności',
78
+ tabAdjustments: 'Korekty',
79
+ tabHistory: 'Historia',
80
+ tabItemsEdit: 'Edycja pozycji',
81
+ itemDate: 'Data wylotu',
82
+ itemPrice: 'Cena jedn. (PLN)',
83
+ itemRemove: 'Usuń',
84
+ itemRestore: 'Przywróć',
85
+ assignPeople: 'Uczestnicy',
86
+ addTrip: 'Dobierz wycieczkę',
87
+ pickTrip: 'Wybierz wycieczkę…',
88
+ saveItems: 'Zapisz zmiany',
89
+ itemsInvalid: 'Każda pozycja musi mieć ≥1 osobę; potrzebna ≥1 pozycja.',
90
+ removedTag: 'do usunięcia',
91
+ surchargeLabel: 'Dopłaty',
92
+ manualDiscountLabel: 'Rabaty ręczne',
93
+ addAdjustment: 'Dodaj korektę',
94
+ voidAdj: 'Cofnij',
95
+ adjLabel: 'Opis',
96
+ adjReason: 'Powód',
97
+ kindSurcharge: 'Dopłata',
98
+ kindDiscount: 'Rabat',
99
+ voidedTag: 'Cofnięta',
100
+ noAdjustments: 'Brak korekt',
101
+ adjWho: 'Dodał/a',
102
+ adjKind: 'Rodzaj'
103
+ }
104
+ : {
105
+ loading: 'Loading…',
106
+ notFound: 'Booking not found',
107
+ gross: 'Total',
108
+ paid: 'Paid',
109
+ balance: 'Balance',
110
+ discount: 'Discount',
111
+ perks: 'Perks',
112
+ items: 'Items',
113
+ payments: 'Payment ledger',
114
+ reminders: 'Reminders',
115
+ kind: 'Kind',
116
+ amount: 'Amount',
117
+ pstatus: 'Status',
118
+ when: 'When',
119
+ trip: 'Trip',
120
+ count: 'Pax',
121
+ unit: 'Unit price',
122
+ participant: 'Participant',
123
+ noData: 'No participant data',
124
+ none: 'None',
125
+ actions: 'Actions',
126
+ amountPln: 'Amount (PLN)',
127
+ sendLink: 'Send payment link',
128
+ recordCash: 'Record cash',
129
+ changeStatus: 'Change status',
130
+ cancel: 'Cancel booking',
131
+ sendEmail: 'Send email',
132
+ refund: 'Refund',
133
+ tabs: 'Booking sections',
134
+ tabOverview: 'Overview',
135
+ tabParticipants: 'Participant data',
136
+ tabPayments: 'Payments',
137
+ tabAdjustments: 'Adjustments',
138
+ tabHistory: 'History',
139
+ tabItemsEdit: 'Edit items',
140
+ itemDate: 'Departure date',
141
+ itemPrice: 'Unit price (PLN)',
142
+ itemRemove: 'Remove',
143
+ itemRestore: 'Restore',
144
+ assignPeople: 'Participants',
145
+ addTrip: 'Add trip',
146
+ pickTrip: 'Pick a trip…',
147
+ saveItems: 'Save changes',
148
+ itemsInvalid: 'Each item needs ≥1 person; at least 1 item required.',
149
+ removedTag: 'to remove',
150
+ surchargeLabel: 'Surcharges',
151
+ manualDiscountLabel: 'Manual discounts',
152
+ addAdjustment: 'Add adjustment',
153
+ voidAdj: 'Void',
154
+ adjLabel: 'Label',
155
+ adjReason: 'Reason',
156
+ kindSurcharge: 'Surcharge',
157
+ kindDiscount: 'Discount',
158
+ voidedTag: 'Voided',
159
+ noAdjustments: 'No adjustments',
160
+ adjWho: 'Added by',
161
+ adjKind: 'Kind'
162
+ }
163
+ );
164
+
165
+ const TABS = $derived<Array<{ key: TabKey; label: string }>>([
166
+ { key: 'overview', label: t.tabOverview },
167
+ { key: 'participants', label: t.tabParticipants },
168
+ { key: 'payments', label: t.tabPayments },
169
+ { key: 'adjustments', label: t.tabAdjustments },
170
+ { key: 'history', label: t.tabHistory },
171
+ { key: 'items-edit', label: t.tabItemsEdit }
172
+ ]);
173
+
174
+ // Rethrows on failure so the panel can surface a per-card validation message.
175
+ async function saveParticipant(participantId: string, data: Record<string, unknown>) {
176
+ await remotes.updateParticipantData({ participantId, data });
177
+ await detailQuery.refresh();
178
+ }
179
+
180
+ async function run(fn: () => Promise<unknown>) {
181
+ saving = true;
182
+ error = null;
183
+ try {
184
+ await fn();
185
+ await detailQuery.refresh();
186
+ } catch (e) {
187
+ error = e instanceof Error ? e.message : 'Error';
188
+ } finally {
189
+ saving = false;
190
+ }
191
+ }
192
+
193
+ const addLink = () =>
194
+ run(async () => {
195
+ const res = await remotes.addPaymentLink({ bookingId: id, amount: Math.round(linkAmount * 100) });
196
+ if (res.paymentLink) window.open(res.paymentLink, '_blank');
197
+ linkAmount = 0;
198
+ });
199
+ const recordCash = () =>
200
+ run(async () => {
201
+ await remotes.recordCashPayment({ bookingId: id, amount: Math.round(cashAmount * 100) });
202
+ cashAmount = 0;
203
+ });
204
+ const refund = (paymentId: string) =>
205
+ run(() => remotes.refundBookingPayment({ paymentId }));
206
+ const changeStatus = () =>
207
+ run(async () => {
208
+ await remotes.changeBookingStatus({ bookingId: id, to: newStatus });
209
+ newStatus = '';
210
+ });
211
+ const cancel = () => run(() => remotes.cancelBooking({ bookingId: id, reason: 'manual' }));
212
+ const sendEmail = () => run(() => remotes.sendBookingEmailCmd({ bookingId: id, kind: emailKind }));
213
+ const addAdjustment = () =>
214
+ run(async () => {
215
+ await remotes.addAdjustment({
216
+ bookingId: id,
217
+ kind: adjKind,
218
+ amount: Math.round(adjAmount * 100),
219
+ label: adjLabelInput.trim(),
220
+ reason: adjReasonInput.trim() || undefined
221
+ });
222
+ adjAmount = 0;
223
+ adjLabelInput = '';
224
+ adjReasonInput = '';
225
+ });
226
+ const voidAdjustment = (adjustmentId: string) => run(() => remotes.voidAdjustment({ adjustmentId }));
227
+
228
+ // --- Items edit tab ---------------------------------------------------------
229
+ type EditItem = {
230
+ key: string; // existing item.id or 'new-N'
231
+ tripRef: string;
232
+ title: string;
233
+ dateStr: string; // 'YYYY-MM-DD' or ''
234
+ priceZl: number;
235
+ assigned: Record<string, boolean>; // personId -> assigned
236
+ removed: boolean;
237
+ isNew: boolean;
238
+ };
239
+
240
+ const catalogQuery = $derived(remotes.listBookingCatalog());
241
+ const catalog = $derived(catalogQuery.current ?? []);
242
+
243
+ const toDateStr = (d: string | Date | null | undefined) => {
244
+ if (!d) return '';
245
+ const dt = typeof d === 'string' ? new Date(d) : d;
246
+ return Number.isNaN(dt.getTime()) ? '' : dt.toISOString().slice(0, 10);
247
+ };
248
+
249
+ const personLabel = (data: Record<string, unknown>) =>
250
+ String(data.fullName ?? data.name ?? Object.values(data).find((v) => typeof v === 'string') ?? '');
251
+
252
+ const people = $derived(
253
+ (detail?.persons ?? []).map((p) => ({ id: p.id, label: personLabel(p.data) || p.id }))
254
+ );
255
+
256
+ // Per-trip person data that must be preserved on edit.
257
+ const origData = $derived.by(() => {
258
+ const m: Record<string, Record<string, unknown>> = {};
259
+ for (const a of detail?.assignments ?? []) m[`${a.itemId}:${a.personId}`] = a.data;
260
+ return m;
261
+ });
262
+
263
+ let editItems = $state<EditItem[]>([]);
264
+ let pickedRef = $state('');
265
+ let newCounter = 0;
266
+
267
+ // Reseed the editable model only when the booking, item count or person count
268
+ // changes — so ongoing edits aren't clobbered on every re-render.
269
+ let seededKey: string | null = null;
270
+ $effect(() => {
271
+ if (!detail) return;
272
+ const key = `${detail.booking.id}:${detail.items.length}:${detail.persons.length}`;
273
+ if (key === seededKey) return;
274
+ seededKey = key;
275
+ editItems = detail.items.map((it) => ({
276
+ key: it.id,
277
+ tripRef: it.tripRef,
278
+ title: it.tripTitle,
279
+ dateStr: toDateStr(it.departureDate),
280
+ priceZl: it.unitPriceSnapshot / 100,
281
+ assigned: Object.fromEntries(
282
+ detail.persons.map((p) => [
283
+ p.id,
284
+ detail.assignments.some((a) => a.itemId === it.id && a.personId === p.id)
285
+ ])
286
+ ),
287
+ removed: false,
288
+ isNew: false
289
+ }));
290
+ });
291
+
292
+ function addTrip() {
293
+ if (!pickedRef) return;
294
+ editItems.push({
295
+ key: `new-${newCounter++}`,
296
+ tripRef: pickedRef,
297
+ title: catalog.find((c) => c.ref === pickedRef)?.title ?? pickedRef,
298
+ dateStr: '',
299
+ priceZl: 0,
300
+ assigned: Object.fromEntries(people.map((p) => [p.id, true])),
301
+ removed: false,
302
+ isNew: true
303
+ });
304
+ pickedRef = '';
305
+ }
306
+
307
+ function toggleRemove(key: string) {
308
+ const it = editItems.find((i) => i.key === key);
309
+ if (it) it.removed = !it.removed;
310
+ }
311
+
312
+ function canSave() {
313
+ const kept = editItems.filter((i) => !i.removed);
314
+ return kept.length >= 1 && kept.every((i) => people.some((p) => i.assigned[p.id]));
315
+ }
316
+
317
+ function saveItems() {
318
+ run(async () => {
319
+ if (!detail) return;
320
+ const persons = detail.persons.map((p) => ({ ref: p.id, identity: p.data }));
321
+ const items = editItems
322
+ .filter((i) => !i.removed)
323
+ .map((i) => ({
324
+ tripRef: i.tripRef,
325
+ departureDate: i.dateStr ? new Date(`${i.dateStr}T00:00:00`) : null,
326
+ unitPrice: Math.round(i.priceZl * 100),
327
+ assignments: people
328
+ .filter((p) => i.assigned[p.id])
329
+ .map((p) => ({ personRef: p.id, data: origData[`${i.key}:${p.id}`] ?? {} }))
330
+ }));
331
+ await remotes.editBookingStaff({ bookingId: id, changes: { persons, items } });
332
+ });
333
+ }
334
+ </script>
335
+
336
+ {#if !detailQuery.ready}
337
+ <p class="text-muted-foreground px-6 py-4 text-sm">{t.loading}</p>
338
+ {:else if !detail}
339
+ <p class="text-muted-foreground px-6 py-4 text-sm">{t.notFound}</p>
340
+ {:else}
341
+ {@const b = detail.booking}
342
+ <PageHeader title={b.number} />
343
+ <div class="space-y-6 px-4 sm:px-6">
344
+ <nav class="border-border flex flex-wrap gap-1 border-b" aria-label={t.tabs}>
345
+ {#each TABS as tab (tab.key)}
346
+ <button
347
+ type="button"
348
+ class="-mb-px border-b-2 px-3 py-2 text-sm font-medium transition-colors {activeTab === tab.key
349
+ ? 'border-primary text-primary'
350
+ : 'text-muted-foreground hover:text-foreground border-transparent'}"
351
+ onclick={() => (activeTab = tab.key)}
352
+ >
353
+ {tab.label}
354
+ </button>
355
+ {/each}
356
+ </nav>
357
+
358
+ {#if error}
359
+ <p class="text-destructive text-sm">{error}</p>
360
+ {/if}
361
+
362
+ {#if activeTab === 'overview'}
363
+ <section class="flex flex-wrap items-center gap-3 text-sm">
364
+ <StatusBadge variant="booking" status={b.status} />
365
+ <span class="text-muted-foreground">{t.gross}: {formatCentsPrice(b.totals.gross, b.currency)}</span>
366
+ {#if b.totals.discount > 0}
367
+ <span class="text-success">{t.discount}: −{formatCentsPrice(b.totals.discount, b.currency)}</span>
368
+ {/if}
369
+ {#if (b.totals.surcharge ?? 0) > 0}
370
+ <span class="text-muted-foreground">{t.surchargeLabel}: +{formatCentsPrice(b.totals.surcharge ?? 0, b.currency)}</span>
371
+ {/if}
372
+ {#if (b.totals.manualDiscount ?? 0) > 0}
373
+ <span class="text-success">{t.manualDiscountLabel}: −{formatCentsPrice(b.totals.manualDiscount ?? 0, b.currency)}</span>
374
+ {/if}
375
+ <span class="text-muted-foreground">{t.paid}: {formatCentsPrice(b.totals.paid, b.currency)}</span>
376
+ <span class="font-medium">{t.balance}: {formatCentsPrice(b.totals.balance, b.currency)}</span>
377
+ {#each b.totals.perks ?? [] as perk (perk.label)}
378
+ <span class="bg-lavender-lighter text-primary inline-flex items-center rounded-full px-2 py-0.5 text-xs">🎁 {perk.label}</span>
379
+ {/each}
380
+ </section>
381
+
382
+ <CustomerPanel
383
+ customer={(b.customer ?? {}) as Record<string, unknown>}
384
+ dateFieldKey={detail.arrivalDateField}
385
+ fields={detail.customerFields}
386
+ />
387
+
388
+ <section>
389
+ <h2 class="mb-2 text-sm font-semibold">{t.items}</h2>
390
+ <table class="w-full text-sm">
391
+ <thead>
392
+ <tr class="text-muted-foreground border-border border-b text-left">
393
+ <th class="py-2 pr-4 font-medium">{t.trip}</th>
394
+ <th class="py-2 pr-4 font-medium">{t.when}</th>
395
+ <th class="py-2 pr-4 text-right font-medium">{t.count}</th>
396
+ <th class="py-2 pr-4 text-right font-medium">{t.unit}</th>
397
+ </tr>
398
+ </thead>
399
+ <tbody>
400
+ {#each detail.items as it (it.id)}
401
+ <tr class="border-border/60 border-b align-top">
402
+ <td class="py-2 pr-4 font-medium">{it.tripTitle}</td>
403
+ <td class="text-muted-foreground py-2 pr-4">
404
+ {it.departureDate ? formatDateTime(it.departureDate, interfaceLanguage.current) : '—'}
405
+ </td>
406
+ <td class="py-2 pr-4 text-right">{it.participantsCount}</td>
407
+ <td class="py-2 pr-4 text-right">{formatCentsPrice(it.unitPriceSnapshot, b.currency)}</td>
408
+ </tr>
409
+ {/each}
410
+ </tbody>
411
+ </table>
412
+ </section>
413
+
414
+ <section class="border-border space-y-3 rounded-xl border p-4">
415
+ <h2 class="text-sm font-semibold">{t.actions}</h2>
416
+ <div class="flex flex-wrap items-end gap-2">
417
+ <label class="text-sm">
418
+ <span class="text-muted-foreground block text-xs">{t.amountPln}</span>
419
+ <input type="number" min="0" step="0.01" bind:value={linkAmount} class="border-border w-28 rounded-md border px-2 py-1" />
420
+ </label>
421
+ <Button disabled={saving || linkAmount <= 0} onclick={addLink}>{t.sendLink}</Button>
422
+ </div>
423
+ <div class="flex flex-wrap items-end gap-2">
424
+ <label class="text-sm">
425
+ <span class="text-muted-foreground block text-xs">{t.amountPln}</span>
426
+ <input type="number" min="0" step="0.01" bind:value={cashAmount} class="border-border w-28 rounded-md border px-2 py-1" />
427
+ </label>
428
+ <Button variant="secondary" disabled={saving || cashAmount <= 0} onclick={recordCash}>{t.recordCash}</Button>
429
+ </div>
430
+ <div class="flex flex-wrap items-end gap-2">
431
+ <select bind:value={newStatus} class="border-border rounded-md border px-2 py-1 text-sm">
432
+ <option value="">—</option>
433
+ {#each BOOKING_STATUSES as s (s)}
434
+ <option value={s}>{s}</option>
435
+ {/each}
436
+ </select>
437
+ <Button variant="outline" disabled={saving || !newStatus} onclick={changeStatus}>{t.changeStatus}</Button>
438
+ </div>
439
+ <div class="flex flex-wrap items-end gap-2">
440
+ <select bind:value={emailKind} class="border-border rounded-md border px-2 py-1 text-sm">
441
+ {#each EMAIL_KINDS as k (k)}
442
+ <option value={k}>{k}</option>
443
+ {/each}
444
+ </select>
445
+ <Button variant="outline" disabled={saving} onclick={sendEmail}>{t.sendEmail}</Button>
446
+ </div>
447
+ <Button variant="outline" disabled={saving} onclick={cancel}>{t.cancel}</Button>
448
+ </section>
449
+ {:else if activeTab === 'participants'}
450
+ <ParticipantDataPanel
451
+ items={detail.items}
452
+ participants={detail.participants}
453
+ ageFlag={detail.ageFlag}
454
+ lang={interfaceLanguage.current}
455
+ onSave={saveParticipant}
456
+ />
457
+ {:else if activeTab === 'payments'}
458
+ <section>
459
+ <h2 class="mb-2 text-sm font-semibold">{t.payments}</h2>
460
+ {#if detail.payments.length === 0}
461
+ <p class="text-muted-foreground text-sm">{t.none}</p>
462
+ {:else}
463
+ <table class="w-full text-sm">
464
+ <thead>
465
+ <tr class="text-muted-foreground border-border border-b text-left">
466
+ <th class="py-2 pr-4 font-medium">{t.kind}</th>
467
+ <th class="py-2 pr-4 text-right font-medium">{t.amount}</th>
468
+ <th class="py-2 pr-4 font-medium">{t.pstatus}</th>
469
+ <th class="py-2 font-medium"></th>
470
+ </tr>
471
+ </thead>
472
+ <tbody>
473
+ {#each detail.payments as p (p.id)}
474
+ <tr class="border-border/60 border-b">
475
+ <td class="py-2 pr-4">{p.kind}</td>
476
+ <td class="py-2 pr-4 text-right">{formatCentsPrice(p.amount, b.currency)}</td>
477
+ <td class="py-2 pr-4">{p.status}</td>
478
+ <td class="py-2 text-right">
479
+ {#if p.status === 'paid'}
480
+ <Button variant="outline" size="sm" disabled={saving} onclick={() => refund(p.id)}>
481
+ {t.refund}
482
+ </Button>
483
+ {/if}
484
+ </td>
485
+ </tr>
486
+ {/each}
487
+ </tbody>
488
+ </table>
489
+ {/if}
490
+ </section>
491
+ {:else if activeTab === 'adjustments'}
492
+ <section class="border-border space-y-3 rounded-xl border p-4">
493
+ <h2 class="text-sm font-semibold">{t.addAdjustment}</h2>
494
+ <div class="flex flex-wrap items-end gap-2">
495
+ <label class="text-sm">
496
+ <span class="text-muted-foreground block text-xs">{t.adjKind}</span>
497
+ <select bind:value={adjKind} class="border-border rounded-md border px-2 py-1 text-sm">
498
+ <option value="manual_surcharge">{t.kindSurcharge}</option>
499
+ <option value="manual_discount">{t.kindDiscount}</option>
500
+ </select>
501
+ </label>
502
+ <label class="text-sm">
503
+ <span class="text-muted-foreground block text-xs">{t.amountPln}</span>
504
+ <input type="number" min="0" step="0.01" bind:value={adjAmount} class="border-border w-28 rounded-md border px-2 py-1" />
505
+ </label>
506
+ <label class="text-sm">
507
+ <span class="text-muted-foreground block text-xs">{t.adjLabel}</span>
508
+ <input type="text" bind:value={adjLabelInput} placeholder="Rower elektryczny" class="border-border w-48 rounded-md border px-2 py-1" />
509
+ </label>
510
+ <label class="text-sm">
511
+ <span class="text-muted-foreground block text-xs">{t.adjReason}</span>
512
+ <input type="text" bind:value={adjReasonInput} class="border-border w-48 rounded-md border px-2 py-1" />
513
+ </label>
514
+ <Button disabled={saving || adjAmount <= 0 || !adjLabelInput.trim()} onclick={addAdjustment}>{t.addAdjustment}</Button>
515
+ </div>
516
+ </section>
517
+
518
+ <section>
519
+ {#if detail.adjustments.length === 0}
520
+ <p class="text-muted-foreground text-sm">{t.noAdjustments}</p>
521
+ {:else}
522
+ <table class="w-full text-sm">
523
+ <thead>
524
+ <tr class="text-muted-foreground border-border border-b text-left">
525
+ <th class="py-2 pr-4 font-medium">{t.adjKind}</th>
526
+ <th class="py-2 pr-4 text-right font-medium">{t.amount}</th>
527
+ <th class="py-2 pr-4 font-medium">{t.adjLabel}</th>
528
+ <th class="py-2 pr-4 font-medium">{t.adjReason}</th>
529
+ <th class="py-2 pr-4 font-medium">{t.adjWho}</th>
530
+ <th class="py-2 font-medium"></th>
531
+ </tr>
532
+ </thead>
533
+ <tbody>
534
+ {#each detail.adjustments as a (a.id)}
535
+ <tr class="border-border/60 border-b align-top {a.voidedAt ? 'text-muted-foreground opacity-60' : ''}">
536
+ <td class="py-2 pr-4">{a.kind === 'manual_surcharge' ? t.kindSurcharge : t.kindDiscount}</td>
537
+ <td class="py-2 pr-4 text-right">{a.amount >= 0 ? '+' : '−'}{formatCentsPrice(Math.abs(a.amount), b.currency)}</td>
538
+ <td class="py-2 pr-4 font-medium">{a.label}</td>
539
+ <td class="text-muted-foreground py-2 pr-4">{a.reason ?? '—'}</td>
540
+ <td class="text-muted-foreground py-2 pr-4">
541
+ {a.createdBy} · {formatDateTime(a.createdAt, interfaceLanguage.current)}
542
+ </td>
543
+ <td class="py-2 text-right">
544
+ {#if a.voidedAt}
545
+ <span class="bg-muted text-muted-foreground inline-flex items-center rounded-full px-2 py-0.5 text-xs">
546
+ {t.voidedTag} ({a.voidedBy})
547
+ </span>
548
+ {:else}
549
+ <Button variant="outline" size="sm" disabled={saving} onclick={() => voidAdjustment(a.id)}>
550
+ {t.voidAdj}
551
+ </Button>
552
+ {/if}
553
+ </td>
554
+ </tr>
555
+ {/each}
556
+ </tbody>
557
+ </table>
558
+ {/if}
559
+ </section>
560
+ {:else if activeTab === 'history'}
561
+ <section>
562
+ <h2 class="mb-2 text-sm font-semibold">{t.reminders}</h2>
563
+ {#if detail.reminders.length === 0}
564
+ <p class="text-muted-foreground text-sm">{t.none}</p>
565
+ {:else}
566
+ <ul class="text-muted-foreground space-y-1 text-sm">
567
+ {#each detail.reminders as r (r.id)}
568
+ <li>
569
+ {r.type} — {formatDateTime(r.scheduledFor, interfaceLanguage.current)}
570
+ {r.sentAt ? '✓' : ''}
571
+ </li>
572
+ {/each}
573
+ </ul>
574
+ {/if}
575
+ </section>
576
+ {:else if activeTab === 'items-edit'}
577
+ <section class="space-y-3">
578
+ {#each editItems as it (it.key)}
579
+ <div class="border-border rounded-xl border p-4 {it.removed ? 'opacity-50' : ''}">
580
+ <div class="mb-3 flex items-center gap-2">
581
+ <h3 class="text-sm font-semibold">{it.title}</h3>
582
+ {#if it.removed}
583
+ <span class="bg-muted text-muted-foreground inline-flex items-center rounded-full px-2 py-0.5 text-xs">
584
+ {t.removedTag}
585
+ </span>
586
+ {/if}
587
+ </div>
588
+ <div class="flex flex-wrap items-end gap-3">
589
+ <label class="text-sm">
590
+ <span class="text-muted-foreground block text-xs">{t.itemDate}</span>
591
+ <input type="date" bind:value={it.dateStr} class="border-border rounded-md border px-2 py-1" />
592
+ </label>
593
+ <label class="text-sm">
594
+ <span class="text-muted-foreground block text-xs">{t.itemPrice}</span>
595
+ <input type="number" min="0" step="0.01" bind:value={it.priceZl} class="border-border w-28 rounded-md border px-2 py-1" />
596
+ </label>
597
+ </div>
598
+ <fieldset class="mt-3">
599
+ <legend class="text-muted-foreground mb-1 text-xs">{t.assignPeople}</legend>
600
+ <div class="flex flex-wrap gap-3">
601
+ {#each people as p (p.id)}
602
+ <label class="flex items-center gap-1.5 text-sm">
603
+ <input type="checkbox" bind:checked={it.assigned[p.id]} />
604
+ {p.label}
605
+ </label>
606
+ {/each}
607
+ </div>
608
+ </fieldset>
609
+ <div class="mt-3">
610
+ <Button variant="outline" size="sm" onclick={() => toggleRemove(it.key)}>
611
+ {it.removed ? t.itemRestore : t.itemRemove}
612
+ </Button>
613
+ </div>
614
+ </div>
615
+ {/each}
616
+
617
+ <div class="border-border flex flex-wrap items-end gap-2 rounded-xl border p-4">
618
+ <label class="text-sm">
619
+ <span class="text-muted-foreground block text-xs">{t.addTrip}</span>
620
+ <select bind:value={pickedRef} class="border-border rounded-md border px-2 py-1 text-sm">
621
+ <option value="">{t.pickTrip}</option>
622
+ {#each catalog as c (c.ref)}
623
+ <option value={c.ref}>{c.title}</option>
624
+ {/each}
625
+ </select>
626
+ </label>
627
+ <Button disabled={!pickedRef} onclick={addTrip}>{t.addTrip}</Button>
628
+ </div>
629
+
630
+ {#if !canSave()}
631
+ <p class="text-destructive text-sm">{t.itemsInvalid}</p>
632
+ {/if}
633
+ <Button disabled={saving || !canSave()} onclick={saveItems}>{t.saveItems}</Button>
634
+ </section>
635
+ {/if}
636
+ </div>
637
+ {/if}
@@ -0,0 +1,3 @@
1
+ declare const ReservationDetailPage: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type ReservationDetailPage = ReturnType<typeof ReservationDetailPage>;
3
+ export default ReservationDetailPage;