@flusys/ng-email 1.1.0-beta → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,13 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { inject, signal, computed, ChangeDetectionStrategy, Component } from '@angular/core';
3
3
  import { Router } from '@angular/router';
4
- import * as i1 from '@angular/forms';
5
- import { FormsModule } from '@angular/forms';
6
4
  import { firstValueFrom } from 'rxjs';
7
- import { ConfirmationService, MessageService } from 'primeng/api';
8
5
  import { APP_CONFIG, DEFAULT_APP_NAME } from '@flusys/ng-core';
9
6
  import { LAYOUT_AUTH_STATE } from '@flusys/ng-layout';
10
- import { AngularModule, PrimeModule } from '@flusys/ng-shared';
7
+ import { EMAIL_TEMPLATE_PERMISSIONS, AngularModule, PrimeModule, HasPermissionDirective } from '@flusys/ng-shared';
8
+ import { ConfirmationService, MessageService } from 'primeng/api';
11
9
  import { EmailTemplateApiService, EmailConfigApiService, EmailSendService } from './flusys-ng-email.mjs';
10
+ import * as i1 from '@angular/forms';
12
11
  import * as i2 from 'primeng/button';
13
12
  import * as i3 from 'primeng/confirmdialog';
14
13
  import * as i4 from 'primeng/dialog';
@@ -16,7 +15,7 @@ import * as i5 from 'primeng/inputtext';
16
15
  import * as i6 from 'primeng/select';
17
16
  import * as i7 from 'primeng/table';
18
17
  import * as i8 from 'primeng/tag';
19
- import * as i9 from 'primeng/toast';
18
+ import * as i8$1 from 'primeng/toast';
20
19
  import * as i10 from 'primeng/tooltip';
21
20
  import * as i11 from '@angular/common';
22
21
 
@@ -24,6 +23,8 @@ import * as i11 from '@angular/common';
24
23
  * Email template list component
25
24
  */
