@weaver-apps/service-desk 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,763 @@
1
+ import { a as serviceDeskIconNames, s as serviceDeskHost, t as SERVICE_DESK_GATEWAY } from "./weaver-apps-service-desk-service-desk-gateway-BK42Rgow.mjs";
2
+ import * as i0 from "@angular/core";
3
+ import { CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, Component, computed, inject, input, output, resource, signal, viewChild } from "@angular/core";
4
+ import { TranslocoPipe, TranslocoService, translateObjectSignal } from "@jsverse/transloco";
5
+ import { toSignal } from "@angular/core/rxjs-interop";
6
+ import { ActivatedRoute } from "@angular/router";
7
+ import { map } from "rxjs";
8
+ import { DialogRef } from "@loomweaver/plugin-sdk";
9
+ import { DataGrid, ListFilter, listingDatasource } from "@weaver-apps/data-grid";
10
+ function activeLocale() {
11
+ const transloco = inject(TranslocoService);
12
+ return toSignal(transloco.langChanges$, { initialValue: transloco.getActiveLang() });
13
+ }
14
+ const ServiceRequestOrigin = {
15
+ manual: "manual",
16
+ voiceChat: "voiceChat",
17
+ textChat: "textChat",
18
+ widget: "widget",
19
+ email: "email",
20
+ phone: "phone"
21
+ };
22
+ const ServiceRequestPriority = {
23
+ low: "low",
24
+ normal: "normal",
25
+ high: "high",
26
+ urgent: "urgent"
27
+ };
28
+ const ServiceRequestStatus = {
29
+ open: "open",
30
+ inProgress: "inProgress",
31
+ completed: "completed",
32
+ cancelled: "cancelled"
33
+ };
34
+ const ServiceRequestType = {
35
+ inquiry: "inquiry",
36
+ task: "task",
37
+ callbackRequest: "callbackRequest",
38
+ conversationSummary: "conversationSummary",
39
+ complaint: "complaint",
40
+ quote: "quote"
41
+ };
42
+ const requestTypes = Object.values(ServiceRequestType);
43
+ const requestPriorities = Object.values(ServiceRequestPriority);
44
+ const requestOrigins = Object.values(ServiceRequestOrigin);
45
+ const settableStatuses = [ServiceRequestStatus.open, ServiceRequestStatus.inProgress];
46
+ function isClosed(status) {
47
+ return status === ServiceRequestStatus.completed || status === ServiceRequestStatus.cancelled;
48
+ }
49
+ function localDate(value) {
50
+ const pad = (part) => `${part}`.padStart(2, "0");
51
+ return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}`;
52
+ }
53
+ function pick(choices, value) {
54
+ return choices.find((choice) => choice === value);
55
+ }
56
+ var RequestDialog = class RequestDialog {
57
+ dialog = inject(DialogRef);
58
+ gateway = inject(SERVICE_DESK_GATEWAY);
59
+ data = this.dialog.data;
60
+ existing = this.data.request;
61
+ editing = this.existing !== null;
62
+ closed = this.existing !== null && isClosed(this.existing.status);
63
+ types = requestTypes;
64
+ priorities = requestPriorities;
65
+ origins = requestOrigins;
66
+ statuses = settableStatuses;
67
+ title = signal(this.existing?.title ?? "", ...ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []);
68
+ type = signal(this.existing?.type ?? ServiceRequestType.inquiry, ...ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []);
69
+ priority = signal(this.existing?.priority ?? ServiceRequestPriority.normal, ...ngDevMode ? [{ debugName: "priority" }] : /* istanbul ignore next */ []);
70
+ origin = signal(this.existing?.origin ?? ServiceRequestOrigin.manual, ...ngDevMode ? [{ debugName: "origin" }] : /* istanbul ignore next */ []);
71
+ status = signal(this.existing?.status ?? "open", ...ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []);
72
+ contactName = signal(this.existing?.contactName ?? "", ...ngDevMode ? [{ debugName: "contactName" }] : /* istanbul ignore next */ []);
73
+ contactPhone = signal(this.existing?.contactPhone ?? "", ...ngDevMode ? [{ debugName: "contactPhone" }] : /* istanbul ignore next */ []);
74
+ contactEmail = signal(this.existing?.contactEmail ?? "", ...ngDevMode ? [{ debugName: "contactEmail" }] : /* istanbul ignore next */ []);
75
+ assignee = signal(this.existing?.assignee ?? "", ...ngDevMode ? [{ debugName: "assignee" }] : /* istanbul ignore next */ []);
76
+ dueDate = signal(this.existing?.dueAt ? localDate(new Date(this.existing.dueAt)) : "", ...ngDevMode ? [{ debugName: "dueDate" }] : /* istanbul ignore next */ []);
77
+ description = signal(this.existing?.description ?? "", ...ngDevMode ? [{ debugName: "description" }] : /* istanbul ignore next */ []);
78
+ cancellationReason = this.existing?.cancellationReason ?? null;
79
+ saving = signal(false, ...ngDevMode ? [{ debugName: "saving" }] : /* istanbul ignore next */ []);
80
+ problem = signal("", ...ngDevMode ? [{ debugName: "problem" }] : /* istanbul ignore next */ []);
81
+ persisted = false;
82
+ bind(target, event) {
83
+ target.set(event.target.value);
84
+ }
85
+ chooseType(event) {
86
+ this.choose(this.type, requestTypes, event);
87
+ }
88
+ choosePriority(event) {
89
+ this.choose(this.priority, requestPriorities, event);
90
+ }
91
+ chooseOrigin(event) {
92
+ this.choose(this.origin, requestOrigins, event);
93
+ }
94
+ chooseStatus(event) {
95
+ this.choose(this.status, settableStatuses, event);
96
+ }
97
+ close() {
98
+ this.dialog.close(this.persisted);
99
+ }
100
+ async save() {
101
+ if (!this.title().trim()) {
102
+ this.problem.set("weaver-apps-service-desk.dialog.titleRequired");
103
+ return;
104
+ }
105
+ this.saving.set(true);
106
+ try {
107
+ const changes = this.changes();
108
+ const outcome = this.existing ? await this.gateway.edit(this.data.deskId, this.existing.id, changes) : await this.gateway.raise(this.data.deskId, {
109
+ ...changes,
110
+ origin: this.origin()
111
+ });
112
+ this.settle(await this.applyStatus(outcome));
113
+ } catch {
114
+ this.problem.set("weaver-apps-service-desk.errors.save");
115
+ } finally {
116
+ this.saving.set(false);
117
+ }
118
+ }
119
+ changes() {
120
+ const keptDue = this.existing?.dueAt && localDate(new Date(this.existing.dueAt)) === this.dueDate() ? this.existing.dueAt : null;
121
+ if (keptDue) return {
122
+ ...this.fields(),
123
+ dueAt: keptDue
124
+ };
125
+ const due = this.dueDate() ? /* @__PURE__ */ new Date(`${this.dueDate()}T12:00`) : null;
126
+ return {
127
+ ...this.fields(),
128
+ dueAt: due && !Number.isNaN(due.getTime()) ? due.toISOString() : null
129
+ };
130
+ }
131
+ fields() {
132
+ return {
133
+ title: this.title().trim(),
134
+ description: this.optional(this.description()),
135
+ type: this.type(),
136
+ priority: this.priority(),
137
+ contactName: this.optional(this.contactName()),
138
+ contactPhone: this.optional(this.contactPhone()),
139
+ contactEmail: this.optional(this.contactEmail()),
140
+ assignee: this.optional(this.assignee())
141
+ };
142
+ }
143
+ async applyStatus(outcome) {
144
+ if (outcome.kind === "saved") {
145
+ this.existing = outcome.request;
146
+ this.persisted = true;
147
+ }
148
+ if (outcome.kind !== "saved" || outcome.request.status === this.status()) return outcome;
149
+ return this.gateway.setStatus(this.data.deskId, outcome.request.id, this.status());
150
+ }
151
+ settle(outcome) {
152
+ const messages = {
153
+ closed: "weaver-apps-service-desk.dialog.closed",
154
+ changed: "weaver-apps-service-desk.dialog.changed",
155
+ invalid: "weaver-apps-service-desk.dialog.invalid",
156
+ missing: "weaver-apps-service-desk.dialog.missing"
157
+ };
158
+ if (outcome.kind === "saved") {
159
+ this.dialog.close(true);
160
+ return;
161
+ }
162
+ this.problem.set(messages[outcome.kind]);
163
+ }
164
+ choose(target, choices, event) {
165
+ const chosen = pick(choices, event.target.value);
166
+ if (chosen) target.set(chosen);
167
+ }
168
+ optional(value) {
169
+ return value.trim() ? value.trim() : null;
170
+ }
171
+ static ɵfac = i0.ɵɵngDeclareFactory({
172
+ minVersion: "12.0.0",
173
+ version: "22.2.3",
174
+ ngImport: i0,
175
+ type: RequestDialog,
176
+ deps: [],
177
+ target: i0.ɵɵFactoryTarget.Component
178
+ });
179
+ static ɵcmp = i0.ɵɵngDeclareComponent({
180
+ minVersion: "17.0.0",
181
+ version: "22.2.3",
182
+ type: RequestDialog,
183
+ isStandalone: true,
184
+ selector: "wa-request-dialog",
185
+ ngImport: i0,
186
+ 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 {{ 'weaver-apps-service-desk.dialog.closedNote' | transloco }}\n @if (cancellationReason) {\n {{ 'weaver-apps-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 {{ 'weaver-apps-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 {{ 'weaver-apps-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()\">{{ 'weaver-apps-service-desk.type.' + choice | transloco }}</option>\n }\n </select>\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-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()\">{{ 'weaver-apps-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 {{ 'weaver-apps-service-desk.fields.status' | transloco }}\n <select class=\"lw-field\" [disabled]=\"closed\" (change)=\"chooseStatus($event)\">\n @if (closed) {\n <option selected>{{ 'weaver-apps-service-desk.status.' + status() | transloco }}</option>\n } @else {\n @for (choice of statuses; track choice) {\n <option [value]=\"choice\" [selected]=\"choice === status()\">{{ 'weaver-apps-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 {{ 'weaver-apps-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()\">{{ 'weaver-apps-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 {{ 'weaver-apps-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 {{ 'weaver-apps-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 {{ 'weaver-apps-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 {{ 'weaver-apps-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 {{ 'weaver-apps-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 {{ 'weaver-apps-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 ? 'weaver-apps-service-desk.dialog.close' : 'weaver-apps-service-desk.dialog.cancel') | transloco }}</button>\n @if (!closed) {\n <button type=\"submit\" class=\"lw-btn lw-btn--primary\" [disabled]=\"saving()\">{{ 'weaver-apps-service-desk.dialog.save' | transloco }}</button>\n }\n </div>\n</form>\n",
187
+ dependencies: [{
188
+ kind: "pipe",
189
+ type: TranslocoPipe,
190
+ name: "transloco"
191
+ }],
192
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
193
+ });
194
+ };
195
+ i0.ɵɵngDeclareClassMetadata({
196
+ minVersion: "12.0.0",
197
+ version: "22.2.3",
198
+ ngImport: i0,
199
+ type: RequestDialog,
200
+ decorators: [{
201
+ type: Component,
202
+ args: [{
203
+ selector: "wa-request-dialog",
204
+ imports: [TranslocoPipe],
205
+ changeDetection: ChangeDetectionStrategy.OnPush,
206
+ 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 {{ 'weaver-apps-service-desk.dialog.closedNote' | transloco }}\n @if (cancellationReason) {\n {{ 'weaver-apps-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 {{ 'weaver-apps-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 {{ 'weaver-apps-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()\">{{ 'weaver-apps-service-desk.type.' + choice | transloco }}</option>\n }\n </select>\n </label>\n <label class=\"flex flex-col gap-1 text-sm text-content\">\n {{ 'weaver-apps-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()\">{{ 'weaver-apps-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 {{ 'weaver-apps-service-desk.fields.status' | transloco }}\n <select class=\"lw-field\" [disabled]=\"closed\" (change)=\"chooseStatus($event)\">\n @if (closed) {\n <option selected>{{ 'weaver-apps-service-desk.status.' + status() | transloco }}</option>\n } @else {\n @for (choice of statuses; track choice) {\n <option [value]=\"choice\" [selected]=\"choice === status()\">{{ 'weaver-apps-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 {{ 'weaver-apps-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()\">{{ 'weaver-apps-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 {{ 'weaver-apps-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 {{ 'weaver-apps-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 {{ 'weaver-apps-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 {{ 'weaver-apps-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 {{ 'weaver-apps-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 {{ 'weaver-apps-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 ? 'weaver-apps-service-desk.dialog.close' : 'weaver-apps-service-desk.dialog.cancel') | transloco }}</button>\n @if (!closed) {\n <button type=\"submit\" class=\"lw-btn lw-btn--primary\" [disabled]=\"saving()\">{{ 'weaver-apps-service-desk.dialog.save' | transloco }}</button>\n }\n </div>\n</form>\n"
207
+ }]
208
+ }]
209
+ });
210
+ var RequestCell = class RequestCell {
211
+ params = null;
212
+ kind = signal("title", ...ngDevMode ? [{ debugName: "kind" }] : /* istanbul ignore next */ []);
213
+ request = signal(null, ...ngDevMode ? [{ debugName: "request" }] : /* istanbul ignore next */ []);
214
+ locale = signal("en", ...ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []);
215
+ closed = computed(() => {
216
+ const request = this.request();
217
+ return request !== null && isClosed(request.status);
218
+ }, ...ngDevMode ? [{ debugName: "closed" }] : /* istanbul ignore next */ []);
219
+ dates = computed(() => {
220
+ const request = this.request();
221
+ const date = new Intl.DateTimeFormat(this.locale(), {
222
+ day: "2-digit",
223
+ month: "2-digit",
224
+ year: "numeric"
225
+ });
226
+ return {
227
+ raised: request ? date.format(new Date(request.raisedAt)) : "",
228
+ due: request?.dueAt ? date.format(new Date(request.dueAt)) : ""
229
+ };
230
+ }, ...ngDevMode ? [{ debugName: "dates" }] : /* istanbul ignore next */ []);
231
+ agInit(params) {
232
+ this.params = params;
233
+ this.kind.set(params.kind);
234
+ this.request.set(params.data ?? null);
235
+ this.locale.set(params.locale());
236
+ }
237
+ refresh(params) {
238
+ this.agInit(params);
239
+ return true;
240
+ }
241
+ openActions(event) {
242
+ const request = this.request();
243
+ if (request) this.params?.actions({
244
+ request,
245
+ x: event.clientX,
246
+ y: event.clientY
247
+ });
248
+ }
249
+ static ɵfac = i0.ɵɵngDeclareFactory({
250
+ minVersion: "12.0.0",
251
+ version: "22.2.3",
252
+ ngImport: i0,
253
+ type: RequestCell,
254
+ deps: [],
255
+ target: i0.ɵɵFactoryTarget.Component
256
+ });
257
+ static ɵcmp = i0.ɵɵngDeclareComponent({
258
+ minVersion: "17.0.0",
259
+ version: "22.2.3",
260
+ type: RequestCell,
261
+ isStandalone: true,
262
+ selector: "wa-request-cell",
263
+ ngImport: i0,
264
+ template: "@if (request(); as request) {\n @switch (kind()) {\n @case ('title') {\n <span class=\"font-medium\" [class.text-content]=\"!closed()\" [class.line-through]=\"request.status === 'cancelled'\" data-testid=\"request-title\">{{ request.title }}</span>\n }\n @case ('type') {\n <span class=\"rounded-full border border-border px-2 py-0.5 text-xs text-content\">{{ 'weaver-apps-service-desk.type.' + request.type | transloco }}</span>\n }\n @case ('priority') {\n <span class=\"rounded-full border border-border px-2 py-0.5 text-xs\" [class.text-negative]=\"request.priority === 'urgent'\" [class.font-semibold]=\"request.priority === 'urgent' || request.priority === 'high'\" data-testid=\"request-priority\">{{ 'weaver-apps-service-desk.priority.' + request.priority | transloco }}</span>\n }\n @case ('status') {\n <span class=\"rounded-full border border-border px-2 py-0.5 text-xs text-content\" data-testid=\"request-status\">{{ 'weaver-apps-service-desk.status.' + request.status | transloco }}</span>\n }\n @case ('contact') {\n <span>{{ request.contactName ?? '–' }}</span>\n @if (request.contactPhone ?? request.contactEmail; as reach) {\n <span class=\"ml-2 text-content-faint\">{{ reach }}</span>\n }\n }\n @case ('raised') {\n <span>{{ dates().raised }}</span>\n }\n @case ('due') {\n <span>{{ dates().due }}</span>\n }\n @case ('actions') {\n <button type=\"button\" class=\"relative lw-btn lw-btn--ghost lw-btn--sm lw-btn--icon\" [attr.aria-label]=\"'weaver-apps-service-desk.actions.more' | transloco\" (click)=\"openActions($event)\">\n <lw-icon name=\"more\" size=\"1rem\"></lw-icon>\n </button>\n }\n }\n}\n",
265
+ dependencies: [{
266
+ kind: "pipe",
267
+ type: TranslocoPipe,
268
+ name: "transloco"
269
+ }],
270
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
271
+ });
272
+ };
273
+ i0.ɵɵngDeclareClassMetadata({
274
+ minVersion: "12.0.0",
275
+ version: "22.2.3",
276
+ ngImport: i0,
277
+ type: RequestCell,
278
+ decorators: [{
279
+ type: Component,
280
+ args: [{
281
+ selector: "wa-request-cell",
282
+ imports: [TranslocoPipe],
283
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
284
+ changeDetection: ChangeDetectionStrategy.OnPush,
285
+ template: "@if (request(); as request) {\n @switch (kind()) {\n @case ('title') {\n <span class=\"font-medium\" [class.text-content]=\"!closed()\" [class.line-through]=\"request.status === 'cancelled'\" data-testid=\"request-title\">{{ request.title }}</span>\n }\n @case ('type') {\n <span class=\"rounded-full border border-border px-2 py-0.5 text-xs text-content\">{{ 'weaver-apps-service-desk.type.' + request.type | transloco }}</span>\n }\n @case ('priority') {\n <span class=\"rounded-full border border-border px-2 py-0.5 text-xs\" [class.text-negative]=\"request.priority === 'urgent'\" [class.font-semibold]=\"request.priority === 'urgent' || request.priority === 'high'\" data-testid=\"request-priority\">{{ 'weaver-apps-service-desk.priority.' + request.priority | transloco }}</span>\n }\n @case ('status') {\n <span class=\"rounded-full border border-border px-2 py-0.5 text-xs text-content\" data-testid=\"request-status\">{{ 'weaver-apps-service-desk.status.' + request.status | transloco }}</span>\n }\n @case ('contact') {\n <span>{{ request.contactName ?? '–' }}</span>\n @if (request.contactPhone ?? request.contactEmail; as reach) {\n <span class=\"ml-2 text-content-faint\">{{ reach }}</span>\n }\n }\n @case ('raised') {\n <span>{{ dates().raised }}</span>\n }\n @case ('due') {\n <span>{{ dates().due }}</span>\n }\n @case ('actions') {\n <button type=\"button\" class=\"relative lw-btn lw-btn--ghost lw-btn--sm lw-btn--icon\" [attr.aria-label]=\"'weaver-apps-service-desk.actions.more' | transloco\" (click)=\"openActions($event)\">\n <lw-icon name=\"more\" size=\"1rem\"></lw-icon>\n </button>\n }\n }\n}\n"
286
+ }]
287
+ }]
288
+ });
289
+ const requestColumn = {
290
+ title: "title",
291
+ type: "type",
292
+ priority: "priority",
293
+ status: "status",
294
+ contact: "contact",
295
+ raised: "raisedAt",
296
+ due: "dueAt",
297
+ actions: "actions"
298
+ };
299
+ const defaultStatuses = [
300
+ ServiceRequestStatus.open,
301
+ ServiceRequestStatus.inProgress,
302
+ ServiceRequestStatus.completed
303
+ ];
304
+ const defaultRequestFilter = { [requestColumn.status]: { values: [...defaultStatuses] } };
305
+ const sortableColumns = /* @__PURE__ */ new Set([
306
+ requestColumn.title,
307
+ requestColumn.priority,
308
+ requestColumn.status,
309
+ requestColumn.raised,
310
+ requestColumn.due
311
+ ]);
312
+ function listingOf(request) {
313
+ const { filters, sort } = request;
314
+ return {
315
+ ...sort && sortableColumns.has(sort.column) ? {
316
+ sort: sort.column,
317
+ descending: sort.descending
318
+ } : {},
319
+ ...textOf("text", filters[requestColumn.title]),
320
+ ...textOf("contact", filters[requestColumn.contact]),
321
+ ...valuesOf("type", filters[requestColumn.type]),
322
+ ...valuesOf("status", filters[requestColumn.status]),
323
+ ...valuesOf("priority", filters[requestColumn.priority]),
324
+ offset: request.offset,
325
+ limit: request.limit
326
+ };
327
+ }
328
+ function textOf(key, filter) {
329
+ const text = filter?.filter?.trim();
330
+ return text ? { [key]: text } : {};
331
+ }
332
+ function valuesOf(key, filter) {
333
+ const values = filter?.values ?? [];
334
+ return values.length > 0 ? { [key]: [...values] } : {};
335
+ }
336
+ const allStatuses = [
337
+ "open",
338
+ "inProgress",
339
+ "completed",
340
+ "cancelled"
341
+ ];
342
+ const actionsWidthPx = 72;
343
+ const titleMinWidthPx = 160;
344
+ const contactMinWidthPx = 140;
345
+ const narrowMinWidthPx = 112;
346
+ var RequestGrid = class RequestGrid {
347
+ table = translateObjectSignal("weaver-apps-service-desk.table");
348
+ types = translateObjectSignal("weaver-apps-service-desk.type");
349
+ statuses = translateObjectSignal("weaver-apps-service-desk.status");
350
+ priorities = translateObjectSignal("weaver-apps-service-desk.priority");
351
+ api = null;
352
+ fetch = input.required(...ngDevMode ? [{ debugName: "fetch" }] : /* istanbul ignore next */ []);
353
+ locale = input.required(...ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []);
354
+ requestActions = output();
355
+ counted = output();
356
+ failed = output();
357
+ translated = computed(() => typeof this.table() === "object" && typeof this.table()["title"] === "string", ...ngDevMode ? [{ debugName: "translated" }] : /* istanbul ignore next */ []);
358
+ empty = computed(() => ({ noRowsToShow: this.table()["empty"] ?? "" }), ...ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []);
359
+ options = computed(() => ({
360
+ columnDefs: this.columns(this.table()),
361
+ defaultColDef: {
362
+ sortable: false,
363
+ resizable: true,
364
+ suppressHeaderMenuButton: true
365
+ },
366
+ datasource: listingDatasource(this.fetch(), {
367
+ answered: (answer) => this.counted.emit(answer.total),
368
+ failed: () => this.failed.emit()
369
+ }),
370
+ initialState: {
371
+ sort: { sortModel: [{
372
+ colId: requestColumn.raised,
373
+ sort: "desc"
374
+ }] },
375
+ filter: { filterModel: { ...defaultRequestFilter } }
376
+ },
377
+ rowClassRules: { "text-content-faint": (params) => !!params.data && isClosed(params.data.status) }
378
+ }), ...ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []);
379
+ reload() {
380
+ this.api?.refreshInfiniteCache();
381
+ }
382
+ ready(api) {
383
+ this.api = api;
384
+ }
385
+ columns(table) {
386
+ const narrow = {
387
+ flex: 1,
388
+ minWidth: narrowMinWidthPx
389
+ };
390
+ return [
391
+ {
392
+ ...this.cell(requestColumn.title, "title", table["title"]),
393
+ sortable: true,
394
+ flex: 3,
395
+ minWidth: titleMinWidthPx,
396
+ filter: "agTextColumnFilter",
397
+ filterParams: {
398
+ filterOptions: ["contains"],
399
+ maxNumConditions: 1
400
+ }
401
+ },
402
+ {
403
+ ...this.cell(requestColumn.type, "type", table["type"]),
404
+ ...narrow,
405
+ filter: ListFilter,
406
+ filterParams: this.choices(requestTypes, this.types)
407
+ },
408
+ {
409
+ ...this.cell(requestColumn.priority, "priority", table["priority"]),
410
+ ...narrow,
411
+ sortable: true,
412
+ filter: ListFilter,
413
+ filterParams: this.choices(requestPriorities, this.priorities)
414
+ },
415
+ {
416
+ ...this.cell(requestColumn.status, "status", table["status"]),
417
+ ...narrow,
418
+ sortable: true,
419
+ filter: ListFilter,
420
+ filterParams: this.choices(allStatuses, this.statuses)
421
+ },
422
+ {
423
+ ...this.cell(requestColumn.contact, "contact", table["contact"]),
424
+ flex: 2,
425
+ minWidth: contactMinWidthPx,
426
+ filter: "agTextColumnFilter",
427
+ filterParams: {
428
+ filterOptions: ["contains"],
429
+ maxNumConditions: 1
430
+ }
431
+ },
432
+ {
433
+ ...this.cell(requestColumn.raised, "raised", table["raised"]),
434
+ ...narrow,
435
+ sortable: true,
436
+ sortingOrder: ["desc", "asc"]
437
+ },
438
+ {
439
+ ...this.cell(requestColumn.due, "due", table["due"]),
440
+ ...narrow,
441
+ sortable: true
442
+ },
443
+ {
444
+ ...this.cell(requestColumn.actions, "actions", ""),
445
+ width: actionsWidthPx,
446
+ resizable: false,
447
+ sortable: false
448
+ }
449
+ ];
450
+ }
451
+ cell(colId, kind, headerName) {
452
+ return {
453
+ colId,
454
+ headerName: headerName ?? "",
455
+ cellRenderer: RequestCell,
456
+ cellRendererParams: {
457
+ kind,
458
+ locale: () => this.locale(),
459
+ actions: (actions) => this.requestActions.emit(actions)
460
+ }
461
+ };
462
+ }
463
+ choices(values, labels) {
464
+ return { options: () => values.map((value) => ({
465
+ value,
466
+ label: labels()[value] ?? value
467
+ })) };
468
+ }
469
+ static ɵfac = i0.ɵɵngDeclareFactory({
470
+ minVersion: "12.0.0",
471
+ version: "22.2.3",
472
+ ngImport: i0,
473
+ type: RequestGrid,
474
+ deps: [],
475
+ target: i0.ɵɵFactoryTarget.Component
476
+ });
477
+ static ɵcmp = i0.ɵɵngDeclareComponent({
478
+ minVersion: "17.0.0",
479
+ version: "22.2.3",
480
+ type: RequestGrid,
481
+ isStandalone: true,
482
+ selector: "wa-request-grid",
483
+ inputs: {
484
+ fetch: {
485
+ classPropertyName: "fetch",
486
+ publicName: "fetch",
487
+ isSignal: true,
488
+ isRequired: true,
489
+ transformFunction: null
490
+ },
491
+ locale: {
492
+ classPropertyName: "locale",
493
+ publicName: "locale",
494
+ isSignal: true,
495
+ isRequired: true,
496
+ transformFunction: null
497
+ }
498
+ },
499
+ outputs: {
500
+ requestActions: "requestActions",
501
+ counted: "counted",
502
+ failed: "failed"
503
+ },
504
+ host: { classAttribute: "block h-full w-full" },
505
+ ngImport: i0,
506
+ template: "@if (translated()) {\n <wa-data-grid [options]=\"options()\" [localeOverrides]=\"empty()\" (ready)=\"ready($event)\" />\n}\n",
507
+ dependencies: [{
508
+ kind: "component",
509
+ type: DataGrid,
510
+ selector: "wa-data-grid",
511
+ inputs: ["options", "localeOverrides"],
512
+ outputs: ["ready"]
513
+ }],
514
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
515
+ });
516
+ };
517
+ i0.ɵɵngDeclareClassMetadata({
518
+ minVersion: "12.0.0",
519
+ version: "22.2.3",
520
+ ngImport: i0,
521
+ type: RequestGrid,
522
+ decorators: [{
523
+ type: Component,
524
+ args: [{
525
+ selector: "wa-request-grid",
526
+ imports: [DataGrid],
527
+ changeDetection: ChangeDetectionStrategy.OnPush,
528
+ host: { class: "block h-full w-full" },
529
+ template: "@if (translated()) {\n <wa-data-grid [options]=\"options()\" [localeOverrides]=\"empty()\" (ready)=\"ready($event)\" />\n}\n"
530
+ }]
531
+ }],
532
+ propDecorators: {
533
+ fetch: [{
534
+ type: i0.Input,
535
+ args: [{
536
+ isSignal: true,
537
+ alias: "fetch",
538
+ required: true
539
+ }]
540
+ }],
541
+ locale: [{
542
+ type: i0.Input,
543
+ args: [{
544
+ isSignal: true,
545
+ alias: "locale",
546
+ required: true
547
+ }]
548
+ }],
549
+ requestActions: [{
550
+ type: i0.Output,
551
+ args: ["requestActions"]
552
+ }],
553
+ counted: [{
554
+ type: i0.Output,
555
+ args: ["counted"]
556
+ }],
557
+ failed: [{
558
+ type: i0.Output,
559
+ args: ["failed"]
560
+ }]
561
+ }
562
+ });
563
+ var DeskPage = class DeskPage {
564
+ gateway = inject(SERVICE_DESK_GATEWAY);
565
+ route = inject(ActivatedRoute);
566
+ grid = viewChild(RequestGrid, ...ngDevMode ? [{ debugName: "grid" }] : /* istanbul ignore next */ []);
567
+ locale = activeLocale();
568
+ deskId = toSignal(this.route.paramMap.pipe(map((params) => params.get("deskId") ?? "")), { initialValue: this.route.snapshot.paramMap.get("deskId") ?? "" });
569
+ count = signal(null, ...ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []);
570
+ loadFailed = signal(false, ...ngDevMode ? [{ debugName: "loadFailed" }] : /* istanbul ignore next */ []);
571
+ desk = resource({
572
+ ...ngDevMode ? { debugName: "desk" } : /* istanbul ignore next */ {},
573
+ params: () => this.deskId(),
574
+ loader: ({ params }) => this.gateway.desk(params)
575
+ });
576
+ fetch = computed(() => {
577
+ const deskId = this.deskId();
578
+ return async (request) => {
579
+ const page = await this.gateway.requests(deskId, listingOf(request));
580
+ return {
581
+ items: page.items,
582
+ total: Number(page.total)
583
+ };
584
+ };
585
+ }, ...ngDevMode ? [{ debugName: "fetch" }] : /* istanbul ignore next */ []);
586
+ counted(total) {
587
+ this.loadFailed.set(false);
588
+ this.count.set(total);
589
+ }
590
+ failed() {
591
+ this.loadFailed.set(true);
592
+ }
593
+ add() {
594
+ this.openDialog({
595
+ deskId: this.deskId(),
596
+ request: null
597
+ }, "weaver-apps-service-desk.dialog.addTitle");
598
+ }
599
+ actions({ request, x, y }) {
600
+ const closed = isClosed(request.status);
601
+ serviceDeskHost.ui.openMenu([
602
+ {
603
+ label: closed ? "weaver-apps-service-desk.actions.view" : "weaver-apps-service-desk.actions.edit",
604
+ icon: closed ? "preview" : "edit",
605
+ run: () => this.open(request)
606
+ },
607
+ ...closed ? [] : [{
608
+ label: "weaver-apps-service-desk.actions.complete",
609
+ icon: serviceDeskIconNames.complete,
610
+ run: () => void this.complete(request)
611
+ }, {
612
+ label: "weaver-apps-service-desk.actions.cancel",
613
+ icon: serviceDeskIconNames.cancel,
614
+ run: () => void this.cancel(request)
615
+ }],
616
+ {
617
+ label: "weaver-apps-service-desk.actions.delete",
618
+ icon: "trash",
619
+ run: () => void this.remove(request)
620
+ }
621
+ ], {
622
+ x,
623
+ y
624
+ });
625
+ }
626
+ open(request) {
627
+ const closed = isClosed(request.status);
628
+ this.openDialog({
629
+ deskId: this.deskId(),
630
+ request
631
+ }, closed ? "weaver-apps-service-desk.dialog.viewTitle" : "weaver-apps-service-desk.dialog.editTitle");
632
+ }
633
+ async complete(request) {
634
+ await this.attempt(async () => this.report(await this.gateway.complete(this.deskId(), request.id), "weaver-apps-service-desk.complete.done"));
635
+ }
636
+ async cancel(request) {
637
+ const reason = await serviceDeskHost.ui.prompt({
638
+ title: "weaver-apps-service-desk.cancel.title",
639
+ message: "weaver-apps-service-desk.cancel.message",
640
+ confirmLabel: "weaver-apps-service-desk.cancel.confirm",
641
+ tone: "warning"
642
+ });
643
+ if (reason === null) return;
644
+ await this.attempt(async () => this.report(await this.gateway.cancel(this.deskId(), request.id, reason.trim() || null), "weaver-apps-service-desk.cancel.done"));
645
+ }
646
+ async remove(request) {
647
+ if (!await serviceDeskHost.ui.confirm({
648
+ title: "weaver-apps-service-desk.delete.title",
649
+ message: "weaver-apps-service-desk.delete.message",
650
+ confirmLabel: "weaver-apps-service-desk.delete.confirm",
651
+ tone: "danger"
652
+ })) return;
653
+ await this.attempt(async () => {
654
+ await this.gateway.remove(this.deskId(), request.id);
655
+ serviceDeskHost.ui.toast({
656
+ message: "weaver-apps-service-desk.delete.done",
657
+ kind: "success"
658
+ });
659
+ this.reload();
660
+ });
661
+ }
662
+ reload() {
663
+ this.grid()?.reload();
664
+ }
665
+ async attempt(work) {
666
+ try {
667
+ await work();
668
+ } catch {
669
+ serviceDeskHost.ui.toast({
670
+ message: "weaver-apps-service-desk.errors.save",
671
+ kind: "error"
672
+ });
673
+ this.reload();
674
+ }
675
+ }
676
+ openDialog(data, title) {
677
+ serviceDeskHost.ui.open(RequestDialog, {
678
+ data,
679
+ title,
680
+ size: "lg",
681
+ dismiss: "explicit"
682
+ }).closed.then((saved) => {
683
+ if (saved) {
684
+ serviceDeskHost.ui.toast({
685
+ message: "weaver-apps-service-desk.dialog.saved",
686
+ kind: "success"
687
+ });
688
+ this.reload();
689
+ }
690
+ });
691
+ }
692
+ report(outcome, done) {
693
+ const saved = outcome.kind === "saved";
694
+ serviceDeskHost.ui.toast({
695
+ message: saved ? done : "weaver-apps-service-desk.errors.save",
696
+ kind: saved ? "success" : "error"
697
+ });
698
+ this.reload();
699
+ }
700
+ static ɵfac = i0.ɵɵngDeclareFactory({
701
+ minVersion: "12.0.0",
702
+ version: "22.2.3",
703
+ ngImport: i0,
704
+ type: DeskPage,
705
+ deps: [],
706
+ target: i0.ɵɵFactoryTarget.Component
707
+ });
708
+ static ɵcmp = i0.ɵɵngDeclareComponent({
709
+ minVersion: "17.0.0",
710
+ version: "22.2.3",
711
+ type: DeskPage,
712
+ isStandalone: true,
713
+ selector: "wa-desk-page",
714
+ viewQueries: [{
715
+ propertyName: "grid",
716
+ first: true,
717
+ predicate: RequestGrid,
718
+ descendants: true,
719
+ isSignal: true
720
+ }],
721
+ ngImport: i0,
722
+ template: "<div class=\"flex h-full flex-col gap-4 p-6\">\n <div class=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <h2 class=\"text-lg font-semibold text-content\">{{ desk.value()?.name ?? ('weaver-apps-service-desk.title' | transloco) }}</h2>\n <div class=\"flex flex-wrap items-center gap-3\">\n @if (count() !== null) {\n <span class=\"text-sm text-content-faint\" data-testid=\"request-count\">{{ 'weaver-apps-service-desk.table.count' | transloco: { count: count() } }}</span>\n }\n <button type=\"button\" class=\"lw-btn lw-btn--primary\" (click)=\"add()\">\n <lw-icon name=\"add\" size=\"1rem\"></lw-icon>\n {{ 'weaver-apps-service-desk.actions.add' | transloco }}\n </button>\n </div>\n </div>\n\n @if (loadFailed()) {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ 'weaver-apps-service-desk.errors.load' | transloco }}</p>\n }\n <div class=\"min-h-80 flex-1\">\n <wa-request-grid [fetch]=\"fetch()\" [locale]=\"locale()\" (requestActions)=\"actions($event)\" (counted)=\"counted($event)\" (failed)=\"failed()\" />\n </div>\n</div>\n",
723
+ dependencies: [{
724
+ kind: "component",
725
+ type: RequestGrid,
726
+ selector: "wa-request-grid",
727
+ inputs: ["fetch", "locale"],
728
+ outputs: [
729
+ "requestActions",
730
+ "counted",
731
+ "failed"
732
+ ]
733
+ }, {
734
+ kind: "pipe",
735
+ type: TranslocoPipe,
736
+ name: "transloco"
737
+ }],
738
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
739
+ });
740
+ };
741
+ i0.ɵɵngDeclareClassMetadata({
742
+ minVersion: "12.0.0",
743
+ version: "22.2.3",
744
+ ngImport: i0,
745
+ type: DeskPage,
746
+ decorators: [{
747
+ type: Component,
748
+ args: [{
749
+ selector: "wa-desk-page",
750
+ imports: [TranslocoPipe, RequestGrid],
751
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
752
+ changeDetection: ChangeDetectionStrategy.OnPush,
753
+ template: "<div class=\"flex h-full flex-col gap-4 p-6\">\n <div class=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <h2 class=\"text-lg font-semibold text-content\">{{ desk.value()?.name ?? ('weaver-apps-service-desk.title' | transloco) }}</h2>\n <div class=\"flex flex-wrap items-center gap-3\">\n @if (count() !== null) {\n <span class=\"text-sm text-content-faint\" data-testid=\"request-count\">{{ 'weaver-apps-service-desk.table.count' | transloco: { count: count() } }}</span>\n }\n <button type=\"button\" class=\"lw-btn lw-btn--primary\" (click)=\"add()\">\n <lw-icon name=\"add\" size=\"1rem\"></lw-icon>\n {{ 'weaver-apps-service-desk.actions.add' | transloco }}\n </button>\n </div>\n </div>\n\n @if (loadFailed()) {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ 'weaver-apps-service-desk.errors.load' | transloco }}</p>\n }\n <div class=\"min-h-80 flex-1\">\n <wa-request-grid [fetch]=\"fetch()\" [locale]=\"locale()\" (requestActions)=\"actions($event)\" (counted)=\"counted($event)\" (failed)=\"failed()\" />\n </div>\n</div>\n"
754
+ }]
755
+ }],
756
+ propDecorators: { grid: [{
757
+ type: i0.ViewChild,
758
+ args: [i0.forwardRef(() => RequestGrid), { isSignal: true }]
759
+ }] }
760
+ });
761
+ export { DeskPage };
762
+
763
+ //# sourceMappingURL=weaver-apps-service-desk-desk-page-CgKsdNiZ.mjs.map