accomadesc 0.4.7 → 0.4.9

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