@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
|