@weaver-apps/calendar 0.0.0-stage → 0.1.2
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 +33 -2
- package/fesm2022/weaver-apps-calendar.mjs +770 -0
- package/fesm2022/weaver-apps-calendar.mjs.map +1 -0
- package/i18n/calendar/de.json +85 -0
- package/i18n/calendar/en.json +85 -0
- package/package.json +30 -3
- package/types/weaver-apps-calendar.d.ts +201 -0
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Kalender",
|
|
3
|
+
"calendars": {
|
|
4
|
+
"title": "Kalender",
|
|
5
|
+
"create": "Neuer Kalender",
|
|
6
|
+
"createMessage": "Wie soll der Kalender heißen?",
|
|
7
|
+
"createConfirm": "Anlegen",
|
|
8
|
+
"empty": "Es gibt noch keinen Kalender."
|
|
9
|
+
},
|
|
10
|
+
"search": {
|
|
11
|
+
"label": "Nach Kontakt suchen",
|
|
12
|
+
"placeholder": "Name, Telefon oder E-Mail"
|
|
13
|
+
},
|
|
14
|
+
"week": {
|
|
15
|
+
"previous": "Vorige Woche",
|
|
16
|
+
"next": "Nächste Woche",
|
|
17
|
+
"current": "Diese Woche"
|
|
18
|
+
},
|
|
19
|
+
"table": {
|
|
20
|
+
"when": "Datum und Uhrzeit",
|
|
21
|
+
"title": "Titel",
|
|
22
|
+
"contact": "Kontakt",
|
|
23
|
+
"location": "Ort",
|
|
24
|
+
"status": "Status",
|
|
25
|
+
"actions": "Aktionen",
|
|
26
|
+
"empty": "Keine Termine in dieser Woche."
|
|
27
|
+
},
|
|
28
|
+
"status": {
|
|
29
|
+
"scheduled": "Geplant",
|
|
30
|
+
"confirmed": "Bestätigt",
|
|
31
|
+
"completed": "Erledigt",
|
|
32
|
+
"noShow": "Nicht erschienen",
|
|
33
|
+
"cancelled": "Abgesagt"
|
|
34
|
+
},
|
|
35
|
+
"actions": {
|
|
36
|
+
"add": "Termin hinzufügen",
|
|
37
|
+
"edit": "Bearbeiten",
|
|
38
|
+
"cancel": "Termin absagen",
|
|
39
|
+
"delete": "Löschen"
|
|
40
|
+
},
|
|
41
|
+
"fields": {
|
|
42
|
+
"title": "Titel",
|
|
43
|
+
"date": "Beginnt am",
|
|
44
|
+
"start": "Beginn",
|
|
45
|
+
"end": "Ende",
|
|
46
|
+
"contactName": "Name des Kontakts",
|
|
47
|
+
"contactPhone": "Telefon",
|
|
48
|
+
"contactEmail": "E-Mail",
|
|
49
|
+
"location": "Ort",
|
|
50
|
+
"description": "Beschreibung",
|
|
51
|
+
"status": "Status",
|
|
52
|
+
"endDate": "Endet am"
|
|
53
|
+
},
|
|
54
|
+
"dialog": {
|
|
55
|
+
"addTitle": "Termin hinzufügen",
|
|
56
|
+
"editTitle": "Termin bearbeiten",
|
|
57
|
+
"save": "Speichern",
|
|
58
|
+
"cancel": "Abbrechen",
|
|
59
|
+
"saved": "Der Termin wurde gespeichert.",
|
|
60
|
+
"titleRequired": "Gib dem Termin einen Titel.",
|
|
61
|
+
"periodInvalid": "Das Ende muss nach dem Beginn liegen.",
|
|
62
|
+
"conflict": "Diese Zeit ist schon belegt durch:",
|
|
63
|
+
"closed": "Der Termin ist abgesagt und lässt sich nicht mehr ändern.",
|
|
64
|
+
"changed": "Jemand anderes hat den Termin inzwischen geändert. Schließe ihn und öffne ihn neu.",
|
|
65
|
+
"invalid": "Der Kalender hat diese Angaben nicht angenommen.",
|
|
66
|
+
"missing": "Den Termin gibt es nicht mehr."
|
|
67
|
+
},
|
|
68
|
+
"cancel": {
|
|
69
|
+
"title": "Termin absagen",
|
|
70
|
+
"message": "Warum wird der Termin abgesagt? Du kannst das leer lassen.",
|
|
71
|
+
"confirm": "Termin absagen",
|
|
72
|
+
"done": "Der Termin wurde abgesagt."
|
|
73
|
+
},
|
|
74
|
+
"delete": {
|
|
75
|
+
"title": "Termin löschen",
|
|
76
|
+
"message": "Der Termin wird endgültig entfernt.",
|
|
77
|
+
"confirm": "Löschen",
|
|
78
|
+
"done": "Der Termin wurde gelöscht."
|
|
79
|
+
},
|
|
80
|
+
"errors": {
|
|
81
|
+
"load": "Der Kalender ließ sich nicht laden.",
|
|
82
|
+
"save": "Der Termin ließ sich nicht speichern.",
|
|
83
|
+
"create": "Der Kalender ließ sich nicht anlegen."
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Calendar",
|
|
3
|
+
"calendars": {
|
|
4
|
+
"title": "Calendars",
|
|
5
|
+
"create": "New calendar",
|
|
6
|
+
"createMessage": "What should the calendar be called?",
|
|
7
|
+
"createConfirm": "Create",
|
|
8
|
+
"empty": "There is no calendar yet."
|
|
9
|
+
},
|
|
10
|
+
"search": {
|
|
11
|
+
"label": "Search by contact",
|
|
12
|
+
"placeholder": "Name, phone or email"
|
|
13
|
+
},
|
|
14
|
+
"week": {
|
|
15
|
+
"previous": "Previous week",
|
|
16
|
+
"next": "Next week",
|
|
17
|
+
"current": "This week"
|
|
18
|
+
},
|
|
19
|
+
"table": {
|
|
20
|
+
"when": "Date and time",
|
|
21
|
+
"title": "Title",
|
|
22
|
+
"contact": "Contact",
|
|
23
|
+
"location": "Location",
|
|
24
|
+
"status": "Status",
|
|
25
|
+
"actions": "Actions",
|
|
26
|
+
"empty": "No appointments this week."
|
|
27
|
+
},
|
|
28
|
+
"status": {
|
|
29
|
+
"scheduled": "Scheduled",
|
|
30
|
+
"confirmed": "Confirmed",
|
|
31
|
+
"completed": "Completed",
|
|
32
|
+
"noShow": "No-show",
|
|
33
|
+
"cancelled": "Cancelled"
|
|
34
|
+
},
|
|
35
|
+
"actions": {
|
|
36
|
+
"add": "Add appointment",
|
|
37
|
+
"edit": "Edit",
|
|
38
|
+
"cancel": "Cancel appointment",
|
|
39
|
+
"delete": "Delete"
|
|
40
|
+
},
|
|
41
|
+
"fields": {
|
|
42
|
+
"title": "Title",
|
|
43
|
+
"date": "Starts on",
|
|
44
|
+
"start": "Start",
|
|
45
|
+
"end": "End",
|
|
46
|
+
"contactName": "Contact name",
|
|
47
|
+
"contactPhone": "Phone",
|
|
48
|
+
"contactEmail": "Email",
|
|
49
|
+
"location": "Location",
|
|
50
|
+
"description": "Description",
|
|
51
|
+
"status": "Status",
|
|
52
|
+
"endDate": "Ends on"
|
|
53
|
+
},
|
|
54
|
+
"dialog": {
|
|
55
|
+
"addTitle": "Add appointment",
|
|
56
|
+
"editTitle": "Edit appointment",
|
|
57
|
+
"save": "Save",
|
|
58
|
+
"cancel": "Cancel",
|
|
59
|
+
"saved": "The appointment was saved.",
|
|
60
|
+
"titleRequired": "Give the appointment a title.",
|
|
61
|
+
"periodInvalid": "The end has to be after the start.",
|
|
62
|
+
"conflict": "This time is already taken by:",
|
|
63
|
+
"closed": "The appointment is cancelled and cannot be changed.",
|
|
64
|
+
"changed": "Someone else changed the appointment meanwhile. Close and open it again.",
|
|
65
|
+
"invalid": "The calendar did not accept these details.",
|
|
66
|
+
"missing": "The appointment no longer exists."
|
|
67
|
+
},
|
|
68
|
+
"cancel": {
|
|
69
|
+
"title": "Cancel appointment",
|
|
70
|
+
"message": "Why is the appointment cancelled? You can leave this empty.",
|
|
71
|
+
"confirm": "Cancel appointment",
|
|
72
|
+
"done": "The appointment was cancelled."
|
|
73
|
+
},
|
|
74
|
+
"delete": {
|
|
75
|
+
"title": "Delete appointment",
|
|
76
|
+
"message": "The appointment is removed for good.",
|
|
77
|
+
"confirm": "Delete",
|
|
78
|
+
"done": "The appointment was deleted."
|
|
79
|
+
},
|
|
80
|
+
"errors": {
|
|
81
|
+
"load": "The calendar could not be loaded.",
|
|
82
|
+
"save": "The appointment could not be saved.",
|
|
83
|
+
"create": "The calendar could not be created."
|
|
84
|
+
}
|
|
85
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weaver-apps/calendar",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.2",
|
|
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
|
+
"license": "Apache-2.0",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public",
|
|
8
|
+
"registry": "https://registry.npmjs.org/"
|
|
9
|
+
},
|
|
10
|
+
"peerDependencies": {
|
|
11
|
+
"@angular/common": "^22.2.0",
|
|
12
|
+
"@angular/core": "^22.2.0",
|
|
13
|
+
"@angular/router": "^22.2.0",
|
|
14
|
+
"@jsverse/transloco": "^8.4.0",
|
|
15
|
+
"@loomweaver/plugin-sdk": ">=0.20.0 <1.0.0"
|
|
16
|
+
},
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"module": "fesm2022/weaver-apps-calendar.mjs",
|
|
19
|
+
"typings": "types/weaver-apps-calendar.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
"./package.json": {
|
|
22
|
+
"default": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./types/weaver-apps-calendar.d.ts",
|
|
26
|
+
"default": "./fesm2022/weaver-apps-calendar.mjs"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"type": "module",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"tslib": "^2.3.0"
|
|
32
|
+
}
|
|
6
33
|
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { Plugin } from "@loomweaver/plugin-sdk";
|
|
2
|
+
import { InjectionToken, Provider } from "@angular/core";
|
|
3
|
+
interface CalendarPluginOptions {
|
|
4
|
+
readonly railItem?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function createCalendarPlugin(options?: CalendarPluginOptions): Plugin;
|
|
7
|
+
export declare const calendarPlugin: Plugin;
|
|
8
|
+
export declare const calendarListPath = "calendar";
|
|
9
|
+
export declare function calendarPath(calendarId: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Generated by orval 🍺
|
|
12
|
+
* Do not edit manually.
|
|
13
|
+
* Weaver Apps
|
|
14
|
+
* OpenAPI spec version: 1.0.0
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Books an appointment, or replaces what an existing one says.
|
|
18
|
+
*/
|
|
19
|
+
interface AppointmentRequest {
|
|
20
|
+
/**
|
|
21
|
+
* What the appointment is.
|
|
22
|
+
* @minLength 1
|
|
23
|
+
* @maxLength 200
|
|
24
|
+
*/
|
|
25
|
+
title: string;
|
|
26
|
+
/** When it starts; any offset, stored in UTC. */
|
|
27
|
+
start: string;
|
|
28
|
+
/** When it ends; after Start. */
|
|
29
|
+
end: string;
|
|
30
|
+
/**
|
|
31
|
+
* More about it.
|
|
32
|
+
* @minLength 0
|
|
33
|
+
* @maxLength 4000
|
|
34
|
+
* @nullable
|
|
35
|
+
*/
|
|
36
|
+
description: string | null;
|
|
37
|
+
/**
|
|
38
|
+
* Where it takes place.
|
|
39
|
+
* @minLength 0
|
|
40
|
+
* @maxLength 500
|
|
41
|
+
* @nullable
|
|
42
|
+
*/
|
|
43
|
+
location: string | null;
|
|
44
|
+
/**
|
|
45
|
+
* Who it is with.
|
|
46
|
+
* @minLength 0
|
|
47
|
+
* @maxLength 200
|
|
48
|
+
* @nullable
|
|
49
|
+
*/
|
|
50
|
+
contactName: string | null;
|
|
51
|
+
/**
|
|
52
|
+
* Their phone number.
|
|
53
|
+
* @minLength 0
|
|
54
|
+
* @maxLength 50
|
|
55
|
+
* @nullable
|
|
56
|
+
*/
|
|
57
|
+
contactPhone: string | null;
|
|
58
|
+
/**
|
|
59
|
+
* Their email address.
|
|
60
|
+
* @minLength 0
|
|
61
|
+
* @maxLength 320
|
|
62
|
+
* @nullable
|
|
63
|
+
*/
|
|
64
|
+
contactEmail: string | null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Generated by orval 🍺
|
|
68
|
+
* Do not edit manually.
|
|
69
|
+
* Weaver Apps
|
|
70
|
+
* OpenAPI spec version: 1.0.0
|
|
71
|
+
*/
|
|
72
|
+
/**
|
|
73
|
+
* Where an appointment stands.
|
|
74
|
+
*/
|
|
75
|
+
type AppointmentStatus = typeof AppointmentStatus[keyof typeof AppointmentStatus];
|
|
76
|
+
declare const AppointmentStatus: {
|
|
77
|
+
readonly scheduled: "scheduled";
|
|
78
|
+
readonly confirmed: "confirmed";
|
|
79
|
+
readonly completed: "completed";
|
|
80
|
+
readonly noShow: "noShow";
|
|
81
|
+
readonly cancelled: "cancelled";
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* An appointment.
|
|
85
|
+
*/
|
|
86
|
+
interface AppointmentResponse {
|
|
87
|
+
/** The appointment's id. */
|
|
88
|
+
id: string;
|
|
89
|
+
/** The calendar it is in. */
|
|
90
|
+
calendarId: string;
|
|
91
|
+
/** What it is. */
|
|
92
|
+
title: string;
|
|
93
|
+
/** When it starts, in UTC. */
|
|
94
|
+
start: string;
|
|
95
|
+
/** When it ends, in UTC. */
|
|
96
|
+
end: string;
|
|
97
|
+
/**
|
|
98
|
+
* More about it.
|
|
99
|
+
* @nullable
|
|
100
|
+
*/
|
|
101
|
+
description: string | null;
|
|
102
|
+
/**
|
|
103
|
+
* Where it takes place.
|
|
104
|
+
* @nullable
|
|
105
|
+
*/
|
|
106
|
+
location: string | null;
|
|
107
|
+
/**
|
|
108
|
+
* Who it is with.
|
|
109
|
+
* @nullable
|
|
110
|
+
*/
|
|
111
|
+
contactName: string | null;
|
|
112
|
+
/**
|
|
113
|
+
* Their phone number.
|
|
114
|
+
* @nullable
|
|
115
|
+
*/
|
|
116
|
+
contactPhone: string | null;
|
|
117
|
+
/**
|
|
118
|
+
* Their email address.
|
|
119
|
+
* @nullable
|
|
120
|
+
*/
|
|
121
|
+
contactEmail: string | null;
|
|
122
|
+
/** Where it stands. */
|
|
123
|
+
status: AppointmentStatus;
|
|
124
|
+
/**
|
|
125
|
+
* Why it was cancelled, if it was and anyone said.
|
|
126
|
+
* @nullable
|
|
127
|
+
*/
|
|
128
|
+
cancellationReason: string | null;
|
|
129
|
+
/** When it was booked. */
|
|
130
|
+
bookedAt: string;
|
|
131
|
+
/**
|
|
132
|
+
* When it was last changed.
|
|
133
|
+
* @nullable
|
|
134
|
+
*/
|
|
135
|
+
changedAt: string | null;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Generated by orval 🍺
|
|
139
|
+
* Do not edit manually.
|
|
140
|
+
* Weaver Apps
|
|
141
|
+
* OpenAPI spec version: 1.0.0
|
|
142
|
+
*/
|
|
143
|
+
/**
|
|
144
|
+
* A calendar of the caller's tenant.
|
|
145
|
+
*/
|
|
146
|
+
interface CalendarResponse {
|
|
147
|
+
/** The id the caller chose. */
|
|
148
|
+
id: string;
|
|
149
|
+
/** The display name. */
|
|
150
|
+
name: string;
|
|
151
|
+
/** When it was created. */
|
|
152
|
+
createdAt: string;
|
|
153
|
+
}
|
|
154
|
+
interface CalendarRange {
|
|
155
|
+
readonly from: Date;
|
|
156
|
+
readonly until: Date;
|
|
157
|
+
}
|
|
158
|
+
interface ConflictingAppointment {
|
|
159
|
+
readonly id: string;
|
|
160
|
+
readonly start: string;
|
|
161
|
+
readonly end: string;
|
|
162
|
+
}
|
|
163
|
+
type AppointmentOutcome = {
|
|
164
|
+
readonly kind: 'saved';
|
|
165
|
+
readonly appointment: AppointmentResponse;
|
|
166
|
+
} | {
|
|
167
|
+
readonly kind: 'conflict';
|
|
168
|
+
readonly conflicts: readonly ConflictingAppointment[];
|
|
169
|
+
} | {
|
|
170
|
+
readonly kind: 'closed';
|
|
171
|
+
} | {
|
|
172
|
+
readonly kind: 'changed';
|
|
173
|
+
} | {
|
|
174
|
+
readonly kind: 'invalid';
|
|
175
|
+
} | {
|
|
176
|
+
readonly kind: 'missing';
|
|
177
|
+
};
|
|
178
|
+
interface CalendarGateway {
|
|
179
|
+
calendars(): Promise<readonly CalendarResponse[]>;
|
|
180
|
+
calendar(calendarId: string): Promise<CalendarResponse | null>;
|
|
181
|
+
saveCalendar(calendarId: string, name: string): Promise<CalendarResponse>;
|
|
182
|
+
appointments(calendarId: string, range: CalendarRange, contact?: string, signal?: AbortSignal): Promise<readonly AppointmentResponse[]>;
|
|
183
|
+
book(calendarId: string, request: AppointmentRequest): Promise<AppointmentOutcome>;
|
|
184
|
+
update(calendarId: string, appointmentId: string, request: AppointmentRequest): Promise<AppointmentOutcome>;
|
|
185
|
+
cancel(calendarId: string, appointmentId: string, reason: string | null): Promise<AppointmentOutcome>;
|
|
186
|
+
setStatus(calendarId: string, appointmentId: string, status: AppointmentStatus): Promise<AppointmentOutcome>;
|
|
187
|
+
remove(calendarId: string, appointmentId: string): Promise<void>;
|
|
188
|
+
}
|
|
189
|
+
export declare class CalendarGatewayError extends Error {
|
|
190
|
+
readonly status: number;
|
|
191
|
+
constructor(status: number);
|
|
192
|
+
}
|
|
193
|
+
export declare const CALENDAR_GATEWAY: InjectionToken<CalendarGateway>;
|
|
194
|
+
export declare function provideCalendarGateway(gateway: CalendarGateway): Provider;
|
|
195
|
+
interface HttpCalendarGatewayOptions {
|
|
196
|
+
readonly baseUrl: string;
|
|
197
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
198
|
+
readonly fetch?: typeof fetch;
|
|
199
|
+
}
|
|
200
|
+
export declare function createHttpCalendarGateway(options: HttpCalendarGatewayOptions): CalendarGateway;
|
|
201
|
+
export type { AppointmentOutcome, AppointmentRequest, AppointmentResponse, AppointmentStatus, CalendarGateway, CalendarPluginOptions, CalendarRange, CalendarResponse, ConflictingAppointment, HttpCalendarGatewayOptions };
|