@weaver-apps/service-desk 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.
@@ -0,0 +1,773 @@
1
+ import * as i0 from "@angular/core";
2
+ import { CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, Component, DestroyRef, InjectionToken, computed, effect, inject, input, output, resource, signal } from "@angular/core";
3
+ import { TranslocoPipe, TranslocoService } from "@jsverse/transloco";
4
+ import { toSignal } from "@angular/core/rxjs-interop";
5
+ import { ActivatedRoute } from "@angular/router";
6
+ import { map } from "rxjs";
7
+ import { DialogRef } from "@loomweaver/plugin-sdk";
8
+ var ServiceDeskGatewayError = class extends Error {
9
+ status;
10
+ constructor(status) {
11
+ super(`The service desk answered ${status}.`);
12
+ this.status = status;
13
+ }
14
+ };
15
+ const SERVICE_DESK_GATEWAY = new InjectionToken("weaver-apps.service-desk.gateway");
16
+ function provideServiceDeskGateway(gateway) {
17
+ return {
18
+ provide: SERVICE_DESK_GATEWAY,
19
+ useValue: gateway
20
+ };
21
+ }
22
+ var ServiceDeskHost = class {
23
+ ctx;
24
+ bind(ctx) {
25
+ this.ctx = ctx;
26
+ }
27
+ unbind() {
28
+ this.ctx = void 0;
29
+ }
30
+ get ui() {
31
+ if (!this.ctx) throw new Error("The service-desk plugin is not active.");
32
+ return this.ctx.ui;
33
+ }
34
+ navigate(path) {
35
+ this.ctx?.navigateContent(path);
36
+ }
37
+ };
38
+ const serviceDeskHost = new ServiceDeskHost();
39
+ const deskListPath = "service-desk";
40
+ function deskPath(deskId) {
41
+ return `${deskListPath}/${deskId}`;
42
+ }
43
+ var DeskListPage = class DeskListPage {
44
+ gateway = inject(SERVICE_DESK_GATEWAY);
45
+ desks = resource({
46
+ ...ngDevMode ? { debugName: "desks" } : /* istanbul ignore next */ {},
47
+ loader: () => this.gateway.desks()
48
+ });
49
+ open(deskId) {
50
+ serviceDeskHost.navigate(deskPath(deskId));
51
+ }
52
+ async create() {
53
+ const name = await serviceDeskHost.ui.prompt({
54
+ title: "service-desk.desks.create",
55
+ message: "service-desk.desks.createMessage",
56
+ confirmLabel: "service-desk.desks.createConfirm"
57
+ });
58
+ if (!name?.trim()) return;
59
+ try {
60
+ const desk = await this.gateway.saveDesk(crypto.randomUUID(), name.trim());
61
+ this.open(desk.id);
62
+ } catch {
63
+ serviceDeskHost.ui.toast({
64
+ message: "service-desk.errors.create",
65
+ kind: "error"
66
+ });
67
+ }
68
+ }
69
+ static ɵfac = i0.ɵɵngDeclareFactory({
70
+ minVersion: "12.0.0",
71
+ version: "22.2.3",
72
+ ngImport: i0,
73
+ type: DeskListPage,
74
+ deps: [],
75
+ target: i0.ɵɵFactoryTarget.Component
76
+ });
77
+ static ɵcmp = i0.ɵɵngDeclareComponent({
78
+ minVersion: "17.0.0",
79
+ version: "22.2.3",
80
+ type: DeskListPage,
81
+ isStandalone: true,
82
+ selector: "wa-desk-list-page",
83
+ 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\">{{ 'service-desk.desks.title' | transloco }}</h2>\n <button type=\"button\" class=\"lw-btn lw-btn--primary\" (click)=\"create()\">{{ 'service-desk.desks.create' | transloco }}</button>\n </div>\n @if (desks.error()) {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ 'service-desk.errors.load' | transloco }}</p>\n } @else if (desks.hasValue()) {\n <ul role=\"list\" class=\"divide-y divide-border rounded-xl border border-border bg-surface\">\n @for (desk of desks.value(); track desk.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(desk.id)\">\n <lw-icon name=\"serviceDesk\" size=\"1.25rem\"></lw-icon>\n <span>{{ desk.name }}</span>\n </button>\n </li>\n } @empty {\n <li class=\"px-4 py-8 text-center text-sm text-content-faint\">{{ 'service-desk.desks.empty' | transloco }}</li>\n }\n </ul>\n }\n</div>\n",
85
+ dependencies: [{
86
+ kind: "pipe",
87
+ type: TranslocoPipe,
88
+ name: "transloco"
89
+ }],
90
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
91
+ });
92
+ };
93
+ i0.ɵɵngDeclareClassMetadata({
94
+ minVersion: "12.0.0",
95
+ version: "22.2.3",
96
+ ngImport: i0,
97
+ type: DeskListPage,
98
+ decorators: [{
99
+ type: Component,
100
+ args: [{
101
+ selector: "wa-desk-list-page",
102
+ imports: [TranslocoPipe],
103
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
104
+ 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\">{{ 'service-desk.desks.title' | transloco }}</h2>\n <button type=\"button\" class=\"lw-btn lw-btn--primary\" (click)=\"create()\">{{ 'service-desk.desks.create' | transloco }}</button>\n </div>\n @if (desks.error()) {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ 'service-desk.errors.load' | transloco }}</p>\n } @else if (desks.hasValue()) {\n <ul role=\"list\" class=\"divide-y divide-border rounded-xl border border-border bg-surface\">\n @for (desk of desks.value(); track desk.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(desk.id)\">\n <lw-icon name=\"serviceDesk\" size=\"1.25rem\"></lw-icon>\n <span>{{ desk.name }}</span>\n </button>\n </li>\n } @empty {\n <li class=\"px-4 py-8 text-center text-sm text-content-faint\">{{ 'service-desk.desks.empty' | transloco }}</li>\n }\n </ul>\n }\n</div>\n"
106
+ }]
107
+ }]
108
+ });
109
+ function activeLocale() {
110
+ const transloco = inject(TranslocoService);
111
+ return toSignal(transloco.langChanges$, { initialValue: transloco.getActiveLang() });
112
+ }
113
+ const ServiceRequestOrigin = {
114
+ manual: "manual",
115
+ voiceChat: "voiceChat",
116
+ textChat: "textChat",
117
+ widget: "widget",
118
+ email: "email",
119
+ phone: "phone"
120
+ };
121
+ const ServiceRequestPriority = {
122
+ low: "low",
123
+ normal: "normal",
124
+ high: "high",
125
+ urgent: "urgent"
126
+ };
127
+ const ServiceRequestStatus = {
128
+ open: "open",
129
+ inProgress: "inProgress",
130
+ completed: "completed",
131
+ cancelled: "cancelled"
132
+ };
133
+ const ServiceRequestType = {
134
+ inquiry: "inquiry",
135
+ task: "task",
136
+ callbackRequest: "callbackRequest",
137
+ conversationSummary: "conversationSummary",
138
+ complaint: "complaint",
139
+ quote: "quote"
140
+ };
141
+ const requestTypes = Object.values(ServiceRequestType);
142
+ const requestPriorities = Object.values(ServiceRequestPriority);
143
+ const requestOrigins = Object.values(ServiceRequestOrigin);
144
+ const settableStatuses = [ServiceRequestStatus.open, ServiceRequestStatus.inProgress];
145
+ const statusFilters = [
146
+ "active",
147
+ "open",
148
+ "inProgress",
149
+ "completed",
150
+ "cancelled",
151
+ "all"
152
+ ];
153
+ function isClosed(status) {
154
+ return status === ServiceRequestStatus.completed || status === ServiceRequestStatus.cancelled;
155
+ }
156
+ function localDate(value) {
157
+ const pad = (part) => `${part}`.padStart(2, "0");
158
+ return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}`;
159
+ }
160
+ function pick(choices, value) {
161
+ return choices.find((choice) => choice === value);
162
+ }
163
+ var RequestDialog = class RequestDialog {
164
+ dialog = inject(DialogRef);
165
+ gateway = inject(SERVICE_DESK_GATEWAY);
166
+ data = this.dialog.data;
167
+ existing = this.data.request;
168
+ editing = this.existing !== null;
169
+ closed = this.existing !== null && isClosed(this.existing.status);
170
+ types = requestTypes;
171
+ priorities = requestPriorities;
172
+ origins = requestOrigins;
173
+ statuses = settableStatuses;
174
+ title = signal(this.existing?.title ?? "", ...ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []);
175
+ type = signal(this.existing?.type ?? ServiceRequestType.inquiry, ...ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []);
176
+ priority = signal(this.existing?.priority ?? ServiceRequestPriority.normal, ...ngDevMode ? [{ debugName: "priority" }] : /* istanbul ignore next */ []);
177
+ origin = signal(this.existing?.origin ?? ServiceRequestOrigin.manual, ...ngDevMode ? [{ debugName: "origin" }] : /* istanbul ignore next */ []);
178
+ status = signal(this.existing?.status ?? "open", ...ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []);
179
+ contactName = signal(this.existing?.contactName ?? "", ...ngDevMode ? [{ debugName: "contactName" }] : /* istanbul ignore next */ []);
180
+ contactPhone = signal(this.existing?.contactPhone ?? "", ...ngDevMode ? [{ debugName: "contactPhone" }] : /* istanbul ignore next */ []);
181
+ contactEmail = signal(this.existing?.contactEmail ?? "", ...ngDevMode ? [{ debugName: "contactEmail" }] : /* istanbul ignore next */ []);
182
+ assignee = signal(this.existing?.assignee ?? "", ...ngDevMode ? [{ debugName: "assignee" }] : /* istanbul ignore next */ []);
183
+ dueDate = signal(this.existing?.dueAt ? localDate(new Date(this.existing.dueAt)) : "", ...ngDevMode ? [{ debugName: "dueDate" }] : /* istanbul ignore next */ []);
184
+ description = signal(this.existing?.description ?? "", ...ngDevMode ? [{ debugName: "description" }] : /* istanbul ignore next */ []);
185
+ cancellationReason = this.existing?.cancellationReason ?? null;
186
+ saving = signal(false, ...ngDevMode ? [{ debugName: "saving" }] : /* istanbul ignore next */ []);
187
+ problem = signal("", ...ngDevMode ? [{ debugName: "problem" }] : /* istanbul ignore next */ []);
188
+ persisted = false;
189
+ bind(target, event) {
190
+ target.set(event.target.value);
191
+ }
192
+ chooseType(event) {
193
+ this.choose(this.type, requestTypes, event);
194
+ }
195
+ choosePriority(event) {
196
+ this.choose(this.priority, requestPriorities, event);
197
+ }
198
+ chooseOrigin(event) {
199
+ this.choose(this.origin, requestOrigins, event);
200
+ }
201
+ chooseStatus(event) {
202
+ this.choose(this.status, settableStatuses, event);
203
+ }
204
+ close() {
205
+ this.dialog.close(this.persisted);
206
+ }
207
+ async save() {
208
+ if (!this.title().trim()) {
209
+ this.problem.set("service-desk.dialog.titleRequired");
210
+ return;
211
+ }
212
+ this.saving.set(true);
213
+ try {
214
+ const changes = this.changes();
215
+ const outcome = this.existing ? await this.gateway.edit(this.data.deskId, this.existing.id, changes) : await this.gateway.raise(this.data.deskId, {
216
+ ...changes,
217
+ origin: this.origin()
218
+ });
219
+ this.settle(await this.applyStatus(outcome));
220
+ } catch {
221
+ this.problem.set("service-desk.errors.save");
222
+ } finally {
223
+ this.saving.set(false);
224
+ }
225
+ }
226
+ changes() {
227
+ const keptDue = this.existing?.dueAt && localDate(new Date(this.existing.dueAt)) === this.dueDate() ? this.existing.dueAt : null;
228
+ if (keptDue) return {
229
+ ...this.fields(),
230
+ dueAt: keptDue
231
+ };
232
+ const due = this.dueDate() ? /* @__PURE__ */ new Date(`${this.dueDate()}T12:00`) : null;
233
+ return {
234
+ ...this.fields(),
235
+ dueAt: due && !Number.isNaN(due.getTime()) ? due.toISOString() : null
236
+ };
237
+ }
238
+ fields() {
239
+ return {
240
+ title: this.title().trim(),
241
+ description: this.optional(this.description()),
242
+ type: this.type(),
243
+ priority: this.priority(),
244
+ contactName: this.optional(this.contactName()),
245
+ contactPhone: this.optional(this.contactPhone()),
246
+ contactEmail: this.optional(this.contactEmail()),
247
+ assignee: this.optional(this.assignee())
248
+ };
249
+ }
250
+ async applyStatus(outcome) {
251
+ if (outcome.kind === "saved") {
252
+ this.existing = outcome.request;
253
+ this.persisted = true;
254
+ }
255
+ if (outcome.kind !== "saved" || outcome.request.status === this.status()) return outcome;
256
+ return this.gateway.setStatus(this.data.deskId, outcome.request.id, this.status());
257
+ }
258
+ settle(outcome) {
259
+ const messages = {
260
+ closed: "service-desk.dialog.closed",
261
+ changed: "service-desk.dialog.changed",
262
+ invalid: "service-desk.dialog.invalid",
263
+ missing: "service-desk.dialog.missing"
264
+ };
265
+ if (outcome.kind === "saved") {
266
+ this.dialog.close(true);
267
+ return;
268
+ }
269
+ this.problem.set(messages[outcome.kind]);
270
+ }
271
+ choose(target, choices, event) {
272
+ const chosen = pick(choices, event.target.value);
273
+ if (chosen) target.set(chosen);
274
+ }
275
+ optional(value) {
276
+ return value.trim() ? value.trim() : null;
277
+ }
278
+ static ɵfac = i0.ɵɵngDeclareFactory({
279
+ minVersion: "12.0.0",
280
+ version: "22.2.3",
281
+ ngImport: i0,
282
+ type: RequestDialog,
283
+ deps: [],
284
+ target: i0.ɵɵFactoryTarget.Component
285
+ });
286
+ static ɵcmp = i0.ɵɵngDeclareComponent({
287
+ minVersion: "17.0.0",
288
+ version: "22.2.3",
289
+ type: RequestDialog,
290
+ isStandalone: true,
291
+ selector: "wa-request-dialog",
292
+ ngImport: i0,
293
+ template: "<form class=\"flex flex-col gap-4\" (submit)=\"$event.preventDefault(); save()\">\n @if (closed) {\n <p class=\"text-sm text-content-faint\" role=\"status\">\n {{ 'service-desk.dialog.closedNote' | transloco }}\n @if (cancellationReason) {\n {{ 'service-desk.dialog.reason' | transloco: { reason: cancellationReason } }}\n }\n </p>\n }\n\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'service-desk.fields.title' | transloco }}\n <input class=\"lw-field\" required [disabled]=\"closed\" [value]=\"title()\" (input)=\"bind(title, $event)\" />\n </label>\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 {{ 'service-desk.fields.type' | transloco }}\n <select class=\"lw-field\" [disabled]=\"closed\" (change)=\"chooseType($event)\">\n @for (choice of types; track choice) {\n <option [value]=\"choice\" [selected]=\"choice === type()\">{{ 'service-desk.type.' + choice | transloco }}</option>\n }\n </select>\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'service-desk.fields.priority' | transloco }}\n <select class=\"lw-field\" [disabled]=\"closed\" (change)=\"choosePriority($event)\">\n @for (choice of priorities; track choice) {\n <option [value]=\"choice\" [selected]=\"choice === priority()\">{{ 'service-desk.priority.' + choice | transloco }}</option>\n }\n </select>\n </label>\n @if (editing) {\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'service-desk.fields.status' | transloco }}\n <select class=\"lw-field\" [disabled]=\"closed\" (change)=\"chooseStatus($event)\">\n @if (closed) {\n <option selected>{{ 'service-desk.status.' + status() | transloco }}</option>\n } @else {\n @for (choice of statuses; track choice) {\n <option [value]=\"choice\" [selected]=\"choice === status()\">{{ 'service-desk.status.' + choice | transloco }}</option>\n }\n }\n </select>\n </label>\n } @else {\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'service-desk.fields.origin' | transloco }}\n <select class=\"lw-field\" (change)=\"chooseOrigin($event)\">\n @for (choice of origins; track choice) {\n <option [value]=\"choice\" [selected]=\"choice === origin()\">{{ 'service-desk.origin.' + choice | transloco }}</option>\n }\n </select>\n </label>\n }\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 {{ 'service-desk.fields.contactName' | transloco }}\n <input class=\"lw-field\" [disabled]=\"closed\" [value]=\"contactName()\" (input)=\"bind(contactName, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'service-desk.fields.contactPhone' | transloco }}\n <input class=\"lw-field\" type=\"tel\" [disabled]=\"closed\" [value]=\"contactPhone()\" (input)=\"bind(contactPhone, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'service-desk.fields.contactEmail' | transloco }}\n <input class=\"lw-field\" type=\"email\" [disabled]=\"closed\" [value]=\"contactEmail()\" (input)=\"bind(contactEmail, $event)\" />\n </label>\n </div>\n\n <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'service-desk.fields.assignee' | transloco }}\n <input class=\"lw-field\" [disabled]=\"closed\" [value]=\"assignee()\" (input)=\"bind(assignee, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'service-desk.fields.due' | transloco }}\n <input class=\"lw-field\" type=\"date\" [disabled]=\"closed\" [value]=\"dueDate()\" (input)=\"bind(dueDate, $event)\" />\n </label>\n </div>\n\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'service-desk.fields.description' | transloco }}\n <textarea class=\"lw-field\" rows=\"3\" [disabled]=\"closed\" [value]=\"description()\" (input)=\"bind(description, $event)\"></textarea>\n </label>\n\n @if (problem()) {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ problem() | transloco }}</p>\n }\n\n <div class=\"flex justify-end gap-2\">\n <button type=\"button\" class=\"lw-btn lw-btn--default\" (click)=\"close()\">{{ (closed ? 'service-desk.dialog.close' : 'service-desk.dialog.cancel') | transloco }}</button>\n @if (!closed) {\n <button type=\"submit\" class=\"lw-btn lw-btn--primary\" [disabled]=\"saving()\">{{ 'service-desk.dialog.save' | transloco }}</button>\n }\n </div>\n</form>\n",
294
+ dependencies: [{
295
+ kind: "pipe",
296
+ type: TranslocoPipe,
297
+ name: "transloco"
298
+ }],
299
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
300
+ });
301
+ };
302
+ i0.ɵɵngDeclareClassMetadata({
303
+ minVersion: "12.0.0",
304
+ version: "22.2.3",
305
+ ngImport: i0,
306
+ type: RequestDialog,
307
+ decorators: [{
308
+ type: Component,
309
+ args: [{
310
+ selector: "wa-request-dialog",
311
+ imports: [TranslocoPipe],
312
+ changeDetection: ChangeDetectionStrategy.OnPush,
313
+ template: "<form class=\"flex flex-col gap-4\" (submit)=\"$event.preventDefault(); save()\">\n @if (closed) {\n <p class=\"text-sm text-content-faint\" role=\"status\">\n {{ 'service-desk.dialog.closedNote' | transloco }}\n @if (cancellationReason) {\n {{ 'service-desk.dialog.reason' | transloco: { reason: cancellationReason } }}\n }\n </p>\n }\n\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'service-desk.fields.title' | transloco }}\n <input class=\"lw-field\" required [disabled]=\"closed\" [value]=\"title()\" (input)=\"bind(title, $event)\" />\n </label>\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 {{ 'service-desk.fields.type' | transloco }}\n <select class=\"lw-field\" [disabled]=\"closed\" (change)=\"chooseType($event)\">\n @for (choice of types; track choice) {\n <option [value]=\"choice\" [selected]=\"choice === type()\">{{ 'service-desk.type.' + choice | transloco }}</option>\n }\n </select>\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'service-desk.fields.priority' | transloco }}\n <select class=\"lw-field\" [disabled]=\"closed\" (change)=\"choosePriority($event)\">\n @for (choice of priorities; track choice) {\n <option [value]=\"choice\" [selected]=\"choice === priority()\">{{ 'service-desk.priority.' + choice | transloco }}</option>\n }\n </select>\n </label>\n @if (editing) {\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'service-desk.fields.status' | transloco }}\n <select class=\"lw-field\" [disabled]=\"closed\" (change)=\"chooseStatus($event)\">\n @if (closed) {\n <option selected>{{ 'service-desk.status.' + status() | transloco }}</option>\n } @else {\n @for (choice of statuses; track choice) {\n <option [value]=\"choice\" [selected]=\"choice === status()\">{{ 'service-desk.status.' + choice | transloco }}</option>\n }\n }\n </select>\n </label>\n } @else {\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'service-desk.fields.origin' | transloco }}\n <select class=\"lw-field\" (change)=\"chooseOrigin($event)\">\n @for (choice of origins; track choice) {\n <option [value]=\"choice\" [selected]=\"choice === origin()\">{{ 'service-desk.origin.' + choice | transloco }}</option>\n }\n </select>\n </label>\n }\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 {{ 'service-desk.fields.contactName' | transloco }}\n <input class=\"lw-field\" [disabled]=\"closed\" [value]=\"contactName()\" (input)=\"bind(contactName, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'service-desk.fields.contactPhone' | transloco }}\n <input class=\"lw-field\" type=\"tel\" [disabled]=\"closed\" [value]=\"contactPhone()\" (input)=\"bind(contactPhone, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'service-desk.fields.contactEmail' | transloco }}\n <input class=\"lw-field\" type=\"email\" [disabled]=\"closed\" [value]=\"contactEmail()\" (input)=\"bind(contactEmail, $event)\" />\n </label>\n </div>\n\n <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'service-desk.fields.assignee' | transloco }}\n <input class=\"lw-field\" [disabled]=\"closed\" [value]=\"assignee()\" (input)=\"bind(assignee, $event)\" />\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'service-desk.fields.due' | transloco }}\n <input class=\"lw-field\" type=\"date\" [disabled]=\"closed\" [value]=\"dueDate()\" (input)=\"bind(dueDate, $event)\" />\n </label>\n </div>\n\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'service-desk.fields.description' | transloco }}\n <textarea class=\"lw-field\" rows=\"3\" [disabled]=\"closed\" [value]=\"description()\" (input)=\"bind(description, $event)\"></textarea>\n </label>\n\n @if (problem()) {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ problem() | transloco }}</p>\n }\n\n <div class=\"flex justify-end gap-2\">\n <button type=\"button\" class=\"lw-btn lw-btn--default\" (click)=\"close()\">{{ (closed ? 'service-desk.dialog.close' : 'service-desk.dialog.cancel') | transloco }}</button>\n @if (!closed) {\n <button type=\"submit\" class=\"lw-btn lw-btn--primary\" [disabled]=\"saving()\">{{ 'service-desk.dialog.save' | transloco }}</button>\n }\n </div>\n</form>\n"
314
+ }]
315
+ }]
316
+ });
317
+ var RequestTable = class RequestTable {
318
+ requests = input.required(...ngDevMode ? [{ debugName: "requests" }] : /* istanbul ignore next */ []);
319
+ locale = input.required(...ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []);
320
+ requestActions = output();
321
+ rows = computed(() => {
322
+ const date = new Intl.DateTimeFormat(this.locale(), {
323
+ day: "2-digit",
324
+ month: "2-digit",
325
+ year: "numeric"
326
+ });
327
+ return this.requests().map((request) => ({
328
+ request,
329
+ raised: date.format(new Date(request.raisedAt)),
330
+ due: request.dueAt ? date.format(new Date(request.dueAt)) : null,
331
+ closed: isClosed(request.status)
332
+ }));
333
+ }, ...ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []);
334
+ static ɵfac = i0.ɵɵngDeclareFactory({
335
+ minVersion: "12.0.0",
336
+ version: "22.2.3",
337
+ ngImport: i0,
338
+ type: RequestTable,
339
+ deps: [],
340
+ target: i0.ɵɵFactoryTarget.Component
341
+ });
342
+ static ɵcmp = i0.ɵɵngDeclareComponent({
343
+ minVersion: "17.0.0",
344
+ version: "22.2.3",
345
+ type: RequestTable,
346
+ isStandalone: true,
347
+ selector: "wa-request-table",
348
+ inputs: {
349
+ requests: {
350
+ classPropertyName: "requests",
351
+ publicName: "requests",
352
+ isSignal: true,
353
+ isRequired: true,
354
+ transformFunction: null
355
+ },
356
+ locale: {
357
+ classPropertyName: "locale",
358
+ publicName: "locale",
359
+ isSignal: true,
360
+ isRequired: true,
361
+ transformFunction: null
362
+ }
363
+ },
364
+ outputs: { requestActions: "requestActions" },
365
+ ngImport: i0,
366
+ 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\">{{ 'service-desk.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\">{{ 'service-desk.table.type' | transloco }}</th>\n <th scope=\"col\" class=\"px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-content-faint\">{{ 'service-desk.table.priority' | transloco }}</th>\n <th scope=\"col\" class=\"px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-content-faint\">{{ 'service-desk.table.status' | transloco }}</th>\n <th scope=\"col\" class=\"px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-content-faint\">{{ 'service-desk.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\">{{ 'service-desk.table.dates' | transloco }}</th>\n <th scope=\"col\" class=\"px-4 py-2\"><span class=\"sr-only\">{{ 'service-desk.table.actions' | transloco }}</span></th>\n </tr>\n </thead>\n <tbody>\n @for (row of rows(); track row.request.id) {\n <tr class=\"border-b border-border\" [class.text-content-faint]=\"row.closed\" data-testid=\"request-row\">\n <td class=\"min-w-48 px-4 py-3 font-medium text-content\" [class.line-through]=\"row.request.status === 'cancelled'\">{{ row.request.title }}</td>\n <td class=\"px-4 py-3 whitespace-nowrap\">\n <span class=\"rounded-full border border-border px-2 py-0.5 text-xs text-content\">{{ 'service-desk.type.' + row.request.type | transloco }}</span>\n </td>\n <td class=\"px-4 py-3 whitespace-nowrap\">\n <span class=\"rounded-full border border-border px-2 py-0.5 text-xs\" [class.text-negative]=\"row.request.priority === 'urgent'\" [class.font-semibold]=\"row.request.priority === 'urgent' || row.request.priority === 'high'\" data-testid=\"request-priority\">{{ 'service-desk.priority.' + row.request.priority | transloco }}</span>\n </td>\n <td class=\"px-4 py-3 whitespace-nowrap\">\n <span class=\"rounded-full border border-border px-2 py-0.5 text-xs text-content\" data-testid=\"request-status\">{{ 'service-desk.status.' + row.request.status | transloco }}</span>\n </td>\n <td class=\"px-4 py-3\">\n <div class=\"text-content\">{{ row.request.contactName ?? '–' }}</div>\n @if (row.request.contactPhone ?? row.request.contactEmail; as reach) {\n <div class=\"text-content-faint\">{{ reach }}</div>\n }\n </td>\n <td class=\"px-4 py-3 whitespace-nowrap\">\n <div class=\"text-content\">{{ row.raised }}</div>\n @if (row.due) {\n <div class=\"text-content-faint\">{{ 'service-desk.table.dueOn' | transloco: { date: row.due } }}</div>\n }\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]=\"'service-desk.actions.more' | transloco\" (click)=\"requestActions.emit({ request: row.request, x: $event.clientX, y: $event.clientY })\">\n <lw-icon name=\"more\" size=\"1rem\"></lw-icon>\n <lw-tooltip [attr.text]=\"'service-desk.actions.more' | transloco\" position=\"top\"></lw-tooltip>\n </button>\n </td>\n </tr>\n } @empty {\n <tr>\n <td colspan=\"7\" class=\"px-4 py-8 text-center text-content-faint\">{{ 'service-desk.table.empty' | transloco }}</td>\n </tr>\n }\n </tbody>\n</table>\n",
367
+ dependencies: [{
368
+ kind: "pipe",
369
+ type: TranslocoPipe,
370
+ name: "transloco"
371
+ }],
372
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
373
+ });
374
+ };
375
+ i0.ɵɵngDeclareClassMetadata({
376
+ minVersion: "12.0.0",
377
+ version: "22.2.3",
378
+ ngImport: i0,
379
+ type: RequestTable,
380
+ decorators: [{
381
+ type: Component,
382
+ args: [{
383
+ selector: "wa-request-table",
384
+ imports: [TranslocoPipe],
385
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
386
+ changeDetection: ChangeDetectionStrategy.OnPush,
387
+ 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\">{{ 'service-desk.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\">{{ 'service-desk.table.type' | transloco }}</th>\n <th scope=\"col\" class=\"px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-content-faint\">{{ 'service-desk.table.priority' | transloco }}</th>\n <th scope=\"col\" class=\"px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-content-faint\">{{ 'service-desk.table.status' | transloco }}</th>\n <th scope=\"col\" class=\"px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-content-faint\">{{ 'service-desk.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\">{{ 'service-desk.table.dates' | transloco }}</th>\n <th scope=\"col\" class=\"px-4 py-2\"><span class=\"sr-only\">{{ 'service-desk.table.actions' | transloco }}</span></th>\n </tr>\n </thead>\n <tbody>\n @for (row of rows(); track row.request.id) {\n <tr class=\"border-b border-border\" [class.text-content-faint]=\"row.closed\" data-testid=\"request-row\">\n <td class=\"min-w-48 px-4 py-3 font-medium text-content\" [class.line-through]=\"row.request.status === 'cancelled'\">{{ row.request.title }}</td>\n <td class=\"px-4 py-3 whitespace-nowrap\">\n <span class=\"rounded-full border border-border px-2 py-0.5 text-xs text-content\">{{ 'service-desk.type.' + row.request.type | transloco }}</span>\n </td>\n <td class=\"px-4 py-3 whitespace-nowrap\">\n <span class=\"rounded-full border border-border px-2 py-0.5 text-xs\" [class.text-negative]=\"row.request.priority === 'urgent'\" [class.font-semibold]=\"row.request.priority === 'urgent' || row.request.priority === 'high'\" data-testid=\"request-priority\">{{ 'service-desk.priority.' + row.request.priority | transloco }}</span>\n </td>\n <td class=\"px-4 py-3 whitespace-nowrap\">\n <span class=\"rounded-full border border-border px-2 py-0.5 text-xs text-content\" data-testid=\"request-status\">{{ 'service-desk.status.' + row.request.status | transloco }}</span>\n </td>\n <td class=\"px-4 py-3\">\n <div class=\"text-content\">{{ row.request.contactName ?? '–' }}</div>\n @if (row.request.contactPhone ?? row.request.contactEmail; as reach) {\n <div class=\"text-content-faint\">{{ reach }}</div>\n }\n </td>\n <td class=\"px-4 py-3 whitespace-nowrap\">\n <div class=\"text-content\">{{ row.raised }}</div>\n @if (row.due) {\n <div class=\"text-content-faint\">{{ 'service-desk.table.dueOn' | transloco: { date: row.due } }}</div>\n }\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]=\"'service-desk.actions.more' | transloco\" (click)=\"requestActions.emit({ request: row.request, x: $event.clientX, y: $event.clientY })\">\n <lw-icon name=\"more\" size=\"1rem\"></lw-icon>\n <lw-tooltip [attr.text]=\"'service-desk.actions.more' | transloco\" position=\"top\"></lw-tooltip>\n </button>\n </td>\n </tr>\n } @empty {\n <tr>\n <td colspan=\"7\" class=\"px-4 py-8 text-center text-content-faint\">{{ 'service-desk.table.empty' | transloco }}</td>\n </tr>\n }\n </tbody>\n</table>\n"
388
+ }]
389
+ }],
390
+ propDecorators: {
391
+ requests: [{
392
+ type: i0.Input,
393
+ args: [{
394
+ isSignal: true,
395
+ alias: "requests",
396
+ required: true
397
+ }]
398
+ }],
399
+ locale: [{
400
+ type: i0.Input,
401
+ args: [{
402
+ isSignal: true,
403
+ alias: "locale",
404
+ required: true
405
+ }]
406
+ }],
407
+ requestActions: [{
408
+ type: i0.Output,
409
+ args: ["requestActions"]
410
+ }]
411
+ }
412
+ });
413
+ const pageSize = 25;
414
+ const searchDelayMs = 300;
415
+ var DeskPage = class DeskPage {
416
+ gateway = inject(SERVICE_DESK_GATEWAY);
417
+ route = inject(ActivatedRoute);
418
+ searchTimer;
419
+ locale = activeLocale();
420
+ filters = statusFilters;
421
+ deskId = toSignal(this.route.paramMap.pipe(map((params) => params.get("deskId") ?? "")), { initialValue: this.route.snapshot.paramMap.get("deskId") ?? "" });
422
+ text = signal("", ...ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []);
423
+ filter = signal("active", ...ngDevMode ? [{ debugName: "filter" }] : /* istanbul ignore next */ []);
424
+ offset = signal(0, ...ngDevMode ? [{ debugName: "offset" }] : /* istanbul ignore next */ []);
425
+ desk = resource({
426
+ ...ngDevMode ? { debugName: "desk" } : /* istanbul ignore next */ {},
427
+ params: () => this.deskId(),
428
+ loader: ({ params }) => this.gateway.desk(params)
429
+ });
430
+ page = resource({
431
+ ...ngDevMode ? { debugName: "page" } : /* istanbul ignore next */ {},
432
+ params: () => ({
433
+ deskId: this.deskId(),
434
+ text: this.text().trim(),
435
+ filter: this.filter(),
436
+ offset: this.offset()
437
+ }),
438
+ loader: ({ params, abortSignal }) => this.gateway.requests(params.deskId, {
439
+ text: params.text || void 0,
440
+ ...params.filter === "active" ? {} : params.filter === "all" ? { includeCancelled: true } : { status: params.filter },
441
+ offset: params.offset,
442
+ limit: pageSize
443
+ }, abortSignal)
444
+ });
445
+ constructor() {
446
+ inject(DestroyRef).onDestroy(() => clearTimeout(this.searchTimer));
447
+ effect(() => {
448
+ const total = this.total();
449
+ if (this.page.hasValue() && this.offset() > 0 && this.offset() >= total) this.offset.set(Math.max(0, Math.ceil(total / pageSize) - 1) * pageSize);
450
+ });
451
+ }
452
+ total = computed(() => Number(this.page.value()?.total ?? 0), ...ngDevMode ? [{ debugName: "total" }] : /* istanbul ignore next */ []);
453
+ range = computed(() => {
454
+ const total = this.total();
455
+ return {
456
+ first: total === 0 ? 0 : this.offset() + 1,
457
+ last: Math.min(this.offset() + pageSize, total),
458
+ total
459
+ };
460
+ }, ...ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []);
461
+ hasPrevious = computed(() => this.offset() > 0, ...ngDevMode ? [{ debugName: "hasPrevious" }] : /* istanbul ignore next */ []);
462
+ hasNext = computed(() => this.offset() + pageSize < this.total(), ...ngDevMode ? [{ debugName: "hasNext" }] : /* istanbul ignore next */ []);
463
+ search(event) {
464
+ const value = event.target.value;
465
+ clearTimeout(this.searchTimer);
466
+ this.searchTimer = setTimeout(() => {
467
+ this.text.set(value);
468
+ this.offset.set(0);
469
+ }, searchDelayMs);
470
+ }
471
+ chooseFilter(event) {
472
+ const chosen = statusFilters.find((filter) => filter === event.target.value);
473
+ if (chosen) {
474
+ this.filter.set(chosen);
475
+ this.offset.set(0);
476
+ }
477
+ }
478
+ previous() {
479
+ this.offset.update((offset) => Math.max(0, offset - pageSize));
480
+ }
481
+ next() {
482
+ this.offset.update((offset) => offset + pageSize);
483
+ }
484
+ add() {
485
+ this.openDialog({
486
+ deskId: this.deskId(),
487
+ request: null
488
+ }, "service-desk.dialog.addTitle");
489
+ }
490
+ actions({ request, x, y }) {
491
+ const closed = isClosed(request.status);
492
+ serviceDeskHost.ui.openMenu([
493
+ {
494
+ label: closed ? "service-desk.actions.view" : "service-desk.actions.edit",
495
+ icon: closed ? "preview" : "edit",
496
+ run: () => this.open(request)
497
+ },
498
+ ...closed ? [] : [{
499
+ label: "service-desk.actions.complete",
500
+ icon: "serviceDeskComplete",
501
+ run: () => void this.complete(request)
502
+ }, {
503
+ label: "service-desk.actions.cancel",
504
+ icon: "serviceDeskCancel",
505
+ run: () => void this.cancel(request)
506
+ }],
507
+ {
508
+ label: "service-desk.actions.delete",
509
+ icon: "trash",
510
+ run: () => void this.remove(request)
511
+ }
512
+ ], {
513
+ x,
514
+ y
515
+ });
516
+ }
517
+ open(request) {
518
+ const closed = isClosed(request.status);
519
+ this.openDialog({
520
+ deskId: this.deskId(),
521
+ request
522
+ }, closed ? "service-desk.dialog.viewTitle" : "service-desk.dialog.editTitle");
523
+ }
524
+ async complete(request) {
525
+ await this.attempt(async () => this.report(await this.gateway.complete(this.deskId(), request.id), "service-desk.complete.done"));
526
+ }
527
+ async cancel(request) {
528
+ const reason = await serviceDeskHost.ui.prompt({
529
+ title: "service-desk.cancel.title",
530
+ message: "service-desk.cancel.message",
531
+ confirmLabel: "service-desk.cancel.confirm",
532
+ tone: "warning"
533
+ });
534
+ if (reason === null) return;
535
+ await this.attempt(async () => this.report(await this.gateway.cancel(this.deskId(), request.id, reason.trim() || null), "service-desk.cancel.done"));
536
+ }
537
+ async remove(request) {
538
+ if (!await serviceDeskHost.ui.confirm({
539
+ title: "service-desk.delete.title",
540
+ message: "service-desk.delete.message",
541
+ confirmLabel: "service-desk.delete.confirm",
542
+ tone: "danger"
543
+ })) return;
544
+ await this.attempt(async () => {
545
+ await this.gateway.remove(this.deskId(), request.id);
546
+ serviceDeskHost.ui.toast({
547
+ message: "service-desk.delete.done",
548
+ kind: "success"
549
+ });
550
+ this.page.reload();
551
+ });
552
+ }
553
+ async attempt(work) {
554
+ try {
555
+ await work();
556
+ } catch {
557
+ serviceDeskHost.ui.toast({
558
+ message: "service-desk.errors.save",
559
+ kind: "error"
560
+ });
561
+ this.page.reload();
562
+ }
563
+ }
564
+ openDialog(data, title) {
565
+ serviceDeskHost.ui.open(RequestDialog, {
566
+ data,
567
+ title,
568
+ size: "lg",
569
+ dismiss: "explicit"
570
+ }).closed.then((saved) => {
571
+ if (saved) {
572
+ serviceDeskHost.ui.toast({
573
+ message: "service-desk.dialog.saved",
574
+ kind: "success"
575
+ });
576
+ this.page.reload();
577
+ }
578
+ });
579
+ }
580
+ report(outcome, done) {
581
+ const saved = outcome.kind === "saved";
582
+ serviceDeskHost.ui.toast({
583
+ message: saved ? done : "service-desk.errors.save",
584
+ kind: saved ? "success" : "error"
585
+ });
586
+ this.page.reload();
587
+ }
588
+ static ɵfac = i0.ɵɵngDeclareFactory({
589
+ minVersion: "12.0.0",
590
+ version: "22.2.3",
591
+ ngImport: i0,
592
+ type: DeskPage,
593
+ deps: [],
594
+ target: i0.ɵɵFactoryTarget.Component
595
+ });
596
+ static ɵcmp = i0.ɵɵngDeclareComponent({
597
+ minVersion: "17.0.0",
598
+ version: "22.2.3",
599
+ type: DeskPage,
600
+ isStandalone: true,
601
+ selector: "wa-desk-page",
602
+ ngImport: i0,
603
+ 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\">{{ desk.value()?.name ?? ('service-desk.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]=\"'service-desk.search.label' | transloco\"\n [attr.placeholder]=\"'service-desk.search.placeholder' | transloco\"\n (input)=\"search($event)\"\n />\n <select class=\"lw-field w-64\" [attr.aria-label]=\"'service-desk.filter.label' | transloco\" (change)=\"chooseFilter($event)\">\n @for (choice of filters; track choice) {\n <option [value]=\"choice\" [selected]=\"choice === filter()\">{{ 'service-desk.filter.' + choice | transloco }}</option>\n }\n </select>\n <button type=\"button\" class=\"lw-btn lw-btn--primary\" (click)=\"add()\">\n <lw-icon name=\"add\" size=\"1rem\"></lw-icon>\n {{ 'service-desk.actions.add' | transloco }}\n </button>\n </div>\n </div>\n\n @if (page.error()) {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ 'service-desk.errors.load' | transloco }}</p>\n } @else {\n <div class=\"overflow-x-auto rounded-xl border border-border bg-surface\">\n <wa-request-table\n [requests]=\"page.value()?.items ?? []\"\n [locale]=\"locale()\"\n (requestActions)=\"actions($event)\"\n />\n </div>\n <div class=\"flex items-center justify-end gap-2 text-sm text-content-faint\">\n <span data-testid=\"page-range\">{{ 'service-desk.pager.range' | transloco: range() }}</span>\n <button type=\"button\" class=\"relative lw-btn lw-btn--default lw-btn--sm lw-btn--icon\" [disabled]=\"!hasPrevious()\" [attr.aria-label]=\"'service-desk.pager.previous' | transloco\" (click)=\"previous()\">\n <lw-icon name=\"serviceDeskPrevious\" size=\"1rem\"></lw-icon>\n </button>\n <button type=\"button\" class=\"relative lw-btn lw-btn--default lw-btn--sm lw-btn--icon\" [disabled]=\"!hasNext()\" [attr.aria-label]=\"'service-desk.pager.next' | transloco\" (click)=\"next()\">\n <lw-icon name=\"serviceDeskNext\" size=\"1rem\"></lw-icon>\n </button>\n </div>\n }\n</div>\n",
604
+ dependencies: [{
605
+ kind: "component",
606
+ type: RequestTable,
607
+ selector: "wa-request-table",
608
+ inputs: ["requests", "locale"],
609
+ outputs: ["requestActions"]
610
+ }, {
611
+ kind: "pipe",
612
+ type: TranslocoPipe,
613
+ name: "transloco"
614
+ }],
615
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
616
+ });
617
+ };
618
+ i0.ɵɵngDeclareClassMetadata({
619
+ minVersion: "12.0.0",
620
+ version: "22.2.3",
621
+ ngImport: i0,
622
+ type: DeskPage,
623
+ decorators: [{
624
+ type: Component,
625
+ args: [{
626
+ selector: "wa-desk-page",
627
+ imports: [TranslocoPipe, RequestTable],
628
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
629
+ changeDetection: ChangeDetectionStrategy.OnPush,
630
+ 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\">{{ desk.value()?.name ?? ('service-desk.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]=\"'service-desk.search.label' | transloco\"\n [attr.placeholder]=\"'service-desk.search.placeholder' | transloco\"\n (input)=\"search($event)\"\n />\n <select class=\"lw-field w-64\" [attr.aria-label]=\"'service-desk.filter.label' | transloco\" (change)=\"chooseFilter($event)\">\n @for (choice of filters; track choice) {\n <option [value]=\"choice\" [selected]=\"choice === filter()\">{{ 'service-desk.filter.' + choice | transloco }}</option>\n }\n </select>\n <button type=\"button\" class=\"lw-btn lw-btn--primary\" (click)=\"add()\">\n <lw-icon name=\"add\" size=\"1rem\"></lw-icon>\n {{ 'service-desk.actions.add' | transloco }}\n </button>\n </div>\n </div>\n\n @if (page.error()) {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ 'service-desk.errors.load' | transloco }}</p>\n } @else {\n <div class=\"overflow-x-auto rounded-xl border border-border bg-surface\">\n <wa-request-table\n [requests]=\"page.value()?.items ?? []\"\n [locale]=\"locale()\"\n (requestActions)=\"actions($event)\"\n />\n </div>\n <div class=\"flex items-center justify-end gap-2 text-sm text-content-faint\">\n <span data-testid=\"page-range\">{{ 'service-desk.pager.range' | transloco: range() }}</span>\n <button type=\"button\" class=\"relative lw-btn lw-btn--default lw-btn--sm lw-btn--icon\" [disabled]=\"!hasPrevious()\" [attr.aria-label]=\"'service-desk.pager.previous' | transloco\" (click)=\"previous()\">\n <lw-icon name=\"serviceDeskPrevious\" size=\"1rem\"></lw-icon>\n </button>\n <button type=\"button\" class=\"relative lw-btn lw-btn--default lw-btn--sm lw-btn--icon\" [disabled]=\"!hasNext()\" [attr.aria-label]=\"'service-desk.pager.next' | transloco\" (click)=\"next()\">\n <lw-icon name=\"serviceDeskNext\" size=\"1rem\"></lw-icon>\n </button>\n </div>\n }\n</div>\n"
631
+ }]
632
+ }],
633
+ ctorParameters: () => []
634
+ });
635
+ const stroke = "fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"";
636
+ const serviceDeskIcons = {
637
+ serviceDesk: `<svg viewBox="0 0 24 24" ${stroke}><path d="M4 13.5V7a2.5 2.5 0 0 1 2.5-2.5h11A2.5 2.5 0 0 1 20 7v6.5"/><path d="M3 13.5h5l1.5 2.5h5l1.5-2.5h5V18a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>`,
638
+ serviceDeskComplete: `<svg viewBox="0 0 24 24" ${stroke}><circle cx="12" cy="12" r="8.5"/><path d="M8.5 12.5l2.5 2.5 4.5-5"/></svg>`,
639
+ serviceDeskCancel: `<svg viewBox="0 0 24 24" ${stroke}><circle cx="12" cy="12" r="8.5"/><path d="M9 9l6 6M15 9l-6 6"/></svg>`,
640
+ serviceDeskPrevious: `<svg viewBox="0 0 24 24" ${stroke}><path d="M15 5l-7 7 7 7"/></svg>`,
641
+ serviceDeskNext: `<svg viewBox="0 0 24 24" ${stroke}><path d="M9 5l7 7-7 7"/></svg>`
642
+ };
643
+ function createServiceDeskPlugin(options = {}) {
644
+ return {
645
+ manifest: {
646
+ id: "service-desk",
647
+ name: "Service desk",
648
+ capabilities: [
649
+ "contributions",
650
+ "navigation",
651
+ "ui"
652
+ ]
653
+ },
654
+ activate(ctx) {
655
+ serviceDeskHost.bind(ctx);
656
+ ctx.contributeIcons(serviceDeskIcons);
657
+ ctx.registerSurface({
658
+ id: "service-desk.desks",
659
+ title: "service-desk.desks.title",
660
+ icon: "serviceDesk",
661
+ routable: { path: deskListPath },
662
+ component: DeskListPage
663
+ });
664
+ ctx.registerSurface({
665
+ id: "service-desk.desk",
666
+ title: "service-desk.title",
667
+ icon: "serviceDesk",
668
+ routable: { path: `${deskListPath}/:deskId` },
669
+ component: DeskPage
670
+ });
671
+ if (options.railItem) ctx.registerRailItem({
672
+ id: "service-desk.rail",
673
+ rail: "primary",
674
+ icon: "serviceDesk",
675
+ title: "service-desk.desks.title",
676
+ run: () => ctx.navigateContent(deskListPath)
677
+ });
678
+ },
679
+ deactivate() {
680
+ serviceDeskHost.unbind();
681
+ }
682
+ };
683
+ }
684
+ const serviceDeskPlugin = createServiceDeskPlugin();
685
+ const getServiceDeskDesksSaveUrl = (deskId) => {
686
+ return `/service-desk/desks/${deskId}`;
687
+ };
688
+ const getServiceDeskDesksGetUrl = (deskId) => {
689
+ return `/service-desk/desks/${deskId}`;
690
+ };
691
+ const getServiceDeskDesksListUrl = () => {
692
+ return `/service-desk/desks`;
693
+ };
694
+ const getServiceDeskRequestsRaiseUrl = (deskId) => {
695
+ return `/service-desk/desks/${deskId}/requests`;
696
+ };
697
+ const getServiceDeskRequestsListUrl = (deskId, params) => {
698
+ const normalizedParams = new URLSearchParams();
699
+ Object.entries(params || {}).forEach(([key, value]) => {
700
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : String(value));
701
+ });
702
+ const stringifiedParams = normalizedParams.toString();
703
+ return stringifiedParams.length > 0 ? `/service-desk/desks/${deskId}/requests?${stringifiedParams}` : `/service-desk/desks/${deskId}/requests`;
704
+ };
705
+ const getServiceDeskRequestsEditUrl = (deskId, requestId) => {
706
+ return `/service-desk/desks/${deskId}/requests/${requestId}`;
707
+ };
708
+ const getServiceDeskRequestsDeleteUrl = (deskId, requestId) => {
709
+ return `/service-desk/desks/${deskId}/requests/${requestId}`;
710
+ };
711
+ const getServiceDeskRequestsStatusUrl = (deskId, requestId) => {
712
+ return `/service-desk/desks/${deskId}/requests/${requestId}/status`;
713
+ };
714
+ const getServiceDeskRequestsCompleteUrl = (deskId, requestId) => {
715
+ return `/service-desk/desks/${deskId}/requests/${requestId}/complete`;
716
+ };
717
+ const getServiceDeskRequestsCancelUrl = (deskId, requestId) => {
718
+ return `/service-desk/desks/${deskId}/requests/${requestId}/cancel`;
719
+ };
720
+ const notFound = 404;
721
+ const conflict = 409;
722
+ const badRequest = 400;
723
+ const closedCode = "request-closed";
724
+ function createHttpServiceDeskGateway(options) {
725
+ const base = options.baseUrl.replace(/\/+$/, "");
726
+ const run = options.fetch ?? ((input, init) => fetch(input, init));
727
+ const send = (path, method, body, signal) => run(base + path, {
728
+ method,
729
+ signal,
730
+ credentials: "same-origin",
731
+ headers: {
732
+ Accept: "application/json",
733
+ ...body === void 0 ? {} : { "Content-Type": "application/json" },
734
+ ...options.headers
735
+ },
736
+ body: body === void 0 ? void 0 : JSON.stringify(body)
737
+ });
738
+ const read = async (response) => {
739
+ if (!response.ok) throw new ServiceDeskGatewayError(response.status);
740
+ return await response.json();
741
+ };
742
+ const outcome = async (response) => {
743
+ if (response.ok) return {
744
+ kind: "saved",
745
+ request: await response.json()
746
+ };
747
+ if (response.status === notFound) return { kind: "missing" };
748
+ if (response.status === badRequest) return { kind: "invalid" };
749
+ if (response.status !== conflict) throw new ServiceDeskGatewayError(response.status);
750
+ return (await response.json().catch(() => ({}))).code === closedCode ? { kind: "closed" } : { kind: "changed" };
751
+ };
752
+ return {
753
+ desks: async () => read(await send(getServiceDeskDesksListUrl(), "GET")),
754
+ desk: async (deskId) => {
755
+ const response = await send(getServiceDeskDesksGetUrl(deskId), "GET");
756
+ return response.status === notFound ? null : read(response);
757
+ },
758
+ saveDesk: async (deskId, name) => read(await send(getServiceDeskDesksSaveUrl(deskId), "PUT", { name })),
759
+ requests: async (deskId, listing, signal) => read(await send(getServiceDeskRequestsListUrl(deskId, listing), "GET", void 0, signal)),
760
+ raise: async (deskId, request) => outcome(await send(getServiceDeskRequestsRaiseUrl(deskId), "POST", request)),
761
+ edit: async (deskId, requestId, changes) => outcome(await send(getServiceDeskRequestsEditUrl(deskId, requestId), "PUT", changes)),
762
+ setStatus: async (deskId, requestId, status) => outcome(await send(getServiceDeskRequestsStatusUrl(deskId, requestId), "PUT", { status })),
763
+ complete: async (deskId, requestId) => outcome(await send(getServiceDeskRequestsCompleteUrl(deskId, requestId), "POST")),
764
+ cancel: async (deskId, requestId, reason) => outcome(await send(getServiceDeskRequestsCancelUrl(deskId, requestId), "POST", { reason })),
765
+ remove: async (deskId, requestId) => {
766
+ const response = await send(getServiceDeskRequestsDeleteUrl(deskId, requestId), "DELETE");
767
+ if (!response.ok && response.status !== notFound) throw new ServiceDeskGatewayError(response.status);
768
+ }
769
+ };
770
+ }
771
+ export { SERVICE_DESK_GATEWAY, ServiceDeskGatewayError, createHttpServiceDeskGateway, createServiceDeskPlugin, deskListPath, deskPath, provideServiceDeskGateway, serviceDeskPlugin };
772
+
773
+ //# sourceMappingURL=weaver-apps-service-desk.mjs.map