@flusys/ng-email 1.0.0-rc

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,636 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, signal, computed, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { Router } from '@angular/router';
4
+ import { firstValueFrom } from 'rxjs';
5
+ import { APP_CONFIG, DEFAULT_APP_NAME } from '@flusys/ng-core';
6
+ import { LAYOUT_AUTH_STATE } from '@flusys/ng-layout';
7
+ import { EMAIL_TEMPLATE_PERMISSIONS, AngularModule, PrimeModule, HasPermissionDirective } from '@flusys/ng-shared';
8
+ import { ConfirmationService, MessageService } from 'primeng/api';
9
+ import { EmailTemplateApiService, EmailConfigApiService, EmailSendService } from './flusys-ng-email.mjs';
10
+ import * as i1 from '@angular/forms';
11
+ import * as i2 from 'primeng/button';
12
+ import * as i3 from 'primeng/confirmdialog';
13
+ import * as i4 from 'primeng/dialog';
14
+ import * as i5 from 'primeng/inputtext';
15
+ import * as i6 from 'primeng/select';
16
+ import * as i7 from 'primeng/table';
17
+ import * as i8 from 'primeng/tag';
18
+ import * as i8$1 from 'primeng/toast';
19
+ import * as i10 from 'primeng/tooltip';
20
+ import * as i11 from '@angular/common';
21
+
22
+ /**
23
+ * Email template list component
24
+ */
25
+ class TemplateListComponent {
26
+ // Permission constants for template
27
+ EMAIL_TEMPLATE_PERMISSIONS = EMAIL_TEMPLATE_PERMISSIONS;
28
+ router = inject(Router);
29
+ templateService = inject(EmailTemplateApiService);
30
+ configService = inject(EmailConfigApiService);
31
+ emailSendService = inject(EmailSendService);
32
+ confirmationService = inject(ConfirmationService);
33
+ messageService = inject(MessageService);
34
+ appConfig = inject(APP_CONFIG);
35
+ companyContext = inject(LAYOUT_AUTH_STATE, { optional: true });
36
+ isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
37
+ templates = signal([], ...(ngDevMode ? [{ debugName: "templates" }] : []));
38
+ totalRecords = signal(0, ...(ngDevMode ? [{ debugName: "totalRecords" }] : []));
39
+ pageSize = signal(10, ...(ngDevMode ? [{ debugName: "pageSize" }] : []));
40
+ first = signal(0, ...(ngDevMode ? [{ debugName: "first" }] : []));
41
+ showCompanyInfo = computed(() => this.appConfig.enableCompanyFeature && !!this.companyContext, ...(ngDevMode ? [{ debugName: "showCompanyInfo" }] : []));
42
+ currentCompanyName = computed(() => this.companyContext?.currentCompanyInfo()?.name ?? DEFAULT_APP_NAME, ...(ngDevMode ? [{ debugName: "currentCompanyName" }] : []));
43
+ // Test send dialog (signals for zoneless change detection)
44
+ showTestDialog = signal(false, ...(ngDevMode ? [{ debugName: "showTestDialog" }] : []));
45
+ selectedTemplate = signal(null, ...(ngDevMode ? [{ debugName: "selectedTemplate" }] : []));
46
+ configs = signal([], ...(ngDevMode ? [{ debugName: "configs" }] : []));
47
+ isLoadingConfigs = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingConfigs" }] : []));
48
+ isSendingTest = signal(false, ...(ngDevMode ? [{ debugName: "isSendingTest" }] : []));
49
+ // Form-bound properties as signals for zoneless change detection
50
+ _testSendModel = signal({ configId: '', recipient: '' }, ...(ngDevMode ? [{ debugName: "_testSendModel" }] : []));
51
+ _variableValues = signal({}, ...(ngDevMode ? [{ debugName: "_variableValues" }] : []));
52
+ get testSendModel() {
53
+ return this._testSendModel();
54
+ }
55
+ get variableValues() {
56
+ return this._variableValues();
57
+ }
58
+ updateTestSendModel(field, value) {
59
+ this._testSendModel.update((m) => ({ ...m, [field]: value }));
60
+ }
61
+ updateVariableValue(key, value) {
62
+ this._variableValues.update((v) => ({ ...v, [key]: value }));
63
+ }
64
+ configOptions = computed(() => this.configs().map((c) => ({
65
+ label: `${c.name} (${c.provider})`,
66
+ value: c.id,
67
+ })), ...(ngDevMode ? [{ debugName: "configOptions" }] : []));
68
+ /** Extract variables from template content */
69
+ templateVariables = computed(() => {
70
+ const template = this.selectedTemplate();
71
+ if (!template)
72
+ return [];
73
+ // Combine all content sources to extract variables
74
+ const content = [
75
+ template.subject,
76
+ template.htmlContent,
77
+ template.textContent || '',
78
+ ].join(' ');
79
+ // Find all {{variableName}} patterns
80
+ const matches = content.matchAll(/\{\{(\w+)\}\}/g);
81
+ const variables = new Set();
82
+ for (const match of matches) {
83
+ variables.add(match[1]);
84
+ }
85
+ return Array.from(variables).sort();
86
+ }, ...(ngDevMode ? [{ debugName: "templateVariables" }] : []));
87
+ onCreate() {
88
+ this.router.navigate(['/email/templates/new']);
89
+ }
90
+ onEdit(templateId) {
91
+ this.router.navigate(['/email/templates', templateId]);
92
+ }
93
+ async loadTemplates() {
94
+ this.isLoading.set(true);
95
+ try {
96
+ const response = await firstValueFrom(this.templateService.getAll('', {
97
+ pagination: {
98
+ currentPage: Math.floor(this.first() / this.pageSize()),
99
+ pageSize: this.pageSize(),
100
+ },
101
+ filter: {},
102
+ select: [],
103
+ sort: {},
104
+ }));
105
+ if (response.success) {
106
+ this.templates.set(response.data ?? []);
107
+ this.totalRecords.set(response.meta?.total ?? 0);
108
+ }
109
+ }
110
+ finally {
111
+ this.isLoading.set(false);
112
+ }
113
+ }
114
+ onLazyLoad(event) {
115
+ this.first.set(event.first ?? 0);
116
+ this.pageSize.set(event.rows ?? 10);
117
+ this.loadTemplates();
118
+ }
119
+ async onTestSend(template) {
120
+ this.selectedTemplate.set(template);
121
+ this._testSendModel.set({ configId: '', recipient: '' });
122
+ this._variableValues.set({}); // Reset variable values
123
+ this.showTestDialog.set(true);
124
+ await this.loadConfigs();
125
+ }
126
+ async loadConfigs() {
127
+ this.isLoadingConfigs.set(true);
128
+ try {
129
+ const response = await firstValueFrom(this.configService.getAll('', {
130
+ pagination: { currentPage: 0, pageSize: 100 },
131
+ filter: { isActive: true },
132
+ select: [],
133
+ sort: {},
134
+ }));
135
+ if (response.success) {
136
+ this.configs.set(response.data ?? []);
137
+ }
138
+ }
139
+ finally {
140
+ this.isLoadingConfigs.set(false);
141
+ }
142
+ }
143
+ async sendTestEmail() {
144
+ const template = this.selectedTemplate();
145
+ const model = this.testSendModel;
146
+ if (!template || !model.configId || !model.recipient) {
147
+ this.messageService.add({
148
+ severity: 'warn',
149
+ summary: 'Validation',
150
+ detail: 'Please select a configuration and enter recipient email.',
151
+ });
152
+ return;
153
+ }
154
+ this.isSendingTest.set(true);
155
+ try {
156
+ // Build variables object (only include non-empty values)
157
+ const variables = {};
158
+ for (const [key, value] of Object.entries(this.variableValues)) {
159
+ if (value) {
160
+ variables[key] = value;
161
+ }
162
+ }
163
+ const response = await firstValueFrom(this.emailSendService.sendTemplate({
164
+ templateId: template.id,
165
+ to: model.recipient,
166
+ emailConfigId: model.configId,
167
+ variables: Object.keys(variables).length > 0 ? variables : undefined,
168
+ }));
169
+ if (response.data?.success) {
170
+ this.messageService.add({
171
+ severity: 'success',
172
+ summary: 'Success',
173
+ detail: `Test email sent! Message ID: ${response.data.messageId}`,
174
+ });
175
+ this.showTestDialog.set(false);
176
+ }
177
+ else {
178
+ this.messageService.add({
179
+ severity: 'error',
180
+ summary: 'Error',
181
+ detail: response.data?.error || 'Failed to send test email.',
182
+ });
183
+ }
184
+ }
185
+ catch {
186
+ // Error toast handled by global interceptor
187
+ }
188
+ finally {
189
+ this.isSendingTest.set(false);
190
+ }
191
+ }
192
+ onDelete(template) {
193
+ this.confirmationService.confirm({
194
+ message: `Are you sure you want to delete "${template.name}"?`,
195
+ header: 'Delete Template',
196
+ icon: 'pi pi-exclamation-triangle',
197
+ acceptButtonStyleClass: 'p-button-danger',
198
+ accept: async () => {
199
+ try {
200
+ await this.templateService.deleteAsync({ id: template.id, type: 'delete' });
201
+ this.messageService.add({
202
+ severity: 'success',
203
+ summary: 'Success',
204
+ detail: 'Template deleted successfully.',
205
+ });
206
+ this.loadTemplates();
207
+ }
208
+ catch {
209
+ // Error toast handled by global interceptor
210
+ }
211
+ },
212
+ });
213
+ }
214
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: TemplateListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
215
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: TemplateListComponent, isStandalone: true, selector: "lib-template-list", providers: [ConfirmationService, MessageService], ngImport: i0, template: `
216
+ <div class="card">
217
+ <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 mb-4">
218
+ <div>
219
+ <h3 class="text-lg sm:text-xl font-semibold m-0">Email Templates</h3>
220
+ @if (showCompanyInfo()) {
221
+ <p class="text-sm text-muted-color mt-1">
222
+ Company: {{ currentCompanyName() }}
223
+ </p>
224
+ }
225
+ </div>
226
+ <p-button
227
+ *hasPermission="EMAIL_TEMPLATE_PERMISSIONS.CREATE"
228
+ label="New Template"
229
+ icon="pi pi-plus"
230
+ (onClick)="onCreate()"
231
+ styleClass="w-full sm:w-auto"
232
+ />
233
+ </div>
234
+
235
+ <div class="overflow-x-auto -mx-4 sm:mx-0">
236
+ <p-table
237
+ [value]="templates()"
238
+ [loading]="isLoading()"
239
+ [paginator]="totalRecords() > 0"
240
+ [rows]="pageSize()"
241
+ [first]="first()"
242
+ [totalRecords]="totalRecords()"
243
+ [lazy]="true"
244
+ (onLazyLoad)="onLazyLoad($event)"
245
+ [rowsPerPageOptions]="[10, 25, 50]"
246
+ styleClass="p-datatable-sm"
247
+ [tableStyle]="{ 'min-width': '50rem' }"
248
+ >
249
+ <ng-template #header>
250
+ <tr>
251
+ <th>Name</th>
252
+ <th class="hidden md:table-cell">Slug</th>
253
+ <th class="hidden lg:table-cell">Subject</th>
254
+ <th>Type</th>
255
+ <th>Status</th>
256
+ <th class="hidden xl:table-cell">Created</th>
257
+ <th class="w-[120px]">Actions</th>
258
+ </tr>
259
+ </ng-template>
260
+
261
+ <ng-template #body let-template>
262
+ <tr>
263
+ <td>
264
+ <i class="pi pi-envelope mr-2 text-muted-color"></i>
265
+ {{ template.name }}
266
+ </td>
267
+ <td class="hidden md:table-cell">
268
+ <code class="text-sm bg-surface-100 dark:bg-surface-700 px-2 py-1 rounded">{{ template.slug }}</code>
269
+ </td>
270
+ <td class="hidden lg:table-cell">{{ template.subject }}</td>
271
+ <td>
272
+ <p-tag
273
+ [value]="template.isHtml ? 'HTML' : 'Text'"
274
+ [severity]="template.isHtml ? 'info' : 'secondary'"
275
+ />
276
+ </td>
277
+ <td>
278
+ <p-tag
279
+ [value]="template.isActive ? 'Active' : 'Inactive'"
280
+ [severity]="template.isActive ? 'success' : 'secondary'"
281
+ />
282
+ </td>
283
+ <td class="hidden xl:table-cell">{{ template.createdAt | date: 'short' }}</td>
284
+ <td>
285
+ <div class="flex gap-1">
286
+ <p-button
287
+ *hasPermission="EMAIL_TEMPLATE_PERMISSIONS.UPDATE"
288
+ icon="pi pi-send"
289
+ [text]="true"
290
+ size="small"
291
+ pTooltip="Test Send"
292
+ (onClick)="onTestSend(template)"
293
+ />
294
+ <p-button
295
+ *hasPermission="EMAIL_TEMPLATE_PERMISSIONS.UPDATE"
296
+ icon="pi pi-pencil"
297
+ [text]="true"
298
+ severity="secondary"
299
+ size="small"
300
+ pTooltip="Edit"
301
+ (onClick)="onEdit(template.id)"
302
+ />
303
+ <p-button
304
+ *hasPermission="EMAIL_TEMPLATE_PERMISSIONS.DELETE"
305
+ icon="pi pi-trash"
306
+ [text]="true"
307
+ severity="danger"
308
+ size="small"
309
+ pTooltip="Delete"
310
+ (onClick)="onDelete(template)"
311
+ />
312
+ </div>
313
+ </td>
314
+ </tr>
315
+ </ng-template>
316
+
317
+ <ng-template #emptymessage>
318
+ <tr>
319
+ <td colspan="7" class="text-center py-4 text-muted-color">
320
+ No email templates found. Create your first template to get started.
321
+ </td>
322
+ </tr>
323
+ </ng-template>
324
+ </p-table>
325
+ </div>
326
+ </div>
327
+
328
+ <!-- Test Send Dialog -->
329
+ <p-dialog
330
+ header="Test Send Email"
331
+ [visible]="showTestDialog()"
332
+ (visibleChange)="showTestDialog.set($event)"
333
+ [modal]="true"
334
+ [style]="{ width: '95vw', maxWidth: '500px', maxHeight: '80vh' }"
335
+ [breakpoints]="{ '575px': '95vw' }"
336
+ >
337
+ <div class="flex flex-col gap-4">
338
+ <div class="field">
339
+ <label class="block font-medium mb-2">Template</label>
340
+ <input
341
+ pInputText
342
+ [value]="selectedTemplate()?.name || ''"
343
+ class="w-full"
344
+ [disabled]="true"
345
+ />
346
+ </div>
347
+
348
+ <div class="field">
349
+ <label class="block font-medium mb-2">Email Configuration *</label>
350
+ <p-select
351
+ [ngModel]="testSendModel.configId"
352
+ (ngModelChange)="updateTestSendModel('configId', $event)"
353
+ [options]="configOptions()"
354
+ optionLabel="label"
355
+ optionValue="value"
356
+ placeholder="Select configuration"
357
+ class="w-full"
358
+ [loading]="isLoadingConfigs()"
359
+ />
360
+ </div>
361
+
362
+ <div class="field">
363
+ <label class="block font-medium mb-2">Recipient Email *</label>
364
+ <input
365
+ pInputText
366
+ [ngModel]="testSendModel.recipient"
367
+ (ngModelChange)="updateTestSendModel('recipient', $event)"
368
+ class="w-full"
369
+ placeholder="recipient@example.com"
370
+ />
371
+ </div>
372
+
373
+ <!-- Dynamic Variables -->
374
+ @if (templateVariables().length > 0) {
375
+ <div class="border-t border-surface pt-4 mt-2">
376
+ <h4 class="font-medium mb-3 flex items-center gap-2">
377
+ <i class="pi pi-code text-muted-color"></i>
378
+ Template Variables
379
+ </h4>
380
+ <div class="flex flex-col gap-3">
381
+ @for (variable of templateVariables(); track variable) {
382
+ <div class="field">
383
+ <label class="block text-sm font-medium mb-1">
384
+ <code class="text-primary bg-surface-100 dark:bg-surface-700 px-1 rounded">{{ '{{' + variable + '}}' }}</code>
385
+ </label>
386
+ <input
387
+ pInputText
388
+ [ngModel]="variableValues[variable] || ''"
389
+ (ngModelChange)="updateVariableValue(variable, $event)"
390
+ class="w-full"
391
+ [placeholder]="'Enter value for ' + variable"
392
+ />
393
+ </div>
394
+ }
395
+ </div>
396
+ </div>
397
+ }
398
+ </div>
399
+
400
+ <ng-template #footer>
401
+ <p-button
402
+ label="Cancel"
403
+ severity="secondary"
404
+ [outlined]="true"
405
+ (onClick)="showTestDialog.set(false)"
406
+ />
407
+ <p-button
408
+ label="Send Test"
409
+ icon="pi pi-send"
410
+ [loading]="isSendingTest()"
411
+ (onClick)="sendTestEmail()"
412
+ />
413
+ </ng-template>
414
+ </p-dialog>
415
+
416
+ <p-confirmDialog />
417
+ <p-toast />
418
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i3.ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "component", type: i4.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "directive", type: i5.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "component", type: i6.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: i7.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "component", type: i8.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "component", type: i8$1.Toast, selector: "p-toast", inputs: ["key", "autoZIndex", "baseZIndex", "life", "styleClass", "position", "preventOpenDuplicates", "preventDuplicates", "showTransformOptions", "hideTransformOptions", "showTransitionOptions", "hideTransitionOptions", "motionOptions", "breakpoints"], outputs: ["onClose"] }, { kind: "directive", type: i10.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }, { kind: "pipe", type: i11.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
419
+ }
420
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: TemplateListComponent, decorators: [{
421
+ type: Component,
422
+ args: [{
423
+ selector: 'lib-template-list',
424
+ standalone: true,
425
+ changeDetection: ChangeDetectionStrategy.OnPush,
426
+ imports: [AngularModule, PrimeModule, HasPermissionDirective],
427
+ providers: [ConfirmationService, MessageService],
428
+ template: `
429
+ <div class="card">
430
+ <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 mb-4">
431
+ <div>
432
+ <h3 class="text-lg sm:text-xl font-semibold m-0">Email Templates</h3>
433
+ @if (showCompanyInfo()) {
434
+ <p class="text-sm text-muted-color mt-1">
435
+ Company: {{ currentCompanyName() }}
436
+ </p>
437
+ }
438
+ </div>
439
+ <p-button
440
+ *hasPermission="EMAIL_TEMPLATE_PERMISSIONS.CREATE"
441
+ label="New Template"
442
+ icon="pi pi-plus"
443
+ (onClick)="onCreate()"
444
+ styleClass="w-full sm:w-auto"
445
+ />
446
+ </div>
447
+
448
+ <div class="overflow-x-auto -mx-4 sm:mx-0">
449
+ <p-table
450
+ [value]="templates()"
451
+ [loading]="isLoading()"
452
+ [paginator]="totalRecords() > 0"
453
+ [rows]="pageSize()"
454
+ [first]="first()"
455
+ [totalRecords]="totalRecords()"
456
+ [lazy]="true"
457
+ (onLazyLoad)="onLazyLoad($event)"
458
+ [rowsPerPageOptions]="[10, 25, 50]"
459
+ styleClass="p-datatable-sm"
460
+ [tableStyle]="{ 'min-width': '50rem' }"
461
+ >
462
+ <ng-template #header>
463
+ <tr>
464
+ <th>Name</th>
465
+ <th class="hidden md:table-cell">Slug</th>
466
+ <th class="hidden lg:table-cell">Subject</th>
467
+ <th>Type</th>
468
+ <th>Status</th>
469
+ <th class="hidden xl:table-cell">Created</th>
470
+ <th class="w-[120px]">Actions</th>
471
+ </tr>
472
+ </ng-template>
473
+
474
+ <ng-template #body let-template>
475
+ <tr>
476
+ <td>
477
+ <i class="pi pi-envelope mr-2 text-muted-color"></i>
478
+ {{ template.name }}
479
+ </td>
480
+ <td class="hidden md:table-cell">
481
+ <code class="text-sm bg-surface-100 dark:bg-surface-700 px-2 py-1 rounded">{{ template.slug }}</code>
482
+ </td>
483
+ <td class="hidden lg:table-cell">{{ template.subject }}</td>
484
+ <td>
485
+ <p-tag
486
+ [value]="template.isHtml ? 'HTML' : 'Text'"
487
+ [severity]="template.isHtml ? 'info' : 'secondary'"
488
+ />
489
+ </td>
490
+ <td>
491
+ <p-tag
492
+ [value]="template.isActive ? 'Active' : 'Inactive'"
493
+ [severity]="template.isActive ? 'success' : 'secondary'"
494
+ />
495
+ </td>
496
+ <td class="hidden xl:table-cell">{{ template.createdAt | date: 'short' }}</td>
497
+ <td>
498
+ <div class="flex gap-1">
499
+ <p-button
500
+ *hasPermission="EMAIL_TEMPLATE_PERMISSIONS.UPDATE"
501
+ icon="pi pi-send"
502
+ [text]="true"
503
+ size="small"
504
+ pTooltip="Test Send"
505
+ (onClick)="onTestSend(template)"
506
+ />
507
+ <p-button
508
+ *hasPermission="EMAIL_TEMPLATE_PERMISSIONS.UPDATE"
509
+ icon="pi pi-pencil"
510
+ [text]="true"
511
+ severity="secondary"
512
+ size="small"
513
+ pTooltip="Edit"
514
+ (onClick)="onEdit(template.id)"
515
+ />
516
+ <p-button
517
+ *hasPermission="EMAIL_TEMPLATE_PERMISSIONS.DELETE"
518
+ icon="pi pi-trash"
519
+ [text]="true"
520
+ severity="danger"
521
+ size="small"
522
+ pTooltip="Delete"
523
+ (onClick)="onDelete(template)"
524
+ />
525
+ </div>
526
+ </td>
527
+ </tr>
528
+ </ng-template>
529
+
530
+ <ng-template #emptymessage>
531
+ <tr>
532
+ <td colspan="7" class="text-center py-4 text-muted-color">
533
+ No email templates found. Create your first template to get started.
534
+ </td>
535
+ </tr>
536
+ </ng-template>
537
+ </p-table>
538
+ </div>
539
+ </div>
540
+
541
+ <!-- Test Send Dialog -->
542
+ <p-dialog
543
+ header="Test Send Email"
544
+ [visible]="showTestDialog()"
545
+ (visibleChange)="showTestDialog.set($event)"
546
+ [modal]="true"
547
+ [style]="{ width: '95vw', maxWidth: '500px', maxHeight: '80vh' }"
548
+ [breakpoints]="{ '575px': '95vw' }"
549
+ >
550
+ <div class="flex flex-col gap-4">
551
+ <div class="field">
552
+ <label class="block font-medium mb-2">Template</label>
553
+ <input
554
+ pInputText
555
+ [value]="selectedTemplate()?.name || ''"
556
+ class="w-full"
557
+ [disabled]="true"
558
+ />
559
+ </div>
560
+
561
+ <div class="field">
562
+ <label class="block font-medium mb-2">Email Configuration *</label>
563
+ <p-select
564
+ [ngModel]="testSendModel.configId"
565
+ (ngModelChange)="updateTestSendModel('configId', $event)"
566
+ [options]="configOptions()"
567
+ optionLabel="label"
568
+ optionValue="value"
569
+ placeholder="Select configuration"
570
+ class="w-full"
571
+ [loading]="isLoadingConfigs()"
572
+ />
573
+ </div>
574
+
575
+ <div class="field">
576
+ <label class="block font-medium mb-2">Recipient Email *</label>
577
+ <input
578
+ pInputText
579
+ [ngModel]="testSendModel.recipient"
580
+ (ngModelChange)="updateTestSendModel('recipient', $event)"
581
+ class="w-full"
582
+ placeholder="recipient@example.com"
583
+ />
584
+ </div>
585
+
586
+ <!-- Dynamic Variables -->
587
+ @if (templateVariables().length > 0) {
588
+ <div class="border-t border-surface pt-4 mt-2">
589
+ <h4 class="font-medium mb-3 flex items-center gap-2">
590
+ <i class="pi pi-code text-muted-color"></i>
591
+ Template Variables
592
+ </h4>
593
+ <div class="flex flex-col gap-3">
594
+ @for (variable of templateVariables(); track variable) {
595
+ <div class="field">
596
+ <label class="block text-sm font-medium mb-1">
597
+ <code class="text-primary bg-surface-100 dark:bg-surface-700 px-1 rounded">{{ '{{' + variable + '}}' }}</code>
598
+ </label>
599
+ <input
600
+ pInputText
601
+ [ngModel]="variableValues[variable] || ''"
602
+ (ngModelChange)="updateVariableValue(variable, $event)"
603
+ class="w-full"
604
+ [placeholder]="'Enter value for ' + variable"
605
+ />
606
+ </div>
607
+ }
608
+ </div>
609
+ </div>
610
+ }
611
+ </div>
612
+
613
+ <ng-template #footer>
614
+ <p-button
615
+ label="Cancel"
616
+ severity="secondary"
617
+ [outlined]="true"
618
+ (onClick)="showTestDialog.set(false)"
619
+ />
620
+ <p-button
621
+ label="Send Test"
622
+ icon="pi pi-send"
623
+ [loading]="isSendingTest()"
624
+ (onClick)="sendTestEmail()"
625
+ />
626
+ </ng-template>
627
+ </p-dialog>
628
+
629
+ <p-confirmDialog />
630
+ <p-toast />
631
+ `,
632
+ }]
633
+ }] });
634
+
635
+ export { TemplateListComponent };
636
+ //# sourceMappingURL=flusys-ng-email-template-list.component-91djzx28.mjs.map