@weaver-apps/calendar 0.1.3 → 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 CHANGED
@@ -7,10 +7,15 @@ and holds no key.
7
7
  ## Compose it
8
8
 
9
9
  ```ts
10
- import { createCalendarPlugin, createHttpCalendarGateway, provideCalendarGateway } from '@weaver-apps/calendar';
11
-
12
- provideTranslationNamespaces('calendar'),
13
- provideCapabilityGrants({ calendar: ['contributions', 'navigation', 'ui'] }),
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 that copies
22
- `node_modules/@weaver-apps/calendar/i18n/calendar` to `i18n/calendar`.
23
- - If the host compiles the shell's theme with Tailwind, add the package as an `@source`.
24
-
25
- ## Surfaces
26
-
27
- | Path | What it shows |
28
- |---|---|
29
- | `calendar` | the tenant's calendars |
30
- | `calendar/:calendarId` | one calendar's appointments, a week at a time |
31
-
32
- `calendarPath(id)` builds the second; a host that shows one entry per calendar navigates there.
33
- `createCalendarPlugin({ railItem: true })` adds a rail item that opens the list; without it the host decides
34
- where the calendar is reached from.
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 calendarListPath = "calendar";
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 AppointmentTable = class AppointmentTable {
341
- appointments = input.required(...ngDevMode ? [{ debugName: "appointments" }] : /* istanbul ignore next */ []);
342
- locale = input.required(...ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []);
343
- editAppointment = output();
344
- cancelAppointment = output();
345
- deleteAppointment = output();
346
- rows = computed(() => {
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 this.appointments().map((appointment) => ({
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
- cancelled: appointment.status === "cancelled",
362
- statusKey: `calendar.status.${appointment.status}`
363
- }));
364
- }, ...ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []);
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: "<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",
606
+ template: "@if (translated()) {\n <wa-data-grid [options]=\"options()\" [localeOverrides]=\"empty()\" (ready)=\"ready($event)\" />\n}\n",
402
607
  dependencies: [{
403
- kind: "pipe",
404
- type: TranslocoPipe,
405
- name: "transloco"
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: [TranslocoPipe],
420
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
626
+ imports: [DataGrid],
421
627
  changeDetection: ChangeDetectionStrategy.OnPush,
422
- template: "<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"
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 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",
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 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"
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
- calendarPrevious: `<svg viewBox="0 0 24 24" ${stroke}><path d="M15 5l-7 7 7 7"/></svg>`,
628
- calendarNext: `<svg viewBox="0 0 24 24" ${stroke}><path d="M9 5l7 7-7 7"/></svg>`,
629
- 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>`
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: "calendar",
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: "calendar.calendars",
647
- title: "calendar.calendars.title",
648
- icon: "calendar",
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: "calendar.calendar",
654
- title: "calendar.title",
655
- icon: "calendar",
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: "calendar.rail",
868
+ id: calendarContributionIds.rail,
661
869
  rail: "primary",
662
- icon: "calendar",
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.1.3",
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