@flusys/ng-email 1.1.0-beta

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,595 @@
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 * as i1 from '@angular/forms';
5
+ import { FormsModule } from '@angular/forms';
6
+ import { firstValueFrom } from 'rxjs';
7
+ import { ConfirmationService, MessageService } from 'primeng/api';
8
+ import { APP_CONFIG, DEFAULT_APP_NAME } from '@flusys/ng-core';
9
+ import { LAYOUT_AUTH_STATE } from '@flusys/ng-layout';
10
+ import { AngularModule, PrimeModule } from '@flusys/ng-shared';
11
+ import { EmailTemplateApiService, EmailConfigApiService, EmailSendService } from './flusys-ng-email.mjs';
12
+ import * as i2 from 'primeng/button';
13
+ import * as i3 from 'primeng/confirmdialog';
14
+ import * as i4 from 'primeng/dialog';
15
+ import * as i5 from 'primeng/inputtext';
16
+ import * as i6 from 'primeng/select';
17
+ import * as i7 from 'primeng/table';
18
+ import * as i8 from 'primeng/tag';
19
+ import * as i9 from 'primeng/toast';
20
+ import * as i10 from 'primeng/tooltip';
21
+ import * as i11 from '@angular/common';
22
+
23
+ /**
24
+ * Email template list component
25
+ */
26
+ class TemplateListComponent {
27
+ router = inject(Router);
28
+ templateService = inject(EmailTemplateApiService);
29
+ configService = inject(EmailConfigApiService);
30
+ emailSendService = inject(EmailSendService);
31
+ confirmationService = inject(ConfirmationService);
32
+ messageService = inject(MessageService);
33
+ appConfig = inject(APP_CONFIG);
34
+ companyContext = inject(LAYOUT_AUTH_STATE);
35
+ isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
36
+ templates = signal([], ...(ngDevMode ? [{ debugName: "templates" }] : []));
37
+ totalRecords = signal(0, ...(ngDevMode ? [{ debugName: "totalRecords" }] : []));
38
+ pageSize = signal(10, ...(ngDevMode ? [{ debugName: "pageSize" }] : []));
39
+ currentPage = signal(1, ...(ngDevMode ? [{ debugName: "currentPage" }] : []));
40
+ showCompanyInfo = computed(() => this.appConfig.enableCompanyFeature, ...(ngDevMode ? [{ debugName: "showCompanyInfo" }] : []));
41
+ currentCompanyName = computed(() => this.companyContext.currentCompanyInfo()?.name ?? DEFAULT_APP_NAME, ...(ngDevMode ? [{ debugName: "currentCompanyName" }] : []));
42
+ // Test send dialog
43
+ showTestDialog = false;
44
+ selectedTemplate = signal(null, ...(ngDevMode ? [{ debugName: "selectedTemplate" }] : []));
45
+ configs = signal([], ...(ngDevMode ? [{ debugName: "configs" }] : []));
46
+ isLoadingConfigs = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingConfigs" }] : []));
47
+ isSendingTest = signal(false, ...(ngDevMode ? [{ debugName: "isSendingTest" }] : []));
48
+ // Form-bound properties (ngModel requires plain properties)
49
+ testSendModel = { configId: '', recipient: '' };
50
+ variableValues = {};
51
+ configOptions = computed(() => this.configs().map((c) => ({
52
+ label: `${c.name} (${c.provider})`,
53
+ value: c.id,
54
+ })), ...(ngDevMode ? [{ debugName: "configOptions" }] : []));
55
+ /** Extract variables from template content */
56
+ templateVariables = computed(() => {
57
+ const template = this.selectedTemplate();
58
+ if (!template)
59
+ return [];
60
+ // Combine all content sources to extract variables
61
+ const content = [
62
+ template.subject,
63
+ template.htmlContent,
64
+ template.textContent || '',
65
+ ].join(' ');
66
+ // Find all {{variableName}} patterns
67
+ const matches = content.matchAll(/\{\{(\w+)\}\}/g);
68
+ const variables = new Set();
69
+ for (const match of matches) {
70
+ variables.add(match[1]);
71
+ }
72
+ return Array.from(variables).sort();
73
+ }, ...(ngDevMode ? [{ debugName: "templateVariables" }] : []));
74
+ ngOnInit() {
75
+ this.loadTemplates();
76
+ }
77
+ onCreate() {
78
+ this.router.navigate(['/email/templates/new']);
79
+ }
80
+ onEdit(templateId) {
81
+ this.router.navigate(['/email/templates', templateId]);
82
+ }
83
+ async loadTemplates() {
84
+ this.isLoading.set(true);
85
+ try {
86
+ const response = await firstValueFrom(this.templateService.getAll('', {
87
+ pagination: {
88
+ currentPage: this.currentPage() - 1,
89
+ pageSize: this.pageSize(),
90
+ },
91
+ filter: {},
92
+ select: [],
93
+ sort: {},
94
+ }));
95
+ if (response.success) {
96
+ this.templates.set(response.data ?? []);
97
+ this.totalRecords.set(response.meta?.total ?? 0);
98
+ }
99
+ }
100
+ finally {
101
+ this.isLoading.set(false);
102
+ }
103
+ }
104
+ onPageChange(event) {
105
+ this.currentPage.set(event.first / event.rows + 1);
106
+ this.pageSize.set(event.rows);
107
+ this.loadTemplates();
108
+ }
109
+ async onTestSend(template) {
110
+ this.selectedTemplate.set(template);
111
+ this.testSendModel = { configId: '', recipient: '' };
112
+ this.variableValues = {}; // Reset variable values
113
+ this.showTestDialog = true;
114
+ await this.loadConfigs();
115
+ }
116
+ async loadConfigs() {
117
+ this.isLoadingConfigs.set(true);
118
+ try {
119
+ const response = await firstValueFrom(this.configService.getAll('', {
120
+ pagination: { currentPage: 0, pageSize: 100 },
121
+ filter: { isActive: true },
122
+ select: [],
123
+ sort: {},
124
+ }));
125
+ if (response.success) {
126
+ this.configs.set(response.data ?? []);
127
+ }
128
+ }
129
+ finally {
130
+ this.isLoadingConfigs.set(false);
131
+ }
132
+ }
133
+ async sendTestEmail() {
134
+ const template = this.selectedTemplate();
135
+ if (!template || !this.testSendModel.configId || !this.testSendModel.recipient) {
136
+ this.messageService.add({
137
+ severity: 'warn',
138
+ summary: 'Validation',
139
+ detail: 'Please select a configuration and enter recipient email.',
140
+ });
141
+ return;
142
+ }
143
+ this.isSendingTest.set(true);
144
+ try {
145
+ // Build variables object (only include non-empty values)
146
+ const variables = {};
147
+ for (const [key, value] of Object.entries(this.variableValues)) {
148
+ if (value) {
149
+ variables[key] = value;
150
+ }
151
+ }
152
+ const response = await firstValueFrom(this.emailSendService.sendTemplate({
153
+ templateId: template.id,
154
+ to: this.testSendModel.recipient,
155
+ emailConfigId: this.testSendModel.configId,
156
+ variables: Object.keys(variables).length > 0 ? variables : undefined,
157
+ }));
158
+ if (response.data?.success) {
159
+ this.messageService.add({
160
+ severity: 'success',
161
+ summary: 'Success',
162
+ detail: `Test email sent! Message ID: ${response.data.messageId}`,
163
+ });
164
+ this.showTestDialog = false;
165
+ }
166
+ else {
167
+ this.messageService.add({
168
+ severity: 'error',
169
+ summary: 'Error',
170
+ detail: response.data?.error || 'Failed to send test email.',
171
+ });
172
+ }
173
+ }
174
+ catch (error) {
175
+ this.messageService.add({
176
+ severity: 'error',
177
+ summary: 'Error',
178
+ detail: error?.message || 'Failed to send test email.',
179
+ });
180
+ }
181
+ finally {
182
+ this.isSendingTest.set(false);
183
+ }
184
+ }
185
+ onDelete(template) {
186
+ this.confirmationService.confirm({
187
+ message: `Are you sure you want to delete "${template.name}"?`,
188
+ header: 'Delete Template',
189
+ icon: 'pi pi-exclamation-triangle',
190
+ acceptButtonStyleClass: 'p-button-danger',
191
+ accept: async () => {
192
+ try {
193
+ await this.templateService.deleteAsync({ id: template.id, type: 'delete' });
194
+ this.messageService.add({
195
+ severity: 'success',
196
+ summary: 'Success',
197
+ detail: 'Template deleted successfully.',
198
+ });
199
+ this.loadTemplates();
200
+ }
201
+ catch (error) {
202
+ this.messageService.add({
203
+ severity: 'error',
204
+ summary: 'Error',
205
+ detail: 'Failed to delete template.',
206
+ });
207
+ }
208
+ },
209
+ });
210
+ }
211
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: TemplateListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
212
+ 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: `
213
+ <div class="card">
214
+ <div class="flex justify-between items-center mb-4">
215
+ <div>
216
+ <h3 class="text-xl font-semibold">Email Templates</h3>
217
+ @if (showCompanyInfo()) {
218
+ <p class="text-sm text-gray-600 mt-1">
219
+ Company: {{ currentCompanyName() }}
220
+ </p>
221
+ }
222
+ </div>
223
+ <p-button
224
+ label="New Template"
225
+ icon="pi pi-plus"
226
+ (onClick)="onCreate()"
227
+ />
228
+ </div>
229
+
230
+ <p-table
231
+ [value]="templates()"
232
+ [loading]="isLoading()"
233
+ [paginator]="true"
234
+ [rows]="pageSize()"
235
+ [totalRecords]="totalRecords()"
236
+ [lazy]="true"
237
+ (onLazyLoad)="onPageChange($event)"
238
+ styleClass="p-datatable-sm"
239
+ >
240
+ <ng-template #header>
241
+ <tr>
242
+ <th>Name</th>
243
+ <th>Slug</th>
244
+ <th>Subject</th>
245
+ <th>Type</th>
246
+ <th>Status</th>
247
+ <th>Created</th>
248
+ <th style="width: 150px">Actions</th>
249
+ </tr>
250
+ </ng-template>
251
+
252
+ <ng-template #body let-template>
253
+ <tr>
254
+ <td>
255
+ <i class="pi pi-envelope mr-2"></i>
256
+ {{ template.name }}
257
+ </td>
258
+ <td>
259
+ <code class="text-sm bg-gray-100 px-2 py-1 rounded">{{ template.slug }}</code>
260
+ </td>
261
+ <td>{{ template.subject }}</td>
262
+ <td>
263
+ <p-tag
264
+ [value]="template.isHtml ? 'HTML' : 'Text'"
265
+ [severity]="template.isHtml ? 'info' : 'secondary'"
266
+ />
267
+ </td>
268
+ <td>
269
+ <p-tag
270
+ [value]="template.isActive ? 'Active' : 'Inactive'"
271
+ [severity]="template.isActive ? 'success' : 'secondary'"
272
+ />
273
+ </td>
274
+ <td>{{ template.createdAt | date: 'short' }}</td>
275
+ <td>
276
+ <p-button
277
+ icon="pi pi-send"
278
+ [text]="true"
279
+ size="small"
280
+ pTooltip="Test Send"
281
+ (onClick)="onTestSend(template)"
282
+ />
283
+ <p-button
284
+ icon="pi pi-pencil"
285
+ [text]="true"
286
+ severity="secondary"
287
+ size="small"
288
+ (onClick)="onEdit(template.id)"
289
+ />
290
+ <p-button
291
+ icon="pi pi-trash"
292
+ [text]="true"
293
+ severity="danger"
294
+ size="small"
295
+ (onClick)="onDelete(template)"
296
+ />
297
+ </td>
298
+ </tr>
299
+ </ng-template>
300
+
301
+ <ng-template #emptymessage>
302
+ <tr>
303
+ <td colspan="7" class="text-center py-4">
304
+ No email templates found. Create your first template to get started.
305
+ </td>
306
+ </tr>
307
+ </ng-template>
308
+ </p-table>
309
+ </div>
310
+
311
+ <!-- Test Send Dialog -->
312
+ <p-dialog
313
+ header="Test Send Email"
314
+ [(visible)]="showTestDialog"
315
+ [modal]="true"
316
+ [style]="{ width: '500px', maxHeight: '80vh' }"
317
+ >
318
+ <div class="grid gap-4">
319
+ <div class="field">
320
+ <label class="block font-medium mb-2">Template</label>
321
+ <input
322
+ pInputText
323
+ [value]="selectedTemplate()?.name || ''"
324
+ class="w-full"
325
+ [disabled]="true"
326
+ />
327
+ </div>
328
+
329
+ <div class="field">
330
+ <label class="block font-medium mb-2">Email Configuration *</label>
331
+ <p-select
332
+ [(ngModel)]="testSendModel.configId"
333
+ [options]="configOptions()"
334
+ optionLabel="label"
335
+ optionValue="value"
336
+ placeholder="Select configuration"
337
+ class="w-full"
338
+ [loading]="isLoadingConfigs()"
339
+ />
340
+ </div>
341
+
342
+ <div class="field">
343
+ <label class="block font-medium mb-2">Recipient Email *</label>
344
+ <input
345
+ pInputText
346
+ [(ngModel)]="testSendModel.recipient"
347
+ class="w-full"
348
+ placeholder="recipient@example.com"
349
+ />
350
+ </div>
351
+
352
+ <!-- Dynamic Variables -->
353
+ @if (templateVariables().length > 0) {
354
+ <div class="border-t pt-4 mt-2">
355
+ <h4 class="font-medium mb-3 flex items-center gap-2">
356
+ <i class="pi pi-code"></i>
357
+ Template Variables
358
+ </h4>
359
+ <div class="grid gap-3">
360
+ @for (variable of templateVariables(); track variable) {
361
+ <div class="field">
362
+ <label class="block text-sm font-medium mb-1">
363
+ <code class="text-primary">{{ '{{' + variable + '}}' }}</code>
364
+ </label>
365
+ <input
366
+ pInputText
367
+ [ngModel]="variableValues[variable] || ''"
368
+ (ngModelChange)="variableValues[variable] = $event"
369
+ class="w-full"
370
+ [placeholder]="'Enter value for ' + variable"
371
+ />
372
+ </div>
373
+ }
374
+ </div>
375
+ </div>
376
+ }
377
+ </div>
378
+
379
+ <ng-template #footer>
380
+ <p-button
381
+ label="Cancel"
382
+ [text]="true"
383
+ (onClick)="showTestDialog = false"
384
+ />
385
+ <p-button
386
+ label="Send Test"
387
+ icon="pi pi-send"
388
+ [loading]="isSendingTest()"
389
+ (onClick)="sendTestEmail()"
390
+ />
391
+ </ng-template>
392
+ </p-dialog>
393
+
394
+ <p-confirmDialog />
395
+ <p-toast />
396
+ `, 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: i9.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: "ngmodule", type: FormsModule }, { kind: "pipe", type: i11.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
397
+ }
398
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: TemplateListComponent, decorators: [{
399
+ type: Component,
400
+ args: [{
401
+ selector: 'lib-template-list',
402
+ standalone: true,
403
+ changeDetection: ChangeDetectionStrategy.OnPush,
404
+ imports: [AngularModule, PrimeModule, FormsModule],
405
+ providers: [ConfirmationService, MessageService],
406
+ template: `
407
+ <div class="card">
408
+ <div class="flex justify-between items-center mb-4">
409
+ <div>
410
+ <h3 class="text-xl font-semibold">Email Templates</h3>
411
+ @if (showCompanyInfo()) {
412
+ <p class="text-sm text-gray-600 mt-1">
413
+ Company: {{ currentCompanyName() }}
414
+ </p>
415
+ }
416
+ </div>
417
+ <p-button
418
+ label="New Template"
419
+ icon="pi pi-plus"
420
+ (onClick)="onCreate()"
421
+ />
422
+ </div>
423
+
424
+ <p-table
425
+ [value]="templates()"
426
+ [loading]="isLoading()"
427
+ [paginator]="true"
428
+ [rows]="pageSize()"
429
+ [totalRecords]="totalRecords()"
430
+ [lazy]="true"
431
+ (onLazyLoad)="onPageChange($event)"
432
+ styleClass="p-datatable-sm"
433
+ >
434
+ <ng-template #header>
435
+ <tr>
436
+ <th>Name</th>
437
+ <th>Slug</th>
438
+ <th>Subject</th>
439
+ <th>Type</th>
440
+ <th>Status</th>
441
+ <th>Created</th>
442
+ <th style="width: 150px">Actions</th>
443
+ </tr>
444
+ </ng-template>
445
+
446
+ <ng-template #body let-template>
447
+ <tr>
448
+ <td>
449
+ <i class="pi pi-envelope mr-2"></i>
450
+ {{ template.name }}
451
+ </td>
452
+ <td>
453
+ <code class="text-sm bg-gray-100 px-2 py-1 rounded">{{ template.slug }}</code>
454
+ </td>
455
+ <td>{{ template.subject }}</td>
456
+ <td>
457
+ <p-tag
458
+ [value]="template.isHtml ? 'HTML' : 'Text'"
459
+ [severity]="template.isHtml ? 'info' : 'secondary'"
460
+ />
461
+ </td>
462
+ <td>
463
+ <p-tag
464
+ [value]="template.isActive ? 'Active' : 'Inactive'"
465
+ [severity]="template.isActive ? 'success' : 'secondary'"
466
+ />
467
+ </td>
468
+ <td>{{ template.createdAt | date: 'short' }}</td>
469
+ <td>
470
+ <p-button
471
+ icon="pi pi-send"
472
+ [text]="true"
473
+ size="small"
474
+ pTooltip="Test Send"
475
+ (onClick)="onTestSend(template)"
476
+ />
477
+ <p-button
478
+ icon="pi pi-pencil"
479
+ [text]="true"
480
+ severity="secondary"
481
+ size="small"
482
+ (onClick)="onEdit(template.id)"
483
+ />
484
+ <p-button
485
+ icon="pi pi-trash"
486
+ [text]="true"
487
+ severity="danger"
488
+ size="small"
489
+ (onClick)="onDelete(template)"
490
+ />
491
+ </td>
492
+ </tr>
493
+ </ng-template>
494
+
495
+ <ng-template #emptymessage>
496
+ <tr>
497
+ <td colspan="7" class="text-center py-4">
498
+ No email templates found. Create your first template to get started.
499
+ </td>
500
+ </tr>
501
+ </ng-template>
502
+ </p-table>
503
+ </div>
504
+
505
+ <!-- Test Send Dialog -->
506
+ <p-dialog
507
+ header="Test Send Email"
508
+ [(visible)]="showTestDialog"
509
+ [modal]="true"
510
+ [style]="{ width: '500px', maxHeight: '80vh' }"
511
+ >
512
+ <div class="grid gap-4">
513
+ <div class="field">
514
+ <label class="block font-medium mb-2">Template</label>
515
+ <input
516
+ pInputText
517
+ [value]="selectedTemplate()?.name || ''"
518
+ class="w-full"
519
+ [disabled]="true"
520
+ />
521
+ </div>
522
+
523
+ <div class="field">
524
+ <label class="block font-medium mb-2">Email Configuration *</label>
525
+ <p-select
526
+ [(ngModel)]="testSendModel.configId"
527
+ [options]="configOptions()"
528
+ optionLabel="label"
529
+ optionValue="value"
530
+ placeholder="Select configuration"
531
+ class="w-full"
532
+ [loading]="isLoadingConfigs()"
533
+ />
534
+ </div>
535
+
536
+ <div class="field">
537
+ <label class="block font-medium mb-2">Recipient Email *</label>
538
+ <input
539
+ pInputText
540
+ [(ngModel)]="testSendModel.recipient"
541
+ class="w-full"
542
+ placeholder="recipient@example.com"
543
+ />
544
+ </div>
545
+
546
+ <!-- Dynamic Variables -->
547
+ @if (templateVariables().length > 0) {
548
+ <div class="border-t pt-4 mt-2">
549
+ <h4 class="font-medium mb-3 flex items-center gap-2">
550
+ <i class="pi pi-code"></i>
551
+ Template Variables
552
+ </h4>
553
+ <div class="grid gap-3">
554
+ @for (variable of templateVariables(); track variable) {
555
+ <div class="field">
556
+ <label class="block text-sm font-medium mb-1">
557
+ <code class="text-primary">{{ '{{' + variable + '}}' }}</code>
558
+ </label>
559
+ <input
560
+ pInputText
561
+ [ngModel]="variableValues[variable] || ''"
562
+ (ngModelChange)="variableValues[variable] = $event"
563
+ class="w-full"
564
+ [placeholder]="'Enter value for ' + variable"
565
+ />
566
+ </div>
567
+ }
568
+ </div>
569
+ </div>
570
+ }
571
+ </div>
572
+
573
+ <ng-template #footer>
574
+ <p-button
575
+ label="Cancel"
576
+ [text]="true"
577
+ (onClick)="showTestDialog = false"
578
+ />
579
+ <p-button
580
+ label="Send Test"
581
+ icon="pi pi-send"
582
+ [loading]="isSendingTest()"
583
+ (onClick)="sendTestEmail()"
584
+ />
585
+ </ng-template>
586
+ </p-dialog>
587
+
588
+ <p-confirmDialog />
589
+ <p-toast />
590
+ `,
591
+ }]
592
+ }] });
593
+
594
+ export { TemplateListComponent };
595
+ //# sourceMappingURL=flusys-ng-email-template-list.component-CGJxfZMT.mjs.map