accomadesc 0.3.42 → 0.4.1

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 (39) hide show
  1. package/README.md +130 -34
  2. package/dist/AccoCard.svelte +1 -1
  3. package/dist/CalendarAvailable.svelte +1 -1
  4. package/dist/MainNav.svelte +1 -2
  5. package/dist/PageComponent.svelte +1 -2
  6. package/dist/PhotoGallery.svelte +5 -1
  7. package/dist/Pricing.svelte +81 -168
  8. package/dist/SiteState.svelte.d.ts +1 -1
  9. package/dist/SiteState.svelte.js +21 -13
  10. package/dist/basic/TextInput.svelte +78 -116
  11. package/dist/basic/TextInput.svelte.d.ts +2 -0
  12. package/dist/basic/icons/actions.d.ts +13 -0
  13. package/dist/basic/icons/actions.js +44 -0
  14. package/dist/basic/icons/navigation.d.ts +6 -0
  15. package/dist/basic/icons/navigation.js +16 -0
  16. package/dist/basic/icons/ui.d.ts +13 -0
  17. package/dist/basic/icons/ui.js +44 -0
  18. package/dist/basic/icons.d.ts +4 -0
  19. package/dist/basic/icons.js +51 -413
  20. package/dist/helpers/debounce.js +8 -2
  21. package/dist/helpers/format.js +2 -1
  22. package/dist/helpers/normalizeDate.d.ts +1 -1
  23. package/dist/helpers/normalizeDate.js +25 -16
  24. package/dist/helpers/readICS.js +21 -4
  25. package/dist/index.d.ts +1 -1
  26. package/dist/names/README.md +1 -1
  27. package/dist/names/gen.js +10 -1
  28. package/dist/occuplan/state.svelte.js +7 -3
  29. package/dist/occusplan-link/OccuPlanAvailableInfo.svelte +38 -0
  30. package/dist/occusplan-link/OccuPlanGrid.svelte +375 -0
  31. package/dist/occusplan-link/OccuPlanPicker.svelte +575 -0
  32. package/dist/occusplan-link/OccuPlanRows.svelte +368 -0
  33. package/dist/occusplan-link/OccuPlanWrapper.svelte +108 -0
  34. package/dist/occusplan-link/defaultTranslations.js +157 -0
  35. package/dist/occusplan-link/state.svelte.d.ts +92 -0
  36. package/dist/occusplan-link/state.svelte.js +424 -0
  37. package/dist/svg/LogoSVG.svelte +0 -1
  38. package/dist/types.d.ts +2 -2
  39. package/package.json +10 -4
