@masterteam/notification 0.0.1

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,1056 @@
1
+ import { Avatar } from '@masterteam/components/avatar';
2
+ import * as i0 from '@angular/core';
3
+ import { inject, input, Component, Injectable, signal, computed, effect, untracked, ChangeDetectionStrategy } from '@angular/core';
4
+ import { CommonModule } from '@angular/common';
5
+ import * as i1 from '@angular/forms';
6
+ import { FormControl, Validators, ReactiveFormsModule, FormGroup } from '@angular/forms';
7
+ import * as i2 from '@jsverse/transloco';
8
+ import { TranslocoModule, TranslocoService } from '@jsverse/transloco';
9
+ import { Card } from '@masterteam/components/card';
10
+ import { Button } from '@masterteam/components/button';
11
+ import { SelectField } from '@masterteam/components/select-field';
12
+ import { TextField } from '@masterteam/components/text-field';
13
+ import { TextareaField } from '@masterteam/components/textarea-field';
14
+ import { EditorField } from '@masterteam/components/editor-field';
15
+ import { ToggleField } from '@masterteam/components/toggle-field';
16
+ import { Tabs } from '@masterteam/components/tabs';
17
+ import { Icon } from '@masterteam/icons';
18
+ import { ConfirmationService } from '@masterteam/components/confirmation';
19
+ import { ModalService } from '@masterteam/components/modal';
20
+ import { Skeleton } from 'primeng/skeleton';
21
+ import { ModalRef } from '@masterteam/components/dialog';
22
+ import { HttpClient } from '@angular/common/http';
23
+ import { Action, Selector, State, Store, select } from '@ngxs/store';
24
+ import { CrudStateBase, handleApiRequest, BaseFacade } from '@masterteam/components';
25
+
26
+ class AddTemplateDialog {
27
+ modalService = inject(ModalService);
28
+ ref = inject(ModalRef);
29
+ data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : []));
30
+ templateNameControl = new FormControl('', [Validators.required]);
31
+ onSave() {
32
+ if (this.templateNameControl.valid) {
33
+ this.ref.close({ title: this.templateNameControl.value?.trim() });
34
+ }
35
+ }
36
+ onCancel() {
37
+ this.ref.close();
38
+ }
39
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: AddTemplateDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
40
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.3", type: AddTemplateDialog, isStandalone: true, selector: "mt-add-template-dialog", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div [class]=\"modalService.contentClass\" class=\"p-4\">\n <mt-text-field\n [formControl]=\"templateNameControl\"\n [label]=\"'notification.templateName' | transloco\"\n [placeholder]=\"'notification.enterTemplateName' | transloco\"\n [required]=\"true\"\n />\n</div>\n\n<div [class]=\"modalService.footerClass\">\n <mt-button\n [label]=\"'notification.cancel' | transloco\"\n severity=\"secondary\"\n (onClick)=\"onCancel()\"\n />\n <mt-button\n [label]=\"'notification.save' | transloco\"\n [disabled]=\"!templateNameControl.valid\"\n (onClick)=\"onSave()\"\n />\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "icon", "iconPosition"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "pipe", type: i2.TranslocoPipe, name: "transloco" }] });
41
+ }
42
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: AddTemplateDialog, decorators: [{
43
+ type: Component,
44
+ args: [{ selector: 'mt-add-template-dialog', standalone: true, imports: [
45
+ CommonModule,
46
+ ReactiveFormsModule,
47
+ TranslocoModule,
48
+ TextField,
49
+ Button,
50
+ ], template: "<div [class]=\"modalService.contentClass\" class=\"p-4\">\n <mt-text-field\n [formControl]=\"templateNameControl\"\n [label]=\"'notification.templateName' | transloco\"\n [placeholder]=\"'notification.enterTemplateName' | transloco\"\n [required]=\"true\"\n />\n</div>\n\n<div [class]=\"modalService.footerClass\">\n <mt-button\n [label]=\"'notification.cancel' | transloco\"\n severity=\"secondary\"\n (onClick)=\"onCancel()\"\n />\n <mt-button\n [label]=\"'notification.save' | transloco\"\n [disabled]=\"!templateNameControl.valid\"\n (onClick)=\"onSave()\"\n />\n</div>\n" }]
51
+ }], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }] } });
52
+
53
+ // ============================================================================
54
+ // Action Keys Enum
55
+ // ============================================================================
56
+ var NotificationActionKey;
57
+ (function (NotificationActionKey) {
58
+ NotificationActionKey["GetEvents"] = "getEvents";
59
+ NotificationActionKey["GetReceivers"] = "getReceivers";
60
+ NotificationActionKey["GetProperties"] = "getProperties";
61
+ NotificationActionKey["AddReceivers"] = "addReceivers";
62
+ NotificationActionKey["DeleteReceiver"] = "deleteReceiver";
63
+ NotificationActionKey["AddTemplate"] = "addTemplate";
64
+ NotificationActionKey["UpdateTemplate"] = "updateTemplate";
65
+ NotificationActionKey["DeleteTemplate"] = "deleteTemplate";
66
+ })(NotificationActionKey || (NotificationActionKey = {}));
67
+ const CHANNEL_CONFIGS = [
68
+ {
69
+ key: 'email',
70
+ labelKey: 'notification.channel.email',
71
+ supportsBilingual: true,
72
+ bilingualOptional: true,
73
+ useRichEditor: true,
74
+ templateKey: 'emailTemplate',
75
+ translatableKey: 'emailContentTranslatable',
76
+ },
77
+ {
78
+ key: 'sms',
79
+ labelKey: 'notification.channel.sms',
80
+ supportsBilingual: false,
81
+ bilingualOptional: false,
82
+ useRichEditor: false,
83
+ templateKey: 'SmsTemplate',
84
+ },
85
+ {
86
+ key: 'app',
87
+ labelKey: 'notification.channel.app',
88
+ supportsBilingual: true,
89
+ bilingualOptional: false,
90
+ useRichEditor: true,
91
+ translatableKey: 'appTemplate',
92
+ },
93
+ ];
94
+ /** Helper to get channel config by key */
95
+ function getChannelConfig(key) {
96
+ return CHANNEL_CONFIGS.find((c) => c.key === key) ?? CHANNEL_CONFIGS[0];
97
+ }
98
+ /** Build form control name for a channel field */
99
+ function getFormControlName(ch, type) {
100
+ if (type === 'single')
101
+ return ch.templateKey ?? null;
102
+ if (!ch.translatableKey)
103
+ return null;
104
+ return type === 'en'
105
+ ? `${ch.translatableKey}.en`
106
+ : `${ch.translatableKey}.ar`;
107
+ }
108
+
109
+ // ============================================================================
110
+ // Module Configuration
111
+ // ============================================================================
112
+ class SetModuleInfo {
113
+ moduleType;
114
+ moduleId;
115
+ parentModuleType;
116
+ parentModuleId;
117
+ parentPath;
118
+ static type = '[Notification] Set Module Info';
119
+ constructor(moduleType, moduleId, parentModuleType, parentModuleId, parentPath) {
120
+ this.moduleType = moduleType;
121
+ this.moduleId = moduleId;
122
+ this.parentModuleType = parentModuleType;
123
+ this.parentModuleId = parentModuleId;
124
+ this.parentPath = parentPath;
125
+ }
126
+ }
127
+ // ============================================================================
128
+ // Data Loading Actions
129
+ // ============================================================================
130
+ class GetEvents {
131
+ static type = '[Notification] Get Events';
132
+ }
133
+ class GetReceivers {
134
+ static type = '[Notification] Get Receivers';
135
+ }
136
+ class GetProperties {
137
+ isMultiLang;
138
+ static type = '[Notification] Get Properties';
139
+ constructor(isMultiLang) {
140
+ this.isMultiLang = isMultiLang;
141
+ }
142
+ }
143
+ // ============================================================================
144
+ // Action Mode (Workflow)
145
+ // ============================================================================
146
+ class SetActionMode {
147
+ actionId;
148
+ static type = '[Notification] Set Action Mode';
149
+ constructor(actionId) {
150
+ this.actionId = actionId;
151
+ }
152
+ }
153
+ class GetActionReceivers {
154
+ actionId;
155
+ static type = '[Notification] Get Action Receivers';
156
+ constructor(actionId) {
157
+ this.actionId = actionId;
158
+ }
159
+ }
160
+ class GetActionProperties {
161
+ actionId;
162
+ isMultiLang;
163
+ static type = '[Notification] Get Action Properties';
164
+ constructor(actionId, isMultiLang) {
165
+ this.actionId = actionId;
166
+ this.isMultiLang = isMultiLang;
167
+ }
168
+ }
169
+ // ============================================================================
170
+ // Selection Actions
171
+ // ============================================================================
172
+ class SelectEvent {
173
+ eventName;
174
+ static type = '[Notification] Select Event';
175
+ constructor(eventName) {
176
+ this.eventName = eventName;
177
+ }
178
+ }
179
+ class SelectTemplate {
180
+ templateId;
181
+ static type = '[Notification] Select Template';
182
+ constructor(templateId) {
183
+ this.templateId = templateId;
184
+ }
185
+ }
186
+ // ============================================================================
187
+ // Receiver CRUD
188
+ // ============================================================================
189
+ class AddReceivers {
190
+ templateId;
191
+ payload;
192
+ static type = '[Notification] Add Receivers';
193
+ constructor(templateId, payload) {
194
+ this.templateId = templateId;
195
+ this.payload = payload;
196
+ }
197
+ }
198
+ class DeleteReceiver {
199
+ receiverId;
200
+ static type = '[Notification] Delete Receiver';
201
+ constructor(receiverId) {
202
+ this.receiverId = receiverId;
203
+ }
204
+ }
205
+ // ============================================================================
206
+ // Template CRUD
207
+ // ============================================================================
208
+ class AddTemplate {
209
+ eventId;
210
+ payload;
211
+ static type = '[Notification] Add Template';
212
+ constructor(eventId, payload) {
213
+ this.eventId = eventId;
214
+ this.payload = payload;
215
+ }
216
+ }
217
+ class UpdateTemplate {
218
+ templateId;
219
+ payload;
220
+ static type = '[Notification] Update Template';
221
+ constructor(templateId, payload) {
222
+ this.templateId = templateId;
223
+ this.payload = payload;
224
+ }
225
+ }
226
+ class DeleteTemplate {
227
+ templateId;
228
+ static type = '[Notification] Delete Template';
229
+ constructor(templateId) {
230
+ this.templateId = templateId;
231
+ }
232
+ }
233
+ // ============================================================================
234
+ // Reset State
235
+ // ============================================================================
236
+ class ResetNotificationState {
237
+ static type = '[Notification] Reset State';
238
+ }
239
+
240
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
241
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
242
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
243
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
244
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
245
+ };
246
+ // Default State
247
+ const DEFAULT_STATE = {
248
+ // Module configuration
249
+ moduleType: null,
250
+ moduleId: null,
251
+ parentModuleType: null,
252
+ parentModuleId: null,
253
+ parentPath: '',
254
+ // Action mode
255
+ isActionMode: false,
256
+ selectedActionId: null,
257
+ // Data collections
258
+ events: [],
259
+ templates: [], // Flattened templates
260
+ actions: [],
261
+ receivers: [],
262
+ propertyGroups: [],
263
+ // Selection state
264
+ selectedEventName: null,
265
+ selectedTemplateId: null,
266
+ // Loading state (from LoadingStateShape)
267
+ loadingActive: [],
268
+ errors: {},
269
+ };
270
+ // State Definition
271
+ let NotificationState = class NotificationState extends CrudStateBase {
272
+ http = inject(HttpClient);
273
+ baseUrl = 'notifications';
274
+ // Selectors - Individual selectors for fine-grained reactivity
275
+ static getState(state) {
276
+ return state;
277
+ }
278
+ static getEvents(state) {
279
+ return state.events;
280
+ }
281
+ static getTemplates(state) {
282
+ return state.templates;
283
+ }
284
+ static getReceivers(state) {
285
+ return state.receivers;
286
+ }
287
+ static getPropertyGroups(state) {
288
+ return state.propertyGroups;
289
+ }
290
+ static getSelectedEventName(state) {
291
+ return state.selectedEventName;
292
+ }
293
+ static getSelectedTemplateId(state) {
294
+ return state.selectedTemplateId;
295
+ }
296
+ static getLoadingActive(state) {
297
+ return state.loadingActive;
298
+ }
299
+ static getErrors(state) {
300
+ return state.errors;
301
+ }
302
+ // Derived selectors
303
+ static getSelectedEvent(events, selectedEventName) {
304
+ return events.find((e) => e.eventName === selectedEventName) ?? null;
305
+ }
306
+ static getEventTemplates(templates, selectedEvent) {
307
+ if (!selectedEvent)
308
+ return [];
309
+ return templates.filter((t) => t.eventId === selectedEvent.id);
310
+ }
311
+ static getSelectedTemplate(templates, selectedTemplateId) {
312
+ return templates.find((t) => t.id === selectedTemplateId) ?? null;
313
+ }
314
+ static getAvailableReceivers(receivers, selectedTemplate) {
315
+ if (!selectedTemplate)
316
+ return receivers;
317
+ const templateReceivers = selectedTemplate.receivers ?? [];
318
+ const assignedIds = new Set(templateReceivers.map((r) => r.identifier));
319
+ return receivers.filter((r) => !assignedIds.has(String(r.id)));
320
+ }
321
+ // Helper Methods
322
+ /** API returns events with nested templates - we flatten for state */
323
+ flattenEventsResponse(eventsWithTemplates) {
324
+ const events = [];
325
+ const templates = [];
326
+ for (const event of eventsWithTemplates) {
327
+ const { templates: eventTemplates, ...eventData } = event;
328
+ events.push(eventData);
329
+ templates.push(...eventTemplates.map((t) => ({ ...t, eventId: event.id })));
330
+ }
331
+ return { events, templates };
332
+ }
333
+ flattenReceivers(groups) {
334
+ const result = [];
335
+ for (const group of groups) {
336
+ for (const item of group.items) {
337
+ if ('subGroupName' in item) {
338
+ // Handle subgroups (role-based receivers)
339
+ const subGroup = item;
340
+ result.push(...subGroup.items.map((r) => ({
341
+ ...r,
342
+ title: `${subGroup.subGroupName} - ${r.title}`,
343
+ })));
344
+ }
345
+ else {
346
+ result.push(item);
347
+ }
348
+ }
349
+ }
350
+ return result;
351
+ }
352
+ // Module Configuration
353
+ setModuleInfo(ctx, action) {
354
+ let parentPath = '';
355
+ if (action.parentModuleType && action.parentModuleId) {
356
+ parentPath = `/${action.parentModuleType}/${action.parentModuleId}`;
357
+ }
358
+ else if (action.parentPath) {
359
+ parentPath = action.parentPath;
360
+ }
361
+ ctx.patchState({
362
+ moduleType: action.moduleType,
363
+ moduleId: action.moduleId,
364
+ parentModuleType: action.parentModuleType ?? null,
365
+ parentModuleId: action.parentModuleId ?? null,
366
+ parentPath,
367
+ isActionMode: action.moduleType === 'workflow',
368
+ });
369
+ }
370
+ resetState(ctx) {
371
+ ctx.setState(DEFAULT_STATE);
372
+ }
373
+ // Data Loading Actions (Normal Mode)
374
+ getEvents(ctx) {
375
+ const { moduleType, moduleId } = ctx.getState();
376
+ const req$ = this.http.get(`${this.baseUrl}/${moduleType}/${moduleId}/events`);
377
+ return this.load(ctx, {
378
+ key: NotificationActionKey.GetEvents,
379
+ request$: req$,
380
+ updateState: (_state, data) => {
381
+ const { events, templates } = this.flattenEventsResponse(data ?? []);
382
+ const firstTemplate = templates[0];
383
+ const firstEvent = events.find((e) => e.id === firstTemplate?.eventId);
384
+ return {
385
+ events,
386
+ templates,
387
+ selectedEventName: firstEvent?.eventName ?? null,
388
+ selectedTemplateId: firstTemplate?.id ?? null,
389
+ };
390
+ },
391
+ });
392
+ }
393
+ getReceivers(ctx) {
394
+ const { moduleType, moduleId } = ctx.getState();
395
+ const req$ = this.http.get(`${this.baseUrl}/${moduleType}/${moduleId}/receivers`);
396
+ return handleApiRequest({
397
+ ctx,
398
+ key: NotificationActionKey.GetReceivers,
399
+ request$: req$,
400
+ onSuccess: (response) => ({
401
+ receivers: this.flattenReceivers(response.data ?? []),
402
+ }),
403
+ });
404
+ }
405
+ getProperties(ctx, action) {
406
+ const { moduleType, moduleId } = ctx.getState();
407
+ const url = action.isMultiLang
408
+ ? `${this.baseUrl}/${moduleType}/${moduleId}/properties?IsMultiLanguageEnabled=true`
409
+ : `${this.baseUrl}/${moduleType}/${moduleId}/properties`;
410
+ const req$ = this.http.get(url);
411
+ return handleApiRequest({
412
+ ctx,
413
+ key: NotificationActionKey.GetProperties,
414
+ request$: req$,
415
+ onSuccess: (response) => ({
416
+ propertyGroups: response.data ?? [],
417
+ }),
418
+ });
419
+ }
420
+ // Action Mode (Workflow)
421
+ setActionMode(ctx, action) {
422
+ ctx.patchState({
423
+ selectedActionId: action.actionId,
424
+ });
425
+ }
426
+ getActionReceivers(ctx, action) {
427
+ const req$ = this.http.get(`${this.baseUrl}/action/${action.actionId}/receivers`);
428
+ return handleApiRequest({
429
+ ctx,
430
+ key: NotificationActionKey.GetReceivers,
431
+ request$: req$,
432
+ onSuccess: (response) => ({
433
+ receivers: this.flattenReceivers(response.data ?? []),
434
+ }),
435
+ });
436
+ }
437
+ getActionProperties(ctx, action) {
438
+ const url = action.isMultiLang
439
+ ? `${this.baseUrl}/action/${action.actionId}/properties?IsMultiLanguageEnabled=true`
440
+ : `${this.baseUrl}/action/${action.actionId}/properties`;
441
+ const req$ = this.http.get(url);
442
+ return handleApiRequest({
443
+ ctx,
444
+ key: NotificationActionKey.GetProperties,
445
+ request$: req$,
446
+ onSuccess: (response) => ({
447
+ propertyGroups: response.data ?? [],
448
+ }),
449
+ });
450
+ }
451
+ // Selection Actions
452
+ selectEvent(ctx, action) {
453
+ const state = ctx.getState();
454
+ const event = state.events.find((e) => e.eventName === action.eventName);
455
+ const eventTemplates = state.templates.filter((t) => t.eventId === event?.id);
456
+ ctx.patchState({
457
+ selectedEventName: action.eventName,
458
+ selectedTemplateId: eventTemplates[0]?.id ?? null,
459
+ });
460
+ }
461
+ selectTemplate(ctx, action) {
462
+ ctx.patchState({ selectedTemplateId: action.templateId });
463
+ }
464
+ // Receiver CRUD
465
+ addReceivers(ctx, action) {
466
+ const req$ = this.http.post(`${this.baseUrl}/templates/${action.templateId}`, action.payload);
467
+ return handleApiRequest({
468
+ ctx,
469
+ key: NotificationActionKey.AddReceivers,
470
+ request$: req$,
471
+ onSuccess: (response, state) => {
472
+ const newReceivers = response.data ?? [];
473
+ const templates = this.adapter.updateOne(state.templates, action.templateId, {
474
+ receivers: [
475
+ ...(state.templates.find((t) => t.id === action.templateId)
476
+ ?.receivers ?? []),
477
+ ...newReceivers,
478
+ ],
479
+ }, 'id');
480
+ return { templates };
481
+ },
482
+ });
483
+ }
484
+ deleteReceiver(ctx, action) {
485
+ const state = ctx.getState();
486
+ const template = this.getSelectedTemplate(state);
487
+ if (!template) {
488
+ return;
489
+ }
490
+ const req$ = this.http.delete(`${this.baseUrl}/receivers/${action.receiverId}`);
491
+ return handleApiRequest({
492
+ ctx,
493
+ key: NotificationActionKey.DeleteReceiver,
494
+ request$: req$,
495
+ onSuccess: (_, currentState) => {
496
+ const templates = this.adapter.updateOne(currentState.templates, template.id, {
497
+ receivers: template.receivers.filter((r) => r.id !== action.receiverId),
498
+ }, 'id');
499
+ return { templates };
500
+ },
501
+ });
502
+ }
503
+ // Template CRUD
504
+ addTemplate(ctx, action) {
505
+ const req$ = this.http.post(`${this.baseUrl}/events/${action.eventId}`, action.payload);
506
+ return handleApiRequest({
507
+ ctx,
508
+ key: NotificationActionKey.AddTemplate,
509
+ request$: req$,
510
+ onSuccess: (response, state) => {
511
+ const newTemplate = response.data;
512
+ if (!newTemplate)
513
+ return {};
514
+ return {
515
+ templates: this.adapter.addOne(state.templates, {
516
+ ...newTemplate,
517
+ eventId: action.eventId,
518
+ }),
519
+ selectedTemplateId: newTemplate.id,
520
+ };
521
+ },
522
+ });
523
+ }
524
+ updateTemplate(ctx, action) {
525
+ const req$ = this.http.put(`${this.baseUrl}/templates/${action.templateId}`, action.payload);
526
+ return handleApiRequest({
527
+ ctx,
528
+ key: NotificationActionKey.UpdateTemplate,
529
+ request$: req$,
530
+ onSuccess: (response, state) => {
531
+ const updatedTemplate = response.data;
532
+ if (!updatedTemplate)
533
+ return {};
534
+ // Preserve existing fields that API may not return or shouldn't change
535
+ const existingTemplate = state.templates.find((t) => t.id === action.templateId);
536
+ if (!existingTemplate)
537
+ return {};
538
+ // Only update title and content fields, preserve channel/eventId/receivers
539
+ const templates = this.adapter.upsertOne(state.templates, {
540
+ ...existingTemplate,
541
+ title: updatedTemplate.title,
542
+ emailTemplate: updatedTemplate.emailTemplate,
543
+ emailContentTranslatable: updatedTemplate.emailContentTranslatable,
544
+ smsTemplate: updatedTemplate.smsTemplate,
545
+ appTemplate: updatedTemplate.appTemplate,
546
+ }, 'id');
547
+ return { templates };
548
+ },
549
+ });
550
+ }
551
+ deleteTemplate(ctx, action) {
552
+ const req$ = this.http.delete(`${this.baseUrl}/templates/${action.templateId}`);
553
+ return handleApiRequest({
554
+ ctx,
555
+ key: NotificationActionKey.DeleteTemplate,
556
+ request$: req$,
557
+ onSuccess: (_, state) => {
558
+ const templates = this.adapter.removeOne(state.templates, action.templateId, 'id');
559
+ // Select first template of current event after deletion
560
+ const currentEvent = state.events.find((e) => e.eventName === state.selectedEventName);
561
+ const eventTemplates = templates.filter((t) => t.eventId === currentEvent?.id);
562
+ const systemTemplate = eventTemplates.find((t) => t.isSystem);
563
+ return {
564
+ templates,
565
+ selectedTemplateId: systemTemplate?.id ?? eventTemplates[0]?.id ?? null,
566
+ };
567
+ },
568
+ });
569
+ }
570
+ // Private Helpers
571
+ getSelectedTemplate(state) {
572
+ return (state.templates.find((t) => t.id === state.selectedTemplateId) ?? null);
573
+ }
574
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NotificationState, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
575
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NotificationState });
576
+ };
577
+ __decorate([
578
+ Action(SetModuleInfo)
579
+ ], NotificationState.prototype, "setModuleInfo", null);
580
+ __decorate([
581
+ Action(ResetNotificationState)
582
+ ], NotificationState.prototype, "resetState", null);
583
+ __decorate([
584
+ Action(GetEvents)
585
+ ], NotificationState.prototype, "getEvents", null);
586
+ __decorate([
587
+ Action(GetReceivers)
588
+ ], NotificationState.prototype, "getReceivers", null);
589
+ __decorate([
590
+ Action(GetProperties)
591
+ ], NotificationState.prototype, "getProperties", null);
592
+ __decorate([
593
+ Action(SetActionMode)
594
+ ], NotificationState.prototype, "setActionMode", null);
595
+ __decorate([
596
+ Action(GetActionReceivers)
597
+ ], NotificationState.prototype, "getActionReceivers", null);
598
+ __decorate([
599
+ Action(GetActionProperties)
600
+ ], NotificationState.prototype, "getActionProperties", null);
601
+ __decorate([
602
+ Action(SelectEvent)
603
+ ], NotificationState.prototype, "selectEvent", null);
604
+ __decorate([
605
+ Action(SelectTemplate)
606
+ ], NotificationState.prototype, "selectTemplate", null);
607
+ __decorate([
608
+ Action(AddReceivers)
609
+ ], NotificationState.prototype, "addReceivers", null);
610
+ __decorate([
611
+ Action(DeleteReceiver)
612
+ ], NotificationState.prototype, "deleteReceiver", null);
613
+ __decorate([
614
+ Action(AddTemplate)
615
+ ], NotificationState.prototype, "addTemplate", null);
616
+ __decorate([
617
+ Action(UpdateTemplate)
618
+ ], NotificationState.prototype, "updateTemplate", null);
619
+ __decorate([
620
+ Action(DeleteTemplate)
621
+ ], NotificationState.prototype, "deleteTemplate", null);
622
+ __decorate([
623
+ Selector()
624
+ ], NotificationState, "getState", null);
625
+ __decorate([
626
+ Selector()
627
+ ], NotificationState, "getEvents", null);
628
+ __decorate([
629
+ Selector()
630
+ ], NotificationState, "getTemplates", null);
631
+ __decorate([
632
+ Selector()
633
+ ], NotificationState, "getReceivers", null);
634
+ __decorate([
635
+ Selector()
636
+ ], NotificationState, "getPropertyGroups", null);
637
+ __decorate([
638
+ Selector()
639
+ ], NotificationState, "getSelectedEventName", null);
640
+ __decorate([
641
+ Selector()
642
+ ], NotificationState, "getSelectedTemplateId", null);
643
+ __decorate([
644
+ Selector()
645
+ ], NotificationState, "getLoadingActive", null);
646
+ __decorate([
647
+ Selector()
648
+ ], NotificationState, "getErrors", null);
649
+ __decorate([
650
+ Selector([
651
+ NotificationState.getEvents,
652
+ NotificationState.getSelectedEventName,
653
+ ])
654
+ ], NotificationState, "getSelectedEvent", null);
655
+ __decorate([
656
+ Selector([
657
+ NotificationState.getTemplates,
658
+ NotificationState.getSelectedEvent,
659
+ ])
660
+ ], NotificationState, "getEventTemplates", null);
661
+ __decorate([
662
+ Selector([
663
+ NotificationState.getTemplates,
664
+ NotificationState.getSelectedTemplateId,
665
+ ])
666
+ ], NotificationState, "getSelectedTemplate", null);
667
+ __decorate([
668
+ Selector([
669
+ NotificationState.getReceivers,
670
+ NotificationState.getSelectedTemplate,
671
+ ])
672
+ ], NotificationState, "getAvailableReceivers", null);
673
+ NotificationState = __decorate([
674
+ State({
675
+ name: 'notification',
676
+ defaults: DEFAULT_STATE,
677
+ })
678
+ ], NotificationState);
679
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NotificationState, decorators: [{
680
+ type: Injectable
681
+ }], propDecorators: { setModuleInfo: [], resetState: [], getEvents: [], getReceivers: [], getProperties: [], setActionMode: [], getActionReceivers: [], getActionProperties: [], selectEvent: [], selectTemplate: [], addReceivers: [], deleteReceiver: [], addTemplate: [], updateTemplate: [], deleteTemplate: [] } });
682
+
683
+ class NotificationFacade extends BaseFacade {
684
+ store = inject(Store);
685
+ // Selectors - Direct signals from state (fine-grained reactivity)
686
+ stateSignal = select(NotificationState.getState);
687
+ // Data selectors
688
+ events = select(NotificationState.getEvents);
689
+ templates = select(NotificationState.getEventTemplates);
690
+ receivers = select(NotificationState.getReceivers);
691
+ propertyGroups = select(NotificationState.getPropertyGroups);
692
+ // Selection selectors
693
+ selectedEventName = select(NotificationState.getSelectedEventName);
694
+ selectedTemplateId = select(NotificationState.getSelectedTemplateId);
695
+ selectedEvent = select(NotificationState.getSelectedEvent);
696
+ selectedTemplate = select(NotificationState.getSelectedTemplate);
697
+ availableReceivers = select(NotificationState.getAvailableReceivers);
698
+ // Required by BaseFacade
699
+ state() {
700
+ return this.stateSignal;
701
+ }
702
+ // Query Pattern (data + loading + error combined)
703
+ eventsQuery = this.query(NotificationActionKey.GetEvents, (state) => state.events);
704
+ receiversQuery = this.query(NotificationActionKey.GetReceivers, (state) => state.receivers);
705
+ propertyGroupsQuery = this.query(NotificationActionKey.GetProperties, (state) => state.propertyGroups);
706
+ // Loading/Error for CRUD operations (not covered by queries)
707
+ isAddingReceivers = this.loading(NotificationActionKey.AddReceivers);
708
+ isDeletingReceiver = this.loading(NotificationActionKey.DeleteReceiver);
709
+ isAddingTemplate = this.loading(NotificationActionKey.AddTemplate);
710
+ isUpdatingTemplate = this.loading(NotificationActionKey.UpdateTemplate);
711
+ isDeletingTemplate = this.loading(NotificationActionKey.DeleteTemplate);
712
+ // Action Dispatchers - Configuration
713
+ setModuleInfo(moduleType, moduleId, parentModuleType, parentModuleId, parentPath) {
714
+ return this.store.dispatch(new SetModuleInfo(moduleType, moduleId, parentModuleType, parentModuleId, parentPath));
715
+ }
716
+ resetState() {
717
+ return this.store.dispatch(new ResetNotificationState());
718
+ }
719
+ // Action Dispatchers - Data Loading
720
+ loadEvents() {
721
+ return this.store.dispatch(new GetEvents());
722
+ }
723
+ loadReceivers() {
724
+ return this.store.dispatch(new GetReceivers());
725
+ }
726
+ loadProperties(isMultiLang) {
727
+ return this.store.dispatch(new GetProperties(isMultiLang));
728
+ }
729
+ // Action Dispatchers - Action Mode (Workflow)
730
+ setActionMode(actionId) {
731
+ return this.store.dispatch(new SetActionMode(actionId));
732
+ }
733
+ loadActionReceivers(actionId) {
734
+ return this.store.dispatch(new GetActionReceivers(actionId));
735
+ }
736
+ loadActionProperties(actionId, isMultiLang) {
737
+ return this.store.dispatch(new GetActionProperties(actionId, isMultiLang));
738
+ }
739
+ // Action Dispatchers - Selection
740
+ selectEvent(eventName) {
741
+ return this.store.dispatch(new SelectEvent(eventName));
742
+ }
743
+ selectTemplate(templateId) {
744
+ return this.store.dispatch(new SelectTemplate(templateId));
745
+ }
746
+ // Action Dispatchers - Receiver CRUD
747
+ addReceivers(templateId, payload) {
748
+ return this.store.dispatch(new AddReceivers(templateId, payload));
749
+ }
750
+ deleteReceiver(receiverId) {
751
+ return this.store.dispatch(new DeleteReceiver(receiverId));
752
+ }
753
+ // Action Dispatchers - Template CRUD
754
+ addTemplate(eventId, payload) {
755
+ return this.store.dispatch(new AddTemplate(eventId, payload));
756
+ }
757
+ updateTemplate(templateId, payload) {
758
+ return this.store.dispatch(new UpdateTemplate(templateId, payload));
759
+ }
760
+ deleteTemplate(templateId) {
761
+ return this.store.dispatch(new DeleteTemplate(templateId));
762
+ }
763
+ /**
764
+ * Load all data for normal mode (non-workflow)
765
+ */
766
+ loadAllData(isMultiLang = true) {
767
+ this.loadEvents();
768
+ this.loadReceivers();
769
+ this.loadProperties(isMultiLang);
770
+ }
771
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NotificationFacade, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
772
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NotificationFacade, providedIn: 'root' });
773
+ }
774
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NotificationFacade, decorators: [{
775
+ type: Injectable,
776
+ args: [{ providedIn: 'root' }]
777
+ }] });
778
+
779
+ class NotificationTemplate {
780
+ facade = inject(NotificationFacade);
781
+ transloco = inject(TranslocoService);
782
+ confirmationService = inject(ConfirmationService);
783
+ modalService = inject(ModalService);
784
+ // ============================================================================
785
+ // Queries from Facade
786
+ // ============================================================================
787
+ eventsQuery = this.facade.eventsQuery;
788
+ propertyGroupsQuery = this.facade.propertyGroupsQuery;
789
+ receiversQuery = this.facade.receiversQuery;
790
+ // ============================================================================
791
+ // Derived State from Facade
792
+ // ============================================================================
793
+ selectedEventName = this.facade.selectedEventName;
794
+ selectedTemplateId = this.facade.selectedTemplateId;
795
+ selectedEvent = this.facade.selectedEvent;
796
+ selectedTemplate = this.facade.selectedTemplate;
797
+ templates = this.facade.templates;
798
+ availableReceivers = this.facade.availableReceivers;
799
+ // Loading States
800
+ isUpdatingTemplate = this.facade.isUpdatingTemplate;
801
+ isDeletingTemplate = this.facade.isDeletingTemplate;
802
+ isAddingTemplate = this.facade.isAddingTemplate;
803
+ // ============================================================================
804
+ // Local State
805
+ // ============================================================================
806
+ deletingReceiverIds = signal([], ...(ngDevMode ? [{ debugName: "deletingReceiverIds" }] : []));
807
+ // ============================================================================
808
+ // Form - Dynamic controls based on CHANNEL_CONFIGS
809
+ // ============================================================================
810
+ form = this.buildForm();
811
+ propertyPickerControl = new FormControl(null);
812
+ buildForm() {
813
+ const fc = (v = '') => new FormControl(v, { nonNullable: true });
814
+ const controls = {
815
+ event: new FormControl(null),
816
+ title: fc(),
817
+ channel: new FormControl('email', {
818
+ nonNullable: true,
819
+ }),
820
+ };
821
+ for (const ch of CHANNEL_CONFIGS) {
822
+ if (ch.templateKey)
823
+ controls[ch.templateKey] = fc();
824
+ if (ch.translatableKey) {
825
+ // Nested FormGroup to match payload structure { en: '', ar: '' }
826
+ controls[ch.translatableKey] = new FormGroup({
827
+ en: fc(),
828
+ ar: fc(),
829
+ });
830
+ }
831
+ if (ch.bilingualOptional) {
832
+ controls[`${ch.key}Bilingual`] = new FormControl(false, {
833
+ nonNullable: true,
834
+ });
835
+ }
836
+ }
837
+ return new FormGroup(controls);
838
+ }
839
+ // ============================================================================
840
+ // Computed
841
+ // ============================================================================
842
+ channelConfigs = CHANNEL_CONFIGS;
843
+ /** Check if channel should show bilingual fields - reads from form */
844
+ isBilingual(ch) {
845
+ if (!ch.supportsBilingual)
846
+ return false;
847
+ if (!ch.bilingualOptional)
848
+ return true; // forced bilingual (app)
849
+ return this.form.controls[`${ch.key}Bilingual`]?.value ?? false;
850
+ }
851
+ channelOptions = computed(() => CHANNEL_CONFIGS.map((c) => ({
852
+ ...c,
853
+ title: this.transloco.translate(c.labelKey),
854
+ })), ...(ngDevMode ? [{ debugName: "channelOptions" }] : []));
855
+ templateTabs = computed(() => this.templates().map((t) => ({
856
+ label: t.title,
857
+ value: t.id,
858
+ isSystem: t.isSystem,
859
+ })), ...(ngDevMode ? [{ debugName: "templateTabs" }] : []));
860
+ isFormInvalid = computed(() => !this.form.controls['title'].value?.trim(), ...(ngDevMode ? [{ debugName: "isFormInvalid" }] : []));
861
+ constructor() {
862
+ effect(() => {
863
+ const eventName = this.selectedEventName();
864
+ if (eventName && this.form.controls['event'].value !== eventName) {
865
+ this.form.controls['event'].setValue(eventName, { emitEvent: false });
866
+ }
867
+ });
868
+ effect(() => {
869
+ this.selectedTemplateId(); // depend on selected template id
870
+ const template = untracked(this.selectedTemplate);
871
+ if (template)
872
+ this.patchForm(template);
873
+ });
874
+ }
875
+ // ============================================================================
876
+ // Event Handlers
877
+ // ============================================================================
878
+ onEventChange(eventName) {
879
+ if (eventName)
880
+ this.facade.selectEvent(eventName);
881
+ }
882
+ onTemplateTabChange(templateId) {
883
+ this.facade.selectTemplate(templateId);
884
+ }
885
+ // Channel is fixed per template - set on creation only
886
+ onAddProperty(propertyKey) {
887
+ if (!propertyKey)
888
+ return;
889
+ const tag = `{{${propertyKey}}}`;
890
+ const ch = getChannelConfig(this.form.controls['channel'].value);
891
+ if (this.isBilingual(ch) && ch.translatableKey) {
892
+ const group = this.form.get(ch.translatableKey);
893
+ if (group) {
894
+ const enCtrl = group.get('en');
895
+ const arCtrl = group.get('ar');
896
+ enCtrl?.setValue(`${enCtrl.value ?? ''} ${tag}`);
897
+ arCtrl?.setValue(`${arCtrl.value ?? ''} ${tag}`);
898
+ }
899
+ }
900
+ else if (ch.templateKey) {
901
+ this.appendToControl(ch.templateKey, tag);
902
+ }
903
+ setTimeout(() => {
904
+ this.propertyPickerControl.reset();
905
+ }, 0);
906
+ }
907
+ onAddReceiver(receiverId) {
908
+ const template = this.selectedTemplate();
909
+ if (!receiverId || !template)
910
+ return;
911
+ const selectedReceiver = this.availableReceivers().find((r) => r.id === receiverId);
912
+ if (!selectedReceiver)
913
+ return;
914
+ this.facade.addReceivers(template.id, { receivers: [selectedReceiver] });
915
+ }
916
+ onDeleteReceiver(receiver) {
917
+ this.confirmationService.confirmDelete({
918
+ type: 'dialog',
919
+ accept: () => {
920
+ this.deletingReceiverIds.update((ids) => [...ids, receiver.id]);
921
+ this.facade.deleteReceiver(receiver.id).subscribe({
922
+ complete: () => {
923
+ this.deletingReceiverIds.update((ids) => ids.filter((id) => id !== receiver.id));
924
+ },
925
+ error: () => {
926
+ this.deletingReceiverIds.update((ids) => ids.filter((id) => id !== receiver.id));
927
+ },
928
+ });
929
+ },
930
+ });
931
+ }
932
+ isReceiverDeleting(receiverId) {
933
+ return this.deletingReceiverIds().includes(receiverId);
934
+ }
935
+ onSaveTemplate() {
936
+ const template = this.selectedTemplate();
937
+ if (!template || this.isFormInvalid()) {
938
+ this.form.markAllAsTouched();
939
+ return;
940
+ }
941
+ // Build payload dynamically from all channel controls
942
+ const payload = this.buildUpdatePayload(template);
943
+ this.facade.updateTemplate(template.id, payload);
944
+ }
945
+ onDeleteTemplate() {
946
+ const template = this.selectedTemplate();
947
+ if (!template || template.isSystem)
948
+ return;
949
+ this.confirmationService.confirm({
950
+ type: 'dialog',
951
+ header: this.transloco.translate('notification.deleteConfirmation'),
952
+ message: this.transloco.translate('notification.deleteTemplateMessage'),
953
+ icon: 'general.trash-01',
954
+ acceptButton: { severity: 'danger' },
955
+ accept: () => this.facade.deleteTemplate(template.id),
956
+ });
957
+ }
958
+ onAddTemplateClick() {
959
+ const event = this.selectedEvent();
960
+ if (!event)
961
+ return;
962
+ const ref = this.modalService.openModal(AddTemplateDialog, 'dialog', {
963
+ header: this.transloco.translate('notification.addTemplate'),
964
+ styleClass: '!w-[25rem]',
965
+ });
966
+ ref.onClose.subscribe((result) => {
967
+ if (result?.title) {
968
+ this.facade.addTemplate(event.id, {
969
+ title: result.title,
970
+ channel: this.form.controls['channel'].value,
971
+ });
972
+ }
973
+ });
974
+ }
975
+ // ============================================================================
976
+ // Private Methods
977
+ // ============================================================================
978
+ appendToControl(key, value) {
979
+ const ctrl = this.form.controls[key];
980
+ ctrl?.setValue(`${ctrl.value ?? ''} ${value}`);
981
+ }
982
+ patchForm(template) {
983
+ const channel = template.channel || 'email';
984
+ this.form.patchValue({
985
+ title: template.title,
986
+ channel: channel,
987
+ // Set email bilingual based on whether translatable content exists
988
+ emailBilingual: !!template.emailContentTranslatable?.en,
989
+ });
990
+ // Patch all channel controls dynamically
991
+ const t = template;
992
+ for (const ch of CHANNEL_CONFIGS) {
993
+ if (ch.templateKey) {
994
+ this.form
995
+ .get(ch.templateKey)
996
+ ?.setValue(t[ch.templateKey] ?? '');
997
+ }
998
+ if (ch.translatableKey) {
999
+ const trans = t[ch.translatableKey] ?? {};
1000
+ this.form.get(ch.translatableKey)?.patchValue({
1001
+ en: trans.en ?? '',
1002
+ ar: trans.ar ?? '',
1003
+ });
1004
+ }
1005
+ }
1006
+ }
1007
+ buildUpdatePayload(template) {
1008
+ const v = this.form.getRawValue();
1009
+ return {
1010
+ title: v['title'] || template.title,
1011
+ ...this.extractChannelPayload(v),
1012
+ };
1013
+ }
1014
+ extractChannelPayload(formValue) {
1015
+ const payload = {};
1016
+ for (const ch of CHANNEL_CONFIGS) {
1017
+ if (ch.templateKey && formValue[ch.templateKey] !== undefined) {
1018
+ payload[ch.templateKey] = formValue[ch.templateKey] || '';
1019
+ }
1020
+ if (ch.translatableKey && formValue[ch.translatableKey] !== undefined) {
1021
+ payload[ch.translatableKey] = formValue[ch.translatableKey];
1022
+ }
1023
+ }
1024
+ return payload;
1025
+ }
1026
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NotificationTemplate, deps: [], target: i0.ɵɵFactoryTarget.Component });
1027
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: NotificationTemplate, isStandalone: true, selector: "mt-notification-template", ngImport: i0, template: "<div class=\"flex flex-col gap-4\" [formGroup]=\"form\">\n <!-- Loading State -->\n @if (eventsQuery.isPending()) {\n <div class=\"flex flex-col items-center justify-center py-12\">\n <p-skeleton width=\"100%\" height=\"200px\" />\n </div>\n } @else {\n <!-- Events Selector -->\n <div class=\"w-1/2\">\n <mt-select-field\n formControlName=\"event\"\n [options]=\"eventsQuery.data() ?? []\"\n [optionLabel]=\"'title'\"\n [optionValue]=\"'eventName'\"\n [label]=\"'notification.events' | transloco\"\n [placeholder]=\"'notification.selectEvent' | transloco\"\n [required]=\"true\"\n [filter]=\"false\"\n (onChange)=\"onEventChange($event)\"\n />\n </div>\n\n @if ((eventsQuery.data() ?? []).length === 0) {\n <!-- Empty State -->\n <mt-card>\n <div\n class=\"flex flex-col items-center justify-center py-12 text-slate-500\"\n >\n <mt-icon name=\"alert.alert-circle\" class=\"mb-2 h-12 w-12\" />\n <p>{{ \"notification.noEventsFound\" | transloco }}</p>\n </div>\n </mt-card>\n } @else if (selectedEvent()) {\n <!-- Template Tabs -->\n <div class=\"flex items-center justify-between\">\n <mt-tabs\n [active]=\"selectedTemplateId()\"\n [options]=\"templateTabs()\"\n (activeChange)=\"onTemplateTabChange($event)\"\n />\n <mt-button\n icon=\"general.plus\"\n [label]=\"'notification.newNotification' | transloco\"\n size=\"small\"\n [loading]=\"isAddingTemplate()\"\n (onClick)=\"onAddTemplateClick()\"\n />\n </div>\n\n <!-- Template Content -->\n @if (selectedTemplate(); as template) {\n <div class=\"flex flex-col gap-4\">\n <!-- Name -->\n <div class=\"grid grid-cols-2 gap-4\">\n <mt-text-field\n formControlName=\"title\"\n [label]=\"'notification.name' | transloco\"\n [placeholder]=\"'notification.enterName' | transloco\"\n [required]=\"true\"\n />\n </div>\n\n <!-- Channel & Properties Picker Row -->\n <div class=\"grid grid-cols-2 gap-4\">\n <mt-select-field\n formControlName=\"channel\"\n [options]=\"channelOptions()\"\n [optionLabel]=\"'title'\"\n [optionValue]=\"'key'\"\n [label]=\"'notification.channel.label' | transloco\"\n [placeholder]=\"'notification.selectChannel' | transloco\"\n [required]=\"true\"\n [filter]=\"false\"\n [disabled]=\"true\"\n />\n <mt-select-field\n [options]=\"propertyGroupsQuery.data() ?? []\"\n [optionLabel]=\"'name'\"\n [optionValue]=\"'key'\"\n [group]=\"true\"\n [optionGroupLabel]=\"'groupName'\"\n [optionGroupChildren]=\"'properties'\"\n [label]=\"'notification.propertiesList' | transloco\"\n [placeholder]=\"'notification.selectProperty' | transloco\"\n [hasPlaceholderPrefix]=\"false\"\n [filter]=\"true\"\n [filterBy]=\"'name'\"\n [loading]=\"propertyGroupsQuery.isPending()\"\n [formControl]=\"propertyPickerControl\"\n (onChange)=\"onAddProperty($event)\"\n />\n </div>\n\n <!-- Channel Content Editors -->\n @for (ch of channelConfigs; track ch.key) {\n @if (form.controls[\"channel\"].value === ch.key) {\n <!-- Bilingual Content Fields -->\n @if (isBilingual(ch) && ch.translatableKey) {\n <ng-container [formGroupName]=\"ch.translatableKey\">\n @if (ch.useRichEditor) {\n <mt-editor-field\n formControlName=\"en\"\n [label]=\"'notification.english' | transloco\"\n dir=\"ltr\"\n />\n <mt-editor-field\n formControlName=\"ar\"\n [label]=\"'notification.arabic' | transloco\"\n dir=\"rtl\"\n />\n } @else {\n <mt-textarea-field\n formControlName=\"en\"\n [label]=\"'notification.english' | transloco\"\n [rows]=\"6\"\n dir=\"ltr\"\n />\n <mt-textarea-field\n formControlName=\"ar\"\n [label]=\"'notification.arabic' | transloco\"\n [rows]=\"6\"\n dir=\"rtl\"\n />\n }\n </ng-container>\n } @else if (ch.templateKey) {\n <!-- Non-Bilingual Content Field -->\n @if (ch.useRichEditor) {\n <mt-editor-field\n [formControlName]=\"ch.templateKey\"\n [label]=\"'notification.notificationBody' | transloco\"\n />\n } @else {\n <mt-textarea-field\n [formControlName]=\"ch.templateKey\"\n [label]=\"'notification.notificationBody' | transloco\"\n [rows]=\"6\"\n />\n }\n }\n\n <!-- Bilingual Toggle -->\n @if (ch.bilingualOptional) {\n <mt-toggle-field\n [formControlName]=\"ch.key + 'Bilingual'\"\n [label]=\"'notification.bilingual' | transloco\"\n />\n }\n }\n }\n\n <!-- Receivers -->\n <div class=\"grid grid-cols-2 gap-4\">\n <mt-select-field\n [options]=\"availableReceivers()\"\n [optionLabel]=\"'title'\"\n [optionValue]=\"'id'\"\n [label]=\"'notification.receivers' | transloco\"\n [placeholder]=\"'notification.selectReceiver' | transloco\"\n [filter]=\"true\"\n [loading]=\"receiversQuery.isPending()\"\n (onChange)=\"onAddReceiver($event)\"\n />\n </div>\n @if ((template.receivers ?? []).length > 0) {\n <div class=\"grid grid-cols-2 gap-4\">\n <div class=\"flex flex-col gap-2\">\n @for (receiver of template.receivers; track receiver.id) {\n <div\n class=\"flex items-center justify-between rounded-lg border border-surface bg-surface-50 p-1 transition-colors hover:bg-surface-100\"\n >\n <div class=\"flex items-center gap-3\">\n <mt-avatar\n icon=\"user.user-01\"\n shape=\"square\"\n size=\"small\"\n />\n <span class=\"font-medium\">{{ receiver.title }}</span>\n </div>\n @if (!receiver.isSystem) {\n <mt-button\n icon=\"general.trash-01\"\n severity=\"danger\"\n [text]=\"true\"\n [loading]=\"isReceiverDeleting(receiver.id)\"\n (onClick)=\"onDeleteReceiver(receiver)\"\n />\n }\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Actions -->\n <div class=\"flex justify-end gap-2 pt-4\">\n @if (!template.isSystem) {\n <mt-button\n [label]=\"'notification.delete' | transloco\"\n severity=\"danger\"\n [loading]=\"isDeletingTemplate()\"\n [disabled]=\"isUpdatingTemplate()\"\n (onClick)=\"onDeleteTemplate()\"\n />\n }\n <mt-button\n [label]=\"'notification.save' | transloco\"\n [loading]=\"isUpdatingTemplate()\"\n [disabled]=\"isDeletingTemplate() || isFormInvalid()\"\n (onClick)=\"onSaveTemplate()\"\n />\n </div>\n </div>\n }\n }\n }\n</div>\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: SelectField, selector: "mt-select-field", inputs: ["field", "label", "placeholder", "hasPlaceholderPrefix", "class", "readonly", "pInputs", "options", "optionValue", "optionLabel", "filter", "filterBy", "dataKey", "showClear", "clearAfterSelect", "required"], outputs: ["onChange"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "icon", "iconPosition"] }, { kind: "component", type: TextareaField, selector: "mt-textarea-field", inputs: ["field", "label", "placeholder", "class", "readonly", "noErrorStyle", "pInputs", "rows", "required"] }, { kind: "component", type: EditorField, selector: "mt-editor-field", inputs: ["label", "placeholder", "readonly", "theme", "height", "modules", "required"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "labelPosition", "placeholder", "readonly", "pInputs", "required"], outputs: ["onChange"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "size", "fluid", "disabled"], outputs: ["activeChange", "onChange"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "pipe", type: i2.TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1028
+ }
1029
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NotificationTemplate, decorators: [{
1030
+ type: Component,
1031
+ args: [{ selector: 'mt-notification-template', standalone: true, imports: [
1032
+ CommonModule,
1033
+ ReactiveFormsModule,
1034
+ TranslocoModule,
1035
+ Card,
1036
+ Button,
1037
+ SelectField,
1038
+ TextField,
1039
+ TextareaField,
1040
+ EditorField,
1041
+ ToggleField,
1042
+ Tabs,
1043
+ Icon,
1044
+ Skeleton,
1045
+ Avatar,
1046
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col gap-4\" [formGroup]=\"form\">\n <!-- Loading State -->\n @if (eventsQuery.isPending()) {\n <div class=\"flex flex-col items-center justify-center py-12\">\n <p-skeleton width=\"100%\" height=\"200px\" />\n </div>\n } @else {\n <!-- Events Selector -->\n <div class=\"w-1/2\">\n <mt-select-field\n formControlName=\"event\"\n [options]=\"eventsQuery.data() ?? []\"\n [optionLabel]=\"'title'\"\n [optionValue]=\"'eventName'\"\n [label]=\"'notification.events' | transloco\"\n [placeholder]=\"'notification.selectEvent' | transloco\"\n [required]=\"true\"\n [filter]=\"false\"\n (onChange)=\"onEventChange($event)\"\n />\n </div>\n\n @if ((eventsQuery.data() ?? []).length === 0) {\n <!-- Empty State -->\n <mt-card>\n <div\n class=\"flex flex-col items-center justify-center py-12 text-slate-500\"\n >\n <mt-icon name=\"alert.alert-circle\" class=\"mb-2 h-12 w-12\" />\n <p>{{ \"notification.noEventsFound\" | transloco }}</p>\n </div>\n </mt-card>\n } @else if (selectedEvent()) {\n <!-- Template Tabs -->\n <div class=\"flex items-center justify-between\">\n <mt-tabs\n [active]=\"selectedTemplateId()\"\n [options]=\"templateTabs()\"\n (activeChange)=\"onTemplateTabChange($event)\"\n />\n <mt-button\n icon=\"general.plus\"\n [label]=\"'notification.newNotification' | transloco\"\n size=\"small\"\n [loading]=\"isAddingTemplate()\"\n (onClick)=\"onAddTemplateClick()\"\n />\n </div>\n\n <!-- Template Content -->\n @if (selectedTemplate(); as template) {\n <div class=\"flex flex-col gap-4\">\n <!-- Name -->\n <div class=\"grid grid-cols-2 gap-4\">\n <mt-text-field\n formControlName=\"title\"\n [label]=\"'notification.name' | transloco\"\n [placeholder]=\"'notification.enterName' | transloco\"\n [required]=\"true\"\n />\n </div>\n\n <!-- Channel & Properties Picker Row -->\n <div class=\"grid grid-cols-2 gap-4\">\n <mt-select-field\n formControlName=\"channel\"\n [options]=\"channelOptions()\"\n [optionLabel]=\"'title'\"\n [optionValue]=\"'key'\"\n [label]=\"'notification.channel.label' | transloco\"\n [placeholder]=\"'notification.selectChannel' | transloco\"\n [required]=\"true\"\n [filter]=\"false\"\n [disabled]=\"true\"\n />\n <mt-select-field\n [options]=\"propertyGroupsQuery.data() ?? []\"\n [optionLabel]=\"'name'\"\n [optionValue]=\"'key'\"\n [group]=\"true\"\n [optionGroupLabel]=\"'groupName'\"\n [optionGroupChildren]=\"'properties'\"\n [label]=\"'notification.propertiesList' | transloco\"\n [placeholder]=\"'notification.selectProperty' | transloco\"\n [hasPlaceholderPrefix]=\"false\"\n [filter]=\"true\"\n [filterBy]=\"'name'\"\n [loading]=\"propertyGroupsQuery.isPending()\"\n [formControl]=\"propertyPickerControl\"\n (onChange)=\"onAddProperty($event)\"\n />\n </div>\n\n <!-- Channel Content Editors -->\n @for (ch of channelConfigs; track ch.key) {\n @if (form.controls[\"channel\"].value === ch.key) {\n <!-- Bilingual Content Fields -->\n @if (isBilingual(ch) && ch.translatableKey) {\n <ng-container [formGroupName]=\"ch.translatableKey\">\n @if (ch.useRichEditor) {\n <mt-editor-field\n formControlName=\"en\"\n [label]=\"'notification.english' | transloco\"\n dir=\"ltr\"\n />\n <mt-editor-field\n formControlName=\"ar\"\n [label]=\"'notification.arabic' | transloco\"\n dir=\"rtl\"\n />\n } @else {\n <mt-textarea-field\n formControlName=\"en\"\n [label]=\"'notification.english' | transloco\"\n [rows]=\"6\"\n dir=\"ltr\"\n />\n <mt-textarea-field\n formControlName=\"ar\"\n [label]=\"'notification.arabic' | transloco\"\n [rows]=\"6\"\n dir=\"rtl\"\n />\n }\n </ng-container>\n } @else if (ch.templateKey) {\n <!-- Non-Bilingual Content Field -->\n @if (ch.useRichEditor) {\n <mt-editor-field\n [formControlName]=\"ch.templateKey\"\n [label]=\"'notification.notificationBody' | transloco\"\n />\n } @else {\n <mt-textarea-field\n [formControlName]=\"ch.templateKey\"\n [label]=\"'notification.notificationBody' | transloco\"\n [rows]=\"6\"\n />\n }\n }\n\n <!-- Bilingual Toggle -->\n @if (ch.bilingualOptional) {\n <mt-toggle-field\n [formControlName]=\"ch.key + 'Bilingual'\"\n [label]=\"'notification.bilingual' | transloco\"\n />\n }\n }\n }\n\n <!-- Receivers -->\n <div class=\"grid grid-cols-2 gap-4\">\n <mt-select-field\n [options]=\"availableReceivers()\"\n [optionLabel]=\"'title'\"\n [optionValue]=\"'id'\"\n [label]=\"'notification.receivers' | transloco\"\n [placeholder]=\"'notification.selectReceiver' | transloco\"\n [filter]=\"true\"\n [loading]=\"receiversQuery.isPending()\"\n (onChange)=\"onAddReceiver($event)\"\n />\n </div>\n @if ((template.receivers ?? []).length > 0) {\n <div class=\"grid grid-cols-2 gap-4\">\n <div class=\"flex flex-col gap-2\">\n @for (receiver of template.receivers; track receiver.id) {\n <div\n class=\"flex items-center justify-between rounded-lg border border-surface bg-surface-50 p-1 transition-colors hover:bg-surface-100\"\n >\n <div class=\"flex items-center gap-3\">\n <mt-avatar\n icon=\"user.user-01\"\n shape=\"square\"\n size=\"small\"\n />\n <span class=\"font-medium\">{{ receiver.title }}</span>\n </div>\n @if (!receiver.isSystem) {\n <mt-button\n icon=\"general.trash-01\"\n severity=\"danger\"\n [text]=\"true\"\n [loading]=\"isReceiverDeleting(receiver.id)\"\n (onClick)=\"onDeleteReceiver(receiver)\"\n />\n }\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Actions -->\n <div class=\"flex justify-end gap-2 pt-4\">\n @if (!template.isSystem) {\n <mt-button\n [label]=\"'notification.delete' | transloco\"\n severity=\"danger\"\n [loading]=\"isDeletingTemplate()\"\n [disabled]=\"isUpdatingTemplate()\"\n (onClick)=\"onDeleteTemplate()\"\n />\n }\n <mt-button\n [label]=\"'notification.save' | transloco\"\n [loading]=\"isUpdatingTemplate()\"\n [disabled]=\"isDeletingTemplate() || isFormInvalid()\"\n (onClick)=\"onSaveTemplate()\"\n />\n </div>\n </div>\n }\n }\n }\n</div>\n", styles: [":host{display:block}\n"] }]
1047
+ }], ctorParameters: () => [] });
1048
+
1049
+ const APP_STATES = [NotificationState];
1050
+
1051
+ /**
1052
+ * Generated bundle index. Do not edit.
1053
+ */
1054
+
1055
+ export { APP_STATES, AddReceivers, AddTemplate, AddTemplateDialog, CHANNEL_CONFIGS, DeleteReceiver, DeleteTemplate, GetActionProperties, GetActionReceivers, GetEvents, GetProperties, GetReceivers, NotificationActionKey, NotificationFacade, NotificationState, NotificationTemplate, ResetNotificationState, SelectEvent, SelectTemplate, SetActionMode, SetModuleInfo, UpdateTemplate, getChannelConfig, getFormControlName };
1056
+ //# sourceMappingURL=masterteam-notification.mjs.map