@weaver-apps/calendar 0.1.2 → 0.2.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.
- package/README.md +42 -18
- package/fesm2022/weaver-apps-calendar.mjs +276 -68
- package/fesm2022/weaver-apps-calendar.mjs.map +1 -1
- package/package.json +10 -6
- package/types/weaver-apps-calendar.d.ts +7 -1
- /package/i18n/{calendar → weaver-apps-calendar}/de.json +0 -0
- /package/i18n/{calendar → weaver-apps-calendar}/en.json +0 -0
package/README.md
CHANGED
|
@@ -7,10 +7,15 @@ and holds no key.
|
|
|
7
7
|
## Compose it
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
import {
|
|
11
|
+
calendarPluginId,
|
|
12
|
+
createCalendarPlugin,
|
|
13
|
+
createHttpCalendarGateway,
|
|
14
|
+
provideCalendarGateway,
|
|
15
|
+
} from '@weaver-apps/calendar';
|
|
16
|
+
|
|
17
|
+
provideTranslationNamespaces(calendarPluginId),
|
|
18
|
+
provideCapabilityGrants({ [calendarPluginId]: ['contributions', 'navigation', 'ui'] }),
|
|
14
19
|
...providePlugins(createCalendarPlugin({ railItem: true })),
|
|
15
20
|
provideCalendarGateway(createHttpCalendarGateway({ baseUrl: '/weaver-apps' })),
|
|
16
21
|
```
|
|
@@ -18,17 +23,36 @@ provideCalendarGateway(createHttpCalendarGateway({ baseUrl: '/weaver-apps' })),
|
|
|
18
23
|
- `baseUrl` is a same-origin path the host's backend forwards to the server, adding the forwarding key and
|
|
19
24
|
the signed-in person's tenant (`X-API-Key`, `X-Act-For-Tenant`). The gateway appends the server's own
|
|
20
25
|
routes, such as `/calendar/calendars/{id}/appointments`.
|
|
21
|
-
- Serve the translations: the package carries them under `i18n/calendar`; add an assets glob
|
|
22
|
-
`node_modules/@weaver-apps/calendar/i18n/calendar` to `i18n/calendar`.
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
- Serve the translations: the package carries them under `i18n/weaver-apps-calendar`; add an assets glob
|
|
27
|
+
that copies `node_modules/@weaver-apps/calendar/i18n/weaver-apps-calendar` to `i18n/weaver-apps-calendar`.
|
|
28
|
+
- Install AG Grid, a peer dependency the list is drawn with: `npm i ag-grid-community@^36.2 ag-grid-angular@^36.2
|
|
29
|
+
@ag-grid-community/locale@^36.2`. `@weaver-apps/data-grid`, the grid both Weaver Apps weavers share, comes along as a
|
|
30
|
+
dependency; the host composes nothing for it.
|
|
31
|
+
- If the host compiles the shell's theme with Tailwind, add the package and `@weaver-apps/data-grid` as an `@source`.
|
|
32
|
+
|
|
33
|
+
## Names in the host
|
|
34
|
+
|
|
35
|
+
The plugin id is `weaver-apps-calendar` (`calendarPluginId`). Every id the plugin registers and every icon
|
|
36
|
+
it contributes starts with `weaver-apps-calendar.`, its routes live under `weaver-apps-calendar/`, and its
|
|
37
|
+
translations are the namespace `weaver-apps-calendar`. The ids are exported as `calendarContributionIds`.
|
|
38
|
+
|
|
39
|
+
| Id | Path | What it shows |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| `weaver-apps-calendar.calendars` | `weaver-apps-calendar` | the tenant's calendars |
|
|
42
|
+
| `weaver-apps-calendar.calendar` | `weaver-apps-calendar/:calendarId` | one calendar's appointments, a week at a time |
|
|
43
|
+
| `weaver-apps-calendar.rail` | | the rail item, only with `railItem: true` |
|
|
44
|
+
|
|
45
|
+
A calendar's week is a grid: a click on a column header sorts it (earliest first by default), and the status
|
|
46
|
+
column filters by any number of statuses.
|
|
47
|
+
|
|
48
|
+
`calendarPath(id)` builds the address of one calendar; a host that shows one entry per calendar navigates
|
|
49
|
+
there. `createCalendarPlugin({ railItem: true })` adds a rail item that opens the list; without it the host
|
|
50
|
+
decides where the calendar is reached from.
|
|
51
|
+
|
|
52
|
+
## Moving from 0.1.x
|
|
53
|
+
|
|
54
|
+
0.2.0 draws the list with AG Grid, so a host installs it (see above). It also renamed everything the plugin puts
|
|
55
|
+
into a host; a host composing it changes three places: the key of
|
|
56
|
+
its capability grant (`calendar` becomes `weaver-apps-calendar`), the translation namespace it declares, and
|
|
57
|
+
the folder it serves the translations from (`i18n/calendar` becomes `i18n/weaver-apps-calendar`). Addresses
|
|
58
|
+
saved under `calendar/...` no longer resolve; build them with `calendarPath`.
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import * as i0 from "@angular/core";
|
|
2
|
-
import { CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, Component, DestroyRef, InjectionToken, computed, inject, input, output, resource, signal } from "@angular/core";
|
|
3
|
-
import { TranslocoPipe, TranslocoService } from "@jsverse/transloco";
|
|
2
|
+
import { CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, Component, DestroyRef, InjectionToken, computed, effect, inject, input, output, resource, signal } from "@angular/core";
|
|
3
|
+
import { TranslocoPipe, TranslocoService, translateObjectSignal } from "@jsverse/transloco";
|
|
4
4
|
import { toSignal } from "@angular/core/rxjs-interop";
|
|
5
5
|
import { ActivatedRoute } from "@angular/router";
|
|
6
6
|
import { map } from "rxjs";
|
|
7
7
|
import { DialogRef } from "@loomweaver/plugin-sdk";
|
|
8
|
+
import { DataGrid, ListFilter } from "@weaver-apps/data-grid";
|
|
8
9
|
var CalendarGatewayError = class extends Error {
|
|
9
10
|
status;
|
|
10
11
|
constructor(status) {
|
|
@@ -36,7 +37,19 @@ var CalendarHost = class {
|
|
|
36
37
|
}
|
|
37
38
|
};
|
|
38
39
|
const calendarHost = new CalendarHost();
|
|
39
|
-
const
|
|
40
|
+
const calendarPluginId = "weaver-apps-calendar";
|
|
41
|
+
const calendarContributionIds = {
|
|
42
|
+
calendars: `${calendarPluginId}.calendars`,
|
|
43
|
+
calendar: `${calendarPluginId}.calendar`,
|
|
44
|
+
rail: `${calendarPluginId}.rail`
|
|
45
|
+
};
|
|
46
|
+
const calendarIconNames = {
|
|
47
|
+
calendar: `${calendarPluginId}.calendar`,
|
|
48
|
+
previous: `${calendarPluginId}.previous`,
|
|
49
|
+
next: `${calendarPluginId}.next`,
|
|
50
|
+
cancel: `${calendarPluginId}.cancel`
|
|
51
|
+
};
|
|
52
|
+
const calendarListPath = calendarPluginId;
|
|
40
53
|
function calendarPath(calendarId) {
|
|
41
54
|
return `${calendarListPath}/${calendarId}`;
|
|
42
55
|
}
|
|
@@ -51,9 +64,9 @@ var CalendarListPage = class CalendarListPage {
|
|
|
51
64
|
}
|
|
52
65
|
async create() {
|
|
53
66
|
const name = await calendarHost.ui.prompt({
|
|
54
|
-
title: "calendar.calendars.create",
|
|
55
|
-
message: "calendar.calendars.createMessage",
|
|
56
|
-
confirmLabel: "calendar.calendars.createConfirm"
|
|
67
|
+
title: "weaver-apps-calendar.calendars.create",
|
|
68
|
+
message: "weaver-apps-calendar.calendars.createMessage",
|
|
69
|
+
confirmLabel: "weaver-apps-calendar.calendars.createConfirm"
|
|
57
70
|
});
|
|
58
71
|
if (!name?.trim()) return;
|
|
59
72
|
try {
|
|
@@ -61,7 +74,7 @@ var CalendarListPage = class CalendarListPage {
|
|
|
61
74
|
this.open(calendar.id);
|
|
62
75
|
} catch {
|
|
63
76
|
calendarHost.ui.toast({
|
|
64
|
-
message: "calendar.errors.create",
|
|
77
|
+
message: "weaver-apps-calendar.errors.create",
|
|
65
78
|
kind: "error"
|
|
66
79
|
});
|
|
67
80
|
}
|
|
@@ -81,7 +94,7 @@ var CalendarListPage = class CalendarListPage {
|
|
|
81
94
|
isStandalone: true,
|
|
82
95
|
selector: "wa-calendar-list-page",
|
|
83
96
|
ngImport: i0,
|
|
84
|
-
template: "<div class=\"flex h-full flex-col gap-4 overflow-auto p-6\">\n <div class=\"flex items-center justify-between gap-4\">\n <h2 class=\"text-lg font-semibold text-content\">{{ 'calendar.calendars.title' | transloco }}</h2>\n <button type=\"button\" class=\"lw-btn lw-btn--primary\" (click)=\"create()\">{{ 'calendar.calendars.create' | transloco }}</button>\n </div>\n @if (calendars.error()) {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ 'calendar.errors.load' | transloco }}</p>\n } @else if (calendars.hasValue()) {\n <ul role=\"list\" class=\"divide-y divide-border rounded-xl border border-border bg-surface\">\n @for (calendar of calendars.value(); track calendar.id) {\n <li>\n <button type=\"button\" class=\"flex w-full items-center gap-3 px-4 py-3 text-left text-sm text-content hover:bg-surface-overlay\" (click)=\"open(calendar.id)\">\n <lw-icon name=\"calendar\" size=\"1.25rem\"></lw-icon>\n <span>{{ calendar.name }}</span>\n </button>\n </li>\n } @empty {\n <li class=\"px-4 py-8 text-center text-sm text-content-faint\">{{ 'calendar.calendars.empty' | transloco }}</li>\n }\n </ul>\n }\n</div>\n",
|
|
97
|
+
template: "<div class=\"flex h-full flex-col gap-4 overflow-auto p-6\">\n <div class=\"flex items-center justify-between gap-4\">\n <h2 class=\"text-lg font-semibold text-content\">{{ 'weaver-apps-calendar.calendars.title' | transloco }}</h2>\n <button type=\"button\" class=\"lw-btn lw-btn--primary\" (click)=\"create()\">{{ 'weaver-apps-calendar.calendars.create' | transloco }}</button>\n </div>\n @if (calendars.error()) {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ 'weaver-apps-calendar.errors.load' | transloco }}</p>\n } @else if (calendars.hasValue()) {\n <ul role=\"list\" class=\"divide-y divide-border rounded-xl border border-border bg-surface\">\n @for (calendar of calendars.value(); track calendar.id) {\n <li>\n <button type=\"button\" class=\"flex w-full items-center gap-3 px-4 py-3 text-left text-sm text-content hover:bg-surface-overlay\" (click)=\"open(calendar.id)\">\n <lw-icon name=\"weaver-apps-calendar.calendar\" size=\"1.25rem\"></lw-icon>\n <span>{{ calendar.name }}</span>\n </button>\n </li>\n } @empty {\n <li class=\"px-4 py-8 text-center text-sm text-content-faint\">{{ 'weaver-apps-calendar.calendars.empty' | transloco }}</li>\n }\n </ul>\n }\n</div>\n",
|
|
85
98
|
dependencies: [{
|
|
86
99
|
kind: "pipe",
|
|
87
100
|
type: TranslocoPipe,
|
|
@@ -102,7 +115,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
102
115
|
imports: [TranslocoPipe],
|
|
103
116
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
104
117
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
105
|
-
template: "<div class=\"flex h-full flex-col gap-4 overflow-auto p-6\">\n <div class=\"flex items-center justify-between gap-4\">\n <h2 class=\"text-lg font-semibold text-content\">{{ 'calendar.calendars.title' | transloco }}</h2>\n <button type=\"button\" class=\"lw-btn lw-btn--primary\" (click)=\"create()\">{{ 'calendar.calendars.create' | transloco }}</button>\n </div>\n @if (calendars.error()) {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ 'calendar.errors.load' | transloco }}</p>\n } @else if (calendars.hasValue()) {\n <ul role=\"list\" class=\"divide-y divide-border rounded-xl border border-border bg-surface\">\n @for (calendar of calendars.value(); track calendar.id) {\n <li>\n <button type=\"button\" class=\"flex w-full items-center gap-3 px-4 py-3 text-left text-sm text-content hover:bg-surface-overlay\" (click)=\"open(calendar.id)\">\n <lw-icon name=\"calendar\" size=\"1.25rem\"></lw-icon>\n <span>{{ calendar.name }}</span>\n </button>\n </li>\n } @empty {\n <li class=\"px-4 py-8 text-center text-sm text-content-faint\">{{ 'calendar.calendars.empty' | transloco }}</li>\n }\n </ul>\n }\n</div>\n"
|
|
118
|
+
template: "<div class=\"flex h-full flex-col gap-4 overflow-auto p-6\">\n <div class=\"flex items-center justify-between gap-4\">\n <h2 class=\"text-lg font-semibold text-content\">{{ 'weaver-apps-calendar.calendars.title' | transloco }}</h2>\n <button type=\"button\" class=\"lw-btn lw-btn--primary\" (click)=\"create()\">{{ 'weaver-apps-calendar.calendars.create' | transloco }}</button>\n </div>\n @if (calendars.error()) {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ 'weaver-apps-calendar.errors.load' | transloco }}</p>\n } @else if (calendars.hasValue()) {\n <ul role=\"list\" class=\"divide-y divide-border rounded-xl border border-border bg-surface\">\n @for (calendar of calendars.value(); track calendar.id) {\n <li>\n <button type=\"button\" class=\"flex w-full items-center gap-3 px-4 py-3 text-left text-sm text-content hover:bg-surface-overlay\" (click)=\"open(calendar.id)\">\n <lw-icon name=\"weaver-apps-calendar.calendar\" size=\"1.25rem\"></lw-icon>\n <span>{{ calendar.name }}</span>\n </button>\n </li>\n } @empty {\n <li class=\"px-4 py-8 text-center text-sm text-content-faint\">{{ 'weaver-apps-calendar.calendars.empty' | transloco }}</li>\n }\n </ul>\n }\n</div>\n"
|
|
106
119
|
}]
|
|
107
120
|
}]
|
|
108
121
|
});
|
|
@@ -218,7 +231,7 @@ var AppointmentDialog = class AppointmentDialog {
|
|
|
218
231
|
} catch {
|
|
219
232
|
this.problem.set({
|
|
220
233
|
kind: "message",
|
|
221
|
-
key: "calendar.errors.save"
|
|
234
|
+
key: "weaver-apps-calendar.errors.save"
|
|
222
235
|
});
|
|
223
236
|
} finally {
|
|
224
237
|
this.saving.set(false);
|
|
@@ -230,14 +243,14 @@ var AppointmentDialog = class AppointmentDialog {
|
|
|
230
243
|
if (!this.title().trim()) {
|
|
231
244
|
this.problem.set({
|
|
232
245
|
kind: "message",
|
|
233
|
-
key: "calendar.dialog.titleRequired"
|
|
246
|
+
key: "weaver-apps-calendar.dialog.titleRequired"
|
|
234
247
|
});
|
|
235
248
|
return null;
|
|
236
249
|
}
|
|
237
250
|
if (!startsAt || !endsAt || endsAt <= startsAt) {
|
|
238
251
|
this.problem.set({
|
|
239
252
|
kind: "message",
|
|
240
|
-
key: "calendar.dialog.periodInvalid"
|
|
253
|
+
key: "weaver-apps-calendar.dialog.periodInvalid"
|
|
241
254
|
});
|
|
242
255
|
return null;
|
|
243
256
|
}
|
|
@@ -274,24 +287,24 @@ var AppointmentDialog = class AppointmentDialog {
|
|
|
274
287
|
case "closed":
|
|
275
288
|
this.problem.set({
|
|
276
289
|
kind: "message",
|
|
277
|
-
key: "calendar.dialog.closed"
|
|
290
|
+
key: "weaver-apps-calendar.dialog.closed"
|
|
278
291
|
});
|
|
279
292
|
return;
|
|
280
293
|
case "changed":
|
|
281
294
|
this.problem.set({
|
|
282
295
|
kind: "message",
|
|
283
|
-
key: "calendar.dialog.changed"
|
|
296
|
+
key: "weaver-apps-calendar.dialog.changed"
|
|
284
297
|
});
|
|
285
298
|
return;
|
|
286
299
|
case "invalid":
|
|
287
300
|
this.problem.set({
|
|
288
301
|
kind: "message",
|
|
289
|
-
key: "calendar.dialog.invalid"
|
|
302
|
+
key: "weaver-apps-calendar.dialog.invalid"
|
|
290
303
|
});
|
|
291
304
|
return;
|
|
292
305
|
default: this.problem.set({
|
|
293
306
|
kind: "message",
|
|
294
|
-
key: "calendar.dialog.missing"
|
|
307
|
+
key: "weaver-apps-calendar.dialog.missing"
|
|
295
308
|
});
|
|
296
309
|
}
|
|
297
310
|
}
|
|
@@ -313,7 +326,7 @@ var AppointmentDialog = class AppointmentDialog {
|
|
|
313
326
|
isStandalone: true,
|
|
314
327
|
selector: "wa-appointment-dialog",
|
|
315
328
|
ngImport: i0,
|
|
316
|
-
template: "<form class=\"flex flex-col gap-4\" (submit)=\"$event.preventDefault(); save()\">\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.title' | transloco }}\n <input class=\"lw-field\" required [value]=\"title()\" (input)=\"bind(title, $event)\" />\n </label>\n\n <div class=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.date' | transloco }}\n <input class=\"lw-field\" type=\"date\" required [value]=\"date()\" (input)=\"bindDate($event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.start' | transloco }}\n <input class=\"lw-field\" type=\"time\" required [value]=\"start()\" (input)=\"bind(start, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.endDate' | transloco }}\n <input class=\"lw-field\" type=\"date\" required [value]=\"endDate()\" (input)=\"bind(endDate, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.end' | transloco }}\n <input class=\"lw-field\" type=\"time\" required [value]=\"end()\" (input)=\"bind(end, $event)\" />\n </label>\n </div>\n\n <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.contactName' | transloco }}\n <input class=\"lw-field\" [value]=\"contactName()\" (input)=\"bind(contactName, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.contactPhone' | transloco }}\n <input class=\"lw-field\" type=\"tel\" [value]=\"contactPhone()\" (input)=\"bind(contactPhone, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.contactEmail' | transloco }}\n <input class=\"lw-field\" type=\"email\" [value]=\"contactEmail()\" (input)=\"bind(contactEmail, $event)\" />\n </label>\n </div>\n\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.location' | transloco }}\n <input class=\"lw-field\" [value]=\"location()\" (input)=\"bind(location, $event)\" />\n </label>\n\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.description' | transloco }}\n <textarea class=\"lw-field\" rows=\"3\" [value]=\"description()\" (input)=\"bind(description, $event)\"></textarea>\n </label>\n\n @if (editing) {\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.status' | transloco }}\n <select class=\"lw-field\" [value]=\"status()\" (change)=\"chooseStatus($event)\">\n @for (choice of statuses; track choice) {\n <option [value]=\"choice\" [selected]=\"choice === status()\">{{ 'calendar.status.' + choice | transloco }}</option>\n }\n </select>\n </label>\n }\n\n @switch (problem().kind) {\n @case ('message') {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ problemKey() | transloco }}</p>\n }\n @case ('conflict') {\n <div class=\"text-sm text-negative\" role=\"alert\">\n <p>{{ 'calendar.dialog.conflict' | transloco }}</p>\n <ul class=\"list-disc pl-5\">\n @for (time of conflictTimes(); track time) {\n <li>{{ time }}</li>\n }\n </ul>\n </div>\n }\n }\n\n <div class=\"flex justify-end gap-2\">\n <button type=\"button\" class=\"lw-btn lw-btn--default\" (click)=\"close()\">{{ 'calendar.dialog.cancel' | transloco }}</button>\n <button type=\"submit\" class=\"lw-btn lw-btn--primary\" [disabled]=\"saving()\">{{ 'calendar.dialog.save' | transloco }}</button>\n </div>\n</form>\n",
|
|
329
|
+
template: "<form class=\"flex flex-col gap-4\" (submit)=\"$event.preventDefault(); save()\">\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.title' | transloco }}\n <input class=\"lw-field\" required [value]=\"title()\" (input)=\"bind(title, $event)\" />\n </label>\n\n <div class=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.date' | transloco }}\n <input class=\"lw-field\" type=\"date\" required [value]=\"date()\" (input)=\"bindDate($event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.start' | transloco }}\n <input class=\"lw-field\" type=\"time\" required [value]=\"start()\" (input)=\"bind(start, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.endDate' | transloco }}\n <input class=\"lw-field\" type=\"date\" required [value]=\"endDate()\" (input)=\"bind(endDate, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.end' | transloco }}\n <input class=\"lw-field\" type=\"time\" required [value]=\"end()\" (input)=\"bind(end, $event)\" />\n </label>\n </div>\n\n <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.contactName' | transloco }}\n <input class=\"lw-field\" [value]=\"contactName()\" (input)=\"bind(contactName, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.contactPhone' | transloco }}\n <input class=\"lw-field\" type=\"tel\" [value]=\"contactPhone()\" (input)=\"bind(contactPhone, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.contactEmail' | transloco }}\n <input class=\"lw-field\" type=\"email\" [value]=\"contactEmail()\" (input)=\"bind(contactEmail, $event)\" />\n </label>\n </div>\n\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.location' | transloco }}\n <input class=\"lw-field\" [value]=\"location()\" (input)=\"bind(location, $event)\" />\n </label>\n\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.description' | transloco }}\n <textarea class=\"lw-field\" rows=\"3\" [value]=\"description()\" (input)=\"bind(description, $event)\"></textarea>\n </label>\n\n @if (editing) {\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.status' | transloco }}\n <select class=\"lw-field\" [value]=\"status()\" (change)=\"chooseStatus($event)\">\n @for (choice of statuses; track choice) {\n <option [value]=\"choice\" [selected]=\"choice === status()\">{{ 'weaver-apps-calendar.status.' + choice | transloco }}</option>\n }\n </select>\n </label>\n }\n\n @switch (problem().kind) {\n @case ('message') {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ problemKey() | transloco }}</p>\n }\n @case ('conflict') {\n <div class=\"text-sm text-negative\" role=\"alert\">\n <p>{{ 'weaver-apps-calendar.dialog.conflict' | transloco }}</p>\n <ul class=\"list-disc pl-5\">\n @for (time of conflictTimes(); track time) {\n <li>{{ time }}</li>\n }\n </ul>\n </div>\n }\n }\n\n <div class=\"flex justify-end gap-2\">\n <button type=\"button\" class=\"lw-btn lw-btn--default\" (click)=\"close()\">{{ 'weaver-apps-calendar.dialog.cancel' | transloco }}</button>\n <button type=\"submit\" class=\"lw-btn lw-btn--primary\" [disabled]=\"saving()\">{{ 'weaver-apps-calendar.dialog.save' | transloco }}</button>\n </div>\n</form>\n",
|
|
317
330
|
dependencies: [{
|
|
318
331
|
kind: "pipe",
|
|
319
332
|
type: TranslocoPipe,
|
|
@@ -333,17 +346,22 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
333
346
|
selector: "wa-appointment-dialog",
|
|
334
347
|
imports: [TranslocoPipe],
|
|
335
348
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
336
|
-
template: "<form class=\"flex flex-col gap-4\" (submit)=\"$event.preventDefault(); save()\">\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.title' | transloco }}\n <input class=\"lw-field\" required [value]=\"title()\" (input)=\"bind(title, $event)\" />\n </label>\n\n <div class=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.date' | transloco }}\n <input class=\"lw-field\" type=\"date\" required [value]=\"date()\" (input)=\"bindDate($event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.start' | transloco }}\n <input class=\"lw-field\" type=\"time\" required [value]=\"start()\" (input)=\"bind(start, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.endDate' | transloco }}\n <input class=\"lw-field\" type=\"date\" required [value]=\"endDate()\" (input)=\"bind(endDate, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.end' | transloco }}\n <input class=\"lw-field\" type=\"time\" required [value]=\"end()\" (input)=\"bind(end, $event)\" />\n </label>\n </div>\n\n <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.contactName' | transloco }}\n <input class=\"lw-field\" [value]=\"contactName()\" (input)=\"bind(contactName, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.contactPhone' | transloco }}\n <input class=\"lw-field\" type=\"tel\" [value]=\"contactPhone()\" (input)=\"bind(contactPhone, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.contactEmail' | transloco }}\n <input class=\"lw-field\" type=\"email\" [value]=\"contactEmail()\" (input)=\"bind(contactEmail, $event)\" />\n </label>\n </div>\n\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.location' | transloco }}\n <input class=\"lw-field\" [value]=\"location()\" (input)=\"bind(location, $event)\" />\n </label>\n\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.description' | transloco }}\n <textarea class=\"lw-field\" rows=\"3\" [value]=\"description()\" (input)=\"bind(description, $event)\"></textarea>\n </label>\n\n @if (editing) {\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.status' | transloco }}\n <select class=\"lw-field\" [value]=\"status()\" (change)=\"chooseStatus($event)\">\n @for (choice of statuses; track choice) {\n <option [value]=\"choice\" [selected]=\"choice === status()\">{{ 'calendar.status.' + choice | transloco }}</option>\n }\n </select>\n </label>\n }\n\n @switch (problem().kind) {\n @case ('message') {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ problemKey() | transloco }}</p>\n }\n @case ('conflict') {\n <div class=\"text-sm text-negative\" role=\"alert\">\n <p>{{ 'calendar.dialog.conflict' | transloco }}</p>\n <ul class=\"list-disc pl-5\">\n @for (time of conflictTimes(); track time) {\n <li>{{ time }}</li>\n }\n </ul>\n </div>\n }\n }\n\n <div class=\"flex justify-end gap-2\">\n <button type=\"button\" class=\"lw-btn lw-btn--default\" (click)=\"close()\">{{ 'calendar.dialog.cancel' | transloco }}</button>\n <button type=\"submit\" class=\"lw-btn lw-btn--primary\" [disabled]=\"saving()\">{{ 'calendar.dialog.save' | transloco }}</button>\n </div>\n</form>\n"
|
|
349
|
+
template: "<form class=\"flex flex-col gap-4\" (submit)=\"$event.preventDefault(); save()\">\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.title' | transloco }}\n <input class=\"lw-field\" required [value]=\"title()\" (input)=\"bind(title, $event)\" />\n </label>\n\n <div class=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.date' | transloco }}\n <input class=\"lw-field\" type=\"date\" required [value]=\"date()\" (input)=\"bindDate($event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.start' | transloco }}\n <input class=\"lw-field\" type=\"time\" required [value]=\"start()\" (input)=\"bind(start, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.endDate' | transloco }}\n <input class=\"lw-field\" type=\"date\" required [value]=\"endDate()\" (input)=\"bind(endDate, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.end' | transloco }}\n <input class=\"lw-field\" type=\"time\" required [value]=\"end()\" (input)=\"bind(end, $event)\" />\n </label>\n </div>\n\n <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.contactName' | transloco }}\n <input class=\"lw-field\" [value]=\"contactName()\" (input)=\"bind(contactName, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.contactPhone' | transloco }}\n <input class=\"lw-field\" type=\"tel\" [value]=\"contactPhone()\" (input)=\"bind(contactPhone, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.contactEmail' | transloco }}\n <input class=\"lw-field\" type=\"email\" [value]=\"contactEmail()\" (input)=\"bind(contactEmail, $event)\" />\n </label>\n </div>\n\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.location' | transloco }}\n <input class=\"lw-field\" [value]=\"location()\" (input)=\"bind(location, $event)\" />\n </label>\n\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.description' | transloco }}\n <textarea class=\"lw-field\" rows=\"3\" [value]=\"description()\" (input)=\"bind(description, $event)\"></textarea>\n </label>\n\n @if (editing) {\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.status' | transloco }}\n <select class=\"lw-field\" [value]=\"status()\" (change)=\"chooseStatus($event)\">\n @for (choice of statuses; track choice) {\n <option [value]=\"choice\" [selected]=\"choice === status()\">{{ 'weaver-apps-calendar.status.' + choice | transloco }}</option>\n }\n </select>\n </label>\n }\n\n @switch (problem().kind) {\n @case ('message') {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ problemKey() | transloco }}</p>\n }\n @case ('conflict') {\n <div class=\"text-sm text-negative\" role=\"alert\">\n <p>{{ 'weaver-apps-calendar.dialog.conflict' | transloco }}</p>\n <ul class=\"list-disc pl-5\">\n @for (time of conflictTimes(); track time) {\n <li>{{ time }}</li>\n }\n </ul>\n </div>\n }\n }\n\n <div class=\"flex justify-end gap-2\">\n <button type=\"button\" class=\"lw-btn lw-btn--default\" (click)=\"close()\">{{ 'weaver-apps-calendar.dialog.cancel' | transloco }}</button>\n <button type=\"submit\" class=\"lw-btn lw-btn--primary\" [disabled]=\"saving()\">{{ 'weaver-apps-calendar.dialog.save' | transloco }}</button>\n </div>\n</form>\n"
|
|
337
350
|
}]
|
|
338
351
|
}]
|
|
339
352
|
});
|
|
340
|
-
var
|
|
341
|
-
|
|
342
|
-
locale =
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
353
|
+
var AppointmentCell = class AppointmentCell {
|
|
354
|
+
params = null;
|
|
355
|
+
locale = signal("en", ...ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []);
|
|
356
|
+
kind = signal("title", ...ngDevMode ? [{ debugName: "kind" }] : /* istanbul ignore next */ []);
|
|
357
|
+
appointment = signal(null, ...ngDevMode ? [{ debugName: "appointment" }] : /* istanbul ignore next */ []);
|
|
358
|
+
cancelled = computed(() => this.appointment()?.status === "cancelled", ...ngDevMode ? [{ debugName: "cancelled" }] : /* istanbul ignore next */ []);
|
|
359
|
+
when = computed(() => {
|
|
360
|
+
const appointment = this.appointment();
|
|
361
|
+
if (!appointment) return {
|
|
362
|
+
day: "",
|
|
363
|
+
hours: ""
|
|
364
|
+
};
|
|
347
365
|
const day = new Intl.DateTimeFormat(this.locale(), {
|
|
348
366
|
weekday: "short",
|
|
349
367
|
day: "2-digit",
|
|
@@ -354,14 +372,200 @@ var AppointmentTable = class AppointmentTable {
|
|
|
354
372
|
hour: "2-digit",
|
|
355
373
|
minute: "2-digit"
|
|
356
374
|
});
|
|
357
|
-
return
|
|
358
|
-
appointment,
|
|
375
|
+
return {
|
|
359
376
|
day: day.format(new Date(appointment.start)),
|
|
360
|
-
hours: `${time.format(new Date(appointment.start))} – ${time.format(new Date(appointment.end))}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
377
|
+
hours: `${time.format(new Date(appointment.start))} – ${time.format(new Date(appointment.end))}`
|
|
378
|
+
};
|
|
379
|
+
}, ...ngDevMode ? [{ debugName: "when" }] : /* istanbul ignore next */ []);
|
|
380
|
+
agInit(params) {
|
|
381
|
+
this.params = params;
|
|
382
|
+
this.kind.set(params.kind);
|
|
383
|
+
this.appointment.set(params.data ?? null);
|
|
384
|
+
this.locale.set(params.locale());
|
|
385
|
+
}
|
|
386
|
+
refresh(params) {
|
|
387
|
+
this.agInit(params);
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
edit() {
|
|
391
|
+
this.hand(this.params?.edit);
|
|
392
|
+
}
|
|
393
|
+
cancel() {
|
|
394
|
+
this.hand(this.params?.cancel);
|
|
395
|
+
}
|
|
396
|
+
remove() {
|
|
397
|
+
this.hand(this.params?.remove);
|
|
398
|
+
}
|
|
399
|
+
hand(to) {
|
|
400
|
+
const appointment = this.appointment();
|
|
401
|
+
if (appointment) to?.(appointment);
|
|
402
|
+
}
|
|
403
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
404
|
+
minVersion: "12.0.0",
|
|
405
|
+
version: "22.2.3",
|
|
406
|
+
ngImport: i0,
|
|
407
|
+
type: AppointmentCell,
|
|
408
|
+
deps: [],
|
|
409
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
410
|
+
});
|
|
411
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
412
|
+
minVersion: "17.0.0",
|
|
413
|
+
version: "22.2.3",
|
|
414
|
+
type: AppointmentCell,
|
|
415
|
+
isStandalone: true,
|
|
416
|
+
selector: "wa-appointment-cell",
|
|
417
|
+
ngImport: i0,
|
|
418
|
+
template: "@if (appointment(); as appointment) {\n @switch (kind()) {\n @case ('when') {\n <span class=\"font-medium\" [class.text-content]=\"!cancelled()\" [class.line-through]=\"cancelled()\">{{ when().day }}</span>\n <span class=\"ml-2 text-content-faint\" [class.line-through]=\"cancelled()\">{{ when().hours }}</span>\n }\n @case ('title') {\n <span class=\"font-medium\" [class.text-content]=\"!cancelled()\" [class.line-through]=\"cancelled()\">{{ appointment.title }}</span>\n }\n @case ('contact') {\n <span [class.line-through]=\"cancelled()\">{{ appointment.contactName ?? '–' }}</span>\n @if (appointment.contactPhone) {\n <span class=\"ml-2 text-content-faint\" [class.line-through]=\"cancelled()\">{{ appointment.contactPhone }}</span>\n }\n }\n @case ('location') {\n <span class=\"text-content-faint\" [class.line-through]=\"cancelled()\">{{ appointment.location ?? '–' }}</span>\n }\n @case ('status') {\n <span class=\"rounded-full border border-border px-2 py-0.5 text-xs text-content\" data-testid=\"appointment-status\">{{ 'weaver-apps-calendar.status.' + appointment.status | transloco }}</span>\n }\n @case ('actions') {\n <span class=\"flex justify-end\">\n <button type=\"button\" class=\"relative lw-btn lw-btn--ghost lw-btn--sm lw-btn--icon\" [attr.aria-label]=\"'weaver-apps-calendar.actions.edit' | transloco\" (click)=\"edit()\">\n <lw-icon name=\"edit\" size=\"1rem\"></lw-icon>\n </button>\n @if (!cancelled()) {\n <button type=\"button\" class=\"relative lw-btn lw-btn--ghost lw-btn--sm lw-btn--icon\" [attr.aria-label]=\"'weaver-apps-calendar.actions.cancel' | transloco\" (click)=\"cancel()\">\n <lw-icon name=\"weaver-apps-calendar.cancel\" size=\"1rem\"></lw-icon>\n </button>\n }\n <button type=\"button\" class=\"relative lw-btn lw-btn--ghost lw-btn--sm lw-btn--icon\" [attr.aria-label]=\"'weaver-apps-calendar.actions.delete' | transloco\" (click)=\"remove()\">\n <lw-icon name=\"trash\" size=\"1rem\"></lw-icon>\n </button>\n </span>\n }\n }\n}\n",
|
|
419
|
+
dependencies: [{
|
|
420
|
+
kind: "pipe",
|
|
421
|
+
type: TranslocoPipe,
|
|
422
|
+
name: "transloco"
|
|
423
|
+
}],
|
|
424
|
+
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
425
|
+
});
|
|
426
|
+
};
|
|
427
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
428
|
+
minVersion: "12.0.0",
|
|
429
|
+
version: "22.2.3",
|
|
430
|
+
ngImport: i0,
|
|
431
|
+
type: AppointmentCell,
|
|
432
|
+
decorators: [{
|
|
433
|
+
type: Component,
|
|
434
|
+
args: [{
|
|
435
|
+
selector: "wa-appointment-cell",
|
|
436
|
+
imports: [TranslocoPipe],
|
|
437
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
438
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
439
|
+
template: "@if (appointment(); as appointment) {\n @switch (kind()) {\n @case ('when') {\n <span class=\"font-medium\" [class.text-content]=\"!cancelled()\" [class.line-through]=\"cancelled()\">{{ when().day }}</span>\n <span class=\"ml-2 text-content-faint\" [class.line-through]=\"cancelled()\">{{ when().hours }}</span>\n }\n @case ('title') {\n <span class=\"font-medium\" [class.text-content]=\"!cancelled()\" [class.line-through]=\"cancelled()\">{{ appointment.title }}</span>\n }\n @case ('contact') {\n <span [class.line-through]=\"cancelled()\">{{ appointment.contactName ?? '–' }}</span>\n @if (appointment.contactPhone) {\n <span class=\"ml-2 text-content-faint\" [class.line-through]=\"cancelled()\">{{ appointment.contactPhone }}</span>\n }\n }\n @case ('location') {\n <span class=\"text-content-faint\" [class.line-through]=\"cancelled()\">{{ appointment.location ?? '–' }}</span>\n }\n @case ('status') {\n <span class=\"rounded-full border border-border px-2 py-0.5 text-xs text-content\" data-testid=\"appointment-status\">{{ 'weaver-apps-calendar.status.' + appointment.status | transloco }}</span>\n }\n @case ('actions') {\n <span class=\"flex justify-end\">\n <button type=\"button\" class=\"relative lw-btn lw-btn--ghost lw-btn--sm lw-btn--icon\" [attr.aria-label]=\"'weaver-apps-calendar.actions.edit' | transloco\" (click)=\"edit()\">\n <lw-icon name=\"edit\" size=\"1rem\"></lw-icon>\n </button>\n @if (!cancelled()) {\n <button type=\"button\" class=\"relative lw-btn lw-btn--ghost lw-btn--sm lw-btn--icon\" [attr.aria-label]=\"'weaver-apps-calendar.actions.cancel' | transloco\" (click)=\"cancel()\">\n <lw-icon name=\"weaver-apps-calendar.cancel\" size=\"1rem\"></lw-icon>\n </button>\n }\n <button type=\"button\" class=\"relative lw-btn lw-btn--ghost lw-btn--sm lw-btn--icon\" [attr.aria-label]=\"'weaver-apps-calendar.actions.delete' | transloco\" (click)=\"remove()\">\n <lw-icon name=\"trash\" size=\"1rem\"></lw-icon>\n </button>\n </span>\n }\n }\n}\n"
|
|
440
|
+
}]
|
|
441
|
+
}]
|
|
442
|
+
});
|
|
443
|
+
const appointmentColumn = {
|
|
444
|
+
when: "start",
|
|
445
|
+
title: "title",
|
|
446
|
+
contact: "contact",
|
|
447
|
+
location: "location",
|
|
448
|
+
status: "status",
|
|
449
|
+
actions: "actions"
|
|
450
|
+
};
|
|
451
|
+
const actionsWidthPx = 136;
|
|
452
|
+
const whenMinWidthPx = 230;
|
|
453
|
+
const textMinWidthPx = 110;
|
|
454
|
+
const locationMinWidthPx = 90;
|
|
455
|
+
const statusMinWidthPx = 112;
|
|
456
|
+
function byStart(left, right) {
|
|
457
|
+
return Date.parse(left ?? "") - Date.parse(right ?? "");
|
|
458
|
+
}
|
|
459
|
+
function statusOf(appointment) {
|
|
460
|
+
return appointment.status;
|
|
461
|
+
}
|
|
462
|
+
var AppointmentTable = class AppointmentTable {
|
|
463
|
+
table = translateObjectSignal("weaver-apps-calendar.table");
|
|
464
|
+
statuses = translateObjectSignal("weaver-apps-calendar.status");
|
|
465
|
+
api = signal(null, ...ngDevMode ? [{ debugName: "api" }] : /* istanbul ignore next */ []);
|
|
466
|
+
appointments = input.required(...ngDevMode ? [{ debugName: "appointments" }] : /* istanbul ignore next */ []);
|
|
467
|
+
locale = input.required(...ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []);
|
|
468
|
+
editAppointment = output();
|
|
469
|
+
cancelAppointment = output();
|
|
470
|
+
deleteAppointment = output();
|
|
471
|
+
translated = computed(() => typeof this.table() === "object" && typeof this.table()["title"] === "string", ...ngDevMode ? [{ debugName: "translated" }] : /* istanbul ignore next */ []);
|
|
472
|
+
empty = computed(() => ({ noRowsToShow: this.table()["empty"] ?? "" }), ...ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []);
|
|
473
|
+
options = computed(() => ({
|
|
474
|
+
rowModelType: "clientSide",
|
|
475
|
+
columnDefs: this.columns(this.table()),
|
|
476
|
+
defaultColDef: {
|
|
477
|
+
sortable: false,
|
|
478
|
+
resizable: true,
|
|
479
|
+
suppressHeaderMenuButton: true
|
|
480
|
+
},
|
|
481
|
+
getRowId: (params) => params.data.id,
|
|
482
|
+
initialState: { sort: { sortModel: [{
|
|
483
|
+
colId: appointmentColumn.when,
|
|
484
|
+
sort: "asc"
|
|
485
|
+
}] } },
|
|
486
|
+
rowClassRules: { "text-content-faint": (params) => params.data?.status === AppointmentStatus.cancelled }
|
|
487
|
+
}), ...ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []);
|
|
488
|
+
constructor() {
|
|
489
|
+
effect(() => {
|
|
490
|
+
const api = this.api();
|
|
491
|
+
const appointments = this.appointments();
|
|
492
|
+
if (api && !api.isDestroyed()) api.setGridOption("rowData", [...appointments]);
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
ready(api) {
|
|
496
|
+
this.api.set(api);
|
|
497
|
+
}
|
|
498
|
+
columns(table) {
|
|
499
|
+
return [
|
|
500
|
+
{
|
|
501
|
+
...this.cell(appointmentColumn.when, "when", table["when"]),
|
|
502
|
+
field: "start",
|
|
503
|
+
sortable: true,
|
|
504
|
+
comparator: byStart,
|
|
505
|
+
flex: 2,
|
|
506
|
+
minWidth: whenMinWidthPx
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
...this.cell(appointmentColumn.title, "title", table["title"]),
|
|
510
|
+
field: "title",
|
|
511
|
+
sortable: true,
|
|
512
|
+
flex: 2,
|
|
513
|
+
minWidth: textMinWidthPx
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
...this.cell(appointmentColumn.contact, "contact", table["contact"]),
|
|
517
|
+
valueGetter: (params) => params.data?.contactName ?? "",
|
|
518
|
+
sortable: true,
|
|
519
|
+
flex: 2,
|
|
520
|
+
minWidth: textMinWidthPx
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
...this.cell(appointmentColumn.location, "location", table["location"]),
|
|
524
|
+
field: "location",
|
|
525
|
+
sortable: true,
|
|
526
|
+
flex: 1,
|
|
527
|
+
minWidth: locationMinWidthPx
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
...this.cell(appointmentColumn.status, "status", table["status"]),
|
|
531
|
+
field: "status",
|
|
532
|
+
sortable: true,
|
|
533
|
+
flex: 1,
|
|
534
|
+
minWidth: statusMinWidthPx,
|
|
535
|
+
filter: ListFilter,
|
|
536
|
+
filterParams: this.statusChoices()
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
...this.cell(appointmentColumn.actions, "actions", ""),
|
|
540
|
+
width: actionsWidthPx,
|
|
541
|
+
resizable: false
|
|
542
|
+
}
|
|
543
|
+
];
|
|
544
|
+
}
|
|
545
|
+
cell(colId, kind, headerName) {
|
|
546
|
+
return {
|
|
547
|
+
colId,
|
|
548
|
+
headerName: headerName ?? "",
|
|
549
|
+
cellRenderer: AppointmentCell,
|
|
550
|
+
cellRendererParams: {
|
|
551
|
+
kind,
|
|
552
|
+
locale: () => this.locale(),
|
|
553
|
+
edit: (appointment) => this.editAppointment.emit(appointment),
|
|
554
|
+
cancel: (appointment) => this.cancelAppointment.emit(appointment),
|
|
555
|
+
remove: (appointment) => this.deleteAppointment.emit(appointment)
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
statusChoices() {
|
|
560
|
+
const values = Object.values(AppointmentStatus);
|
|
561
|
+
return {
|
|
562
|
+
options: () => values.map((value) => ({
|
|
563
|
+
value,
|
|
564
|
+
label: this.statuses()[value] ?? value
|
|
565
|
+
})),
|
|
566
|
+
rowValue: statusOf
|
|
567
|
+
};
|
|
568
|
+
}
|
|
365
569
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
366
570
|
minVersion: "12.0.0",
|
|
367
571
|
version: "22.2.3",
|
|
@@ -397,12 +601,15 @@ var AppointmentTable = class AppointmentTable {
|
|
|
397
601
|
cancelAppointment: "cancelAppointment",
|
|
398
602
|
deleteAppointment: "deleteAppointment"
|
|
399
603
|
},
|
|
604
|
+
host: { classAttribute: "block h-full w-full" },
|
|
400
605
|
ngImport: i0,
|
|
401
|
-
template: "
|
|
606
|
+
template: "@if (translated()) {\n <wa-data-grid [options]=\"options()\" [localeOverrides]=\"empty()\" (ready)=\"ready($event)\" />\n}\n",
|
|
402
607
|
dependencies: [{
|
|
403
|
-
kind: "
|
|
404
|
-
type:
|
|
405
|
-
|
|
608
|
+
kind: "component",
|
|
609
|
+
type: DataGrid,
|
|
610
|
+
selector: "wa-data-grid",
|
|
611
|
+
inputs: ["options", "localeOverrides"],
|
|
612
|
+
outputs: ["ready"]
|
|
406
613
|
}],
|
|
407
614
|
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
408
615
|
});
|
|
@@ -416,12 +623,13 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
416
623
|
type: Component,
|
|
417
624
|
args: [{
|
|
418
625
|
selector: "wa-appointment-table",
|
|
419
|
-
imports: [
|
|
420
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
626
|
+
imports: [DataGrid],
|
|
421
627
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
422
|
-
|
|
628
|
+
host: { class: "block h-full w-full" },
|
|
629
|
+
template: "@if (translated()) {\n <wa-data-grid [options]=\"options()\" [localeOverrides]=\"empty()\" (ready)=\"ready($event)\" />\n}\n"
|
|
423
630
|
}]
|
|
424
631
|
}],
|
|
632
|
+
ctorParameters: () => [],
|
|
425
633
|
propDecorators: {
|
|
426
634
|
appointments: [{
|
|
427
635
|
type: i0.Input,
|
|
@@ -512,45 +720,45 @@ var CalendarPage = class CalendarPage {
|
|
|
512
720
|
calendarId: this.calendarId(),
|
|
513
721
|
appointment: null,
|
|
514
722
|
day
|
|
515
|
-
}, "calendar.dialog.addTitle");
|
|
723
|
+
}, "weaver-apps-calendar.dialog.addTitle");
|
|
516
724
|
}
|
|
517
725
|
edit(appointment) {
|
|
518
726
|
this.openDialog({
|
|
519
727
|
calendarId: this.calendarId(),
|
|
520
728
|
appointment,
|
|
521
729
|
day: new Date(appointment.start)
|
|
522
|
-
}, "calendar.dialog.editTitle");
|
|
730
|
+
}, "weaver-apps-calendar.dialog.editTitle");
|
|
523
731
|
}
|
|
524
732
|
async cancel(appointment) {
|
|
525
733
|
const reason = await calendarHost.ui.prompt({
|
|
526
|
-
title: "calendar.cancel.title",
|
|
527
|
-
message: "calendar.cancel.message",
|
|
528
|
-
confirmLabel: "calendar.cancel.confirm",
|
|
734
|
+
title: "weaver-apps-calendar.cancel.title",
|
|
735
|
+
message: "weaver-apps-calendar.cancel.message",
|
|
736
|
+
confirmLabel: "weaver-apps-calendar.cancel.confirm",
|
|
529
737
|
tone: "warning"
|
|
530
738
|
});
|
|
531
739
|
if (reason === null) return;
|
|
532
740
|
await this.attempt(async () => {
|
|
533
741
|
const outcome = await this.gateway.cancel(this.calendarId(), appointment.id, reason.trim() || null);
|
|
534
|
-
this.report(outcome.kind === "saved" ? "calendar.cancel.done" : "calendar.errors.save", outcome.kind === "saved");
|
|
742
|
+
this.report(outcome.kind === "saved" ? "weaver-apps-calendar.cancel.done" : "weaver-apps-calendar.errors.save", outcome.kind === "saved");
|
|
535
743
|
});
|
|
536
744
|
}
|
|
537
745
|
async remove(appointment) {
|
|
538
746
|
if (!await calendarHost.ui.confirm({
|
|
539
|
-
title: "calendar.delete.title",
|
|
540
|
-
message: "calendar.delete.message",
|
|
541
|
-
confirmLabel: "calendar.delete.confirm",
|
|
747
|
+
title: "weaver-apps-calendar.delete.title",
|
|
748
|
+
message: "weaver-apps-calendar.delete.message",
|
|
749
|
+
confirmLabel: "weaver-apps-calendar.delete.confirm",
|
|
542
750
|
tone: "danger"
|
|
543
751
|
})) return;
|
|
544
752
|
await this.attempt(async () => {
|
|
545
753
|
await this.gateway.remove(this.calendarId(), appointment.id);
|
|
546
|
-
this.report("calendar.delete.done", true);
|
|
754
|
+
this.report("weaver-apps-calendar.delete.done", true);
|
|
547
755
|
});
|
|
548
756
|
}
|
|
549
757
|
async attempt(work) {
|
|
550
758
|
try {
|
|
551
759
|
await work();
|
|
552
760
|
} catch {
|
|
553
|
-
this.report("calendar.errors.save", false);
|
|
761
|
+
this.report("weaver-apps-calendar.errors.save", false);
|
|
554
762
|
}
|
|
555
763
|
}
|
|
556
764
|
openDialog(data, title) {
|
|
@@ -560,7 +768,7 @@ var CalendarPage = class CalendarPage {
|
|
|
560
768
|
size: "lg",
|
|
561
769
|
dismiss: "explicit"
|
|
562
770
|
}).closed.then((saved) => {
|
|
563
|
-
if (saved) this.report("calendar.dialog.saved", true);
|
|
771
|
+
if (saved) this.report("weaver-apps-calendar.dialog.saved", true);
|
|
564
772
|
});
|
|
565
773
|
}
|
|
566
774
|
report(message, success) {
|
|
@@ -585,7 +793,7 @@ var CalendarPage = class CalendarPage {
|
|
|
585
793
|
isStandalone: true,
|
|
586
794
|
selector: "wa-calendar-page",
|
|
587
795
|
ngImport: i0,
|
|
588
|
-
template: "<div class=\"flex h-full flex-col gap-4
|
|
796
|
+
template: "<div class=\"flex h-full flex-col gap-4 p-6\">\n <div class=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <h2 class=\"text-lg font-semibold text-content\">{{ calendar.value()?.name ?? ('weaver-apps-calendar.title' | transloco) }}</h2>\n <div class=\"flex flex-wrap items-center gap-2\">\n <input\n type=\"search\"\n class=\"lw-field w-56\"\n [attr.aria-label]=\"'weaver-apps-calendar.search.label' | transloco\"\n [attr.placeholder]=\"'weaver-apps-calendar.search.placeholder' | transloco\"\n (input)=\"search($event)\"\n />\n <button type=\"button\" class=\"lw-btn lw-btn--primary\" (click)=\"add()\">\n <lw-icon name=\"add\" size=\"1rem\"></lw-icon>\n {{ 'weaver-apps-calendar.actions.add' | transloco }}\n </button>\n </div>\n </div>\n\n <div class=\"flex items-center gap-2\">\n <button type=\"button\" class=\"relative lw-btn lw-btn--default lw-btn--sm lw-btn--icon\" [attr.aria-label]=\"'weaver-apps-calendar.week.previous' | transloco\" (click)=\"previousWeek()\">\n <lw-icon name=\"weaver-apps-calendar.previous\" size=\"1rem\"></lw-icon>\n <lw-tooltip [attr.text]=\"'weaver-apps-calendar.week.previous' | transloco\" position=\"bottom\"></lw-tooltip>\n </button>\n <button type=\"button\" class=\"lw-btn lw-btn--default lw-btn--sm\" (click)=\"thisWeek()\">{{ 'weaver-apps-calendar.week.current' | transloco }}</button>\n <button type=\"button\" class=\"relative lw-btn lw-btn--default lw-btn--sm lw-btn--icon\" [attr.aria-label]=\"'weaver-apps-calendar.week.next' | transloco\" (click)=\"nextWeek()\">\n <lw-icon name=\"weaver-apps-calendar.next\" size=\"1rem\"></lw-icon>\n <lw-tooltip [attr.text]=\"'weaver-apps-calendar.week.next' | transloco\" position=\"bottom\"></lw-tooltip>\n </button>\n <span class=\"text-sm font-medium text-content\" data-testid=\"week-label\">{{ weekLabel() }}</span>\n </div>\n\n @if (appointments.error()) {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ 'weaver-apps-calendar.errors.load' | transloco }}</p>\n } @else {\n <div class=\"min-h-80 flex-1\">\n <wa-appointment-table\n [appointments]=\"appointments.value() ?? []\"\n [locale]=\"locale()\"\n (editAppointment)=\"edit($event)\"\n (cancelAppointment)=\"cancel($event)\"\n (deleteAppointment)=\"remove($event)\"\n />\n </div>\n }\n</div>\n",
|
|
589
797
|
dependencies: [{
|
|
590
798
|
kind: "component",
|
|
591
799
|
type: AppointmentTable,
|
|
@@ -616,22 +824,22 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
616
824
|
imports: [TranslocoPipe, AppointmentTable],
|
|
617
825
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
618
826
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
619
|
-
template: "<div class=\"flex h-full flex-col gap-4
|
|
827
|
+
template: "<div class=\"flex h-full flex-col gap-4 p-6\">\n <div class=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <h2 class=\"text-lg font-semibold text-content\">{{ calendar.value()?.name ?? ('weaver-apps-calendar.title' | transloco) }}</h2>\n <div class=\"flex flex-wrap items-center gap-2\">\n <input\n type=\"search\"\n class=\"lw-field w-56\"\n [attr.aria-label]=\"'weaver-apps-calendar.search.label' | transloco\"\n [attr.placeholder]=\"'weaver-apps-calendar.search.placeholder' | transloco\"\n (input)=\"search($event)\"\n />\n <button type=\"button\" class=\"lw-btn lw-btn--primary\" (click)=\"add()\">\n <lw-icon name=\"add\" size=\"1rem\"></lw-icon>\n {{ 'weaver-apps-calendar.actions.add' | transloco }}\n </button>\n </div>\n </div>\n\n <div class=\"flex items-center gap-2\">\n <button type=\"button\" class=\"relative lw-btn lw-btn--default lw-btn--sm lw-btn--icon\" [attr.aria-label]=\"'weaver-apps-calendar.week.previous' | transloco\" (click)=\"previousWeek()\">\n <lw-icon name=\"weaver-apps-calendar.previous\" size=\"1rem\"></lw-icon>\n <lw-tooltip [attr.text]=\"'weaver-apps-calendar.week.previous' | transloco\" position=\"bottom\"></lw-tooltip>\n </button>\n <button type=\"button\" class=\"lw-btn lw-btn--default lw-btn--sm\" (click)=\"thisWeek()\">{{ 'weaver-apps-calendar.week.current' | transloco }}</button>\n <button type=\"button\" class=\"relative lw-btn lw-btn--default lw-btn--sm lw-btn--icon\" [attr.aria-label]=\"'weaver-apps-calendar.week.next' | transloco\" (click)=\"nextWeek()\">\n <lw-icon name=\"weaver-apps-calendar.next\" size=\"1rem\"></lw-icon>\n <lw-tooltip [attr.text]=\"'weaver-apps-calendar.week.next' | transloco\" position=\"bottom\"></lw-tooltip>\n </button>\n <span class=\"text-sm font-medium text-content\" data-testid=\"week-label\">{{ weekLabel() }}</span>\n </div>\n\n @if (appointments.error()) {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ 'weaver-apps-calendar.errors.load' | transloco }}</p>\n } @else {\n <div class=\"min-h-80 flex-1\">\n <wa-appointment-table\n [appointments]=\"appointments.value() ?? []\"\n [locale]=\"locale()\"\n (editAppointment)=\"edit($event)\"\n (cancelAppointment)=\"cancel($event)\"\n (deleteAppointment)=\"remove($event)\"\n />\n </div>\n }\n</div>\n"
|
|
620
828
|
}]
|
|
621
829
|
}],
|
|
622
830
|
ctorParameters: () => []
|
|
623
831
|
});
|
|
624
832
|
const stroke = "fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"";
|
|
625
833
|
const calendarIcons = {
|
|
626
|
-
calendar: `<svg viewBox="0 0 24 24" ${stroke}><rect x="3.5" y="5" width="17" height="15" rx="2.5"/><path d="M3.5 10h17M8 3v4M16 3v4"/></svg>`,
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
834
|
+
[calendarIconNames.calendar]: `<svg viewBox="0 0 24 24" ${stroke}><rect x="3.5" y="5" width="17" height="15" rx="2.5"/><path d="M3.5 10h17M8 3v4M16 3v4"/></svg>`,
|
|
835
|
+
[calendarIconNames.previous]: `<svg viewBox="0 0 24 24" ${stroke}><path d="M15 5l-7 7 7 7"/></svg>`,
|
|
836
|
+
[calendarIconNames.next]: `<svg viewBox="0 0 24 24" ${stroke}><path d="M9 5l7 7-7 7"/></svg>`,
|
|
837
|
+
[calendarIconNames.cancel]: `<svg viewBox="0 0 24 24" ${stroke}><circle cx="12" cy="12" r="8.5"/><path d="M9 9l6 6M15 9l-6 6"/></svg>`
|
|
630
838
|
};
|
|
631
839
|
function createCalendarPlugin(options = {}) {
|
|
632
840
|
return {
|
|
633
841
|
manifest: {
|
|
634
|
-
id:
|
|
842
|
+
id: calendarPluginId,
|
|
635
843
|
name: "Calendar",
|
|
636
844
|
capabilities: [
|
|
637
845
|
"contributions",
|
|
@@ -643,24 +851,24 @@ function createCalendarPlugin(options = {}) {
|
|
|
643
851
|
calendarHost.bind(ctx);
|
|
644
852
|
ctx.contributeIcons(calendarIcons);
|
|
645
853
|
ctx.registerSurface({
|
|
646
|
-
id:
|
|
647
|
-
title: "calendar.calendars.title",
|
|
648
|
-
icon:
|
|
854
|
+
id: calendarContributionIds.calendars,
|
|
855
|
+
title: "weaver-apps-calendar.calendars.title",
|
|
856
|
+
icon: calendarIconNames.calendar,
|
|
649
857
|
routable: { path: calendarListPath },
|
|
650
858
|
component: CalendarListPage
|
|
651
859
|
});
|
|
652
860
|
ctx.registerSurface({
|
|
653
|
-
id:
|
|
654
|
-
title: "calendar.title",
|
|
655
|
-
icon:
|
|
861
|
+
id: calendarContributionIds.calendar,
|
|
862
|
+
title: "weaver-apps-calendar.title",
|
|
863
|
+
icon: calendarIconNames.calendar,
|
|
656
864
|
routable: { path: `${calendarListPath}/:calendarId` },
|
|
657
865
|
component: CalendarPage
|
|
658
866
|
});
|
|
659
867
|
if (options.railItem) ctx.registerRailItem({
|
|
660
|
-
id:
|
|
868
|
+
id: calendarContributionIds.rail,
|
|
661
869
|
rail: "primary",
|
|
662
|
-
icon:
|
|
663
|
-
title: "calendar.calendars.title",
|
|
870
|
+
icon: calendarIconNames.calendar,
|
|
871
|
+
title: "weaver-apps-calendar.calendars.title",
|
|
664
872
|
run: () => ctx.navigateContent(calendarListPath)
|
|
665
873
|
});
|
|
666
874
|
},
|
|
@@ -765,6 +973,6 @@ function createHttpCalendarGateway(options) {
|
|
|
765
973
|
}
|
|
766
974
|
};
|
|
767
975
|
}
|
|
768
|
-
export { CALENDAR_GATEWAY, CalendarGatewayError, calendarListPath, calendarPath, calendarPlugin, createCalendarPlugin, createHttpCalendarGateway, provideCalendarGateway };
|
|
976
|
+
export { CALENDAR_GATEWAY, CalendarGatewayError, calendarContributionIds, calendarListPath, calendarPath, calendarPlugin, calendarPluginId, createCalendarPlugin, createHttpCalendarGateway, provideCalendarGateway };
|
|
769
977
|
|
|
770
978
|
//# sourceMappingURL=weaver-apps-calendar.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"weaver-apps-calendar.mjs","names":[],"sources":["../../../../libs/calendar/src/lib/gateway/calendar-gateway.ts","../../../../libs/calendar/src/lib/plugin/calendar-host.ts","../../../../libs/calendar/src/lib/plugin/calendar-paths.ts","../../../../libs/calendar/src/lib/views/calendar-list-page.ts","../../../../libs/calendar/src/lib/views/calendar-list-page.html","../../../../libs/calendar/src/lib/week.ts","../../../../libs/calendar/src/lib/views/active-locale.ts","../../../../libs/calendar/src/lib/api/models/appointmentStatus.ts","../../../../libs/calendar/src/lib/views/appointment-dialog.ts","../../../../libs/calendar/src/lib/views/appointment-dialog.html","../../../../libs/calendar/src/lib/views/appointment-table.ts","../../../../libs/calendar/src/lib/views/appointment-table.html","../../../../libs/calendar/src/lib/views/calendar-page.ts","../../../../libs/calendar/src/lib/views/calendar-page.html","../../../../libs/calendar/src/lib/plugin/calendar-icons.ts","../../../../libs/calendar/src/lib/plugin/calendar.plugin.ts","../../../../libs/calendar/src/lib/api/endpoints.ts","../../../../libs/calendar/src/lib/gateway/http-calendar-gateway.ts"],"sourcesContent":["import { InjectionToken, Provider } from '@angular/core';\nimport type { AppointmentRequest, AppointmentResponse, AppointmentStatus, CalendarResponse } from '../api/models';\n\nexport interface CalendarRange {\n readonly from: Date;\n readonly until: Date;\n}\n\nexport interface ConflictingAppointment {\n readonly id: string;\n readonly start: string;\n readonly end: string;\n}\n\nexport type AppointmentOutcome =\n | { readonly kind: 'saved'; readonly appointment: AppointmentResponse }\n | { readonly kind: 'conflict'; readonly conflicts: readonly ConflictingAppointment[] }\n | { readonly kind: 'closed' }\n | { readonly kind: 'changed' }\n | { readonly kind: 'invalid' }\n | { readonly kind: 'missing' };\n\nexport interface CalendarGateway {\n calendars(): Promise<readonly CalendarResponse[]>;\n calendar(calendarId: string): Promise<CalendarResponse | null>;\n saveCalendar(calendarId: string, name: string): Promise<CalendarResponse>;\n appointments(calendarId: string, range: CalendarRange, contact?: string, signal?: AbortSignal): Promise<readonly AppointmentResponse[]>;\n book(calendarId: string, request: AppointmentRequest): Promise<AppointmentOutcome>;\n update(calendarId: string, appointmentId: string, request: AppointmentRequest): Promise<AppointmentOutcome>;\n cancel(calendarId: string, appointmentId: string, reason: string | null): Promise<AppointmentOutcome>;\n setStatus(calendarId: string, appointmentId: string, status: AppointmentStatus): Promise<AppointmentOutcome>;\n remove(calendarId: string, appointmentId: string): Promise<void>;\n}\n\nexport class CalendarGatewayError extends Error {\n constructor(readonly status: number) {\n super(`The calendar answered ${status}.`);\n }\n}\n\nexport const CALENDAR_GATEWAY = new InjectionToken<CalendarGateway>('weaver-apps.calendar.gateway');\n\nexport function provideCalendarGateway(gateway: CalendarGateway): Provider {\n return { provide: CALENDAR_GATEWAY, useValue: gateway };\n}\n","import type { PluginContext } from '@loomweaver/plugin-sdk';\n\ntype HostContext = Pick<PluginContext, 'ui' | 'navigateContent'>;\n\nclass CalendarHost {\n private ctx?: HostContext;\n\n bind(ctx: HostContext): void {\n this.ctx = ctx;\n }\n\n unbind(): void {\n this.ctx = undefined;\n }\n\n get ui(): HostContext['ui'] {\n if (!this.ctx) {\n throw new Error('The calendar plugin is not active.');\n }\n return this.ctx.ui;\n }\n\n navigate(path: string): void {\n this.ctx?.navigateContent(path);\n }\n}\n\nexport const calendarHost = new CalendarHost();\n","export const calendarListPath = 'calendar';\n\nexport function calendarPath(calendarId: string): string {\n return `${calendarListPath}/${calendarId}`;\n}\n","import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, inject, resource } from '@angular/core';\nimport { TranslocoPipe } from '@jsverse/transloco';\nimport { CALENDAR_GATEWAY } from '../gateway/calendar-gateway';\nimport { calendarHost } from '../plugin/calendar-host';\nimport { calendarPath } from '../plugin/calendar-paths';\n\n@Component({\n selector: 'wa-calendar-list-page',\n imports: [TranslocoPipe],\n schemas: [CUSTOM_ELEMENTS_SCHEMA],\n templateUrl: './calendar-list-page.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class CalendarListPage {\n private readonly gateway = inject(CALENDAR_GATEWAY);\n\n protected readonly calendars = resource({ loader: () => this.gateway.calendars() });\n\n protected open(calendarId: string): void {\n calendarHost.navigate(calendarPath(calendarId));\n }\n\n protected async create(): Promise<void> {\n const name = await calendarHost.ui.prompt({\n title: 'calendar.calendars.create',\n message: 'calendar.calendars.createMessage',\n confirmLabel: 'calendar.calendars.createConfirm',\n });\n if (!name?.trim()) {\n return;\n }\n try {\n const calendar = await this.gateway.saveCalendar(crypto.randomUUID(), name.trim());\n this.open(calendar.id);\n } catch {\n calendarHost.ui.toast({ message: 'calendar.errors.create', kind: 'error' });\n }\n }\n}\n","<div class=\"flex h-full flex-col gap-4 overflow-auto p-6\">\n <div class=\"flex items-center justify-between gap-4\">\n <h2 class=\"text-lg font-semibold text-content\">{{ 'calendar.calendars.title' | transloco }}</h2>\n <button type=\"button\" class=\"lw-btn lw-btn--primary\" (click)=\"create()\">{{ 'calendar.calendars.create' | transloco }}</button>\n </div>\n @if (calendars.error()) {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ 'calendar.errors.load' | transloco }}</p>\n } @else if (calendars.hasValue()) {\n <ul role=\"list\" class=\"divide-y divide-border rounded-xl border border-border bg-surface\">\n @for (calendar of calendars.value(); track calendar.id) {\n <li>\n <button type=\"button\" class=\"flex w-full items-center gap-3 px-4 py-3 text-left text-sm text-content hover:bg-surface-overlay\" (click)=\"open(calendar.id)\">\n <lw-icon name=\"calendar\" size=\"1.25rem\"></lw-icon>\n <span>{{ calendar.name }}</span>\n </button>\n </li>\n } @empty {\n <li class=\"px-4 py-8 text-center text-sm text-content-faint\">{{ 'calendar.calendars.empty' | transloco }}</li>\n }\n </ul>\n }\n</div>\n","import type { CalendarRange } from './gateway/calendar-gateway';\n\nconst daysPerWeek = 7;\nconst sunday = 0;\nconst daysFromSundayToMonday = 6;\n\nexport function startOfWeek(date: Date): Date {\n const start = new Date(date.getFullYear(), date.getMonth(), date.getDate());\n const offset = start.getDay() === sunday ? daysFromSundayToMonday : start.getDay() - 1;\n start.setDate(start.getDate() - offset);\n return start;\n}\n\nexport function addWeeks(weekStart: Date, weeks: number): Date {\n const moved = new Date(weekStart);\n moved.setDate(moved.getDate() + weeks * daysPerWeek);\n return moved;\n}\n\nexport function weekRange(weekStart: Date): CalendarRange {\n return { from: weekStart, until: addWeeks(weekStart, 1) };\n}\n\nexport function localDate(value: Date): string {\n const pad = (part: number) => `${part}`.padStart(2, '0');\n return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}`;\n}\n\nexport function localTime(value: Date): string {\n const pad = (part: number) => `${part}`.padStart(2, '0');\n return `${pad(value.getHours())}:${pad(value.getMinutes())}`;\n}\n\nexport function combine(date: string, time: string): Date | null {\n const combined = new Date(`${date}T${time}`);\n return Number.isNaN(combined.getTime()) ? null : combined;\n}\n","import { inject, Signal } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { TranslocoService } from '@jsverse/transloco';\n\nexport function activeLocale(): Signal<string> {\n const transloco = inject(TranslocoService);\n return toSignal(transloco.langChanges$, { initialValue: transloco.getActiveLang() });\n}\n","/**\n * Generated by orval 🍺\n * Do not edit manually.\n * Weaver Apps\n * OpenAPI spec version: 1.0.0\n */\n\n/**\n * Where an appointment stands.\n */\nexport type AppointmentStatus = typeof AppointmentStatus[keyof typeof AppointmentStatus];\n\n\nexport const AppointmentStatus = {\n scheduled: 'scheduled',\n confirmed: 'confirmed',\n completed: 'completed',\n noShow: 'noShow',\n cancelled: 'cancelled',\n} as const;\n","import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';\nimport { DialogRef } from '@loomweaver/plugin-sdk';\nimport { TranslocoPipe } from '@jsverse/transloco';\nimport { AppointmentStatus, type AppointmentRequest, type AppointmentResponse } from '../api/models';\nimport { AppointmentOutcome, CALENDAR_GATEWAY, ConflictingAppointment } from '../gateway/calendar-gateway';\nimport { combine, localDate, localTime } from '../week';\nimport { activeLocale } from './active-locale';\n\nexport interface AppointmentDialogData {\n readonly calendarId: string;\n readonly appointment: AppointmentResponse | null;\n readonly day: Date;\n}\n\ntype Problem =\n | { readonly kind: 'none' }\n | { readonly kind: 'message'; readonly key: string }\n | { readonly kind: 'conflict'; readonly conflicts: readonly ConflictingAppointment[] };\n\nconst defaultStart = '09:00';\nconst defaultEnd = '10:00';\nconst settableStatuses: readonly AppointmentStatus[] = [\n AppointmentStatus.scheduled,\n AppointmentStatus.confirmed,\n AppointmentStatus.completed,\n AppointmentStatus.noShow,\n];\n\n@Component({\n selector: 'wa-appointment-dialog',\n imports: [TranslocoPipe],\n templateUrl: './appointment-dialog.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class AppointmentDialog {\n private readonly dialog = inject<DialogRef<boolean>>(DialogRef);\n private readonly gateway = inject(CALENDAR_GATEWAY);\n private readonly data = this.dialog.data as AppointmentDialogData;\n private existing = this.data.appointment;\n\n protected readonly locale = activeLocale();\n protected readonly editing = this.existing !== null;\n protected readonly statuses = settableStatuses;\n\n protected readonly title = signal(this.existing?.title ?? '');\n protected readonly date = signal(localDate(this.existing ? new Date(this.existing.start) : this.data.day));\n protected readonly start = signal(this.existing ? localTime(new Date(this.existing.start)) : defaultStart);\n protected readonly endDate = signal(localDate(this.existing ? new Date(this.existing.end) : this.data.day));\n protected readonly end = signal(this.existing ? localTime(new Date(this.existing.end)) : defaultEnd);\n protected readonly location = signal(this.existing?.location ?? '');\n protected readonly contactName = signal(this.existing?.contactName ?? '');\n protected readonly contactPhone = signal(this.existing?.contactPhone ?? '');\n protected readonly contactEmail = signal(this.existing?.contactEmail ?? '');\n protected readonly description = signal(this.existing?.description ?? '');\n protected readonly status = signal<AppointmentStatus>(this.existing?.status ?? AppointmentStatus.scheduled);\n protected readonly saving = signal(false);\n protected readonly problem = signal<Problem>({ kind: 'none' });\n private persisted = false;\n\n protected readonly problemKey = computed(() => {\n const problem = this.problem();\n return problem.kind === 'message' ? problem.key : '';\n });\n\n protected readonly conflictTimes = computed(() => {\n const problem = this.problem();\n if (problem.kind !== 'conflict') {\n return [];\n }\n const format = new Intl.DateTimeFormat(this.locale(), { dateStyle: 'short', timeStyle: 'short' });\n return problem.conflicts.map((conflict) => `${format.format(new Date(conflict.start))} – ${format.format(new Date(conflict.end))}`);\n });\n\n protected bind(target: { set(value: string): void }, event: Event): void {\n target.set((event.target as HTMLInputElement).value);\n }\n\n protected bindDate(event: Event): void {\n const value = (event.target as HTMLInputElement).value;\n if (this.endDate() === this.date()) {\n this.endDate.set(value);\n }\n this.date.set(value);\n }\n\n protected chooseStatus(event: Event): void {\n const chosen = settableStatuses.find((status) => status === (event.target as HTMLSelectElement).value);\n if (chosen) {\n this.status.set(chosen);\n }\n }\n\n protected close(): void {\n this.dialog.close(this.persisted);\n }\n\n protected async save(): Promise<void> {\n const request = this.request();\n if (!request) {\n return;\n }\n this.saving.set(true);\n try {\n const outcome = this.existing\n ? await this.gateway.update(this.data.calendarId, this.existing.id, request)\n : await this.gateway.book(this.data.calendarId, request);\n const final = await this.applyStatus(outcome);\n this.settle(final);\n } catch {\n this.problem.set({ kind: 'message', key: 'calendar.errors.save' });\n } finally {\n this.saving.set(false);\n }\n }\n\n private request(): AppointmentRequest | null {\n const startsAt = combine(this.date(), this.start());\n const endsAt = combine(this.endDate(), this.end());\n if (!this.title().trim()) {\n this.problem.set({ kind: 'message', key: 'calendar.dialog.titleRequired' });\n return null;\n }\n if (!startsAt || !endsAt || endsAt <= startsAt) {\n this.problem.set({ kind: 'message', key: 'calendar.dialog.periodInvalid' });\n return null;\n }\n return {\n title: this.title().trim(),\n start: startsAt.toISOString(),\n end: endsAt.toISOString(),\n description: this.optional(this.description()),\n location: this.optional(this.location()),\n contactName: this.optional(this.contactName()),\n contactPhone: this.optional(this.contactPhone()),\n contactEmail: this.optional(this.contactEmail()),\n };\n }\n\n private async applyStatus(outcome: AppointmentOutcome): Promise<AppointmentOutcome> {\n if (outcome.kind === 'saved') {\n this.existing = outcome.appointment;\n this.persisted = true;\n }\n if (outcome.kind !== 'saved' || outcome.appointment.status === this.status()) {\n return outcome;\n }\n return this.gateway.setStatus(this.data.calendarId, outcome.appointment.id, this.status());\n }\n\n private settle(outcome: AppointmentOutcome): void {\n switch (outcome.kind) {\n case 'saved':\n this.dialog.close(true);\n return;\n case 'conflict':\n this.problem.set({ kind: 'conflict', conflicts: outcome.conflicts });\n return;\n case 'closed':\n this.problem.set({ kind: 'message', key: 'calendar.dialog.closed' });\n return;\n case 'changed':\n this.problem.set({ kind: 'message', key: 'calendar.dialog.changed' });\n return;\n case 'invalid':\n this.problem.set({ kind: 'message', key: 'calendar.dialog.invalid' });\n return;\n default:\n this.problem.set({ kind: 'message', key: 'calendar.dialog.missing' });\n }\n }\n\n private optional(value: string): string | null {\n return value.trim() ? value.trim() : null;\n }\n}\n","<form class=\"flex flex-col gap-4\" (submit)=\"$event.preventDefault(); save()\">\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.title' | transloco }}\n <input class=\"lw-field\" required [value]=\"title()\" (input)=\"bind(title, $event)\" />\n </label>\n\n <div class=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.date' | transloco }}\n <input class=\"lw-field\" type=\"date\" required [value]=\"date()\" (input)=\"bindDate($event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.start' | transloco }}\n <input class=\"lw-field\" type=\"time\" required [value]=\"start()\" (input)=\"bind(start, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.endDate' | transloco }}\n <input class=\"lw-field\" type=\"date\" required [value]=\"endDate()\" (input)=\"bind(endDate, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.end' | transloco }}\n <input class=\"lw-field\" type=\"time\" required [value]=\"end()\" (input)=\"bind(end, $event)\" />\n </label>\n </div>\n\n <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.contactName' | transloco }}\n <input class=\"lw-field\" [value]=\"contactName()\" (input)=\"bind(contactName, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.contactPhone' | transloco }}\n <input class=\"lw-field\" type=\"tel\" [value]=\"contactPhone()\" (input)=\"bind(contactPhone, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.contactEmail' | transloco }}\n <input class=\"lw-field\" type=\"email\" [value]=\"contactEmail()\" (input)=\"bind(contactEmail, $event)\" />\n </label>\n </div>\n\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.location' | transloco }}\n <input class=\"lw-field\" [value]=\"location()\" (input)=\"bind(location, $event)\" />\n </label>\n\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.description' | transloco }}\n <textarea class=\"lw-field\" rows=\"3\" [value]=\"description()\" (input)=\"bind(description, $event)\"></textarea>\n </label>\n\n @if (editing) {\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'calendar.fields.status' | transloco }}\n <select class=\"lw-field\" [value]=\"status()\" (change)=\"chooseStatus($event)\">\n @for (choice of statuses; track choice) {\n <option [value]=\"choice\" [selected]=\"choice === status()\">{{ 'calendar.status.' + choice | transloco }}</option>\n }\n </select>\n </label>\n }\n\n @switch (problem().kind) {\n @case ('message') {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ problemKey() | transloco }}</p>\n }\n @case ('conflict') {\n <div class=\"text-sm text-negative\" role=\"alert\">\n <p>{{ 'calendar.dialog.conflict' | transloco }}</p>\n <ul class=\"list-disc pl-5\">\n @for (time of conflictTimes(); track time) {\n <li>{{ time }}</li>\n }\n </ul>\n </div>\n }\n }\n\n <div class=\"flex justify-end gap-2\">\n <button type=\"button\" class=\"lw-btn lw-btn--default\" (click)=\"close()\">{{ 'calendar.dialog.cancel' | transloco }}</button>\n <button type=\"submit\" class=\"lw-btn lw-btn--primary\" [disabled]=\"saving()\">{{ 'calendar.dialog.save' | transloco }}</button>\n </div>\n</form>\n","import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, computed, input, output } from '@angular/core';\nimport { TranslocoPipe } from '@jsverse/transloco';\nimport type { AppointmentResponse } from '../api/models';\n\ninterface AppointmentRow {\n readonly appointment: AppointmentResponse;\n readonly day: string;\n readonly hours: string;\n readonly cancelled: boolean;\n readonly statusKey: string;\n}\n\n@Component({\n selector: 'wa-appointment-table',\n imports: [TranslocoPipe],\n schemas: [CUSTOM_ELEMENTS_SCHEMA],\n templateUrl: './appointment-table.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class AppointmentTable {\n readonly appointments = input.required<readonly AppointmentResponse[]>();\n readonly locale = input.required<string>();\n\n readonly editAppointment = output<AppointmentResponse>();\n readonly cancelAppointment = output<AppointmentResponse>();\n readonly deleteAppointment = output<AppointmentResponse>();\n\n protected readonly rows = computed<readonly AppointmentRow[]>(() => {\n const day = new Intl.DateTimeFormat(this.locale(), { weekday: 'short', day: '2-digit', month: '2-digit', year: 'numeric' });\n const time = new Intl.DateTimeFormat(this.locale(), { hour: '2-digit', minute: '2-digit' });\n return this.appointments().map((appointment) => ({\n appointment,\n day: day.format(new Date(appointment.start)),\n hours: `${time.format(new Date(appointment.start))} – ${time.format(new Date(appointment.end))}`,\n cancelled: appointment.status === 'cancelled',\n statusKey: `calendar.status.${appointment.status}`,\n }));\n });\n}\n","<table class=\"w-full border-collapse text-sm\">\n <thead class=\"border-y border-border bg-surface-raised\">\n <tr>\n <th scope=\"col\" class=\"px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-content-faint\">{{ 'calendar.table.when' | transloco }}</th>\n <th scope=\"col\" class=\"px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-content-faint\">{{ 'calendar.table.title' | transloco }}</th>\n <th scope=\"col\" class=\"px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-content-faint\">{{ 'calendar.table.contact' | transloco }}</th>\n <th scope=\"col\" class=\"px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-content-faint\">{{ 'calendar.table.location' | transloco }}</th>\n <th scope=\"col\" class=\"px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-content-faint\">{{ 'calendar.table.status' | transloco }}</th>\n <th scope=\"col\" class=\"px-4 py-2\"><span class=\"sr-only\">{{ 'calendar.table.actions' | transloco }}</span></th>\n </tr>\n </thead>\n <tbody>\n @for (row of rows(); track row.appointment.id) {\n <tr class=\"border-b border-border\" [class.text-content-faint]=\"row.cancelled\" data-testid=\"appointment-row\">\n <td class=\"px-4 py-3 whitespace-nowrap\" [class.line-through]=\"row.cancelled\">\n <div class=\"font-medium text-content\">{{ row.day }}</div>\n <div class=\"text-content-faint\">{{ row.hours }}</div>\n </td>\n <td class=\"px-4 py-3 font-medium text-content\" [class.line-through]=\"row.cancelled\">{{ row.appointment.title }}</td>\n <td class=\"px-4 py-3\" [class.line-through]=\"row.cancelled\">\n <div class=\"text-content\">{{ row.appointment.contactName ?? '–' }}</div>\n @if (row.appointment.contactPhone) {\n <div class=\"text-content-faint\">{{ row.appointment.contactPhone }}</div>\n }\n </td>\n <td class=\"px-4 py-3 text-content-faint\" [class.line-through]=\"row.cancelled\">{{ row.appointment.location ?? '–' }}</td>\n <td class=\"px-4 py-3\">\n <span class=\"rounded-full border border-border px-2 py-0.5 text-xs text-content\" data-testid=\"appointment-status\">{{ row.statusKey | transloco }}</span>\n </td>\n <td class=\"px-4 py-3 text-right whitespace-nowrap\">\n <button type=\"button\" class=\"relative lw-btn lw-btn--ghost lw-btn--sm lw-btn--icon\" [attr.aria-label]=\"'calendar.actions.edit' | transloco\" (click)=\"editAppointment.emit(row.appointment)\">\n <lw-icon name=\"edit\" size=\"1rem\"></lw-icon>\n <lw-tooltip [attr.text]=\"'calendar.actions.edit' | transloco\" position=\"top\"></lw-tooltip>\n </button>\n @if (!row.cancelled) {\n <button type=\"button\" class=\"relative lw-btn lw-btn--ghost lw-btn--sm lw-btn--icon\" [attr.aria-label]=\"'calendar.actions.cancel' | transloco\" (click)=\"cancelAppointment.emit(row.appointment)\">\n <lw-icon name=\"calendarCancel\" size=\"1rem\"></lw-icon>\n <lw-tooltip [attr.text]=\"'calendar.actions.cancel' | transloco\" position=\"top\"></lw-tooltip>\n </button>\n }\n <button type=\"button\" class=\"relative lw-btn lw-btn--ghost lw-btn--sm lw-btn--icon\" [attr.aria-label]=\"'calendar.actions.delete' | transloco\" (click)=\"deleteAppointment.emit(row.appointment)\">\n <lw-icon name=\"trash\" size=\"1rem\"></lw-icon>\n <lw-tooltip [attr.text]=\"'calendar.actions.delete' | transloco\" position=\"top\"></lw-tooltip>\n </button>\n </td>\n </tr>\n } @empty {\n <tr>\n <td colspan=\"6\" class=\"px-4 py-8 text-center text-content-faint\">{{ 'calendar.table.empty' | transloco }}</td>\n </tr>\n }\n </tbody>\n</table>\n","import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, computed, DestroyRef, inject, resource, signal } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { ActivatedRoute } from '@angular/router';\nimport { TranslocoPipe } from '@jsverse/transloco';\nimport { map } from 'rxjs';\nimport type { AppointmentResponse } from '../api/models';\nimport { CALENDAR_GATEWAY } from '../gateway/calendar-gateway';\nimport { calendarHost } from '../plugin/calendar-host';\nimport { addWeeks, startOfWeek, weekRange } from '../week';\nimport { activeLocale } from './active-locale';\nimport { AppointmentDialog, AppointmentDialogData } from './appointment-dialog';\nimport { AppointmentTable } from './appointment-table';\n\nconst searchDelayMs = 300;\n\n@Component({\n selector: 'wa-calendar-page',\n imports: [TranslocoPipe, AppointmentTable],\n schemas: [CUSTOM_ELEMENTS_SCHEMA],\n templateUrl: './calendar-page.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class CalendarPage {\n private readonly gateway = inject(CALENDAR_GATEWAY);\n private readonly route = inject(ActivatedRoute);\n private searchTimer?: ReturnType<typeof setTimeout>;\n\n protected readonly locale = activeLocale();\n protected readonly calendarId = toSignal(this.route.paramMap.pipe(map((params) => params.get('calendarId') ?? '')), {\n initialValue: this.route.snapshot.paramMap.get('calendarId') ?? '',\n });\n protected readonly weekStart = signal(startOfWeek(new Date()));\n protected readonly contact = signal('');\n\n protected readonly calendar = resource({\n params: () => this.calendarId(),\n loader: ({ params }) => this.gateway.calendar(params),\n });\n\n protected readonly appointments = resource({\n params: () => ({ calendarId: this.calendarId(), range: weekRange(this.weekStart()), contact: this.contact().trim() }),\n loader: ({ params, abortSignal }) =>\n this.gateway.appointments(params.calendarId, params.range, params.contact || undefined, abortSignal),\n });\n\n constructor() {\n inject(DestroyRef).onDestroy(() => clearTimeout(this.searchTimer));\n }\n\n protected readonly weekLabel = computed(() => {\n const range = weekRange(this.weekStart());\n const last = addWeeks(range.until, 0);\n last.setDate(last.getDate() - 1);\n const format = new Intl.DateTimeFormat(this.locale(), { day: '2-digit', month: '2-digit', year: 'numeric' });\n return `${format.format(range.from)} – ${format.format(last)}`;\n });\n\n protected previousWeek(): void {\n this.weekStart.update((start) => addWeeks(start, -1));\n }\n\n protected nextWeek(): void {\n this.weekStart.update((start) => addWeeks(start, 1));\n }\n\n protected thisWeek(): void {\n this.weekStart.set(startOfWeek(new Date()));\n }\n\n protected search(event: Event): void {\n const value = (event.target as HTMLInputElement).value;\n clearTimeout(this.searchTimer);\n this.searchTimer = setTimeout(() => this.contact.set(value), searchDelayMs);\n }\n\n protected add(): void {\n const today = new Date();\n const shown = weekRange(this.weekStart());\n const day = today >= shown.from && today < shown.until ? today : this.weekStart();\n this.openDialog({ calendarId: this.calendarId(), appointment: null, day }, 'calendar.dialog.addTitle');\n }\n\n protected edit(appointment: AppointmentResponse): void {\n this.openDialog({ calendarId: this.calendarId(), appointment, day: new Date(appointment.start) }, 'calendar.dialog.editTitle');\n }\n\n protected async cancel(appointment: AppointmentResponse): Promise<void> {\n const reason = await calendarHost.ui.prompt({\n title: 'calendar.cancel.title',\n message: 'calendar.cancel.message',\n confirmLabel: 'calendar.cancel.confirm',\n tone: 'warning',\n });\n if (reason === null) {\n return;\n }\n await this.attempt(async () => {\n const outcome = await this.gateway.cancel(this.calendarId(), appointment.id, reason.trim() || null);\n this.report(outcome.kind === 'saved' ? 'calendar.cancel.done' : 'calendar.errors.save', outcome.kind === 'saved');\n });\n }\n\n protected async remove(appointment: AppointmentResponse): Promise<void> {\n const confirmed = await calendarHost.ui.confirm({\n title: 'calendar.delete.title',\n message: 'calendar.delete.message',\n confirmLabel: 'calendar.delete.confirm',\n tone: 'danger',\n });\n if (!confirmed) {\n return;\n }\n await this.attempt(async () => {\n await this.gateway.remove(this.calendarId(), appointment.id);\n this.report('calendar.delete.done', true);\n });\n }\n\n private async attempt(work: () => Promise<void>): Promise<void> {\n try {\n await work();\n } catch {\n this.report('calendar.errors.save', false);\n }\n }\n\n private openDialog(data: AppointmentDialogData, title: string): void {\n const dialog = calendarHost.ui.open<boolean>(AppointmentDialog, { data, title, size: 'lg', dismiss: 'explicit' });\n void dialog.closed.then((saved) => {\n if (saved) {\n this.report('calendar.dialog.saved', true);\n }\n });\n }\n\n private report(message: string, success: boolean): void {\n calendarHost.ui.toast({ message, kind: success ? 'success' : 'error' });\n this.appointments.reload();\n }\n}\n","<div class=\"flex h-full flex-col gap-4 overflow-auto p-6\">\n <div class=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <h2 class=\"text-lg font-semibold text-content\">{{ calendar.value()?.name ?? ('calendar.title' | transloco) }}</h2>\n <div class=\"flex flex-wrap items-center gap-2\">\n <input\n type=\"search\"\n class=\"lw-field w-56\"\n [attr.aria-label]=\"'calendar.search.label' | transloco\"\n [attr.placeholder]=\"'calendar.search.placeholder' | transloco\"\n (input)=\"search($event)\"\n />\n <button type=\"button\" class=\"lw-btn lw-btn--primary\" (click)=\"add()\">\n <lw-icon name=\"add\" size=\"1rem\"></lw-icon>\n {{ 'calendar.actions.add' | transloco }}\n </button>\n </div>\n </div>\n\n <div class=\"flex items-center gap-2\">\n <button type=\"button\" class=\"relative lw-btn lw-btn--default lw-btn--sm lw-btn--icon\" [attr.aria-label]=\"'calendar.week.previous' | transloco\" (click)=\"previousWeek()\">\n <lw-icon name=\"calendarPrevious\" size=\"1rem\"></lw-icon>\n <lw-tooltip [attr.text]=\"'calendar.week.previous' | transloco\" position=\"bottom\"></lw-tooltip>\n </button>\n <button type=\"button\" class=\"lw-btn lw-btn--default lw-btn--sm\" (click)=\"thisWeek()\">{{ 'calendar.week.current' | transloco }}</button>\n <button type=\"button\" class=\"relative lw-btn lw-btn--default lw-btn--sm lw-btn--icon\" [attr.aria-label]=\"'calendar.week.next' | transloco\" (click)=\"nextWeek()\">\n <lw-icon name=\"calendarNext\" size=\"1rem\"></lw-icon>\n <lw-tooltip [attr.text]=\"'calendar.week.next' | transloco\" position=\"bottom\"></lw-tooltip>\n </button>\n <span class=\"text-sm font-medium text-content\" data-testid=\"week-label\">{{ weekLabel() }}</span>\n </div>\n\n @if (appointments.error()) {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ 'calendar.errors.load' | transloco }}</p>\n } @else {\n <div class=\"overflow-hidden rounded-xl border border-border bg-surface\">\n <wa-appointment-table\n [appointments]=\"appointments.value() ?? []\"\n [locale]=\"locale()\"\n (editAppointment)=\"edit($event)\"\n (cancelAppointment)=\"cancel($event)\"\n (deleteAppointment)=\"remove($event)\"\n />\n </div>\n }\n</div>\n","const stroke = 'fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"';\n\nexport const calendarIcons: Readonly<Record<string, string>> = {\n calendar: `<svg viewBox=\"0 0 24 24\" ${stroke}><rect x=\"3.5\" y=\"5\" width=\"17\" height=\"15\" rx=\"2.5\"/><path d=\"M3.5 10h17M8 3v4M16 3v4\"/></svg>`,\n calendarPrevious: `<svg viewBox=\"0 0 24 24\" ${stroke}><path d=\"M15 5l-7 7 7 7\"/></svg>`,\n calendarNext: `<svg viewBox=\"0 0 24 24\" ${stroke}><path d=\"M9 5l7 7-7 7\"/></svg>`,\n calendarCancel: `<svg viewBox=\"0 0 24 24\" ${stroke}><circle cx=\"12\" cy=\"12\" r=\"8.5\"/><path d=\"M9 9l6 6M15 9l-6 6\"/></svg>`,\n};\n","import { Plugin } from '@loomweaver/plugin-sdk';\nimport { CalendarListPage } from '../views/calendar-list-page';\nimport { CalendarPage } from '../views/calendar-page';\nimport { calendarHost } from './calendar-host';\nimport { calendarIcons } from './calendar-icons';\nimport { calendarListPath } from './calendar-paths';\n\nexport interface CalendarPluginOptions {\n readonly railItem?: boolean;\n}\n\nexport function createCalendarPlugin(options: CalendarPluginOptions = {}): Plugin {\n return {\n manifest: {\n id: 'calendar',\n name: 'Calendar',\n capabilities: ['contributions', 'navigation', 'ui'],\n },\n activate(ctx) {\n calendarHost.bind(ctx);\n ctx.contributeIcons(calendarIcons);\n ctx.registerSurface({\n id: 'calendar.calendars',\n title: 'calendar.calendars.title',\n icon: 'calendar',\n routable: { path: calendarListPath },\n component: CalendarListPage,\n });\n ctx.registerSurface({\n id: 'calendar.calendar',\n title: 'calendar.title',\n icon: 'calendar',\n routable: { path: `${calendarListPath}/:calendarId` },\n component: CalendarPage,\n });\n if (options.railItem) {\n ctx.registerRailItem({\n id: 'calendar.rail',\n rail: 'primary',\n icon: 'calendar',\n title: 'calendar.calendars.title',\n run: () => ctx.navigateContent(calendarListPath),\n });\n }\n },\n deactivate() {\n calendarHost.unbind();\n },\n };\n}\n\nexport const calendarPlugin = createCalendarPlugin();\n","/**\n * Generated by orval 🍺\n * Do not edit manually.\n * Weaver Apps\n * OpenAPI spec version: 1.0.0\n */\nimport type {\n AppointmentRequest,\n AppointmentResponse,\n BusyPeriod,\n CalendarAppointmentsListParams,\n CalendarCalendarsBusyParams,\n CalendarResponse,\n CancelAppointmentRequest,\n ChangeAppointmentStatusRequest,\n SaveCalendarRequest\n} from './models';\n\n\nexport type calendarCalendarsSaveResponse200 = {\n data: CalendarResponse\n status: 200\n}\n\nexport type calendarCalendarsSaveResponse201 = {\n data: CalendarResponse\n status: 201\n}\n\nexport type calendarCalendarsSaveResponseSuccess = (calendarCalendarsSaveResponse200 | calendarCalendarsSaveResponse201) & {\n headers: Headers;\n};\n;\n\nexport type calendarCalendarsSaveResponse = (calendarCalendarsSaveResponseSuccess)\n\nexport const getCalendarCalendarsSaveUrl = (calendarId: string,) => {\n\n\n\n\n return `/calendar/calendars/${calendarId}`\n}\n\nexport const calendarCalendarsSave = async (calendarId: string,\n saveCalendarRequest: SaveCalendarRequest, options?: RequestInit): Promise<calendarCalendarsSaveResponse> => {\n\n const getHeaders = (h?: NonNullable<RequestInit['headers']>): Record<string, string | readonly string[]> => {\n if (!h) return {};\n if (h instanceof Headers) return Object.fromEntries(h.entries());\n if (Symbol.iterator in h) {\n return Object.fromEntries(\n Array.from(h as Iterable<Iterable<string>>, (entry) => Array.from(entry) as [string, string]),\n );\n }\n const headers: Record<string, string | readonly string[]> = {};\n for (const [name, value] of Object.entries<string | readonly string[] | undefined>(h)) {\n if (value !== undefined) headers[name] = value;\n }\n return headers;\n };\nconst res = await fetch(getCalendarCalendarsSaveUrl(calendarId),\n {\n ...options,\n method: 'PUT',\n headers: { 'Content-Type': 'application/json', ...getHeaders(options?.headers) },\n body: JSON.stringify(saveCalendarRequest)\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarCalendarsSaveResponse['data'] = body ? JSON.parse(body) : {}\n return { data, status: res.status, headers: res.headers } as calendarCalendarsSaveResponse\n}\n\n\n\nexport type calendarCalendarsGetResponse200 = {\n data: CalendarResponse\n status: 200\n}\n\nexport type calendarCalendarsGetResponseSuccess = (calendarCalendarsGetResponse200) & {\n headers: Headers;\n};\n;\n\nexport type calendarCalendarsGetResponse = (calendarCalendarsGetResponseSuccess)\n\nexport const getCalendarCalendarsGetUrl = (calendarId: string,) => {\n\n\n\n\n return `/calendar/calendars/${calendarId}`\n}\n\nexport const calendarCalendarsGet = async (calendarId: string, options?: RequestInit): Promise<calendarCalendarsGetResponse> => {\n\n const res = await fetch(getCalendarCalendarsGetUrl(calendarId),\n {\n ...options,\n method: 'GET'\n\n\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarCalendarsGetResponse['data'] = body ? JSON.parse(body) : {}\n return { data, status: res.status, headers: res.headers } as calendarCalendarsGetResponse\n}\n\n\n\nexport type calendarCalendarsDeleteResponse204 = {\n data: void\n status: 204\n}\n\nexport type calendarCalendarsDeleteResponseSuccess = (calendarCalendarsDeleteResponse204) & {\n headers: Headers;\n};\n;\n\nexport type calendarCalendarsDeleteResponse = (calendarCalendarsDeleteResponseSuccess)\n\nexport const getCalendarCalendarsDeleteUrl = (calendarId: string,) => {\n\n\n\n\n return `/calendar/calendars/${calendarId}`\n}\n\nexport const calendarCalendarsDelete = async (calendarId: string, options?: RequestInit): Promise<calendarCalendarsDeleteResponse> => {\n\n const res = await fetch(getCalendarCalendarsDeleteUrl(calendarId),\n {\n ...options,\n method: 'DELETE'\n\n\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarCalendarsDeleteResponse['data'] = body ? JSON.parse(body) : undefined\n return { data, status: res.status, headers: res.headers } as calendarCalendarsDeleteResponse\n}\n\n\n\nexport type calendarCalendarsListResponse200 = {\n data: CalendarResponse[]\n status: 200\n}\n\nexport type calendarCalendarsListResponseSuccess = (calendarCalendarsListResponse200) & {\n headers: Headers;\n};\n;\n\nexport type calendarCalendarsListResponse = (calendarCalendarsListResponseSuccess)\n\nexport const getCalendarCalendarsListUrl = () => {\n\n\n\n\n return `/calendar/calendars`\n}\n\nexport const calendarCalendarsList = async ( options?: RequestInit): Promise<calendarCalendarsListResponse> => {\n\n const res = await fetch(getCalendarCalendarsListUrl(),\n {\n ...options,\n method: 'GET'\n\n\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarCalendarsListResponse['data'] = body ? JSON.parse(body) : {}\n return { data, status: res.status, headers: res.headers } as calendarCalendarsListResponse\n}\n\n\n\nexport type calendarCalendarsBusyResponse200 = {\n data: BusyPeriod[]\n status: 200\n}\n\nexport type calendarCalendarsBusyResponseSuccess = (calendarCalendarsBusyResponse200) & {\n headers: Headers;\n};\n;\n\nexport type calendarCalendarsBusyResponse = (calendarCalendarsBusyResponseSuccess)\n\nexport const getCalendarCalendarsBusyUrl = (calendarId: string,\n params: CalendarCalendarsBusyParams,) => {\n const normalizedParams = new URLSearchParams();\n\n Object.entries(params || {}).forEach(([key, value]) => {\n\n if (value !== undefined) {\n normalizedParams.append(key, value === null ? 'null' : String(value))\n }\n });\n\n const stringifiedParams = normalizedParams.toString();\n\n return stringifiedParams.length > 0 ? `/calendar/calendars/${calendarId}/busy?${stringifiedParams}` : `/calendar/calendars/${calendarId}/busy`\n}\n\nexport const calendarCalendarsBusy = async (calendarId: string,\n params: CalendarCalendarsBusyParams, options?: RequestInit): Promise<calendarCalendarsBusyResponse> => {\n\n const res = await fetch(getCalendarCalendarsBusyUrl(calendarId,params),\n {\n ...options,\n method: 'GET'\n\n\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarCalendarsBusyResponse['data'] = body ? JSON.parse(body) : {}\n return { data, status: res.status, headers: res.headers } as calendarCalendarsBusyResponse\n}\n\n\n\nexport type calendarAppointmentsBookResponse201 = {\n data: AppointmentResponse\n status: 201\n}\n\nexport type calendarAppointmentsBookResponseSuccess = (calendarAppointmentsBookResponse201) & {\n headers: Headers;\n};\n;\n\nexport type calendarAppointmentsBookResponse = (calendarAppointmentsBookResponseSuccess)\n\nexport const getCalendarAppointmentsBookUrl = (calendarId: string,) => {\n\n\n\n\n return `/calendar/calendars/${calendarId}/appointments`\n}\n\nexport const calendarAppointmentsBook = async (calendarId: string,\n appointmentRequest: AppointmentRequest, options?: RequestInit): Promise<calendarAppointmentsBookResponse> => {\n\n const getHeaders = (h?: NonNullable<RequestInit['headers']>): Record<string, string | readonly string[]> => {\n if (!h) return {};\n if (h instanceof Headers) return Object.fromEntries(h.entries());\n if (Symbol.iterator in h) {\n return Object.fromEntries(\n Array.from(h as Iterable<Iterable<string>>, (entry) => Array.from(entry) as [string, string]),\n );\n }\n const headers: Record<string, string | readonly string[]> = {};\n for (const [name, value] of Object.entries<string | readonly string[] | undefined>(h)) {\n if (value !== undefined) headers[name] = value;\n }\n return headers;\n };\nconst res = await fetch(getCalendarAppointmentsBookUrl(calendarId),\n {\n ...options,\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getHeaders(options?.headers) },\n body: JSON.stringify(appointmentRequest)\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarAppointmentsBookResponse['data'] = body ? JSON.parse(body) : {}\n return { data, status: res.status, headers: res.headers } as calendarAppointmentsBookResponse\n}\n\n\n\nexport type calendarAppointmentsListResponse200 = {\n data: AppointmentResponse[]\n status: 200\n}\n\nexport type calendarAppointmentsListResponseSuccess = (calendarAppointmentsListResponse200) & {\n headers: Headers;\n};\n;\n\nexport type calendarAppointmentsListResponse = (calendarAppointmentsListResponseSuccess)\n\nexport const getCalendarAppointmentsListUrl = (calendarId: string,\n params: CalendarAppointmentsListParams,) => {\n const normalizedParams = new URLSearchParams();\n\n Object.entries(params || {}).forEach(([key, value]) => {\n\n if (value !== undefined) {\n normalizedParams.append(key, value === null ? 'null' : String(value))\n }\n });\n\n const stringifiedParams = normalizedParams.toString();\n\n return stringifiedParams.length > 0 ? `/calendar/calendars/${calendarId}/appointments?${stringifiedParams}` : `/calendar/calendars/${calendarId}/appointments`\n}\n\nexport const calendarAppointmentsList = async (calendarId: string,\n params: CalendarAppointmentsListParams, options?: RequestInit): Promise<calendarAppointmentsListResponse> => {\n\n const res = await fetch(getCalendarAppointmentsListUrl(calendarId,params),\n {\n ...options,\n method: 'GET'\n\n\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarAppointmentsListResponse['data'] = body ? JSON.parse(body) : {}\n return { data, status: res.status, headers: res.headers } as calendarAppointmentsListResponse\n}\n\n\n\nexport type calendarAppointmentsGetResponse200 = {\n data: AppointmentResponse\n status: 200\n}\n\nexport type calendarAppointmentsGetResponseSuccess = (calendarAppointmentsGetResponse200) & {\n headers: Headers;\n};\n;\n\nexport type calendarAppointmentsGetResponse = (calendarAppointmentsGetResponseSuccess)\n\nexport const getCalendarAppointmentsGetUrl = (calendarId: string,\n appointmentId: string,) => {\n\n\n\n\n return `/calendar/calendars/${calendarId}/appointments/${appointmentId}`\n}\n\nexport const calendarAppointmentsGet = async (calendarId: string,\n appointmentId: string, options?: RequestInit): Promise<calendarAppointmentsGetResponse> => {\n\n const res = await fetch(getCalendarAppointmentsGetUrl(calendarId,appointmentId),\n {\n ...options,\n method: 'GET'\n\n\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarAppointmentsGetResponse['data'] = body ? JSON.parse(body) : {}\n return { data, status: res.status, headers: res.headers } as calendarAppointmentsGetResponse\n}\n\n\n\nexport type calendarAppointmentsUpdateResponse200 = {\n data: AppointmentResponse\n status: 200\n}\n\nexport type calendarAppointmentsUpdateResponseSuccess = (calendarAppointmentsUpdateResponse200) & {\n headers: Headers;\n};\n;\n\nexport type calendarAppointmentsUpdateResponse = (calendarAppointmentsUpdateResponseSuccess)\n\nexport const getCalendarAppointmentsUpdateUrl = (calendarId: string,\n appointmentId: string,) => {\n\n\n\n\n return `/calendar/calendars/${calendarId}/appointments/${appointmentId}`\n}\n\nexport const calendarAppointmentsUpdate = async (calendarId: string,\n appointmentId: string,\n appointmentRequest: AppointmentRequest, options?: RequestInit): Promise<calendarAppointmentsUpdateResponse> => {\n\n const getHeaders = (h?: NonNullable<RequestInit['headers']>): Record<string, string | readonly string[]> => {\n if (!h) return {};\n if (h instanceof Headers) return Object.fromEntries(h.entries());\n if (Symbol.iterator in h) {\n return Object.fromEntries(\n Array.from(h as Iterable<Iterable<string>>, (entry) => Array.from(entry) as [string, string]),\n );\n }\n const headers: Record<string, string | readonly string[]> = {};\n for (const [name, value] of Object.entries<string | readonly string[] | undefined>(h)) {\n if (value !== undefined) headers[name] = value;\n }\n return headers;\n };\nconst res = await fetch(getCalendarAppointmentsUpdateUrl(calendarId,appointmentId),\n {\n ...options,\n method: 'PUT',\n headers: { 'Content-Type': 'application/json', ...getHeaders(options?.headers) },\n body: JSON.stringify(appointmentRequest)\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarAppointmentsUpdateResponse['data'] = body ? JSON.parse(body) : {}\n return { data, status: res.status, headers: res.headers } as calendarAppointmentsUpdateResponse\n}\n\n\n\nexport type calendarAppointmentsDeleteResponse204 = {\n data: void\n status: 204\n}\n\nexport type calendarAppointmentsDeleteResponseSuccess = (calendarAppointmentsDeleteResponse204) & {\n headers: Headers;\n};\n;\n\nexport type calendarAppointmentsDeleteResponse = (calendarAppointmentsDeleteResponseSuccess)\n\nexport const getCalendarAppointmentsDeleteUrl = (calendarId: string,\n appointmentId: string,) => {\n\n\n\n\n return `/calendar/calendars/${calendarId}/appointments/${appointmentId}`\n}\n\nexport const calendarAppointmentsDelete = async (calendarId: string,\n appointmentId: string, options?: RequestInit): Promise<calendarAppointmentsDeleteResponse> => {\n\n const res = await fetch(getCalendarAppointmentsDeleteUrl(calendarId,appointmentId),\n {\n ...options,\n method: 'DELETE'\n\n\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarAppointmentsDeleteResponse['data'] = body ? JSON.parse(body) : undefined\n return { data, status: res.status, headers: res.headers } as calendarAppointmentsDeleteResponse\n}\n\n\n\nexport type calendarAppointmentsCancelResponse200 = {\n data: AppointmentResponse\n status: 200\n}\n\nexport type calendarAppointmentsCancelResponseSuccess = (calendarAppointmentsCancelResponse200) & {\n headers: Headers;\n};\n;\n\nexport type calendarAppointmentsCancelResponse = (calendarAppointmentsCancelResponseSuccess)\n\nexport const getCalendarAppointmentsCancelUrl = (calendarId: string,\n appointmentId: string,) => {\n\n\n\n\n return `/calendar/calendars/${calendarId}/appointments/${appointmentId}/cancel`\n}\n\nexport const calendarAppointmentsCancel = async (calendarId: string,\n appointmentId: string,\n cancelAppointmentRequest: CancelAppointmentRequest, options?: RequestInit): Promise<calendarAppointmentsCancelResponse> => {\n\n const getHeaders = (h?: NonNullable<RequestInit['headers']>): Record<string, string | readonly string[]> => {\n if (!h) return {};\n if (h instanceof Headers) return Object.fromEntries(h.entries());\n if (Symbol.iterator in h) {\n return Object.fromEntries(\n Array.from(h as Iterable<Iterable<string>>, (entry) => Array.from(entry) as [string, string]),\n );\n }\n const headers: Record<string, string | readonly string[]> = {};\n for (const [name, value] of Object.entries<string | readonly string[] | undefined>(h)) {\n if (value !== undefined) headers[name] = value;\n }\n return headers;\n };\nconst res = await fetch(getCalendarAppointmentsCancelUrl(calendarId,appointmentId),\n {\n ...options,\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getHeaders(options?.headers) },\n body: JSON.stringify(cancelAppointmentRequest)\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarAppointmentsCancelResponse['data'] = body ? JSON.parse(body) : {}\n return { data, status: res.status, headers: res.headers } as calendarAppointmentsCancelResponse\n}\n\n\n\nexport type calendarAppointmentsStatusResponse200 = {\n data: AppointmentResponse\n status: 200\n}\n\nexport type calendarAppointmentsStatusResponseSuccess = (calendarAppointmentsStatusResponse200) & {\n headers: Headers;\n};\n;\n\nexport type calendarAppointmentsStatusResponse = (calendarAppointmentsStatusResponseSuccess)\n\nexport const getCalendarAppointmentsStatusUrl = (calendarId: string,\n appointmentId: string,) => {\n\n\n\n\n return `/calendar/calendars/${calendarId}/appointments/${appointmentId}/status`\n}\n\nexport const calendarAppointmentsStatus = async (calendarId: string,\n appointmentId: string,\n changeAppointmentStatusRequest: ChangeAppointmentStatusRequest, options?: RequestInit): Promise<calendarAppointmentsStatusResponse> => {\n\n const getHeaders = (h?: NonNullable<RequestInit['headers']>): Record<string, string | readonly string[]> => {\n if (!h) return {};\n if (h instanceof Headers) return Object.fromEntries(h.entries());\n if (Symbol.iterator in h) {\n return Object.fromEntries(\n Array.from(h as Iterable<Iterable<string>>, (entry) => Array.from(entry) as [string, string]),\n );\n }\n const headers: Record<string, string | readonly string[]> = {};\n for (const [name, value] of Object.entries<string | readonly string[] | undefined>(h)) {\n if (value !== undefined) headers[name] = value;\n }\n return headers;\n };\nconst res = await fetch(getCalendarAppointmentsStatusUrl(calendarId,appointmentId),\n {\n ...options,\n method: 'PUT',\n headers: { 'Content-Type': 'application/json', ...getHeaders(options?.headers) },\n body: JSON.stringify(changeAppointmentStatusRequest)\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarAppointmentsStatusResponse['data'] = body ? JSON.parse(body) : {}\n return { data, status: res.status, headers: res.headers } as calendarAppointmentsStatusResponse\n}\n\n\n\n","import {\n getCalendarAppointmentsBookUrl,\n getCalendarAppointmentsCancelUrl,\n getCalendarAppointmentsDeleteUrl,\n getCalendarAppointmentsListUrl,\n getCalendarAppointmentsStatusUrl,\n getCalendarAppointmentsUpdateUrl,\n getCalendarCalendarsGetUrl,\n getCalendarCalendarsListUrl,\n getCalendarCalendarsSaveUrl,\n} from '../api/endpoints';\nimport type { AppointmentResponse, CalendarResponse } from '../api/models';\nimport { AppointmentOutcome, CalendarGateway, CalendarGatewayError, ConflictingAppointment } from './calendar-gateway';\n\nexport interface HttpCalendarGatewayOptions {\n readonly baseUrl: string;\n readonly headers?: Readonly<Record<string, string>>;\n readonly fetch?: typeof fetch;\n}\n\nconst notFound = 404;\nconst conflict = 409;\nconst badRequest = 400;\nconst conflictCode = 'appointment-conflict';\nconst closedCode = 'appointment-closed';\n\ninterface Problem {\n readonly code?: string;\n readonly conflicts?: readonly ConflictingAppointment[];\n}\n\nexport function createHttpCalendarGateway(options: HttpCalendarGatewayOptions): CalendarGateway {\n const base = options.baseUrl.replace(/\\/+$/, '');\n const run = options.fetch ?? ((input: RequestInfo | URL, init?: RequestInit) => fetch(input, init));\n\n const send = (path: string, method: string, body?: unknown, signal?: AbortSignal): Promise<Response> =>\n run(base + path, {\n method,\n signal,\n credentials: 'same-origin',\n headers: {\n Accept: 'application/json',\n ...(body === undefined ? {} : { 'Content-Type': 'application/json' }),\n ...options.headers,\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n\n const read = async <T>(response: Response): Promise<T> => {\n if (!response.ok) {\n throw new CalendarGatewayError(response.status);\n }\n const value: T = await response.json();\n return value;\n };\n\n const outcome = async (response: Response): Promise<AppointmentOutcome> => {\n if (response.ok) {\n const appointment: AppointmentResponse = await response.json();\n return { kind: 'saved', appointment };\n }\n if (response.status === notFound) {\n return { kind: 'missing' };\n }\n if (response.status === badRequest) {\n return { kind: 'invalid' };\n }\n if (response.status !== conflict) {\n throw new CalendarGatewayError(response.status);\n }\n const problem: Problem = await response.json().catch(() => ({}));\n if (problem.code === conflictCode) {\n return { kind: 'conflict', conflicts: problem.conflicts ?? [] };\n }\n return problem.code === closedCode ? { kind: 'closed' } : { kind: 'changed' };\n };\n\n return {\n calendars: async () => read<CalendarResponse[]>(await send(getCalendarCalendarsListUrl(), 'GET')),\n calendar: async (calendarId) => {\n const response = await send(getCalendarCalendarsGetUrl(calendarId), 'GET');\n return response.status === notFound ? null : read<CalendarResponse>(response);\n },\n saveCalendar: async (calendarId, name) =>\n read<CalendarResponse>(await send(getCalendarCalendarsSaveUrl(calendarId), 'PUT', { name })),\n appointments: async (calendarId, range, contact, signal) => {\n const params = { from: range.from.toISOString(), until: range.until.toISOString(), ...(contact ? { contact } : {}) };\n return read<AppointmentResponse[]>(await send(getCalendarAppointmentsListUrl(calendarId, params), 'GET', undefined, signal));\n },\n book: async (calendarId, request) => outcome(await send(getCalendarAppointmentsBookUrl(calendarId), 'POST', request)),\n update: async (calendarId, appointmentId, request) =>\n outcome(await send(getCalendarAppointmentsUpdateUrl(calendarId, appointmentId), 'PUT', request)),\n cancel: async (calendarId, appointmentId, reason) =>\n outcome(await send(getCalendarAppointmentsCancelUrl(calendarId, appointmentId), 'POST', { reason })),\n setStatus: async (calendarId, appointmentId, status) =>\n outcome(await send(getCalendarAppointmentsStatusUrl(calendarId, appointmentId), 'PUT', { status })),\n remove: async (calendarId, appointmentId) => {\n const response = await send(getCalendarAppointmentsDeleteUrl(calendarId, appointmentId), 'DELETE');\n if (!response.ok && response.status !== notFound) {\n throw new CalendarGatewayError(response.status);\n }\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"weaver-apps-calendar.mjs","names":[],"sources":["../../../../libs/calendar/src/lib/gateway/calendar-gateway.ts","../../../../libs/calendar/src/lib/plugin/calendar-host.ts","../../../../libs/calendar/src/lib/plugin/calendar-ids.ts","../../../../libs/calendar/src/lib/plugin/calendar-paths.ts","../../../../libs/calendar/src/lib/views/calendar-list-page.ts","../../../../libs/calendar/src/lib/views/calendar-list-page.html","../../../../libs/calendar/src/lib/week.ts","../../../../libs/calendar/src/lib/views/active-locale.ts","../../../../libs/calendar/src/lib/api/models/appointmentStatus.ts","../../../../libs/calendar/src/lib/views/appointment-dialog.ts","../../../../libs/calendar/src/lib/views/appointment-dialog.html","../../../../libs/calendar/src/lib/views/appointment-cell.ts","../../../../libs/calendar/src/lib/views/appointment-cell.html","../../../../libs/calendar/src/lib/views/appointment-table.ts","../../../../libs/calendar/src/lib/views/appointment-table.html","../../../../libs/calendar/src/lib/views/calendar-page.ts","../../../../libs/calendar/src/lib/views/calendar-page.html","../../../../libs/calendar/src/lib/plugin/calendar-icons.ts","../../../../libs/calendar/src/lib/plugin/calendar.plugin.ts","../../../../libs/calendar/src/lib/api/endpoints.ts","../../../../libs/calendar/src/lib/gateway/http-calendar-gateway.ts"],"sourcesContent":["import { InjectionToken, Provider } from '@angular/core';\nimport type { AppointmentRequest, AppointmentResponse, AppointmentStatus, CalendarResponse } from '../api/models';\n\nexport interface CalendarRange {\n readonly from: Date;\n readonly until: Date;\n}\n\nexport interface ConflictingAppointment {\n readonly id: string;\n readonly start: string;\n readonly end: string;\n}\n\nexport type AppointmentOutcome =\n | { readonly kind: 'saved'; readonly appointment: AppointmentResponse }\n | { readonly kind: 'conflict'; readonly conflicts: readonly ConflictingAppointment[] }\n | { readonly kind: 'closed' }\n | { readonly kind: 'changed' }\n | { readonly kind: 'invalid' }\n | { readonly kind: 'missing' };\n\nexport interface CalendarGateway {\n calendars(): Promise<readonly CalendarResponse[]>;\n calendar(calendarId: string): Promise<CalendarResponse | null>;\n saveCalendar(calendarId: string, name: string): Promise<CalendarResponse>;\n appointments(calendarId: string, range: CalendarRange, contact?: string, signal?: AbortSignal): Promise<readonly AppointmentResponse[]>;\n book(calendarId: string, request: AppointmentRequest): Promise<AppointmentOutcome>;\n update(calendarId: string, appointmentId: string, request: AppointmentRequest): Promise<AppointmentOutcome>;\n cancel(calendarId: string, appointmentId: string, reason: string | null): Promise<AppointmentOutcome>;\n setStatus(calendarId: string, appointmentId: string, status: AppointmentStatus): Promise<AppointmentOutcome>;\n remove(calendarId: string, appointmentId: string): Promise<void>;\n}\n\nexport class CalendarGatewayError extends Error {\n constructor(readonly status: number) {\n super(`The calendar answered ${status}.`);\n }\n}\n\nexport const CALENDAR_GATEWAY = new InjectionToken<CalendarGateway>('weaver-apps.calendar.gateway');\n\nexport function provideCalendarGateway(gateway: CalendarGateway): Provider {\n return { provide: CALENDAR_GATEWAY, useValue: gateway };\n}\n","import type { PluginContext } from '@loomweaver/plugin-sdk';\n\ntype HostContext = Pick<PluginContext, 'ui' | 'navigateContent'>;\n\nclass CalendarHost {\n private ctx?: HostContext;\n\n bind(ctx: HostContext): void {\n this.ctx = ctx;\n }\n\n unbind(): void {\n this.ctx = undefined;\n }\n\n get ui(): HostContext['ui'] {\n if (!this.ctx) {\n throw new Error('The calendar plugin is not active.');\n }\n return this.ctx.ui;\n }\n\n navigate(path: string): void {\n this.ctx?.navigateContent(path);\n }\n}\n\nexport const calendarHost = new CalendarHost();\n","export const calendarPluginId = 'weaver-apps-calendar';\n\nexport const calendarContributionIds = {\n calendars: `${calendarPluginId}.calendars`,\n calendar: `${calendarPluginId}.calendar`,\n rail: `${calendarPluginId}.rail`,\n} as const;\n\nexport const calendarIconNames = {\n calendar: `${calendarPluginId}.calendar`,\n previous: `${calendarPluginId}.previous`,\n next: `${calendarPluginId}.next`,\n cancel: `${calendarPluginId}.cancel`,\n} as const;\n","import { calendarPluginId } from './calendar-ids';\n\nexport const calendarListPath = calendarPluginId;\n\nexport function calendarPath(calendarId: string): string {\n return `${calendarListPath}/${calendarId}`;\n}\n","import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, inject, resource } from '@angular/core';\nimport { TranslocoPipe } from '@jsverse/transloco';\nimport { CALENDAR_GATEWAY } from '../gateway/calendar-gateway';\nimport { calendarHost } from '../plugin/calendar-host';\nimport { calendarPath } from '../plugin/calendar-paths';\n\n@Component({\n selector: 'wa-calendar-list-page',\n imports: [TranslocoPipe],\n schemas: [CUSTOM_ELEMENTS_SCHEMA],\n templateUrl: './calendar-list-page.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class CalendarListPage {\n private readonly gateway = inject(CALENDAR_GATEWAY);\n\n protected readonly calendars = resource({ loader: () => this.gateway.calendars() });\n\n protected open(calendarId: string): void {\n calendarHost.navigate(calendarPath(calendarId));\n }\n\n protected async create(): Promise<void> {\n const name = await calendarHost.ui.prompt({\n title: 'weaver-apps-calendar.calendars.create',\n message: 'weaver-apps-calendar.calendars.createMessage',\n confirmLabel: 'weaver-apps-calendar.calendars.createConfirm',\n });\n if (!name?.trim()) {\n return;\n }\n try {\n const calendar = await this.gateway.saveCalendar(crypto.randomUUID(), name.trim());\n this.open(calendar.id);\n } catch {\n calendarHost.ui.toast({ message: 'weaver-apps-calendar.errors.create', kind: 'error' });\n }\n }\n}\n","<div class=\"flex h-full flex-col gap-4 overflow-auto p-6\">\n <div class=\"flex items-center justify-between gap-4\">\n <h2 class=\"text-lg font-semibold text-content\">{{ 'weaver-apps-calendar.calendars.title' | transloco }}</h2>\n <button type=\"button\" class=\"lw-btn lw-btn--primary\" (click)=\"create()\">{{ 'weaver-apps-calendar.calendars.create' | transloco }}</button>\n </div>\n @if (calendars.error()) {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ 'weaver-apps-calendar.errors.load' | transloco }}</p>\n } @else if (calendars.hasValue()) {\n <ul role=\"list\" class=\"divide-y divide-border rounded-xl border border-border bg-surface\">\n @for (calendar of calendars.value(); track calendar.id) {\n <li>\n <button type=\"button\" class=\"flex w-full items-center gap-3 px-4 py-3 text-left text-sm text-content hover:bg-surface-overlay\" (click)=\"open(calendar.id)\">\n <lw-icon name=\"weaver-apps-calendar.calendar\" size=\"1.25rem\"></lw-icon>\n <span>{{ calendar.name }}</span>\n </button>\n </li>\n } @empty {\n <li class=\"px-4 py-8 text-center text-sm text-content-faint\">{{ 'weaver-apps-calendar.calendars.empty' | transloco }}</li>\n }\n </ul>\n }\n</div>\n","import type { CalendarRange } from './gateway/calendar-gateway';\n\nconst daysPerWeek = 7;\nconst sunday = 0;\nconst daysFromSundayToMonday = 6;\n\nexport function startOfWeek(date: Date): Date {\n const start = new Date(date.getFullYear(), date.getMonth(), date.getDate());\n const offset = start.getDay() === sunday ? daysFromSundayToMonday : start.getDay() - 1;\n start.setDate(start.getDate() - offset);\n return start;\n}\n\nexport function addWeeks(weekStart: Date, weeks: number): Date {\n const moved = new Date(weekStart);\n moved.setDate(moved.getDate() + weeks * daysPerWeek);\n return moved;\n}\n\nexport function weekRange(weekStart: Date): CalendarRange {\n return { from: weekStart, until: addWeeks(weekStart, 1) };\n}\n\nexport function localDate(value: Date): string {\n const pad = (part: number) => `${part}`.padStart(2, '0');\n return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}`;\n}\n\nexport function localTime(value: Date): string {\n const pad = (part: number) => `${part}`.padStart(2, '0');\n return `${pad(value.getHours())}:${pad(value.getMinutes())}`;\n}\n\nexport function combine(date: string, time: string): Date | null {\n const combined = new Date(`${date}T${time}`);\n return Number.isNaN(combined.getTime()) ? null : combined;\n}\n","import { inject, Signal } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { TranslocoService } from '@jsverse/transloco';\n\nexport function activeLocale(): Signal<string> {\n const transloco = inject(TranslocoService);\n return toSignal(transloco.langChanges$, { initialValue: transloco.getActiveLang() });\n}\n","/**\n * Generated by orval 🍺\n * Do not edit manually.\n * Weaver Apps\n * OpenAPI spec version: 1.0.0\n */\n\n/**\n * Where an appointment stands.\n */\nexport type AppointmentStatus = typeof AppointmentStatus[keyof typeof AppointmentStatus];\n\n\nexport const AppointmentStatus = {\n scheduled: 'scheduled',\n confirmed: 'confirmed',\n completed: 'completed',\n noShow: 'noShow',\n cancelled: 'cancelled',\n} as const;\n","import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';\nimport { DialogRef } from '@loomweaver/plugin-sdk';\nimport { TranslocoPipe } from '@jsverse/transloco';\nimport { AppointmentStatus, type AppointmentRequest, type AppointmentResponse } from '../api/models';\nimport { AppointmentOutcome, CALENDAR_GATEWAY, ConflictingAppointment } from '../gateway/calendar-gateway';\nimport { combine, localDate, localTime } from '../week';\nimport { activeLocale } from './active-locale';\n\nexport interface AppointmentDialogData {\n readonly calendarId: string;\n readonly appointment: AppointmentResponse | null;\n readonly day: Date;\n}\n\ntype Problem =\n | { readonly kind: 'none' }\n | { readonly kind: 'message'; readonly key: string }\n | { readonly kind: 'conflict'; readonly conflicts: readonly ConflictingAppointment[] };\n\nconst defaultStart = '09:00';\nconst defaultEnd = '10:00';\nconst settableStatuses: readonly AppointmentStatus[] = [\n AppointmentStatus.scheduled,\n AppointmentStatus.confirmed,\n AppointmentStatus.completed,\n AppointmentStatus.noShow,\n];\n\n@Component({\n selector: 'wa-appointment-dialog',\n imports: [TranslocoPipe],\n templateUrl: './appointment-dialog.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class AppointmentDialog {\n private readonly dialog = inject<DialogRef<boolean>>(DialogRef);\n private readonly gateway = inject(CALENDAR_GATEWAY);\n private readonly data = this.dialog.data as AppointmentDialogData;\n private existing = this.data.appointment;\n\n protected readonly locale = activeLocale();\n protected readonly editing = this.existing !== null;\n protected readonly statuses = settableStatuses;\n\n protected readonly title = signal(this.existing?.title ?? '');\n protected readonly date = signal(localDate(this.existing ? new Date(this.existing.start) : this.data.day));\n protected readonly start = signal(this.existing ? localTime(new Date(this.existing.start)) : defaultStart);\n protected readonly endDate = signal(localDate(this.existing ? new Date(this.existing.end) : this.data.day));\n protected readonly end = signal(this.existing ? localTime(new Date(this.existing.end)) : defaultEnd);\n protected readonly location = signal(this.existing?.location ?? '');\n protected readonly contactName = signal(this.existing?.contactName ?? '');\n protected readonly contactPhone = signal(this.existing?.contactPhone ?? '');\n protected readonly contactEmail = signal(this.existing?.contactEmail ?? '');\n protected readonly description = signal(this.existing?.description ?? '');\n protected readonly status = signal<AppointmentStatus>(this.existing?.status ?? AppointmentStatus.scheduled);\n protected readonly saving = signal(false);\n protected readonly problem = signal<Problem>({ kind: 'none' });\n private persisted = false;\n\n protected readonly problemKey = computed(() => {\n const problem = this.problem();\n return problem.kind === 'message' ? problem.key : '';\n });\n\n protected readonly conflictTimes = computed(() => {\n const problem = this.problem();\n if (problem.kind !== 'conflict') {\n return [];\n }\n const format = new Intl.DateTimeFormat(this.locale(), { dateStyle: 'short', timeStyle: 'short' });\n return problem.conflicts.map((conflict) => `${format.format(new Date(conflict.start))} – ${format.format(new Date(conflict.end))}`);\n });\n\n protected bind(target: { set(value: string): void }, event: Event): void {\n target.set((event.target as HTMLInputElement).value);\n }\n\n protected bindDate(event: Event): void {\n const value = (event.target as HTMLInputElement).value;\n if (this.endDate() === this.date()) {\n this.endDate.set(value);\n }\n this.date.set(value);\n }\n\n protected chooseStatus(event: Event): void {\n const chosen = settableStatuses.find((status) => status === (event.target as HTMLSelectElement).value);\n if (chosen) {\n this.status.set(chosen);\n }\n }\n\n protected close(): void {\n this.dialog.close(this.persisted);\n }\n\n protected async save(): Promise<void> {\n const request = this.request();\n if (!request) {\n return;\n }\n this.saving.set(true);\n try {\n const outcome = this.existing\n ? await this.gateway.update(this.data.calendarId, this.existing.id, request)\n : await this.gateway.book(this.data.calendarId, request);\n const final = await this.applyStatus(outcome);\n this.settle(final);\n } catch {\n this.problem.set({ kind: 'message', key: 'weaver-apps-calendar.errors.save' });\n } finally {\n this.saving.set(false);\n }\n }\n\n private request(): AppointmentRequest | null {\n const startsAt = combine(this.date(), this.start());\n const endsAt = combine(this.endDate(), this.end());\n if (!this.title().trim()) {\n this.problem.set({ kind: 'message', key: 'weaver-apps-calendar.dialog.titleRequired' });\n return null;\n }\n if (!startsAt || !endsAt || endsAt <= startsAt) {\n this.problem.set({ kind: 'message', key: 'weaver-apps-calendar.dialog.periodInvalid' });\n return null;\n }\n return {\n title: this.title().trim(),\n start: startsAt.toISOString(),\n end: endsAt.toISOString(),\n description: this.optional(this.description()),\n location: this.optional(this.location()),\n contactName: this.optional(this.contactName()),\n contactPhone: this.optional(this.contactPhone()),\n contactEmail: this.optional(this.contactEmail()),\n };\n }\n\n private async applyStatus(outcome: AppointmentOutcome): Promise<AppointmentOutcome> {\n if (outcome.kind === 'saved') {\n this.existing = outcome.appointment;\n this.persisted = true;\n }\n if (outcome.kind !== 'saved' || outcome.appointment.status === this.status()) {\n return outcome;\n }\n return this.gateway.setStatus(this.data.calendarId, outcome.appointment.id, this.status());\n }\n\n private settle(outcome: AppointmentOutcome): void {\n switch (outcome.kind) {\n case 'saved':\n this.dialog.close(true);\n return;\n case 'conflict':\n this.problem.set({ kind: 'conflict', conflicts: outcome.conflicts });\n return;\n case 'closed':\n this.problem.set({ kind: 'message', key: 'weaver-apps-calendar.dialog.closed' });\n return;\n case 'changed':\n this.problem.set({ kind: 'message', key: 'weaver-apps-calendar.dialog.changed' });\n return;\n case 'invalid':\n this.problem.set({ kind: 'message', key: 'weaver-apps-calendar.dialog.invalid' });\n return;\n default:\n this.problem.set({ kind: 'message', key: 'weaver-apps-calendar.dialog.missing' });\n }\n }\n\n private optional(value: string): string | null {\n return value.trim() ? value.trim() : null;\n }\n}\n","<form class=\"flex flex-col gap-4\" (submit)=\"$event.preventDefault(); save()\">\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.title' | transloco }}\n <input class=\"lw-field\" required [value]=\"title()\" (input)=\"bind(title, $event)\" />\n </label>\n\n <div class=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.date' | transloco }}\n <input class=\"lw-field\" type=\"date\" required [value]=\"date()\" (input)=\"bindDate($event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.start' | transloco }}\n <input class=\"lw-field\" type=\"time\" required [value]=\"start()\" (input)=\"bind(start, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.endDate' | transloco }}\n <input class=\"lw-field\" type=\"date\" required [value]=\"endDate()\" (input)=\"bind(endDate, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.end' | transloco }}\n <input class=\"lw-field\" type=\"time\" required [value]=\"end()\" (input)=\"bind(end, $event)\" />\n </label>\n </div>\n\n <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.contactName' | transloco }}\n <input class=\"lw-field\" [value]=\"contactName()\" (input)=\"bind(contactName, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.contactPhone' | transloco }}\n <input class=\"lw-field\" type=\"tel\" [value]=\"contactPhone()\" (input)=\"bind(contactPhone, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.contactEmail' | transloco }}\n <input class=\"lw-field\" type=\"email\" [value]=\"contactEmail()\" (input)=\"bind(contactEmail, $event)\" />\n </label>\n </div>\n\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.location' | transloco }}\n <input class=\"lw-field\" [value]=\"location()\" (input)=\"bind(location, $event)\" />\n </label>\n\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.description' | transloco }}\n <textarea class=\"lw-field\" rows=\"3\" [value]=\"description()\" (input)=\"bind(description, $event)\"></textarea>\n </label>\n\n @if (editing) {\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-calendar.fields.status' | transloco }}\n <select class=\"lw-field\" [value]=\"status()\" (change)=\"chooseStatus($event)\">\n @for (choice of statuses; track choice) {\n <option [value]=\"choice\" [selected]=\"choice === status()\">{{ 'weaver-apps-calendar.status.' + choice | transloco }}</option>\n }\n </select>\n </label>\n }\n\n @switch (problem().kind) {\n @case ('message') {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ problemKey() | transloco }}</p>\n }\n @case ('conflict') {\n <div class=\"text-sm text-negative\" role=\"alert\">\n <p>{{ 'weaver-apps-calendar.dialog.conflict' | transloco }}</p>\n <ul class=\"list-disc pl-5\">\n @for (time of conflictTimes(); track time) {\n <li>{{ time }}</li>\n }\n </ul>\n </div>\n }\n }\n\n <div class=\"flex justify-end gap-2\">\n <button type=\"button\" class=\"lw-btn lw-btn--default\" (click)=\"close()\">{{ 'weaver-apps-calendar.dialog.cancel' | transloco }}</button>\n <button type=\"submit\" class=\"lw-btn lw-btn--primary\" [disabled]=\"saving()\">{{ 'weaver-apps-calendar.dialog.save' | transloco }}</button>\n </div>\n</form>\n","import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, computed, signal } from '@angular/core';\nimport { TranslocoPipe } from '@jsverse/transloco';\nimport type { ICellRendererAngularComp } from 'ag-grid-angular';\nimport type { ICellRendererParams } from 'ag-grid-community';\nimport type { AppointmentResponse } from '../api/models';\n\nexport type AppointmentCellKind = 'when' | 'title' | 'contact' | 'location' | 'status' | 'actions';\n\nexport interface AppointmentCellParams {\n readonly kind: AppointmentCellKind;\n readonly locale: () => string;\n readonly edit: (appointment: AppointmentResponse) => void;\n readonly cancel: (appointment: AppointmentResponse) => void;\n readonly remove: (appointment: AppointmentResponse) => void;\n}\n\ntype Params = ICellRendererParams<AppointmentResponse> & AppointmentCellParams;\n\n@Component({\n selector: 'wa-appointment-cell',\n imports: [TranslocoPipe],\n schemas: [CUSTOM_ELEMENTS_SCHEMA],\n templateUrl: './appointment-cell.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class AppointmentCell implements ICellRendererAngularComp {\n private params: Params | null = null;\n private readonly locale = signal('en');\n\n protected readonly kind = signal<AppointmentCellKind>('title');\n protected readonly appointment = signal<AppointmentResponse | null>(null);\n protected readonly cancelled = computed(() => this.appointment()?.status === 'cancelled');\n protected readonly when = computed(() => {\n const appointment = this.appointment();\n if (!appointment) {\n return { day: '', hours: '' };\n }\n const day = new Intl.DateTimeFormat(this.locale(), { weekday: 'short', day: '2-digit', month: '2-digit', year: 'numeric' });\n const time = new Intl.DateTimeFormat(this.locale(), { hour: '2-digit', minute: '2-digit' });\n return {\n day: day.format(new Date(appointment.start)),\n hours: `${time.format(new Date(appointment.start))} – ${time.format(new Date(appointment.end))}`,\n };\n });\n\n agInit(params: Params): void {\n this.params = params;\n this.kind.set(params.kind);\n this.appointment.set(params.data ?? null);\n this.locale.set(params.locale());\n }\n\n refresh(params: Params): boolean {\n this.agInit(params);\n return true;\n }\n\n protected edit(): void {\n this.hand(this.params?.edit);\n }\n\n protected cancel(): void {\n this.hand(this.params?.cancel);\n }\n\n protected remove(): void {\n this.hand(this.params?.remove);\n }\n\n private hand(to: ((appointment: AppointmentResponse) => void) | undefined): void {\n const appointment = this.appointment();\n if (appointment) {\n to?.(appointment);\n }\n }\n}\n","@if (appointment(); as appointment) {\n @switch (kind()) {\n @case ('when') {\n <span class=\"font-medium\" [class.text-content]=\"!cancelled()\" [class.line-through]=\"cancelled()\">{{ when().day }}</span>\n <span class=\"ml-2 text-content-faint\" [class.line-through]=\"cancelled()\">{{ when().hours }}</span>\n }\n @case ('title') {\n <span class=\"font-medium\" [class.text-content]=\"!cancelled()\" [class.line-through]=\"cancelled()\">{{ appointment.title }}</span>\n }\n @case ('contact') {\n <span [class.line-through]=\"cancelled()\">{{ appointment.contactName ?? '–' }}</span>\n @if (appointment.contactPhone) {\n <span class=\"ml-2 text-content-faint\" [class.line-through]=\"cancelled()\">{{ appointment.contactPhone }}</span>\n }\n }\n @case ('location') {\n <span class=\"text-content-faint\" [class.line-through]=\"cancelled()\">{{ appointment.location ?? '–' }}</span>\n }\n @case ('status') {\n <span class=\"rounded-full border border-border px-2 py-0.5 text-xs text-content\" data-testid=\"appointment-status\">{{ 'weaver-apps-calendar.status.' + appointment.status | transloco }}</span>\n }\n @case ('actions') {\n <span class=\"flex justify-end\">\n <button type=\"button\" class=\"relative lw-btn lw-btn--ghost lw-btn--sm lw-btn--icon\" [attr.aria-label]=\"'weaver-apps-calendar.actions.edit' | transloco\" (click)=\"edit()\">\n <lw-icon name=\"edit\" size=\"1rem\"></lw-icon>\n </button>\n @if (!cancelled()) {\n <button type=\"button\" class=\"relative lw-btn lw-btn--ghost lw-btn--sm lw-btn--icon\" [attr.aria-label]=\"'weaver-apps-calendar.actions.cancel' | transloco\" (click)=\"cancel()\">\n <lw-icon name=\"weaver-apps-calendar.cancel\" size=\"1rem\"></lw-icon>\n </button>\n }\n <button type=\"button\" class=\"relative lw-btn lw-btn--ghost lw-btn--sm lw-btn--icon\" [attr.aria-label]=\"'weaver-apps-calendar.actions.delete' | transloco\" (click)=\"remove()\">\n <lw-icon name=\"trash\" size=\"1rem\"></lw-icon>\n </button>\n </span>\n }\n }\n}\n","import { ChangeDetectionStrategy, Component, computed, effect, input, output, signal, type Signal } from '@angular/core';\nimport { translateObjectSignal } from '@jsverse/transloco';\nimport { DataGrid, ListFilter, type ListFilterParams } from '@weaver-apps/data-grid';\nimport type { ColDef, GridApi, GridOptions } from 'ag-grid-community';\nimport { AppointmentStatus, type AppointmentResponse } from '../api/models';\nimport { AppointmentCell, type AppointmentCellKind, type AppointmentCellParams } from './appointment-cell';\n\ntype Texts = Readonly<Record<string, string>>;\n\nexport const appointmentColumn = {\n when: 'start',\n title: 'title',\n contact: 'contact',\n location: 'location',\n status: 'status',\n actions: 'actions',\n} as const;\n\nconst actionsWidthPx = 136;\nconst whenMinWidthPx = 230;\nconst textMinWidthPx = 110;\nconst locationMinWidthPx = 90;\nconst statusMinWidthPx = 112;\n\nexport function byStart(left: string | undefined, right: string | undefined): number {\n return Date.parse(left ?? '') - Date.parse(right ?? '');\n}\n\nexport function statusOf(appointment: AppointmentResponse): string {\n return appointment.status;\n}\n\n@Component({\n selector: 'wa-appointment-table',\n imports: [DataGrid],\n templateUrl: './appointment-table.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: { class: 'block h-full w-full' },\n})\nexport class AppointmentTable {\n private readonly table = translateObjectSignal('weaver-apps-calendar.table') as Signal<Texts>;\n private readonly statuses = translateObjectSignal('weaver-apps-calendar.status') as Signal<Texts>;\n private readonly api = signal<GridApi<AppointmentResponse> | null>(null);\n\n readonly appointments = input.required<readonly AppointmentResponse[]>();\n readonly locale = input.required<string>();\n\n readonly editAppointment = output<AppointmentResponse>();\n readonly cancelAppointment = output<AppointmentResponse>();\n readonly deleteAppointment = output<AppointmentResponse>();\n\n protected readonly translated = computed(() => typeof this.table() === 'object' && typeof this.table()['title'] === 'string');\n\n protected readonly empty = computed(() => ({ noRowsToShow: this.table()['empty'] ?? '' }));\n\n protected readonly options = computed<GridOptions<AppointmentResponse>>(() => ({\n rowModelType: 'clientSide',\n columnDefs: this.columns(this.table()),\n defaultColDef: { sortable: false, resizable: true, suppressHeaderMenuButton: true },\n getRowId: (params) => params.data.id,\n initialState: { sort: { sortModel: [{ colId: appointmentColumn.when, sort: 'asc' }] } },\n rowClassRules: { 'text-content-faint': (params) => params.data?.status === AppointmentStatus.cancelled },\n }));\n\n constructor() {\n effect(() => {\n const api = this.api();\n const appointments = this.appointments();\n if (api && !api.isDestroyed()) {\n api.setGridOption('rowData', [...appointments]);\n }\n });\n }\n\n protected ready(api: GridApi<AppointmentResponse>): void {\n this.api.set(api);\n }\n\n private columns(table: Texts): ColDef<AppointmentResponse>[] {\n return [\n { ...this.cell(appointmentColumn.when, 'when', table['when']), field: 'start', sortable: true, comparator: byStart, flex: 2, minWidth: whenMinWidthPx },\n { ...this.cell(appointmentColumn.title, 'title', table['title']), field: 'title', sortable: true, flex: 2, minWidth: textMinWidthPx },\n {\n ...this.cell(appointmentColumn.contact, 'contact', table['contact']),\n valueGetter: (params) => params.data?.contactName ?? '',\n sortable: true,\n flex: 2,\n minWidth: textMinWidthPx,\n },\n { ...this.cell(appointmentColumn.location, 'location', table['location']), field: 'location', sortable: true, flex: 1, minWidth: locationMinWidthPx },\n {\n ...this.cell(appointmentColumn.status, 'status', table['status']),\n field: 'status',\n sortable: true,\n flex: 1,\n minWidth: statusMinWidthPx,\n filter: ListFilter,\n filterParams: this.statusChoices(),\n },\n { ...this.cell(appointmentColumn.actions, 'actions', ''), width: actionsWidthPx, resizable: false },\n ];\n }\n\n private cell(colId: string, kind: AppointmentCellKind, headerName: string | undefined): ColDef<AppointmentResponse> {\n const params: AppointmentCellParams = {\n kind,\n locale: () => this.locale(),\n edit: (appointment) => this.editAppointment.emit(appointment),\n cancel: (appointment) => this.cancelAppointment.emit(appointment),\n remove: (appointment) => this.deleteAppointment.emit(appointment),\n };\n return { colId, headerName: headerName ?? '', cellRenderer: AppointmentCell, cellRendererParams: params };\n }\n\n private statusChoices(): ListFilterParams {\n const values = Object.values(AppointmentStatus);\n return {\n options: () => values.map((value) => ({ value, label: this.statuses()[value] ?? value })),\n rowValue: statusOf,\n };\n }\n}\n","@if (translated()) {\n <wa-data-grid [options]=\"options()\" [localeOverrides]=\"empty()\" (ready)=\"ready($event)\" />\n}\n","import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, computed, DestroyRef, inject, resource, signal } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { ActivatedRoute } from '@angular/router';\nimport { TranslocoPipe } from '@jsverse/transloco';\nimport { map } from 'rxjs';\nimport type { AppointmentResponse } from '../api/models';\nimport { CALENDAR_GATEWAY } from '../gateway/calendar-gateway';\nimport { calendarHost } from '../plugin/calendar-host';\nimport { addWeeks, startOfWeek, weekRange } from '../week';\nimport { activeLocale } from './active-locale';\nimport { AppointmentDialog, AppointmentDialogData } from './appointment-dialog';\nimport { AppointmentTable } from './appointment-table';\n\nconst searchDelayMs = 300;\n\n@Component({\n selector: 'wa-calendar-page',\n imports: [TranslocoPipe, AppointmentTable],\n schemas: [CUSTOM_ELEMENTS_SCHEMA],\n templateUrl: './calendar-page.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class CalendarPage {\n private readonly gateway = inject(CALENDAR_GATEWAY);\n private readonly route = inject(ActivatedRoute);\n private searchTimer?: ReturnType<typeof setTimeout>;\n\n protected readonly locale = activeLocale();\n protected readonly calendarId = toSignal(this.route.paramMap.pipe(map((params) => params.get('calendarId') ?? '')), {\n initialValue: this.route.snapshot.paramMap.get('calendarId') ?? '',\n });\n protected readonly weekStart = signal(startOfWeek(new Date()));\n protected readonly contact = signal('');\n\n protected readonly calendar = resource({\n params: () => this.calendarId(),\n loader: ({ params }) => this.gateway.calendar(params),\n });\n\n protected readonly appointments = resource({\n params: () => ({ calendarId: this.calendarId(), range: weekRange(this.weekStart()), contact: this.contact().trim() }),\n loader: ({ params, abortSignal }) =>\n this.gateway.appointments(params.calendarId, params.range, params.contact || undefined, abortSignal),\n });\n\n constructor() {\n inject(DestroyRef).onDestroy(() => clearTimeout(this.searchTimer));\n }\n\n protected readonly weekLabel = computed(() => {\n const range = weekRange(this.weekStart());\n const last = addWeeks(range.until, 0);\n last.setDate(last.getDate() - 1);\n const format = new Intl.DateTimeFormat(this.locale(), { day: '2-digit', month: '2-digit', year: 'numeric' });\n return `${format.format(range.from)} – ${format.format(last)}`;\n });\n\n protected previousWeek(): void {\n this.weekStart.update((start) => addWeeks(start, -1));\n }\n\n protected nextWeek(): void {\n this.weekStart.update((start) => addWeeks(start, 1));\n }\n\n protected thisWeek(): void {\n this.weekStart.set(startOfWeek(new Date()));\n }\n\n protected search(event: Event): void {\n const value = (event.target as HTMLInputElement).value;\n clearTimeout(this.searchTimer);\n this.searchTimer = setTimeout(() => this.contact.set(value), searchDelayMs);\n }\n\n protected add(): void {\n const today = new Date();\n const shown = weekRange(this.weekStart());\n const day = today >= shown.from && today < shown.until ? today : this.weekStart();\n this.openDialog({ calendarId: this.calendarId(), appointment: null, day }, 'weaver-apps-calendar.dialog.addTitle');\n }\n\n protected edit(appointment: AppointmentResponse): void {\n this.openDialog({ calendarId: this.calendarId(), appointment, day: new Date(appointment.start) }, 'weaver-apps-calendar.dialog.editTitle');\n }\n\n protected async cancel(appointment: AppointmentResponse): Promise<void> {\n const reason = await calendarHost.ui.prompt({\n title: 'weaver-apps-calendar.cancel.title',\n message: 'weaver-apps-calendar.cancel.message',\n confirmLabel: 'weaver-apps-calendar.cancel.confirm',\n tone: 'warning',\n });\n if (reason === null) {\n return;\n }\n await this.attempt(async () => {\n const outcome = await this.gateway.cancel(this.calendarId(), appointment.id, reason.trim() || null);\n this.report(outcome.kind === 'saved' ? 'weaver-apps-calendar.cancel.done' : 'weaver-apps-calendar.errors.save', outcome.kind === 'saved');\n });\n }\n\n protected async remove(appointment: AppointmentResponse): Promise<void> {\n const confirmed = await calendarHost.ui.confirm({\n title: 'weaver-apps-calendar.delete.title',\n message: 'weaver-apps-calendar.delete.message',\n confirmLabel: 'weaver-apps-calendar.delete.confirm',\n tone: 'danger',\n });\n if (!confirmed) {\n return;\n }\n await this.attempt(async () => {\n await this.gateway.remove(this.calendarId(), appointment.id);\n this.report('weaver-apps-calendar.delete.done', true);\n });\n }\n\n private async attempt(work: () => Promise<void>): Promise<void> {\n try {\n await work();\n } catch {\n this.report('weaver-apps-calendar.errors.save', false);\n }\n }\n\n private openDialog(data: AppointmentDialogData, title: string): void {\n const dialog = calendarHost.ui.open<boolean>(AppointmentDialog, { data, title, size: 'lg', dismiss: 'explicit' });\n void dialog.closed.then((saved) => {\n if (saved) {\n this.report('weaver-apps-calendar.dialog.saved', true);\n }\n });\n }\n\n private report(message: string, success: boolean): void {\n calendarHost.ui.toast({ message, kind: success ? 'success' : 'error' });\n this.appointments.reload();\n }\n}\n","<div class=\"flex h-full flex-col gap-4 p-6\">\n <div class=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <h2 class=\"text-lg font-semibold text-content\">{{ calendar.value()?.name ?? ('weaver-apps-calendar.title' | transloco) }}</h2>\n <div class=\"flex flex-wrap items-center gap-2\">\n <input\n type=\"search\"\n class=\"lw-field w-56\"\n [attr.aria-label]=\"'weaver-apps-calendar.search.label' | transloco\"\n [attr.placeholder]=\"'weaver-apps-calendar.search.placeholder' | transloco\"\n (input)=\"search($event)\"\n />\n <button type=\"button\" class=\"lw-btn lw-btn--primary\" (click)=\"add()\">\n <lw-icon name=\"add\" size=\"1rem\"></lw-icon>\n {{ 'weaver-apps-calendar.actions.add' | transloco }}\n </button>\n </div>\n </div>\n\n <div class=\"flex items-center gap-2\">\n <button type=\"button\" class=\"relative lw-btn lw-btn--default lw-btn--sm lw-btn--icon\" [attr.aria-label]=\"'weaver-apps-calendar.week.previous' | transloco\" (click)=\"previousWeek()\">\n <lw-icon name=\"weaver-apps-calendar.previous\" size=\"1rem\"></lw-icon>\n <lw-tooltip [attr.text]=\"'weaver-apps-calendar.week.previous' | transloco\" position=\"bottom\"></lw-tooltip>\n </button>\n <button type=\"button\" class=\"lw-btn lw-btn--default lw-btn--sm\" (click)=\"thisWeek()\">{{ 'weaver-apps-calendar.week.current' | transloco }}</button>\n <button type=\"button\" class=\"relative lw-btn lw-btn--default lw-btn--sm lw-btn--icon\" [attr.aria-label]=\"'weaver-apps-calendar.week.next' | transloco\" (click)=\"nextWeek()\">\n <lw-icon name=\"weaver-apps-calendar.next\" size=\"1rem\"></lw-icon>\n <lw-tooltip [attr.text]=\"'weaver-apps-calendar.week.next' | transloco\" position=\"bottom\"></lw-tooltip>\n </button>\n <span class=\"text-sm font-medium text-content\" data-testid=\"week-label\">{{ weekLabel() }}</span>\n </div>\n\n @if (appointments.error()) {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ 'weaver-apps-calendar.errors.load' | transloco }}</p>\n } @else {\n <div class=\"min-h-80 flex-1\">\n <wa-appointment-table\n [appointments]=\"appointments.value() ?? []\"\n [locale]=\"locale()\"\n (editAppointment)=\"edit($event)\"\n (cancelAppointment)=\"cancel($event)\"\n (deleteAppointment)=\"remove($event)\"\n />\n </div>\n }\n</div>\n","import { calendarIconNames } from './calendar-ids';\n\nconst stroke = 'fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"';\n\nexport const calendarIcons: Readonly<Record<string, string>> = {\n [calendarIconNames.calendar]: `<svg viewBox=\"0 0 24 24\" ${stroke}><rect x=\"3.5\" y=\"5\" width=\"17\" height=\"15\" rx=\"2.5\"/><path d=\"M3.5 10h17M8 3v4M16 3v4\"/></svg>`,\n [calendarIconNames.previous]: `<svg viewBox=\"0 0 24 24\" ${stroke}><path d=\"M15 5l-7 7 7 7\"/></svg>`,\n [calendarIconNames.next]: `<svg viewBox=\"0 0 24 24\" ${stroke}><path d=\"M9 5l7 7-7 7\"/></svg>`,\n [calendarIconNames.cancel]: `<svg viewBox=\"0 0 24 24\" ${stroke}><circle cx=\"12\" cy=\"12\" r=\"8.5\"/><path d=\"M9 9l6 6M15 9l-6 6\"/></svg>`,\n};\n","import { Plugin } from '@loomweaver/plugin-sdk';\nimport { CalendarListPage } from '../views/calendar-list-page';\nimport { CalendarPage } from '../views/calendar-page';\nimport { calendarHost } from './calendar-host';\nimport { calendarContributionIds, calendarIconNames, calendarPluginId } from './calendar-ids';\nimport { calendarIcons } from './calendar-icons';\nimport { calendarListPath } from './calendar-paths';\n\nexport interface CalendarPluginOptions {\n readonly railItem?: boolean;\n}\n\nexport function createCalendarPlugin(options: CalendarPluginOptions = {}): Plugin {\n return {\n manifest: {\n id: calendarPluginId,\n name: 'Calendar',\n capabilities: ['contributions', 'navigation', 'ui'],\n },\n activate(ctx) {\n calendarHost.bind(ctx);\n ctx.contributeIcons(calendarIcons);\n ctx.registerSurface({\n id: calendarContributionIds.calendars,\n title: 'weaver-apps-calendar.calendars.title',\n icon: calendarIconNames.calendar,\n routable: { path: calendarListPath },\n component: CalendarListPage,\n });\n ctx.registerSurface({\n id: calendarContributionIds.calendar,\n title: 'weaver-apps-calendar.title',\n icon: calendarIconNames.calendar,\n routable: { path: `${calendarListPath}/:calendarId` },\n component: CalendarPage,\n });\n if (options.railItem) {\n ctx.registerRailItem({\n id: calendarContributionIds.rail,\n rail: 'primary',\n icon: calendarIconNames.calendar,\n title: 'weaver-apps-calendar.calendars.title',\n run: () => ctx.navigateContent(calendarListPath),\n });\n }\n },\n deactivate() {\n calendarHost.unbind();\n },\n };\n}\n\nexport const calendarPlugin = createCalendarPlugin();\n","/**\n * Generated by orval 🍺\n * Do not edit manually.\n * Weaver Apps\n * OpenAPI spec version: 1.0.0\n */\nimport type {\n AppointmentRequest,\n AppointmentResponse,\n BusyPeriod,\n CalendarAppointmentsListParams,\n CalendarCalendarsBusyParams,\n CalendarResponse,\n CancelAppointmentRequest,\n ChangeAppointmentStatusRequest,\n SaveCalendarRequest\n} from './models';\n\n\nexport type calendarCalendarsSaveResponse200 = {\n data: CalendarResponse\n status: 200\n}\n\nexport type calendarCalendarsSaveResponse201 = {\n data: CalendarResponse\n status: 201\n}\n\nexport type calendarCalendarsSaveResponseSuccess = (calendarCalendarsSaveResponse200 | calendarCalendarsSaveResponse201) & {\n headers: Headers;\n};\n;\n\nexport type calendarCalendarsSaveResponse = (calendarCalendarsSaveResponseSuccess)\n\nexport const getCalendarCalendarsSaveUrl = (calendarId: string,) => {\n\n\n\n\n return `/calendar/calendars/${calendarId}`\n}\n\nexport const calendarCalendarsSave = async (calendarId: string,\n saveCalendarRequest: SaveCalendarRequest, options?: RequestInit): Promise<calendarCalendarsSaveResponse> => {\n\n const getHeaders = (h?: NonNullable<RequestInit['headers']>): Record<string, string | readonly string[]> => {\n if (!h) return {};\n if (h instanceof Headers) return Object.fromEntries(h.entries());\n if (Symbol.iterator in h) {\n return Object.fromEntries(\n Array.from(h as Iterable<Iterable<string>>, (entry) => Array.from(entry) as [string, string]),\n );\n }\n const headers: Record<string, string | readonly string[]> = {};\n for (const [name, value] of Object.entries<string | readonly string[] | undefined>(h)) {\n if (value !== undefined) headers[name] = value;\n }\n return headers;\n };\nconst res = await fetch(getCalendarCalendarsSaveUrl(calendarId),\n {\n ...options,\n method: 'PUT',\n headers: { 'Content-Type': 'application/json', ...getHeaders(options?.headers) },\n body: JSON.stringify(saveCalendarRequest)\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarCalendarsSaveResponse['data'] = body ? JSON.parse(body) : {}\n return { data, status: res.status, headers: res.headers } as calendarCalendarsSaveResponse\n}\n\n\n\nexport type calendarCalendarsGetResponse200 = {\n data: CalendarResponse\n status: 200\n}\n\nexport type calendarCalendarsGetResponseSuccess = (calendarCalendarsGetResponse200) & {\n headers: Headers;\n};\n;\n\nexport type calendarCalendarsGetResponse = (calendarCalendarsGetResponseSuccess)\n\nexport const getCalendarCalendarsGetUrl = (calendarId: string,) => {\n\n\n\n\n return `/calendar/calendars/${calendarId}`\n}\n\nexport const calendarCalendarsGet = async (calendarId: string, options?: RequestInit): Promise<calendarCalendarsGetResponse> => {\n\n const res = await fetch(getCalendarCalendarsGetUrl(calendarId),\n {\n ...options,\n method: 'GET'\n\n\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarCalendarsGetResponse['data'] = body ? JSON.parse(body) : {}\n return { data, status: res.status, headers: res.headers } as calendarCalendarsGetResponse\n}\n\n\n\nexport type calendarCalendarsDeleteResponse204 = {\n data: void\n status: 204\n}\n\nexport type calendarCalendarsDeleteResponseSuccess = (calendarCalendarsDeleteResponse204) & {\n headers: Headers;\n};\n;\n\nexport type calendarCalendarsDeleteResponse = (calendarCalendarsDeleteResponseSuccess)\n\nexport const getCalendarCalendarsDeleteUrl = (calendarId: string,) => {\n\n\n\n\n return `/calendar/calendars/${calendarId}`\n}\n\nexport const calendarCalendarsDelete = async (calendarId: string, options?: RequestInit): Promise<calendarCalendarsDeleteResponse> => {\n\n const res = await fetch(getCalendarCalendarsDeleteUrl(calendarId),\n {\n ...options,\n method: 'DELETE'\n\n\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarCalendarsDeleteResponse['data'] = body ? JSON.parse(body) : undefined\n return { data, status: res.status, headers: res.headers } as calendarCalendarsDeleteResponse\n}\n\n\n\nexport type calendarCalendarsListResponse200 = {\n data: CalendarResponse[]\n status: 200\n}\n\nexport type calendarCalendarsListResponseSuccess = (calendarCalendarsListResponse200) & {\n headers: Headers;\n};\n;\n\nexport type calendarCalendarsListResponse = (calendarCalendarsListResponseSuccess)\n\nexport const getCalendarCalendarsListUrl = () => {\n\n\n\n\n return `/calendar/calendars`\n}\n\nexport const calendarCalendarsList = async ( options?: RequestInit): Promise<calendarCalendarsListResponse> => {\n\n const res = await fetch(getCalendarCalendarsListUrl(),\n {\n ...options,\n method: 'GET'\n\n\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarCalendarsListResponse['data'] = body ? JSON.parse(body) : {}\n return { data, status: res.status, headers: res.headers } as calendarCalendarsListResponse\n}\n\n\n\nexport type calendarCalendarsBusyResponse200 = {\n data: BusyPeriod[]\n status: 200\n}\n\nexport type calendarCalendarsBusyResponseSuccess = (calendarCalendarsBusyResponse200) & {\n headers: Headers;\n};\n;\n\nexport type calendarCalendarsBusyResponse = (calendarCalendarsBusyResponseSuccess)\n\nexport const getCalendarCalendarsBusyUrl = (calendarId: string,\n params: CalendarCalendarsBusyParams,) => {\n const normalizedParams = new URLSearchParams();\n\n Object.entries(params || {}).forEach(([key, value]) => {\n\n if (value !== undefined) {\n normalizedParams.append(key, value === null ? 'null' : String(value))\n }\n });\n\n const stringifiedParams = normalizedParams.toString();\n\n return stringifiedParams.length > 0 ? `/calendar/calendars/${calendarId}/busy?${stringifiedParams}` : `/calendar/calendars/${calendarId}/busy`\n}\n\nexport const calendarCalendarsBusy = async (calendarId: string,\n params: CalendarCalendarsBusyParams, options?: RequestInit): Promise<calendarCalendarsBusyResponse> => {\n\n const res = await fetch(getCalendarCalendarsBusyUrl(calendarId,params),\n {\n ...options,\n method: 'GET'\n\n\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarCalendarsBusyResponse['data'] = body ? JSON.parse(body) : {}\n return { data, status: res.status, headers: res.headers } as calendarCalendarsBusyResponse\n}\n\n\n\nexport type calendarAppointmentsBookResponse201 = {\n data: AppointmentResponse\n status: 201\n}\n\nexport type calendarAppointmentsBookResponseSuccess = (calendarAppointmentsBookResponse201) & {\n headers: Headers;\n};\n;\n\nexport type calendarAppointmentsBookResponse = (calendarAppointmentsBookResponseSuccess)\n\nexport const getCalendarAppointmentsBookUrl = (calendarId: string,) => {\n\n\n\n\n return `/calendar/calendars/${calendarId}/appointments`\n}\n\nexport const calendarAppointmentsBook = async (calendarId: string,\n appointmentRequest: AppointmentRequest, options?: RequestInit): Promise<calendarAppointmentsBookResponse> => {\n\n const getHeaders = (h?: NonNullable<RequestInit['headers']>): Record<string, string | readonly string[]> => {\n if (!h) return {};\n if (h instanceof Headers) return Object.fromEntries(h.entries());\n if (Symbol.iterator in h) {\n return Object.fromEntries(\n Array.from(h as Iterable<Iterable<string>>, (entry) => Array.from(entry) as [string, string]),\n );\n }\n const headers: Record<string, string | readonly string[]> = {};\n for (const [name, value] of Object.entries<string | readonly string[] | undefined>(h)) {\n if (value !== undefined) headers[name] = value;\n }\n return headers;\n };\nconst res = await fetch(getCalendarAppointmentsBookUrl(calendarId),\n {\n ...options,\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getHeaders(options?.headers) },\n body: JSON.stringify(appointmentRequest)\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarAppointmentsBookResponse['data'] = body ? JSON.parse(body) : {}\n return { data, status: res.status, headers: res.headers } as calendarAppointmentsBookResponse\n}\n\n\n\nexport type calendarAppointmentsListResponse200 = {\n data: AppointmentResponse[]\n status: 200\n}\n\nexport type calendarAppointmentsListResponseSuccess = (calendarAppointmentsListResponse200) & {\n headers: Headers;\n};\n;\n\nexport type calendarAppointmentsListResponse = (calendarAppointmentsListResponseSuccess)\n\nexport const getCalendarAppointmentsListUrl = (calendarId: string,\n params: CalendarAppointmentsListParams,) => {\n const normalizedParams = new URLSearchParams();\n\n Object.entries(params || {}).forEach(([key, value]) => {\n\n if (value !== undefined) {\n normalizedParams.append(key, value === null ? 'null' : String(value))\n }\n });\n\n const stringifiedParams = normalizedParams.toString();\n\n return stringifiedParams.length > 0 ? `/calendar/calendars/${calendarId}/appointments?${stringifiedParams}` : `/calendar/calendars/${calendarId}/appointments`\n}\n\nexport const calendarAppointmentsList = async (calendarId: string,\n params: CalendarAppointmentsListParams, options?: RequestInit): Promise<calendarAppointmentsListResponse> => {\n\n const res = await fetch(getCalendarAppointmentsListUrl(calendarId,params),\n {\n ...options,\n method: 'GET'\n\n\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarAppointmentsListResponse['data'] = body ? JSON.parse(body) : {}\n return { data, status: res.status, headers: res.headers } as calendarAppointmentsListResponse\n}\n\n\n\nexport type calendarAppointmentsGetResponse200 = {\n data: AppointmentResponse\n status: 200\n}\n\nexport type calendarAppointmentsGetResponseSuccess = (calendarAppointmentsGetResponse200) & {\n headers: Headers;\n};\n;\n\nexport type calendarAppointmentsGetResponse = (calendarAppointmentsGetResponseSuccess)\n\nexport const getCalendarAppointmentsGetUrl = (calendarId: string,\n appointmentId: string,) => {\n\n\n\n\n return `/calendar/calendars/${calendarId}/appointments/${appointmentId}`\n}\n\nexport const calendarAppointmentsGet = async (calendarId: string,\n appointmentId: string, options?: RequestInit): Promise<calendarAppointmentsGetResponse> => {\n\n const res = await fetch(getCalendarAppointmentsGetUrl(calendarId,appointmentId),\n {\n ...options,\n method: 'GET'\n\n\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarAppointmentsGetResponse['data'] = body ? JSON.parse(body) : {}\n return { data, status: res.status, headers: res.headers } as calendarAppointmentsGetResponse\n}\n\n\n\nexport type calendarAppointmentsUpdateResponse200 = {\n data: AppointmentResponse\n status: 200\n}\n\nexport type calendarAppointmentsUpdateResponseSuccess = (calendarAppointmentsUpdateResponse200) & {\n headers: Headers;\n};\n;\n\nexport type calendarAppointmentsUpdateResponse = (calendarAppointmentsUpdateResponseSuccess)\n\nexport const getCalendarAppointmentsUpdateUrl = (calendarId: string,\n appointmentId: string,) => {\n\n\n\n\n return `/calendar/calendars/${calendarId}/appointments/${appointmentId}`\n}\n\nexport const calendarAppointmentsUpdate = async (calendarId: string,\n appointmentId: string,\n appointmentRequest: AppointmentRequest, options?: RequestInit): Promise<calendarAppointmentsUpdateResponse> => {\n\n const getHeaders = (h?: NonNullable<RequestInit['headers']>): Record<string, string | readonly string[]> => {\n if (!h) return {};\n if (h instanceof Headers) return Object.fromEntries(h.entries());\n if (Symbol.iterator in h) {\n return Object.fromEntries(\n Array.from(h as Iterable<Iterable<string>>, (entry) => Array.from(entry) as [string, string]),\n );\n }\n const headers: Record<string, string | readonly string[]> = {};\n for (const [name, value] of Object.entries<string | readonly string[] | undefined>(h)) {\n if (value !== undefined) headers[name] = value;\n }\n return headers;\n };\nconst res = await fetch(getCalendarAppointmentsUpdateUrl(calendarId,appointmentId),\n {\n ...options,\n method: 'PUT',\n headers: { 'Content-Type': 'application/json', ...getHeaders(options?.headers) },\n body: JSON.stringify(appointmentRequest)\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarAppointmentsUpdateResponse['data'] = body ? JSON.parse(body) : {}\n return { data, status: res.status, headers: res.headers } as calendarAppointmentsUpdateResponse\n}\n\n\n\nexport type calendarAppointmentsDeleteResponse204 = {\n data: void\n status: 204\n}\n\nexport type calendarAppointmentsDeleteResponseSuccess = (calendarAppointmentsDeleteResponse204) & {\n headers: Headers;\n};\n;\n\nexport type calendarAppointmentsDeleteResponse = (calendarAppointmentsDeleteResponseSuccess)\n\nexport const getCalendarAppointmentsDeleteUrl = (calendarId: string,\n appointmentId: string,) => {\n\n\n\n\n return `/calendar/calendars/${calendarId}/appointments/${appointmentId}`\n}\n\nexport const calendarAppointmentsDelete = async (calendarId: string,\n appointmentId: string, options?: RequestInit): Promise<calendarAppointmentsDeleteResponse> => {\n\n const res = await fetch(getCalendarAppointmentsDeleteUrl(calendarId,appointmentId),\n {\n ...options,\n method: 'DELETE'\n\n\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarAppointmentsDeleteResponse['data'] = body ? JSON.parse(body) : undefined\n return { data, status: res.status, headers: res.headers } as calendarAppointmentsDeleteResponse\n}\n\n\n\nexport type calendarAppointmentsCancelResponse200 = {\n data: AppointmentResponse\n status: 200\n}\n\nexport type calendarAppointmentsCancelResponseSuccess = (calendarAppointmentsCancelResponse200) & {\n headers: Headers;\n};\n;\n\nexport type calendarAppointmentsCancelResponse = (calendarAppointmentsCancelResponseSuccess)\n\nexport const getCalendarAppointmentsCancelUrl = (calendarId: string,\n appointmentId: string,) => {\n\n\n\n\n return `/calendar/calendars/${calendarId}/appointments/${appointmentId}/cancel`\n}\n\nexport const calendarAppointmentsCancel = async (calendarId: string,\n appointmentId: string,\n cancelAppointmentRequest: CancelAppointmentRequest, options?: RequestInit): Promise<calendarAppointmentsCancelResponse> => {\n\n const getHeaders = (h?: NonNullable<RequestInit['headers']>): Record<string, string | readonly string[]> => {\n if (!h) return {};\n if (h instanceof Headers) return Object.fromEntries(h.entries());\n if (Symbol.iterator in h) {\n return Object.fromEntries(\n Array.from(h as Iterable<Iterable<string>>, (entry) => Array.from(entry) as [string, string]),\n );\n }\n const headers: Record<string, string | readonly string[]> = {};\n for (const [name, value] of Object.entries<string | readonly string[] | undefined>(h)) {\n if (value !== undefined) headers[name] = value;\n }\n return headers;\n };\nconst res = await fetch(getCalendarAppointmentsCancelUrl(calendarId,appointmentId),\n {\n ...options,\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...getHeaders(options?.headers) },\n body: JSON.stringify(cancelAppointmentRequest)\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarAppointmentsCancelResponse['data'] = body ? JSON.parse(body) : {}\n return { data, status: res.status, headers: res.headers } as calendarAppointmentsCancelResponse\n}\n\n\n\nexport type calendarAppointmentsStatusResponse200 = {\n data: AppointmentResponse\n status: 200\n}\n\nexport type calendarAppointmentsStatusResponseSuccess = (calendarAppointmentsStatusResponse200) & {\n headers: Headers;\n};\n;\n\nexport type calendarAppointmentsStatusResponse = (calendarAppointmentsStatusResponseSuccess)\n\nexport const getCalendarAppointmentsStatusUrl = (calendarId: string,\n appointmentId: string,) => {\n\n\n\n\n return `/calendar/calendars/${calendarId}/appointments/${appointmentId}/status`\n}\n\nexport const calendarAppointmentsStatus = async (calendarId: string,\n appointmentId: string,\n changeAppointmentStatusRequest: ChangeAppointmentStatusRequest, options?: RequestInit): Promise<calendarAppointmentsStatusResponse> => {\n\n const getHeaders = (h?: NonNullable<RequestInit['headers']>): Record<string, string | readonly string[]> => {\n if (!h) return {};\n if (h instanceof Headers) return Object.fromEntries(h.entries());\n if (Symbol.iterator in h) {\n return Object.fromEntries(\n Array.from(h as Iterable<Iterable<string>>, (entry) => Array.from(entry) as [string, string]),\n );\n }\n const headers: Record<string, string | readonly string[]> = {};\n for (const [name, value] of Object.entries<string | readonly string[] | undefined>(h)) {\n if (value !== undefined) headers[name] = value;\n }\n return headers;\n };\nconst res = await fetch(getCalendarAppointmentsStatusUrl(calendarId,appointmentId),\n {\n ...options,\n method: 'PUT',\n headers: { 'Content-Type': 'application/json', ...getHeaders(options?.headers) },\n body: JSON.stringify(changeAppointmentStatusRequest)\n }\n)\n\n\n const body = [204, 205, 304].includes(res.status) ? null : await res.text();\n\n const data: calendarAppointmentsStatusResponse['data'] = body ? JSON.parse(body) : {}\n return { data, status: res.status, headers: res.headers } as calendarAppointmentsStatusResponse\n}\n\n\n\n","import {\n getCalendarAppointmentsBookUrl,\n getCalendarAppointmentsCancelUrl,\n getCalendarAppointmentsDeleteUrl,\n getCalendarAppointmentsListUrl,\n getCalendarAppointmentsStatusUrl,\n getCalendarAppointmentsUpdateUrl,\n getCalendarCalendarsGetUrl,\n getCalendarCalendarsListUrl,\n getCalendarCalendarsSaveUrl,\n} from '../api/endpoints';\nimport type { AppointmentResponse, CalendarResponse } from '../api/models';\nimport { AppointmentOutcome, CalendarGateway, CalendarGatewayError, ConflictingAppointment } from './calendar-gateway';\n\nexport interface HttpCalendarGatewayOptions {\n readonly baseUrl: string;\n readonly headers?: Readonly<Record<string, string>>;\n readonly fetch?: typeof fetch;\n}\n\nconst notFound = 404;\nconst conflict = 409;\nconst badRequest = 400;\nconst conflictCode = 'appointment-conflict';\nconst closedCode = 'appointment-closed';\n\ninterface Problem {\n readonly code?: string;\n readonly conflicts?: readonly ConflictingAppointment[];\n}\n\nexport function createHttpCalendarGateway(options: HttpCalendarGatewayOptions): CalendarGateway {\n const base = options.baseUrl.replace(/\\/+$/, '');\n const run = options.fetch ?? ((input: RequestInfo | URL, init?: RequestInit) => fetch(input, init));\n\n const send = (path: string, method: string, body?: unknown, signal?: AbortSignal): Promise<Response> =>\n run(base + path, {\n method,\n signal,\n credentials: 'same-origin',\n headers: {\n Accept: 'application/json',\n ...(body === undefined ? {} : { 'Content-Type': 'application/json' }),\n ...options.headers,\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n\n const read = async <T>(response: Response): Promise<T> => {\n if (!response.ok) {\n throw new CalendarGatewayError(response.status);\n }\n const value: T = await response.json();\n return value;\n };\n\n const outcome = async (response: Response): Promise<AppointmentOutcome> => {\n if (response.ok) {\n const appointment: AppointmentResponse = await response.json();\n return { kind: 'saved', appointment };\n }\n if (response.status === notFound) {\n return { kind: 'missing' };\n }\n if (response.status === badRequest) {\n return { kind: 'invalid' };\n }\n if (response.status !== conflict) {\n throw new CalendarGatewayError(response.status);\n }\n const problem: Problem = await response.json().catch(() => ({}));\n if (problem.code === conflictCode) {\n return { kind: 'conflict', conflicts: problem.conflicts ?? [] };\n }\n return problem.code === closedCode ? { kind: 'closed' } : { kind: 'changed' };\n };\n\n return {\n calendars: async () => read<CalendarResponse[]>(await send(getCalendarCalendarsListUrl(), 'GET')),\n calendar: async (calendarId) => {\n const response = await send(getCalendarCalendarsGetUrl(calendarId), 'GET');\n return response.status === notFound ? null : read<CalendarResponse>(response);\n },\n saveCalendar: async (calendarId, name) =>\n read<CalendarResponse>(await send(getCalendarCalendarsSaveUrl(calendarId), 'PUT', { name })),\n appointments: async (calendarId, range, contact, signal) => {\n const params = { from: range.from.toISOString(), until: range.until.toISOString(), ...(contact ? { contact } : {}) };\n return read<AppointmentResponse[]>(await send(getCalendarAppointmentsListUrl(calendarId, params), 'GET', undefined, signal));\n },\n book: async (calendarId, request) => outcome(await send(getCalendarAppointmentsBookUrl(calendarId), 'POST', request)),\n update: async (calendarId, appointmentId, request) =>\n outcome(await send(getCalendarAppointmentsUpdateUrl(calendarId, appointmentId), 'PUT', request)),\n cancel: async (calendarId, appointmentId, reason) =>\n outcome(await send(getCalendarAppointmentsCancelUrl(calendarId, appointmentId), 'POST', { reason })),\n setStatus: async (calendarId, appointmentId, status) =>\n outcome(await send(getCalendarAppointmentsStatusUrl(calendarId, appointmentId), 'PUT', { status })),\n remove: async (calendarId, appointmentId) => {\n const response = await send(getCalendarAppointmentsDeleteUrl(calendarId, appointmentId), 'DELETE');\n if (!response.ok && response.status !== notFound) {\n throw new CalendarGatewayError(response.status);\n }\n },\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weaver-apps/calendar",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "The calendar app's weaver for a LoomWeaver distribution: a calendar's appointments by week, booking, editing, cancelling and deleting, behind a gateway the host supplies.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"publishConfig": {
|
|
@@ -8,13 +8,20 @@
|
|
|
8
8
|
"registry": "https://registry.npmjs.org/"
|
|
9
9
|
},
|
|
10
10
|
"peerDependencies": {
|
|
11
|
+
"@ag-grid-community/locale": "^36.2.0",
|
|
11
12
|
"@angular/common": "^22.2.0",
|
|
12
13
|
"@angular/core": "^22.2.0",
|
|
13
14
|
"@angular/router": "^22.2.0",
|
|
14
15
|
"@jsverse/transloco": "^8.4.0",
|
|
15
|
-
"@loomweaver/plugin-sdk": ">=0.20.0 <1.0.0"
|
|
16
|
+
"@loomweaver/plugin-sdk": ">=0.20.0 <1.0.0",
|
|
17
|
+
"ag-grid-angular": "^36.2.0",
|
|
18
|
+
"ag-grid-community": "^36.2.0"
|
|
16
19
|
},
|
|
17
20
|
"sideEffects": false,
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@weaver-apps/data-grid": "0.2.0",
|
|
23
|
+
"tslib": "^2.3.0"
|
|
24
|
+
},
|
|
18
25
|
"module": "fesm2022/weaver-apps-calendar.mjs",
|
|
19
26
|
"typings": "types/weaver-apps-calendar.d.ts",
|
|
20
27
|
"exports": {
|
|
@@ -26,8 +33,5 @@
|
|
|
26
33
|
"default": "./fesm2022/weaver-apps-calendar.mjs"
|
|
27
34
|
}
|
|
28
35
|
},
|
|
29
|
-
"type": "module"
|
|
30
|
-
"dependencies": {
|
|
31
|
-
"tslib": "^2.3.0"
|
|
32
|
-
}
|
|
36
|
+
"type": "module"
|
|
33
37
|
}
|
|
@@ -5,8 +5,14 @@ interface CalendarPluginOptions {
|
|
|
5
5
|
}
|
|
6
6
|
export declare function createCalendarPlugin(options?: CalendarPluginOptions): Plugin;
|
|
7
7
|
export declare const calendarPlugin: Plugin;
|
|
8
|
-
export declare const calendarListPath = "calendar";
|
|
8
|
+
export declare const calendarListPath = "weaver-apps-calendar";
|
|
9
9
|
export declare function calendarPath(calendarId: string): string;
|
|
10
|
+
export declare const calendarPluginId = "weaver-apps-calendar";
|
|
11
|
+
export declare const calendarContributionIds: {
|
|
12
|
+
readonly calendars: "weaver-apps-calendar.calendars";
|
|
13
|
+
readonly calendar: "weaver-apps-calendar.calendar";
|
|
14
|
+
readonly rail: "weaver-apps-calendar.rail";
|
|
15
|
+
};
|
|
10
16
|
/**
|
|
11
17
|
* Generated by orval 🍺
|
|
12
18
|
* Do not edit manually.
|
|
File without changes
|
|
File without changes
|