@@ -0,0 +1,575 @@
1
+ <script lang="ts">
2
+ import { DateTime, type MonthNumbers } from 'luxon';
3
+ import {
4
+ defaultMonthHeaderFormat,
5
+ defaultMonthLabels,
6
+ defaultWeekdayLabels,
7
+ OccupationState,
8
+ getOccupationState,
9
+ realFirstMonth,
10
+ type OccuplanTranslations,
11
+ } from '../occuplan/state.svelte.js';
12
+ import { onMount, untrack } from 'svelte';
13
+ import Button from '../basic/Button.svelte';
14
+ import Spinner from '../basic/Spinner.svelte';
15
+ import { normalizeDate } from '../helpers/normalizeDate.js';
16
+
17
+ let {
18
+ url,
19
+ debug = false,
20
+ arrival = undefined,
21
+ leave = undefined,
22
+ nextPage = '>',
23
+ prevPage = '<',
24
+ weekdayLabels = defaultWeekdayLabels,
25
+ monthLabels = defaultMonthLabels,
26
+ monthHeaderFormat = defaultMonthHeaderFormat,
27
+ numberOfMonth = 2,
28
+ maxDate = DateTime.utc().plus({ years: 2 }),
29
+ arrivalLabel = 'From',
30
+ leaveLabel = 'To',
31
+ numberOfNights = 'Nights',
32
+ datePickerDateFormat = 'yyyy-MM-dd',
33
+ showArrival = true,
34
+ showLeave = true,
35
+ typeLabels = {
36
+ one: 'BOOKING',
37
+ two: 'RESERVATION',
38
+ three: 'PERSONAL',
39
+ },
40
+ aborted = () => {
41
+ return;
42
+ },
43
+ dateSelected = () => {
44
+ return;
45
+ },
46
+ }: OccuplanTranslations & {
47
+ url: string;
48
+ debug?: boolean;
49
+ arrival?: DateTime;
50
+ leave?: DateTime;
51
+ showArrival?: boolean;
52
+ showLeave?: boolean;
53
+ numberOfMonth?: number;
54
+ maxDate?: DateTime;
55
+ aborted?: () => void;
56
+ dateSelected?: (arrival: DateTime, leave: DateTime) => void;
57
+ } = $props();
58
+
59
+ let occupationState: OccupationState = $derived(getOccupationState(url, debug));
60
+
61
+ const monthHeader = (monthNum: MonthNumbers, year: number): string => {
62
+ const monthLabel = monthLabels[monthNum];
63
+
64
+ let formatted = monthHeaderFormat.replace('{{month}}', monthLabel);
65
+ formatted = formatted.replace('{{year}}', `${year}`);
66
+
67
+ return formatted;
68
+ };
69
+
70
+ let maxWidth = $derived(`${numberOfMonth * 18}rem`);
71
+ let minDate = $derived(occupationState ? occupationState.firstFree(maxDate) : normalizeDate());
72
+
73
+ let page: number = $state(0);
74
+ let rfMonth: DateTime = $derived(
75
+ realFirstMonth(
76
+ (arrival?.month ? arrival.month : minDate.month) as MonthNumbers,
77
+ arrival?.year ? arrival.year : minDate.year,
78
+ numberOfMonth,
79
+ page,
80
+ ),
81
+ );
82
+ let currentMaxDate = $derived(rfMonth.plus({ month: numberOfMonth }));
83
+
84
+ let months: DateTime[] = $derived.by(() => {
85
+ const result = [];
86
+
87
+ let fMonth: DateTime = DateTime.utc(rfMonth.year, rfMonth.month, 1);
88
+ result.push(fMonth);
89
+
90
+ let nMonth = fMonth.plus({ months: 1 });
91
+ for (let c = 1; c < numberOfMonth; c++) {
92
+ result.push(nMonth);
93
+ nMonth = nMonth.plus({ months: 1 });
94
+ }
95
+ return result;
96
+ });
97
+
98
+ const nextClicked = () => {
99
+ page += 1;
100
+ };
101
+
102
+ const prevClicked = () => {
103
+ page -= 1;
104
+ };
105
+
106
+ let requestStart: DateTime | undefined = $state();
107
+ let requestEnd: DateTime | undefined = $state();
108
+
109
+ $effect(() => {
110
+ requestStart = arrival;
111
+ });
112
+
113
+ $effect(() => {
114
+ requestEnd = leave;
115
+ });
116
+
117
+ let earliestStart: DateTime | undefined = $derived(
118
+ occupationState ? occupationState.earliestRequestStart(requestEnd) : undefined,
119
+ );
120
+ let latestEnd: DateTime | undefined = $derived(
121
+ occupationState ? occupationState.latestRequestEnd(maxDate, requestStart) : undefined,
122
+ );
123
+ const requestClicked = (d: DateTime) => {
124
+ if (requestStart) {
125
+ if (requestEnd?.toISO() == d.toISO()) {
126
+ requestEnd = undefined;
127
+ requestedDays = [];
128
+ } else if (requestStart.toISO() == d.toISO()) {
129
+ requestStart = undefined;
130
+ requestEnd = undefined;
131
+ requestedDays = [];
132
+ } else if (d < requestStart) {
133
+ requestStart = d;
134
+ } else {
135
+ requestEnd = d;
136
+ }
137
+ } else {
138
+ requestStart = d;
139
+ requestEnd = undefined;
140
+ }
141
+ };
142
+
143
+ let requestedDays: string[] = $state([]);
144
+ $effect(() => {
145
+ if (requestStart && requestEnd) {
146
+ let d = normalizeDate(requestStart);
147
+
148
+ untrack(() => (requestedDays = [d.toISO() as string]));
149
+ while (d < requestEnd) {
150
+ d = d.plus({ day: 1 });
151
+ untrack(() => requestedDays.push(d.toISO() as string));
152
+ }
153
+ }
154
+ });
155
+
156
+ const requestHovering = (_: DateTime) => {};
157
+
158
+ let monthGridTemplateColumns = `[rowLegend] 1fr [d1] 1fr [d2] 1fr [d3] 1fr [d4] 1fr [d5] 1fr [d6] 1fr [d7] 1fr`;
159
+
160
+ let monthGridTemplateRows = (m: DateTime): string => {
161
+ let res = `[columnLegend] 1fr [w${m.weekNumber}] 1fr`;
162
+ let n = m.plus({ weeks: 1 });
163
+ for (let w = 1; w <= 5; w++) {
164
+ res += ` [w${n.weekNumber}] 1fr`;
165
+ n = n.plus({ weeks: 1 });
166
+ }
167
+ return res;
168
+ };
169
+
170
+ const weeks = (m: DateTime): DateTime[] => {
171
+ let n = m.plus({ weeks: 1 });
172
+ let res: DateTime[] = [m, n];
173
+ for (let i = 1; i <= 4; i++) {
174
+ n = n.plus({ weeks: 1 });
175
+ res.push(n);
176
+ }
177
+ return res;
178
+ };
179
+
180
+ const days = (m: DateTime): DateTime[] => {
181
+ //find first
182
+ let firstDay = normalizeDate(m.startOf('week'));
183
+
184
+ //find last
185
+ let lastDayOfMonth = normalizeDate(m.endOf('month'));
186
+ let lastDay = normalizeDate(lastDayOfMonth.endOf('week'));
187
+
188
+ let n = firstDay.plus({ days: 1 });
189
+ let res: DateTime[] = [firstDay];
190
+
191
+ while (n < lastDay) {
192
+ res.push(n);
193
+ n = n.plus({ days: 1 });
194
+ }
195
+
196
+ return res;
197
+ };
198
+
199
+ const hiddenWeekNum = (m: DateTime, w: DateTime): boolean => {
200
+ let lastDayOfMonth = m.endOf('month');
201
+ let firstDayOfWeek = w.startOf('week');
202
+ return lastDayOfMonth < firstDayOfWeek;
203
+ };
204
+
205
+ onMount(() => {
206
+ occupationState.loadOccupations();
207
+ });
208
+ </script>
209
+
210
+ {#if !occupationState || occupationState.loading}
211
+ <Spinner />
212
+ {/if}
213
+
214
+ <div class="wrapper">
215
+ <section class="occuplan-wrapper" style="max-width: {maxWidth}; width: 100%;">
216
+ <header class="occupation-plan-header">
217
+ <div class="header-controls">
218
+ {#if rfMonth >= minDate}
219
+ <Button text={prevPage} clicked={prevClicked} preventDefault={true} />
220
+ {/if}
221
+ </div>
222
+ <div class="header-text">
223
+ {#if requestStart}
224
+ <span class="header-label">{arrivalLabel}:</span>
225
+ <span>{requestStart.toFormat(datePickerDateFormat)}</span>
226
+ {/if}
227
+ {#if requestEnd}
228
+ <span class="header-label">{leaveLabel}:</span>
229
+ <span>{requestEnd.toFormat(datePickerDateFormat)}</span>
230
+ {/if}
231
+ {#if requestStart && requestEnd}
232
+ <span class="header-label">({numberOfNights}: {requestedDays.length - 1})</span>
233
+ {/if}
234
+ </div>
235
+ <div class="header-controls">
236
+ {#if currentMaxDate <= maxDate}
237
+ <Button text={nextPage} clicked={nextClicked} preventDefault={true} />
238
+ {/if}
239
+ </div>
240
+ </header>
241
+ <main>
242
+ {#each months as m (`${m.year}-${m.month}`)}
243
+ <div class="month">
244
+ <header class="month-header">{monthHeader(m.month as MonthNumbers, m.year)}</header>
245
+ <div
246
+ style="
247
+ grid-template-columns: {monthGridTemplateColumns};
248
+ grid-template-rows: {monthGridTemplateRows(m)};
249
+ "
250
+ class="days"
251
+ >
252
+ <div class="weekday-header" style="grid-area: columnLegend / d1 / columnLegend / d1;">
253
+ {weekdayLabels[1]}
254
+ </div>
255
+ <div class="weekday-header" style="grid-area: columnLegend / d2 / columnLegend / d2;">
256
+ {weekdayLabels[2]}
257
+ </div>
258
+ <div class="weekday-header" style="grid-area: columnLegend / d3 / columnLegend / d3;">
259
+ {weekdayLabels[3]}
260
+ </div>
261
+ <div class="weekday-header" style="grid-area: columnLegend / d4 / columnLegend / d4;">
262
+ {weekdayLabels[4]}
263
+ </div>
264
+ <div class="weekday-header" style="grid-area: columnLegend / d5 / columnLegend / d5;">
265
+ {weekdayLabels[5]}
266
+ </div>
267
+ <div class="weekday-header" style="grid-area: columnLegend / d6 / columnLegend / d6;">
268
+ {weekdayLabels[6]}
269
+ </div>
270
+ <div class="weekday-header" style="grid-area: columnLegend / d7 / columnLegend / d7;">
271
+ {weekdayLabels[7]}
272
+ </div>
273
+
274
+ {#each days(m) as d (`${d.year}-${d.month}-${d.day}`)}
275
+ <div
276
+ class:weekend={[6, 7].includes(d.weekday)}
277
+ class:other-month={m.month !== d.month}
278
+ class="day"
279
+ style="
280
+ grid-area: w{d.weekNumber} / d{d.weekday} / w{d.weekNumber} / d{d.weekday};
281
+ {occupationState?.occupationStyle(
282
+ { day: d.day, month: d.month as MonthNumbers, year: d.year },
283
+ false,
284
+ maxDate,
285
+ )}
286
+ "
287
+ >
288
+ {#if !occupationState?.dayOccupied(d)}
289
+ {#if showArrival && occupationState?.endingOccupation(d) && !occupationState.startingOccupation(d)}
290
+ {#if (earliestStart && earliestStart > d) || (latestEnd && latestEnd < d)}
291
+ {d.day}
292
+ {:else}
293
+ <button
294
+ class:start={requestStart?.toISO() == d.toISO()}
295
+ class="request-button"
296
+ class:request={requestedDays.indexOf(d.toISO() as string) != -1}
297
+ onclick={(e: Event) => {
298
+ e.preventDefault();
299
+ requestClicked(d);
300
+ }}>{d.day}</button
301
+ >
302
+ {/if}
303
+ {:else if (earliestStart && earliestStart > d) || (latestEnd && latestEnd < d)}
304
+ {d.day}
305
+ {:else}
306
+ <button
307
+ class:start={requestStart?.toISO() == d.toISO()}
308
+ class:end={requestEnd?.toISO() == d.toISO()}
309
+ class:request={requestedDays.includes(d.toISO() as string)}
310
+ class="request-button"
311
+ onmouseover={() => requestHovering(d)}
312
+ onfocus={() => requestHovering(d)}
313
+ onclick={(e: Event) => {
314
+ e.preventDefault();
315
+ requestClicked(d);
316
+ }}>{d.day}</button
317
+ >
318
+ {/if}
319
+ {:else if showLeave && occupationState?.startingOccupation(d) && !occupationState.endingOccupation(d) && requestStart}
320
+ {#if (earliestStart && earliestStart > d) || (latestEnd && latestEnd < d)}
321
+ {d.day}
322
+ {:else}
323
+ <button
324
+ class:end={requestEnd?.toISO() == d.toISO()}
325
+ class:request={requestedDays.includes(d.toISO() as string)}
326
+ class="request-button"
327
+ onclick={(e: Event) => {
328
+ e.preventDefault();
329
+ requestClicked(d);
330
+ }}>{d.day}</button
331
+ >
332
+ {/if}
333
+ {:else}
334
+ {d.day}
335
+ {/if}
336
+ </div>
337
+ {/each}
338
+
339
+ {#each weeks(m) as w (`${w.year}-${w.month}-${w.weekNumber}`)}
340
+ <div
341
+ class:hidden={hiddenWeekNum(m, w)}
342
+ class="week-number"
343
+ style="grid-area: w{w.weekNumber} / rowLegend / w{w.weekNumber} / rowLegend;"
344
+ >
345
+ {w.weekNumber}
346
+ </div>
347
+ {/each}
348
+ </div>
349
+ </div>
350
+ {/each}
351
+ </main>
352
+ <footer>
353
+ <div class="legend">
354
+ <span>{typeLabels['one']}</span>
355
+ <div
356
+ class="legend-entry-marker"
357
+ style="background-color: var(--occupation-type-1-bg-color);"
358
+ >
359
+ &nbsp;
360
+ </div>
361
+
362
+ <span>{typeLabels['three']}</span>
363
+ <div
364
+ class="legend-entry-marker"
365
+ style="background-color: rgba(from var(--occupation-type-3-bg-color) r g b / 0.2);"
366
+ >
367
+ &nbsp;
368
+ </div>
369
+
370
+ <span>{typeLabels['two']}</span>
371
+ <div
372
+ class="legend-entry-marker"
373
+ style="background-color: var(--occupation-type-2-bg-color);"
374
+ >
375
+ &nbsp;
376
+ </div>
377
+ </div>
378
+ <div class="footer-controls">
379
+ <Button
380
+ clicked={(e: Event) => {
381
+ e.preventDefault();
382
+ if (requestStart && requestEnd) dateSelected(requestStart, requestEnd);
383
+ }}
384
+ enabled={!!requestStart && !!requestEnd}
385
+ iconName="save"
386
+ size={2.2}
387
+ stopPropagation={true}
388
+ />
389
+ <Button
390
+ clicked={(e: Event) => {
391
+ e.preventDefault();
392
+ aborted();
393
+ }}
394
+ iconName="abort"
395
+ size={2.2}
396
+ stopPropagation={true}
397
+ />
398
+ </div>
399
+ </footer>
400
+ </section>
401
+ </div>
402
+
403
+ <style>
404
+ .wrapper {
405
+ position: relative;
406
+ display: flex;
407
+ justify-content: center;
408
+ }
409
+
410
+ .legend-entry-marker {
411
+ outline: var(--occuplan-grid-border);
412
+ height: 1.1rem;
413
+ width: 1.1rem;
414
+ }
415
+
416
+ .footer-controls {
417
+ display: flex;
418
+ gap: 0.5rem;
419
+ align-items: flex-end;
420
+ }
421
+
422
+ .month-header {
423
+ text-align: center;
424
+ color: var(--occuplan-main-font-color);
425
+ }
426
+
427
+ .hidden {
428
+ display: none;
429
+ }
430
+
431
+ .occupation-plan-header {
432
+ display: flex;
433
+ flex-direction: row;
434
+ width: 100%;
435
+ justify-content: space-between;
436
+ }
437
+
438
+ .week-number {
439
+ display: flex;
440
+ justify-content: center;
441
+ align-items: center;
442
+
443
+ font-style: italic;
444
+ font-weight: lighter;
445
+ background-color: var(--occuplan-weeknum-bg-color);
446
+ color: var(--occuplan-weeknum-font-color);
447
+ }
448
+
449
+ .weekday-header {
450
+ font-size: 1rem;
451
+ display: flex;
452
+ justify-content: center;
453
+ align-items: center;
454
+ background-color: var(--occuplan-days-header-bg-color);
455
+ color: var(--occuplan-days-header-font-color);
456
+ grid-area: columnLegend / d1 / columnLegend / d1;
457
+ }
458
+
459
+ .day {
460
+ text-align: center;
461
+ position: relative;
462
+ font-size: 1.15rem;
463
+ padding: 0.2rem;
464
+ color: var(--occuplan-main-font-color);
465
+ }
466
+
467
+ .weekend {
468
+ font-weight: bold;
469
+ }
470
+
471
+ .occuplan-wrapper {
472
+ height: 100%;
473
+ width: calc(100% - 0.5rem);
474
+ padding: 0.5rem;
475
+ margin: 0;
476
+ display: flex;
477
+ flex-direction: column;
478
+ flex-wrap: nowrap;
479
+ align-items: center;
480
+
481
+ border: var(--occuplan-main-border);
482
+ color: var(--occuplan-main-font-color);
483
+ background-color: var(--occuplan-main-bg-color);
484
+ }
485
+
486
+ main {
487
+ display: grid;
488
+ grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
489
+ gap: 1rem;
490
+ width: 100%;
491
+ }
492
+
493
+ .month {
494
+ display: flex;
495
+ flex-direction: column;
496
+ }
497
+
498
+ .days {
499
+ display: grid;
500
+ }
501
+
502
+ footer {
503
+ display: flex;
504
+ flex-direction: row;
505
+ align-items: stretch;
506
+ justify-content: space-between;
507
+ width: 100%;
508
+ margin-top: 1rem;
509
+ }
510
+
511
+ .legend {
512
+ display: grid;
513
+ row-gap: 0.2rem;
514
+ grid-template-columns: [label] 1fr [marker] 1rem;
515
+ column-gap: 1rem;
516
+ text-transform: capitalize;
517
+ font-variant: small-caps;
518
+ span {
519
+ color: var(--occuplan-main-font-color);
520
+ }
521
+ }
522
+ .header-controls {
523
+ width: 2rem;
524
+ }
525
+
526
+ .header-text {
527
+ display: flex;
528
+ gap: 0.2rem;
529
+ align-items: center;
530
+ font-size: 1.05rem;
531
+ font-weight: bold;
532
+ span {
533
+ color: var(--occuplan-main-font-color);
534
+ }
535
+ }
536
+
537
+ .header-label {
538
+ font-size: 0.9rem;
539
+ font-weight: normal;
540
+ }
541
+
542
+ .request-button {
543
+ line-height: 1.35rem;
544
+ font-size: 1.35rem;
545
+ cursor: pointer;
546
+ box-sizing: border-box;
547
+
548
+ position: absolute;
549
+ top: 0;
550
+ left: 0;
551
+ right: 0;
552
+ bottom: 0;
553
+
554
+ background-color: rgba(from var(--occupation-type-3-bg-color) r g b / 0.1);
555
+ color: var(--occupation-type-3-bg-color);
556
+ border: 1px solid rgba(from var(--occupation-type-3-font-color) r g b / 0.1);
557
+ }
558
+ .request-button:hover {
559
+ background-color: var(--occupation-type-3-font-color);
560
+ color: var(--occupation-type-3-bg-color);
561
+ border: 1px solid var(--occupation-type-3-bg-color);
562
+ }
563
+
564
+ .request-button.start {
565
+ border: 0.2rem solid var(--accept-font-color);
566
+ }
567
+ .request-button.end {
568
+ border: 0.2rem solid var(--alert-font-color);
569
+ }
570
+
571
+ .request-button.request {
572
+ background-color: var(--occupation-type-2-bg-color);
573
+ color: var(--occupation-type-font-bg-color);
574
+ }
575
+ </style>