26
25
  class TemplateListComponent {
26
+ // Permission constants for template
27
+ EMAIL_TEMPLATE_PERMISSIONS = EMAIL_TEMPLATE_PERMISSIONS;
27
28
  router = inject(Router);
28
29
  templateService = inject(EmailTemplateApiService);
29
30
  configService = inject(EmailConfigApiService);
@@ -31,23 +32,35 @@ class TemplateListComponent {
31
32
  confirmationService = inject(ConfirmationService);
32
33
  messageService = inject(MessageService);
33
34
  appConfig = inject(APP_CONFIG);
34
- companyContext = inject(LAYOUT_AUTH_STATE);
35
+ companyContext = inject(LAYOUT_AUTH_STATE, { optional: true });
35
36
  isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
36
37
  templates = signal([], ...(ngDevMode ? [{ debugName: "templates" }] : []));
37
38
  totalRecords = signal(0, ...(ngDevMode ? [{ debugName: "totalRecords" }] : []));
38
39
  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;
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" }] : []));
44
45
  selectedTemplate = signal(null, ...(ngDevMode ? [{ debugName: "selectedTemplate" }] : []));
45
46
  configs = signal([], ...(ngDevMode ? [{ debugName: "configs" }] : []));
46
47
  isLoadingConfigs = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingConfigs" }] : []));
47
48
  isSendingTest = signal(false, ...(ngDevMode ? [{ debugName: "isSendingTest" }] : []));
48
- // Form-bound properties (ngModel requires plain properties)
49
- testSendModel = { configId: '', recipient: '' };
50
- variableValues = {};
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
+ }
51
64
  configOptions = computed(() => this.configs().map((c) => ({
52
65
  label: `${c.name} (${c.provider})`,
53
66
  value: c.id,
@@ -71,9 +84,6 @@ class TemplateListComponent {
71
84
  }
72
85
  return Array.from(variables).sort();
73
86
  }, ...(ngDevMode ? [{ debugName: "templateVariables" }] : []));
74
- ngOnInit() {
75
- this.loadTemplates();
76
- }
77
87
  onCreate() {
78
88
  this.router.navigate(['/email/templates/new']);
79
89
  }
@@ -85,7 +95,7 @@ class TemplateListComponent {
85
95
  try {
86
96
  const response = await firstValueFrom(this.templateService.getAll('', {
87
97
  pagination: {
88
- currentPage: this.currentPage() - 1,
98
+ currentPage: Math.floor(this.first() / this.pageSize()),
89
99
  pageSize: this.pageSize(),
90
100
  },
91
101
  filter: {},
@@ -101,16 +111,16 @@ class TemplateListComponent {
101
111
  this.isLoading.set(false);
102
112
  }
103
113
  }
104
- onPageChange(event) {
105
- this.currentPage.set(event.first / event.rows + 1);
106
- this.pageSize.set(event.rows);
114
+ onLazyLoad(event) {
115
+ this.first.set(event.first ?? 0);
116
+ this.pageSize.set(event.rows ?? 10);
107
117
  this.loadTemplates();
108
118
  }
109
119
  async onTestSend(template) {
110
120
  this.selectedTemplate.set(template);
111
- this.testSendModel = { configId: '', recipient: '' };
112
- this.variableValues = {}; // Reset variable values
113
- this.showTestDialog = true;
121
+ this._testSendModel.set({ configId: '', recipient: '' });
122
+ this._variableValues.set({}); // Reset variable values
123
+ this.showTestDialog.set(true);
114
124
  await this.loadConfigs();
115
125
  }
116
126
  async loadConfigs() {
@@ -132,7 +142,8 @@ class TemplateListComponent {
132
142
  }
133
143
  async sendTestEmail() {
134
144
  const template = this.selectedTemplate();
135
- if (!template || !this.testSendModel.configId || !this.testSendModel.recipient) {
145
+ const model = this.testSendModel;
146
+ if (!template || !model.configId || !model.recipient) {
136
147
  this.messageService.add({
137
148
  severity: 'warn',
138
149
  summary: 'Validation',
@@ -151,8 +162,8 @@ class TemplateListComponent {
151
162
  }
152
163
  const response = await firstValueFrom(this.emailSendService.sendTemplate({
153
164
  templateId: template.id,
154
- to: this.testSendModel.recipient,
155
- emailConfigId: this.testSendModel.configId,
165
+ to: model.recipient,
166
+ emailConfigId: model.configId,
156
167
  variables: Object.keys(variables).length > 0 ? variables : undefined,
157
168
  }));
158
169
  if (response.data?.success) {
@@ -161,7 +172,7 @@ class TemplateListComponent {
161
172
  summary: 'Success',
162
173
  detail: `Test email sent! Message ID: ${response.data.messageId}`,
163
174
  });
164
- this.showTestDialog = false;
175
+ this.showTestDialog.set(false);
165
176
  }
166
177
  else {
167
178
  this.messageService.add({
@@ -171,12 +182,8 @@ class TemplateListComponent {
171
182
  });
172
183
  }
173
184
  }
174
- catch (error) {
175
- this.messageService.add({
176
- severity: 'error',
177
- summary: 'Error',
178
- detail: error?.message || 'Failed to send test email.',
179
- });
185
+ catch {
186
+ // Error toast handled by global interceptor
180
187
  }
181
188
  finally {
182
189
  this.isSendingTest.set(false);
@@ -198,124 +205,136 @@ class TemplateListComponent {
198
205
  });
199
206
  this.loadTemplates();
200
207
  }
201
- catch (error) {
202
- this.messageService.add({
203
- severity: 'error',
204
- summary: 'Error',
205
- detail: 'Failed to delete template.',
206
- });
208
+ catch {
209
+ // Error toast handled by global interceptor
207
210
  }
208
211
  },
209
212
  });
210
213
  }
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: `
214
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: TemplateListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
215
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: TemplateListComponent, isStandalone: true, selector: "lib-template-list", providers: [ConfirmationService, MessageService], ngImport: i0, template: `
213
216
  <div class="card">
214
- <div class="flex justify-between items-center mb-4">
217
+ <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 mb-4">
215
218
  <div>
216
- <h3 class="text-xl font-semibold">Email Templates</h3>
219
+ <h3 class="text-lg sm:text-xl font-semibold m-0">Email Templates</h3>
217
220
  @if (showCompanyInfo()) {
218
- <p class="text-sm text-gray-600 mt-1">
221
+ <p class="text-sm text-muted-color mt-1">
219
222
  Company: {{ currentCompanyName() }}
220
223
  </p>
221
224
  }
222
225
  </div>
223
226
  <p-button
227
+ *hasPermission="EMAIL_TEMPLATE_PERMISSIONS.CREATE"
224
228
  label="New Template"
225
229
  icon="pi pi-plus"
226
230
  (onClick)="onCreate()"
231
+ styleClass="w-full sm:w-auto"
227
232
  />
228
233
  </div>
229
234
 
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>
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>
251
260
 
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>
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>
300
316
 
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>
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>
309
326
  </div>
310
327
 
311
328
  <!-- Test Send Dialog -->
312
329
  <p-dialog
313
330
  header="Test Send Email"
314
- [(visible)]="showTestDialog"
331
+ [visible]="showTestDialog()"
332
+ (visibleChange)="showTestDialog.set($event)"
315
333
  [modal]="true"
316
- [style]="{ width: '500px', maxHeight: '80vh' }"
334
+ [style]="{ width: '95vw', maxWidth: '500px', maxHeight: '80vh' }"
335
+ [breakpoints]="{ '575px': '95vw' }"
317
336
  >
318
- <div class="grid gap-4">
337
+ <div class="flex flex-col gap-4">
319
338
  <div class="field">
320
339
  <label class="block font-medium mb-2">Template</label>
321
340
  <input
@@ -329,7 +348,8 @@ class TemplateListComponent {
329
348
  <div class="field">
330
349
  <label class="block font-medium mb-2">Email Configuration *</label>
331
350
  <p-select
332
- [(ngModel)]="testSendModel.configId"
351
+ [ngModel]="testSendModel.configId"
352
+ (ngModelChange)="updateTestSendModel('configId', $event)"
333
353
  [options]="configOptions()"
334
354
  optionLabel="label"
335
355
  optionValue="value"
@@ -343,7 +363,8 @@ class TemplateListComponent {
343
363
  <label class="block font-medium mb-2">Recipient Email *</label>
344
364
  <input
345
365
  pInputText
346
- [(ngModel)]="testSendModel.recipient"
366
+ [ngModel]="testSendModel.recipient"
367
+ (ngModelChange)="updateTestSendModel('recipient', $event)"
347
368
  class="w-full"
348
369
  placeholder="recipient@example.com"
349
370
  />
@@ -351,21 +372,21 @@ class TemplateListComponent {
351
372
 
352
373
  <!-- Dynamic Variables -->
353
374
  @if (templateVariables().length > 0) {
354
- <div class="border-t pt-4 mt-2">
375
+ <div class="border-t border-surface pt-4 mt-2">
355
376
  <h4 class="font-medium mb-3 flex items-center gap-2">
356
- <i class="pi pi-code"></i>
377
+ <i class="pi pi-code text-muted-color"></i>
357
378
  Template Variables
358
379
  </h4>
359
- <div class="grid gap-3">
380
+ <div class="flex flex-col gap-3">
360
381
  @for (variable of templateVariables(); track variable) {
361
382
  <div class="field">
362
383
  <label class="block text-sm font-medium mb-1">
363
- <code class="text-primary">{{ '{{' + variable + '}}' }}</code>
384
+ <code class="text-primary bg-surface-100 dark:bg-surface-700 px-1 rounded">{{ '{{' + variable + '}}' }}</code>
364
385
  </label>
365
386
  <input
366
387
  pInputText
367
388
  [ngModel]="variableValues[variable] || ''"
368
- (ngModelChange)="variableValues[variable] = $event"
389
+ (ngModelChange)="updateVariableValue(variable, $event)"
369
390
  class="w-full"
370
391
  [placeholder]="'Enter value for ' + variable"
371
392
  />
@@ -379,8 +400,9 @@ class TemplateListComponent {
379
400
  <ng-template #footer>
380
401
  <p-button
381
402
  label="Cancel"
382
- [text]="true"
383
- (onClick)="showTestDialog = false"
403
+ severity="secondary"
404
+ [outlined]="true"
405
+ (onClick)="showTestDialog.set(false)"
384
406
  />
385
407
  <p-button
386
408
  label="Send Test"
@@ -393,123 +415,138 @@ class TemplateListComponent {
393
415
 
394
416
  <p-confirmDialog />
395
417
  <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 });
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 });
397
419
  }
398
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: TemplateListComponent, decorators: [{
420
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: TemplateListComponent, decorators: [{
399
421
  type: Component,
400
422
  args: [{
401
423
  selector: 'lib-template-list',
402
- standalone: true,
403
424
  changeDetection: ChangeDetectionStrategy.OnPush,
404
- imports: [AngularModule, PrimeModule, FormsModule],
425
+ imports: [AngularModule, PrimeModule, HasPermissionDirective],
405
426
  providers: [ConfirmationService, MessageService],
406
427
  template: `
407
428
  <div class="card">
408
- <div class="flex justify-between items-center mb-4">
429
+ <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 mb-4">
409
430
  <div>
410
- <h3 class="text-xl font-semibold">Email Templates</h3>
431
+ <h3 class="text-lg sm:text-xl font-semibold m-0">Email Templates</h3>
411
432
  @if (showCompanyInfo()) {
412
- <p class="text-sm text-gray-600 mt-1">
433
+ <p class="text-sm text-muted-color mt-1">
413
434
  Company: {{ currentCompanyName() }}
414
435
  </p>
415
436
  }
416
437
  </div>
417
438
  <p-button
439
+ *hasPermission="EMAIL_TEMPLATE_PERMISSIONS.CREATE"
418
440
  label="New Template"
419
441
  icon="pi pi-plus"
420
442
  (onClick)="onCreate()"
443
+ styleClass="w-full sm:w-auto"
421
444
  />
422
445
  </div>
423
446
 
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>
447
+ <div class="overflow-x-auto -mx-4 sm:mx-0">
448
+ <p-table
449
+ [value]="templates()"
450
+ [loading]="isLoading()"
451
+ [paginator]="totalRecords() > 0"
452
+ [rows]="pageSize()"
453
+ [first]="first()"
454
+ [totalRecords]="totalRecords()"
455
+ [lazy]="true"
456
+ (onLazyLoad)="onLazyLoad($event)"
457
+ [rowsPerPageOptions]="[10, 25, 50]"
458
+ styleClass="p-datatable-sm"
459
+ [tableStyle]="{ 'min-width': '50rem' }"
460
+ >
461
+ <ng-template #header>
462
+ <tr>
463
+ <th>Name</th>
464
+ <th class="hidden md:table-cell">Slug</th>
465
+ <th class="hidden lg:table-cell">Subject</th>
466
+ <th>Type</th>
467
+ <th>Status</th>
468
+ <th class="hidden xl:table-cell">Created</th>
469
+ <th class="w-[120px]">Actions</th>
470
+ </tr>
471
+ </ng-template>
445
472
 
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>
473
+ <ng-template #body let-template>
474
+ <tr>
475
+ <td>
476
+ <i class="pi pi-envelope mr-2 text-muted-color"></i>
477
+ {{ template.name }}
478
+ </td>
479
+ <td class="hidden md:table-cell">
480
+ <code class="text-sm bg-surface-100 dark:bg-surface-700 px-2 py-1 rounded">{{ template.slug }}</code>
481
+ </td>
482
+ <td class="hidden lg:table-cell">{{ template.subject }}</td>
483
+ <td>
484
+ <p-tag
485
+ [value]="template.isHtml ? 'HTML' : 'Text'"
486
+ [severity]="template.isHtml ? 'info' : 'secondary'"
487
+ />
488
+ </td>
489
+ <td>
490
+ <p-tag
491
+ [value]="template.isActive ? 'Active' : 'Inactive'"
492
+ [severity]="template.isActive ? 'success' : 'secondary'"
493
+ />
494
+ </td>
495
+ <td class="hidden xl:table-cell">{{ template.createdAt | date: 'short' }}</td>
496
+ <td>
497
+ <div class="flex gap-1">
498
+ <p-button
499
+ *hasPermission="EMAIL_TEMPLATE_PERMISSIONS.UPDATE"
500
+ icon="pi pi-send"
501
+ [text]="true"
502
+ size="small"
503
+ pTooltip="Test Send"
504
+ (onClick)="onTestSend(template)"
505
+ />
506
+ <p-button
507
+ *hasPermission="EMAIL_TEMPLATE_PERMISSIONS.UPDATE"
508
+ icon="pi pi-pencil"
509
+ [text]="true"
510
+ severity="secondary"
511
+ size="small"
512
+ pTooltip="Edit"
513
+ (onClick)="onEdit(template.id)"
514
+ />
515
+ <p-button
516
+ *hasPermission="EMAIL_TEMPLATE_PERMISSIONS.DELETE"
517
+ icon="pi pi-trash"
518
+ [text]="true"
519
+ severity="danger"
520
+ size="small"
521
+ pTooltip="Delete"
522
+ (onClick)="onDelete(template)"
523
+ />
524
+ </div>
525
+ </td>
526
+ </tr>
527
+ </ng-template>
494
528
 
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>
529
+ <ng-template #emptymessage>
530
+ <tr>
531
+ <td colspan="7" class="text-center py-4 text-muted-color">
532
+ No email templates found. Create your first template to get started.
533
+ </td>
534
+ </tr>
535
+ </ng-template>
536
+ </p-table>
537
+ </div>
503
538
  </div>
504
539
 
505
540
  <!-- Test Send Dialog -->
506
541
  <p-dialog
507
542
  header="Test Send Email"
508
- [(visible)]="showTestDialog"
543
+ [visible]="showTestDialog()"
544
+ (visibleChange)="showTestDialog.set($event)"
509
545
  [modal]="true"
510
- [style]="{ width: '500px', maxHeight: '80vh' }"
546
+ [style]="{ width: '95vw', maxWidth: '500px', maxHeight: '80vh' }"
547
+ [breakpoints]="{ '575px': '95vw' }"
511
548
  >
512
- <div class="grid gap-4">
549
+ <div class="flex flex-col gap-4">
513
550
  <div class="field">
514
551
  <label class="block font-medium mb-2">Template</label>
515
552
  <input
@@ -523,7 +560,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
523
560
  <div class="field">
524
561
  <label class="block font-medium mb-2">Email Configuration *</label>
525
562
  <p-select
526
- [(ngModel)]="testSendModel.configId"
563
+ [ngModel]="testSendModel.configId"
564
+ (ngModelChange)="updateTestSendModel('configId', $event)"
527
565
  [options]="configOptions()"
528
566
  optionLabel="label"
529
567
  optionValue="value"
@@ -537,7 +575,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
537
575
  <label class="block font-medium mb-2">Recipient Email *</label>
538
576
  <input
539
577
  pInputText
540
- [(ngModel)]="testSendModel.recipient"
578
+ [ngModel]="testSendModel.recipient"
579
+ (ngModelChange)="updateTestSendModel('recipient', $event)"
541
580
  class="w-full"
542
581
  placeholder="recipient@example.com"
543
582
  />
@@ -545,21 +584,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
545
584
 
546
585
  <!-- Dynamic Variables -->
547
586
  @if (templateVariables().length > 0) {
548
- <div class="border-t pt-4 mt-2">
587
+ <div class="border-t border-surface pt-4 mt-2">
549
588
  <h4 class="font-medium mb-3 flex items-center gap-2">
550
- <i class="pi pi-code"></i>
589
+ <i class="pi pi-code text-muted-color"></i>
551
590
  Template Variables
552
591
  </h4>
553
- <div class="grid gap-3">
592
+ <div class="flex flex-col gap-3">
554
593
  @for (variable of templateVariables(); track variable) {
555
594
  <div class="field">
556
595
  <label class="block text-sm font-medium mb-1">
557
- <code class="text-primary">{{ '{{' + variable + '}}' }}</code>
596
+ <code class="text-primary bg-surface-100 dark:bg-surface-700 px-1 rounded">{{ '{{' + variable + '}}' }}</code>
558
597
  </label>
559
598
  <input
560
599
  pInputText
561
600
  [ngModel]="variableValues[variable] || ''"
562
- (ngModelChange)="variableValues[variable] = $event"
601
+ (ngModelChange)="updateVariableValue(variable, $event)"
563
602
  class="w-full"
564
603
  [placeholder]="'Enter value for ' + variable"
565
604
  />
@@ -573,8 +612,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
573
612
  <ng-template #footer>
574
613
  <p-button
575
614
  label="Cancel"
576
- [text]="true"
577
- (onClick)="showTestDialog = false"
615
+ severity="secondary"
616
+ [outlined]="true"
617
+ (onClick)="showTestDialog.set(false)"
578
618
  />
579
619
  <p-button
580
620
  label="Send Test"
@@ -592,4 +632,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
592
632
  }] });
593
633
 
594
634
  export { TemplateListComponent };
595
- //# sourceMappingURL=flusys-ng-email-template-list.component-CGJxfZMT.mjs.map
635
+ //# sourceMappingURL=flusys-ng-email-template-list.component-krrpsjDv.mjs.map