orque-ui 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1970 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Component, EventEmitter, HostListener, Output, Input, signal, forwardRef, ChangeDetectionStrategy, ViewChild, Injectable } from '@angular/core';
3
+ import * as i1$3 from '@angular/common';
4
+ import { DatePipe, CommonModule, NgClass } from '@angular/common';
5
+ import * as i1$1 from '@angular/forms';
6
+ import { FormGroup, FormControl, ReactiveFormsModule, NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
7
+ import { Subject, BehaviorSubject } from 'rxjs';
8
+ import { takeUntil, debounceTime, distinctUntilChanged } from 'rxjs/operators';
9
+ import * as i1 from '@angular/common/http';
10
+ import * as i1$2 from '@angular/router';
11
+ import { RouterModule } from '@angular/router';
12
+
13
+ class OUi {
14
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OUi, deps: [], target: i0.ɵɵFactoryTarget.Component });
15
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.2", type: OUi, isStandalone: true, selector: "o-o-ui", ngImport: i0, template: '', isInline: true });
16
+ }
17
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OUi, decorators: [{
18
+ type: Component,
19
+ args: [{
20
+ selector: 'o-o-ui',
21
+ standalone: true,
22
+ imports: [],
23
+ template: '',
24
+ }]
25
+ }] });
26
+
27
+ /**
28
+ * OPAC JSON Page Rendering Model
29
+ */
30
+
31
+ class ContextSwitcherComponent {
32
+ elementRef;
33
+ currentContext = 'OPAC';
34
+ activeTenant = null;
35
+ contextChange = new EventEmitter();
36
+ dropdownOpen = false;
37
+ constructor(elementRef) {
38
+ this.elementRef = elementRef;
39
+ }
40
+ toggleDropdown() {
41
+ this.dropdownOpen = !this.dropdownOpen;
42
+ }
43
+ isLicensed(product) {
44
+ if (product === 'OPAC')
45
+ return true;
46
+ if (!this.activeTenant || !this.activeTenant.settings_json)
47
+ return false;
48
+ let settings = this.activeTenant.settings_json;
49
+ if (typeof settings === 'string') {
50
+ try {
51
+ settings = JSON.parse(settings);
52
+ }
53
+ catch (e) {
54
+ return false;
55
+ }
56
+ }
57
+ const licensed = settings.licensedProducts || {};
58
+ return !!(licensed[product] && licensed[product].enabled);
59
+ }
60
+ switchContext(product) {
61
+ if (!this.isLicensed(product))
62
+ return;
63
+ this.currentContext = product;
64
+ this.dropdownOpen = false;
65
+ this.contextChange.emit(product);
66
+ }
67
+ getContextLabel() {
68
+ if (this.currentContext === 'OPAC') {
69
+ return 'OPAC Administration';
70
+ }
71
+ return `Orque ${this.currentContext}`;
72
+ }
73
+ onClickOutside(event) {
74
+ if (!this.elementRef.nativeElement.contains(event.target)) {
75
+ this.dropdownOpen = false;
76
+ }
77
+ }
78
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ContextSwitcherComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
79
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ContextSwitcherComponent, isStandalone: true, selector: "o-context-switcher", inputs: { currentContext: "currentContext", activeTenant: "activeTenant" }, outputs: { contextChange: "contextChange" }, host: { listeners: { "document:click": "onClickOutside($event)" } }, ngImport: i0, template: "<div class=\"context-switcher\">\n <button class=\"context-btn\" (click)=\"toggleDropdown()\">\n {{ getContextLabel() }}\n </button>\n <div class=\"context-dropdown\" [class.active]=\"dropdownOpen\">\n <div class=\"context-item\" [class.active]=\"currentContext === 'OPAC'\" (click)=\"switchContext('OPAC')\">\n <span class=\"context-item-title\">OPAC Administration</span>\n <span class=\"context-item-desc\">Master control panel</span>\n </div>\n @if (isLicensed('ERP')) {\n <div class=\"context-item\"\n [class.active]=\"currentContext === 'ERP'\"\n (click)=\"switchContext('ERP')\">\n <span class=\"context-item-title\">Orque ERP</span>\n <span class=\"context-item-desc\">Enterprise Resource Planning</span>\n </div>\n }\n @if (isLicensed('CRM')) {\n <div class=\"context-item\"\n [class.active]=\"currentContext === 'CRM'\"\n (click)=\"switchContext('CRM')\">\n <span class=\"context-item-title\">Orque CRM</span>\n <span class=\"context-item-desc\">Customer Relationship Management</span>\n </div>\n }\n @if (isLicensed('IMS')) {\n <div class=\"context-item\"\n [class.active]=\"currentContext === 'IMS'\"\n (click)=\"switchContext('IMS')\">\n <span class=\"context-item-title\">Orque IMS</span>\n <span class=\"context-item-desc\">Inventory Management System</span>\n </div>\n }\n </div>\n</div>\n", styles: [".context-switcher{position:relative;display:inline-block}.context-btn{background:var(--glass-bg, rgba(22, 17, 45, .45));border:1px solid var(--glass-border, rgba(255, 255, 255, .08));padding:.6rem 1.2rem;border-radius:var(--radius-md, 10px);color:var(--text-primary, #f3f1f8);font-weight:500;cursor:pointer;display:flex;align-items:center;gap:.6rem;transition:var(--transition, all .25s cubic-bezier(.4, 0, .2, 1));font-family:inherit;font-size:.95rem}.context-btn:hover{background:#ffffff0d;border-color:var(--color-primary, #8b5cf6);box-shadow:0 0 10px var(--color-primary-glow, rgba(139, 92, 246, .4))}.context-btn:after{content:\"\\25bc\";font-size:.7rem;color:var(--text-secondary, #9a94b5)}.context-dropdown{position:absolute;top:calc(100% + 8px);left:0;background:#0f0a20f2;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border:1px solid var(--glass-border, rgba(255, 255, 255, .08));border-radius:var(--radius-md, 10px);box-shadow:var(--glass-glow, 0 8px 32px 0 rgba(0, 0, 0, .37)),0 0 20px #00000080;width:240px;overflow:hidden;display:none;z-index:1000}.context-dropdown.active{display:block;animation:slideDown .2s ease-out}.context-item{display:flex;flex-direction:column;padding:.8rem 1.2rem;cursor:pointer;transition:var(--transition, all .25s cubic-bezier(.4, 0, .2, 1));border-bottom:1px solid rgba(255,255,255,.03);text-align:left}.context-item:last-child{border-bottom:none}.context-item:hover{background:#8b5cf626}.context-item.active{border-left:3px solid var(--color-primary, #8b5cf6);background:#8b5cf61a}.context-item-title{font-weight:600;font-size:.95rem;color:var(--text-primary, #f3f1f8)}.context-item-desc{font-size:.75rem;color:var(--text-secondary, #9a94b5);margin-top:.2rem}.context-item.disabled{opacity:.4;cursor:not-allowed;pointer-events:none}@keyframes slideDown{0%{transform:translateY(-10px);opacity:0}to{transform:translateY(0);opacity:1}}\n"] });
80
+ }
81
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ContextSwitcherComponent, decorators: [{
82
+ type: Component,
83
+ args: [{ selector: 'o-context-switcher', standalone: true, imports: [], template: "<div class=\"context-switcher\">\n <button class=\"context-btn\" (click)=\"toggleDropdown()\">\n {{ getContextLabel() }}\n </button>\n <div class=\"context-dropdown\" [class.active]=\"dropdownOpen\">\n <div class=\"context-item\" [class.active]=\"currentContext === 'OPAC'\" (click)=\"switchContext('OPAC')\">\n <span class=\"context-item-title\">OPAC Administration</span>\n <span class=\"context-item-desc\">Master control panel</span>\n </div>\n @if (isLicensed('ERP')) {\n <div class=\"context-item\"\n [class.active]=\"currentContext === 'ERP'\"\n (click)=\"switchContext('ERP')\">\n <span class=\"context-item-title\">Orque ERP</span>\n <span class=\"context-item-desc\">Enterprise Resource Planning</span>\n </div>\n }\n @if (isLicensed('CRM')) {\n <div class=\"context-item\"\n [class.active]=\"currentContext === 'CRM'\"\n (click)=\"switchContext('CRM')\">\n <span class=\"context-item-title\">Orque CRM</span>\n <span class=\"context-item-desc\">Customer Relationship Management</span>\n </div>\n }\n @if (isLicensed('IMS')) {\n <div class=\"context-item\"\n [class.active]=\"currentContext === 'IMS'\"\n (click)=\"switchContext('IMS')\">\n <span class=\"context-item-title\">Orque IMS</span>\n <span class=\"context-item-desc\">Inventory Management System</span>\n </div>\n }\n </div>\n</div>\n", styles: [".context-switcher{position:relative;display:inline-block}.context-btn{background:var(--glass-bg, rgba(22, 17, 45, .45));border:1px solid var(--glass-border, rgba(255, 255, 255, .08));padding:.6rem 1.2rem;border-radius:var(--radius-md, 10px);color:var(--text-primary, #f3f1f8);font-weight:500;cursor:pointer;display:flex;align-items:center;gap:.6rem;transition:var(--transition, all .25s cubic-bezier(.4, 0, .2, 1));font-family:inherit;font-size:.95rem}.context-btn:hover{background:#ffffff0d;border-color:var(--color-primary, #8b5cf6);box-shadow:0 0 10px var(--color-primary-glow, rgba(139, 92, 246, .4))}.context-btn:after{content:\"\\25bc\";font-size:.7rem;color:var(--text-secondary, #9a94b5)}.context-dropdown{position:absolute;top:calc(100% + 8px);left:0;background:#0f0a20f2;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border:1px solid var(--glass-border, rgba(255, 255, 255, .08));border-radius:var(--radius-md, 10px);box-shadow:var(--glass-glow, 0 8px 32px 0 rgba(0, 0, 0, .37)),0 0 20px #00000080;width:240px;overflow:hidden;display:none;z-index:1000}.context-dropdown.active{display:block;animation:slideDown .2s ease-out}.context-item{display:flex;flex-direction:column;padding:.8rem 1.2rem;cursor:pointer;transition:var(--transition, all .25s cubic-bezier(.4, 0, .2, 1));border-bottom:1px solid rgba(255,255,255,.03);text-align:left}.context-item:last-child{border-bottom:none}.context-item:hover{background:#8b5cf626}.context-item.active{border-left:3px solid var(--color-primary, #8b5cf6);background:#8b5cf61a}.context-item-title{font-weight:600;font-size:.95rem;color:var(--text-primary, #f3f1f8)}.context-item-desc{font-size:.75rem;color:var(--text-secondary, #9a94b5);margin-top:.2rem}.context-item.disabled{opacity:.4;cursor:not-allowed;pointer-events:none}@keyframes slideDown{0%{transform:translateY(-10px);opacity:0}to{transform:translateY(0);opacity:1}}\n"] }]
84
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { currentContext: [{
85
+ type: Input
86
+ }], activeTenant: [{
87
+ type: Input
88
+ }], contextChange: [{
89
+ type: Output
90
+ }], onClickOutside: [{
91
+ type: HostListener,
92
+ args: ['document:click', ['$event']]
93
+ }] } });
94
+
95
+ class ToggleTabsComponent {
96
+ tabs = [];
97
+ activeTab = '';
98
+ tabChange = new EventEmitter();
99
+ select(value) {
100
+ this.activeTab = value;
101
+ this.tabChange.emit(value);
102
+ }
103
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ToggleTabsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
104
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ToggleTabsComponent, isStandalone: true, selector: "o-toggle-tabs", inputs: { tabs: "tabs", activeTab: "activeTab" }, outputs: { tabChange: "tabChange" }, ngImport: i0, template: "<div class=\"o-btn-group\">\n @for (tab of tabs; track tab.value) {\n <button\n class=\"o-btn-group__btn\"\n [class.o-btn-group__btn--active]=\"activeTab === tab.value\"\n (click)=\"select(tab.value)\"\n type=\"button\">\n {{ tab.label }}\n </button>\n }\n</div>\n", styles: [".o-btn-group{display:inline-flex;align-items:center;border:1px solid #15104E;border-radius:4px;background-color:#f1f3f9;padding:0;gap:0;overflow:hidden}.o-btn-group__btn{height:38px;padding:0 24px;font-size:14px;font-weight:600;font-family:inherit;color:#15104e;background:#f1f3f9;border:none;border-right:1px solid #15104E;border-radius:0;cursor:pointer;white-space:nowrap;transition:all .1s ease;line-height:38px;outline:none;text-align:center}.o-btn-group__btn:last-child{border-right:none}.o-btn-group__btn:hover:not(.o-btn-group__btn--active){background:#e5e3fd;color:#15104e}.o-btn-group__btn--active,.o-btn-group__btn--active:hover{background-color:#15104e;color:#fff}\n"] });
105
+ }
106
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ToggleTabsComponent, decorators: [{
107
+ type: Component,
108
+ args: [{ selector: 'o-toggle-tabs', standalone: true, imports: [], template: "<div class=\"o-btn-group\">\n @for (tab of tabs; track tab.value) {\n <button\n class=\"o-btn-group__btn\"\n [class.o-btn-group__btn--active]=\"activeTab === tab.value\"\n (click)=\"select(tab.value)\"\n type=\"button\">\n {{ tab.label }}\n </button>\n }\n</div>\n", styles: [".o-btn-group{display:inline-flex;align-items:center;border:1px solid #15104E;border-radius:4px;background-color:#f1f3f9;padding:0;gap:0;overflow:hidden}.o-btn-group__btn{height:38px;padding:0 24px;font-size:14px;font-weight:600;font-family:inherit;color:#15104e;background:#f1f3f9;border:none;border-right:1px solid #15104E;border-radius:0;cursor:pointer;white-space:nowrap;transition:all .1s ease;line-height:38px;outline:none;text-align:center}.o-btn-group__btn:last-child{border-right:none}.o-btn-group__btn:hover:not(.o-btn-group__btn--active){background:#e5e3fd;color:#15104e}.o-btn-group__btn--active,.o-btn-group__btn--active:hover{background-color:#15104e;color:#fff}\n"] }]
109
+ }], propDecorators: { tabs: [{
110
+ type: Input
111
+ }], activeTab: [{
112
+ type: Input
113
+ }], tabChange: [{
114
+ type: Output
115
+ }] } });
116
+
117
+ class DataTableComponent {
118
+ columns = [];
119
+ rows = [];
120
+ selectable = true;
121
+ actions = [];
122
+ rowClick = new EventEmitter();
123
+ menuOpen = new EventEmitter();
124
+ actionClick = new EventEmitter();
125
+ selectionChange = new EventEmitter();
126
+ visibleColumns = [];
127
+ selectedRows = [];
128
+ menuRow = null;
129
+ ngOnChanges() {
130
+ this.visibleColumns = this.columns.filter(c => c.isHeaderView !== false &&
131
+ c.name !== 'checkbox' &&
132
+ c.name !== 'actionSection');
133
+ }
134
+ selectRow(row) {
135
+ this.rowClick.emit(row);
136
+ }
137
+ onCellClick(event, colName, row) {
138
+ event.stopPropagation();
139
+ event.preventDefault();
140
+ if (colName === 'requestId') {
141
+ this.actionClick.emit({ action: 'view', row });
142
+ }
143
+ }
144
+ isSelected(row) {
145
+ return this.selectedRows.includes(row);
146
+ }
147
+ toggleRow(row) {
148
+ if (this.isSelected(row)) {
149
+ this.selectedRows =
150
+ this.selectedRows.filter(r => r !== row);
151
+ }
152
+ else {
153
+ this.selectedRows.push(row);
154
+ }
155
+ this.selectionChange.emit(this.selectedRows);
156
+ }
157
+ toggleAll(event) {
158
+ const checked = event.target.checked;
159
+ this.selectedRows =
160
+ checked ? [...this.rows] : [];
161
+ this.selectionChange.emit(this.selectedRows);
162
+ }
163
+ isAllSelected() {
164
+ return (this.rows.length > 0 &&
165
+ this.selectedRows.length === this.rows.length);
166
+ }
167
+ openMenu(event, row) {
168
+ event.stopPropagation();
169
+ this.menuOpen.emit({
170
+ event,
171
+ row
172
+ });
173
+ this.menuRow =
174
+ this.menuRow === row
175
+ ? null
176
+ : row;
177
+ }
178
+ triggerAction(action, row, event) {
179
+ event.stopPropagation();
180
+ this.actionClick.emit({
181
+ action,
182
+ row
183
+ });
184
+ this.menuRow = null;
185
+ }
186
+ statusClass(status) {
187
+ if (!status) {
188
+ return '';
189
+ }
190
+ switch (status.toLowerCase()) {
191
+ case 'draft':
192
+ return 'status-draft';
193
+ case 'active':
194
+ case 'approved':
195
+ return 'status-active';
196
+ case 'pending':
197
+ case 'returned':
198
+ case 'in progress':
199
+ return 'status-progress';
200
+ case 'rejected':
201
+ case 'cancelled':
202
+ case 'expired':
203
+ return 'status-rejected';
204
+ default:
205
+ return 'status-inactive';
206
+ }
207
+ }
208
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: DataTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
209
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: DataTableComponent, isStandalone: true, selector: "o-data-table", inputs: { columns: "columns", rows: "rows", selectable: "selectable", actions: "actions" }, outputs: { rowClick: "rowClick", menuOpen: "menuOpen", actionClick: "actionClick", selectionChange: "selectionChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"o-table-wrapper\">\n\n <table class=\"o-table\">\n\n <thead>\n <tr>\n\n <!-- Checkbox Column -->\n <th class=\"checkbox-col\">\n <input\n type=\"checkbox\"\n [checked]=\"isAllSelected()\"\n (change)=\"toggleAll($event)\"\n />\n </th>\n\n <!-- Actions Column -->\n <th class=\"action-col\">Actions</th>\n\n <!-- Dynamic Columns -->\n @for (col of visibleColumns; track col.name) {\n <th [style.width]=\"col.width || 'auto'\">\n {{ col.label }}\n </th>\n }\n\n </tr>\n </thead>\n\n <tbody>\n\n <!-- Empty State -->\n @if (rows.length === 0) {\n <tr>\n <td class=\"o-empty-cell\" [attr.colspan]=\"visibleColumns.length + 2\">\n No data found\n </td>\n </tr>\n }\n\n <!-- Data Rows -->\n @for (row of rows; track $index) {\n <tr class=\"table-row\" (click)=\"selectRow(row)\">\n\n <!-- Checkbox -->\n <td class=\"checkbox-col\">\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(row)\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleRow(row)\"\n />\n </td>\n\n <!-- Actions -->\n <td class=\"action-cell\">\n <button\n type=\"button\"\n class=\"action-btn\"\n (click)=\"openMenu($event, row)\"\n title=\"Actions\">\n &#8942;\n </button>\n </td>\n\n <!-- Dynamic Columns -->\n @for (col of visibleColumns; track col.name) {\n <td>\n @if (col.name === 'requestId') {\n <a href=\"javascript:void(0)\" class=\"o-table-link\" (click)=\"onCellClick($event, col.name, row)\">\n {{ row[col.name] || '\u2014' }}\n </a>\n } @else if (col.pipe === 'status') {\n <span class=\"o-status-badge\" [class]=\"statusClass(row[col.name])\">\n {{ row[col.name] || '\u2014' }}\n </span>\n } @else if (col.pipe === 'date') {\n {{ row[col.name] ? (row[col.name] | date:'dd MMM yyyy') : '\u2014' }}\n } @else {\n {{ row[col.name] || '\u2014' }}\n }\n </td>\n }\n\n </tr>\n }\n\n </tbody>\n\n </table>\n\n</div>\n", styles: [".o-table-wrapper{width:100%;overflow-x:auto;border:1px solid #e2e8f0;border-radius:12px;background:#fff}.o-table{width:100%;border-collapse:collapse;min-width:900px}.o-table thead th{background:#f1f3f9;color:#15104e;font-size:13px;font-weight:600;text-transform:none;padding:12px 14px;text-align:left;border-bottom:1px solid #CDCDCD;border-right:1px solid #CDCDCD;white-space:nowrap}.o-table thead th:last-child{border-right:none}.o-table tbody td{padding:12px 14px;font-size:13px;color:#333;border-bottom:1px solid #E2E8F0;border-right:1px solid #E2E8F0;vertical-align:middle}.o-table tbody td:last-child{border-right:none}.o-table-link{color:#1d4ed8;text-decoration:underline;font-weight:600;cursor:pointer;outline:none}.o-table-link:hover{color:#1e40af}.table-row{transition:background .1s ease}.table-row:hover{background:#f1f3f9;cursor:pointer}.table-row:last-child td{border-bottom:none}.checkbox-col{width:52px;text-align:center}.checkbox-col input{width:16px;height:16px;cursor:pointer}.action-col{width:80px;text-align:center}.action-cell{position:relative;text-align:center}.action-btn{width:32px;height:32px;border:none;background:transparent;border-radius:6px;cursor:pointer;font-size:18px;color:#64748b;transition:all .2s ease}.action-btn:hover{background:#f1f5f9;color:#0f172a}.action-menu{position:absolute;top:38px;right:10px;min-width:170px;background:#fff;border:1px solid #e2e8f0;border-radius:10px;box-shadow:0 10px 25px #00000014,0 4px 10px #0000000a;z-index:1000;overflow:hidden}.action-menu-item{width:100%;padding:10px 14px;border:none;background:transparent;text-align:left;font-size:13px;color:#334155;cursor:pointer;transition:background .15s ease}.action-menu-item:hover{background:#f8fafc}.o-empty-cell{text-align:center;padding:48px!important;color:#94a3b8;font-style:italic}.o-status-badge{display:inline-flex;align-items:center;justify-content:center;min-width:90px;padding:5px 12px;border-radius:999px;font-size:12px;font-weight:600}.status-draft{background:#fef3c7;color:#92400e}.status-active{background:#dcfce7;color:#166534}.status-progress{background:#dbeafe;color:#1d4ed8}.status-inactive{background:#f1f5f9;color:#64748b}.status-rejected{background:#fee2e2;color:#b91c1c}.selected-row{background:#eff6ff!important}@media(max-width:768px){.o-table{min-width:700px}.o-table thead th,.o-table tbody td{padding:12px}}\n"], dependencies: [{ kind: "pipe", type: DatePipe, name: "date" }] });
210
+ }
211
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: DataTableComponent, decorators: [{
212
+ type: Component,
213
+ args: [{ selector: 'o-data-table', standalone: true, imports: [DatePipe], template: "<div class=\"o-table-wrapper\">\n\n <table class=\"o-table\">\n\n <thead>\n <tr>\n\n <!-- Checkbox Column -->\n <th class=\"checkbox-col\">\n <input\n type=\"checkbox\"\n [checked]=\"isAllSelected()\"\n (change)=\"toggleAll($event)\"\n />\n </th>\n\n <!-- Actions Column -->\n <th class=\"action-col\">Actions</th>\n\n <!-- Dynamic Columns -->\n @for (col of visibleColumns; track col.name) {\n <th [style.width]=\"col.width || 'auto'\">\n {{ col.label }}\n </th>\n }\n\n </tr>\n </thead>\n\n <tbody>\n\n <!-- Empty State -->\n @if (rows.length === 0) {\n <tr>\n <td class=\"o-empty-cell\" [attr.colspan]=\"visibleColumns.length + 2\">\n No data found\n </td>\n </tr>\n }\n\n <!-- Data Rows -->\n @for (row of rows; track $index) {\n <tr class=\"table-row\" (click)=\"selectRow(row)\">\n\n <!-- Checkbox -->\n <td class=\"checkbox-col\">\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(row)\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleRow(row)\"\n />\n </td>\n\n <!-- Actions -->\n <td class=\"action-cell\">\n <button\n type=\"button\"\n class=\"action-btn\"\n (click)=\"openMenu($event, row)\"\n title=\"Actions\">\n &#8942;\n </button>\n </td>\n\n <!-- Dynamic Columns -->\n @for (col of visibleColumns; track col.name) {\n <td>\n @if (col.name === 'requestId') {\n <a href=\"javascript:void(0)\" class=\"o-table-link\" (click)=\"onCellClick($event, col.name, row)\">\n {{ row[col.name] || '\u2014' }}\n </a>\n } @else if (col.pipe === 'status') {\n <span class=\"o-status-badge\" [class]=\"statusClass(row[col.name])\">\n {{ row[col.name] || '\u2014' }}\n </span>\n } @else if (col.pipe === 'date') {\n {{ row[col.name] ? (row[col.name] | date:'dd MMM yyyy') : '\u2014' }}\n } @else {\n {{ row[col.name] || '\u2014' }}\n }\n </td>\n }\n\n </tr>\n }\n\n </tbody>\n\n </table>\n\n</div>\n", styles: [".o-table-wrapper{width:100%;overflow-x:auto;border:1px solid #e2e8f0;border-radius:12px;background:#fff}.o-table{width:100%;border-collapse:collapse;min-width:900px}.o-table thead th{background:#f1f3f9;color:#15104e;font-size:13px;font-weight:600;text-transform:none;padding:12px 14px;text-align:left;border-bottom:1px solid #CDCDCD;border-right:1px solid #CDCDCD;white-space:nowrap}.o-table thead th:last-child{border-right:none}.o-table tbody td{padding:12px 14px;font-size:13px;color:#333;border-bottom:1px solid #E2E8F0;border-right:1px solid #E2E8F0;vertical-align:middle}.o-table tbody td:last-child{border-right:none}.o-table-link{color:#1d4ed8;text-decoration:underline;font-weight:600;cursor:pointer;outline:none}.o-table-link:hover{color:#1e40af}.table-row{transition:background .1s ease}.table-row:hover{background:#f1f3f9;cursor:pointer}.table-row:last-child td{border-bottom:none}.checkbox-col{width:52px;text-align:center}.checkbox-col input{width:16px;height:16px;cursor:pointer}.action-col{width:80px;text-align:center}.action-cell{position:relative;text-align:center}.action-btn{width:32px;height:32px;border:none;background:transparent;border-radius:6px;cursor:pointer;font-size:18px;color:#64748b;transition:all .2s ease}.action-btn:hover{background:#f1f5f9;color:#0f172a}.action-menu{position:absolute;top:38px;right:10px;min-width:170px;background:#fff;border:1px solid #e2e8f0;border-radius:10px;box-shadow:0 10px 25px #00000014,0 4px 10px #0000000a;z-index:1000;overflow:hidden}.action-menu-item{width:100%;padding:10px 14px;border:none;background:transparent;text-align:left;font-size:13px;color:#334155;cursor:pointer;transition:background .15s ease}.action-menu-item:hover{background:#f8fafc}.o-empty-cell{text-align:center;padding:48px!important;color:#94a3b8;font-style:italic}.o-status-badge{display:inline-flex;align-items:center;justify-content:center;min-width:90px;padding:5px 12px;border-radius:999px;font-size:12px;font-weight:600}.status-draft{background:#fef3c7;color:#92400e}.status-active{background:#dcfce7;color:#166534}.status-progress{background:#dbeafe;color:#1d4ed8}.status-inactive{background:#f1f5f9;color:#64748b}.status-rejected{background:#fee2e2;color:#b91c1c}.selected-row{background:#eff6ff!important}@media(max-width:768px){.o-table{min-width:700px}.o-table thead th,.o-table tbody td{padding:12px}}\n"] }]
214
+ }], propDecorators: { columns: [{
215
+ type: Input
216
+ }], rows: [{
217
+ type: Input
218
+ }], selectable: [{
219
+ type: Input
220
+ }], actions: [{
221
+ type: Input
222
+ }], rowClick: [{
223
+ type: Output
224
+ }], menuOpen: [{
225
+ type: Output
226
+ }], actionClick: [{
227
+ type: Output
228
+ }], selectionChange: [{
229
+ type: Output
230
+ }] } });
231
+
232
+ function buildFromConfig(config) {
233
+ const modules = config.modules || {};
234
+ const system = config.system || {};
235
+ const moduleMetadata = {};
236
+ const planData = [];
237
+ Object.keys(modules).forEach(key => {
238
+ const mod = modules[key];
239
+ moduleMetadata[key] = {
240
+ label: mod.label || key.toUpperCase(),
241
+ desc: mod.desc || '',
242
+ icon: mod.icon || ''
243
+ };
244
+ const accessKeys = {};
245
+ const groups = mod.groups || {};
246
+ Object.keys(groups).forEach(groupName => {
247
+ accessKeys[groupName] = groups[groupName].map(s => ({
248
+ screen: s,
249
+ name: s.substring(1),
250
+ isMark: false,
251
+ isDisabled: false
252
+ }));
253
+ });
254
+ const sysSettings = mod.systemSettings || system['System Settings'] || [];
255
+ accessKeys['System Settings'] = sysSettings.map(s => ({
256
+ screen: s,
257
+ name: s.substring(1),
258
+ isMark: true,
259
+ isDisabled: false
260
+ }));
261
+ const bizMaster = mod.businessMaster || system['Business Master'] || [];
262
+ accessKeys['Business Master'] = bizMaster.map(s => ({
263
+ screen: s,
264
+ name: s.substring(1),
265
+ isMark: true,
266
+ isDisabled: false
267
+ }));
268
+ planData.push({ productName: key, accessKeys });
269
+ });
270
+ return { moduleMetadata, planData };
271
+ }
272
+ class OLicenseModulesComponent {
273
+ http;
274
+ cdr;
275
+ configPath = 'assets/application/pages/license-modules.json';
276
+ disabled = false;
277
+ value = null;
278
+ valueChange = new EventEmitter();
279
+ // Signals
280
+ allData = signal([], /* @ts-ignore */
281
+ ...(ngDevMode ? [{ debugName: "allData" }] : /* istanbul ignore next */ []));
282
+ selectedModules = signal(new Set(), /* @ts-ignore */
283
+ ...(ngDevMode ? [{ debugName: "selectedModules" }] : /* istanbul ignore next */ []));
284
+ isCustomMode = signal(false, /* @ts-ignore */
285
+ ...(ngDevMode ? [{ debugName: "isCustomMode" }] : /* istanbul ignore next */ []));
286
+ searchText = signal('', /* @ts-ignore */
287
+ ...(ngDevMode ? [{ debugName: "searchText" }] : /* istanbul ignore next */ []));
288
+ form = new FormGroup({});
289
+ moduleMetadata = {};
290
+ destroy$ = new Subject();
291
+ configLoaded = false;
292
+ onChange = () => { };
293
+ onTouched = () => { };
294
+ constructor(http, cdr) {
295
+ this.http = http;
296
+ this.cdr = cdr;
297
+ }
298
+ ngOnInit() {
299
+ this.http.get(this.configPath).pipe(takeUntil(this.destroy$)).subscribe({
300
+ next: (config) => {
301
+ const built = buildFromConfig(config);
302
+ this.moduleMetadata = built.moduleMetadata;
303
+ this.allData.set(built.planData);
304
+ this.configLoaded = true;
305
+ this.setupForm();
306
+ this.applyInitialValue();
307
+ this.cdr.markForCheck();
308
+ },
309
+ error: (err) => {
310
+ console.error('[o-license-modules] Failed to load config:', err);
311
+ }
312
+ });
313
+ }
314
+ ngOnChanges(changes) {
315
+ if (changes['value'] && this.configLoaded) {
316
+ this.applyInitialValue();
317
+ }
318
+ }
319
+ ngOnDestroy() {
320
+ this.destroy$.next();
321
+ this.destroy$.complete();
322
+ }
323
+ // ControlValueAccessor
324
+ writeValue(val) {
325
+ this.value = val;
326
+ if (this.configLoaded) {
327
+ this.applyInitialValue();
328
+ }
329
+ }
330
+ registerOnChange(fn) {
331
+ this.onChange = fn;
332
+ }
333
+ registerOnTouched(fn) {
334
+ this.onTouched = fn;
335
+ }
336
+ setDisabledState(isDisabled) {
337
+ this.disabled = isDisabled;
338
+ this.cdr.markForCheck();
339
+ }
340
+ setupForm() {
341
+ const group = {};
342
+ this.allData().forEach(planData => {
343
+ const productGroup = {};
344
+ Object.keys(planData.accessKeys).forEach(groupKey => {
345
+ planData.accessKeys[groupKey].forEach(accessKey => {
346
+ productGroup[accessKey.screen] = new FormControl(false);
347
+ });
348
+ });
349
+ group[planData.productName] = new FormGroup(productGroup);
350
+ });
351
+ this.form = new FormGroup(group);
352
+ this.form.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(() => {
353
+ const output = this.buildOutputValue();
354
+ this.onChange(output);
355
+ this.onTouched();
356
+ this.valueChange.emit(output);
357
+ });
358
+ }
359
+ applyInitialValue() {
360
+ if (!this.value) {
361
+ this.selectedModules.set(new Set());
362
+ this.isCustomMode.set(false);
363
+ return;
364
+ }
365
+ const selectedMods = new Set();
366
+ Object.keys(this.value).forEach(productName => {
367
+ const screens = this.value[productName] || [];
368
+ if (screens.length > 0) {
369
+ selectedMods.add(productName.toLowerCase());
370
+ }
371
+ });
372
+ this.selectedModules.set(selectedMods);
373
+ this.allData().forEach(planData => {
374
+ const pName = planData.productName.toLowerCase();
375
+ const savedScreens = this.value[pName] || this.value[planData.productName] || [];
376
+ const productForm = this.form.get(planData.productName);
377
+ if (!productForm)
378
+ return;
379
+ if (selectedMods.has(pName) && savedScreens.length > 0) {
380
+ savedScreens.forEach(screen => {
381
+ const ctrl = productForm.get(screen);
382
+ if (ctrl)
383
+ ctrl.setValue(true, { emitEvent: false });
384
+ });
385
+ }
386
+ else if (selectedMods.has(pName)) {
387
+ this.checkAllFeaturesForPlan(planData, productForm);
388
+ }
389
+ });
390
+ this.cdr.markForCheck();
391
+ }
392
+ checkAllFeaturesForPlan(planData, productForm) {
393
+ Object.keys(planData.accessKeys).forEach(groupKey => {
394
+ planData.accessKeys[groupKey].forEach(accessKey => {
395
+ const ctrl = productForm.get(accessKey.screen);
396
+ if (ctrl && !accessKey.isDisabled) {
397
+ ctrl.setValue(true, { emitEvent: false });
398
+ }
399
+ });
400
+ });
401
+ }
402
+ // Public methods used in template
403
+ getModuleMetadata(name) {
404
+ return this.moduleMetadata[name.toLowerCase()];
405
+ }
406
+ toggleModule(productName) {
407
+ if (this.disabled)
408
+ return;
409
+ if (productName === 'custom') {
410
+ const next = !this.isCustomMode();
411
+ this.isCustomMode.set(next);
412
+ if (!next) {
413
+ this.isCustomMode.set(false);
414
+ }
415
+ }
416
+ else {
417
+ const current = new Set(this.selectedModules());
418
+ const key = productName.toLowerCase();
419
+ if (current.has(key)) {
420
+ current.delete(key);
421
+ this.uncheckAllForProduct(productName);
422
+ }
423
+ else {
424
+ current.add(key);
425
+ const planData = this.allData().find(p => p.productName.toLowerCase() === key);
426
+ if (planData) {
427
+ const productForm = this.form.get(planData.productName);
428
+ if (productForm) {
429
+ this.checkAllFeaturesForPlan(planData, productForm);
430
+ }
431
+ }
432
+ }
433
+ this.selectedModules.set(current);
434
+ }
435
+ this.cdr.markForCheck();
436
+ const output = this.buildOutputValue();
437
+ this.onChange(output);
438
+ this.onTouched();
439
+ this.valueChange.emit(output);
440
+ }
441
+ uncheckAllForProduct(productName) {
442
+ const productForm = this.form.get(productName);
443
+ if (!productForm)
444
+ return;
445
+ Object.keys(productForm.controls).forEach(key => {
446
+ productForm.get(key)?.setValue(false, { emitEvent: false });
447
+ });
448
+ }
449
+ getFormControl(productName, screen) {
450
+ return this.form.get(productName)?.get(screen);
451
+ }
452
+ filterFeatures(text) {
453
+ this.searchText.set(text);
454
+ this.cdr.markForCheck();
455
+ }
456
+ getDisplayedGroups() {
457
+ const selectedMods = this.selectedModules();
458
+ const isCustom = this.isCustomMode();
459
+ const filter = this.searchText().toLowerCase();
460
+ if (!isCustom && selectedMods.size === 0) {
461
+ return [];
462
+ }
463
+ const groupsMap = new Map();
464
+ this.allData().forEach(planData => {
465
+ const pNameLower = planData.productName.toLowerCase();
466
+ if (!isCustom && !selectedMods.has(pNameLower)) {
467
+ return;
468
+ }
469
+ Object.keys(planData.accessKeys).forEach(groupKey => {
470
+ const keyLower = groupKey.toLowerCase();
471
+ if (!groupsMap.has(keyLower)) {
472
+ groupsMap.set(keyLower, {
473
+ groupName: groupKey,
474
+ featuresMap: new Map()
475
+ });
476
+ }
477
+ const featuresMap = groupsMap.get(keyLower).featuresMap;
478
+ planData.accessKeys[groupKey].forEach(accessKey => {
479
+ if (filter && !accessKey.name.toLowerCase().includes(filter) && !accessKey.screen.toLowerCase().includes(filter)) {
480
+ return;
481
+ }
482
+ const screenLower = accessKey.screen.toLowerCase();
483
+ if (!featuresMap.has(screenLower)) {
484
+ featuresMap.set(screenLower, {
485
+ screen: accessKey.screen,
486
+ name: accessKey.name,
487
+ isDisabled: accessKey.isDisabled,
488
+ productName: planData.productName
489
+ });
490
+ }
491
+ });
492
+ });
493
+ });
494
+ const result = [];
495
+ groupsMap.forEach(data => {
496
+ if (data.featuresMap.size > 0) {
497
+ result.push({
498
+ groupName: data.groupName,
499
+ features: Array.from(data.featuresMap.values())
500
+ });
501
+ }
502
+ });
503
+ return result;
504
+ }
505
+ getTotalFeaturesCount() {
506
+ return this.getDisplayedGroups().reduce((acc, g) => acc + g.features.length, 0);
507
+ }
508
+ getSelectedModulesTitle() {
509
+ const mods = Array.from(this.selectedModules());
510
+ if (mods.length === 0)
511
+ return 'Selected module features';
512
+ const labels = mods.map(m => this.moduleMetadata[m]?.label || m.toUpperCase());
513
+ return labels.join(' + ') + ' modules';
514
+ }
515
+ clearAll() {
516
+ if (this.disabled)
517
+ return;
518
+ this.selectedModules.set(new Set());
519
+ this.isCustomMode.set(false);
520
+ this.allData().forEach(planData => {
521
+ this.uncheckAllForProduct(planData.productName);
522
+ });
523
+ this.cdr.markForCheck();
524
+ const output = this.buildOutputValue();
525
+ this.onChange(output);
526
+ this.onTouched();
527
+ this.valueChange.emit(output);
528
+ }
529
+ buildOutputValue() {
530
+ const output = {};
531
+ this.allData().forEach(planData => {
532
+ const pName = planData.productName;
533
+ const productForm = this.form.get(pName);
534
+ if (!productForm)
535
+ return;
536
+ const checkedScreens = Object.keys(productForm.controls).filter(key => productForm.get(key)?.value === true);
537
+ if (checkedScreens.length > 0) {
538
+ output[pName] = checkedScreens;
539
+ }
540
+ });
541
+ return output;
542
+ }
543
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OLicenseModulesComponent, deps: [{ token: i1.HttpClient }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
544
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: OLicenseModulesComponent, isStandalone: true, selector: "o-license-modules", inputs: { configPath: "configPath", disabled: "disabled", value: "value" }, outputs: { valueChange: "valueChange" }, providers: [
545
+ {
546
+ provide: NG_VALUE_ACCESSOR,
547
+ useExisting: forwardRef(() => OLicenseModulesComponent),
548
+ multi: true
549
+ }
550
+ ], usesOnChanges: true, ngImport: i0, template: "<div class=\"o-lm\" [class.o-lm--disabled]=\"disabled\" [formGroup]=\"form\">\n\n <!-- Step 1: Select Modules -->\n <div class=\"o-lm__step-row\">\n <span class=\"o-lm__step\">1</span>\n <span class=\"o-lm__step-label\">Select modules</span>\n </div>\n\n <div class=\"o-lm__modules-grid\">\n @for (planData of allData(); track planData.productName) {\n @let meta = getModuleMetadata(planData.productName);\n @if (meta) {\n <div\n class=\"o-lm__mod-card\"\n [class.o-lm__mod-card--selected]=\"selectedModules().has(planData.productName.toLowerCase()) && !isCustomMode()\"\n (click)=\"toggleModule(planData.productName)\"\n role=\"button\"\n [attr.aria-pressed]=\"selectedModules().has(planData.productName.toLowerCase())\"\n tabindex=\"0\"\n (keydown.enter)=\"toggleModule(planData.productName)\"\n (keydown.space)=\"$event.preventDefault(); toggleModule(planData.productName)\"\n >\n <div class=\"o-lm__mod-check\">\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M5 12l5 5L20 7\"/></svg>\n </div>\n <div class=\"o-lm__mod-icon\">\n <svg viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path [attr.d]=\"meta.icon\"/>\n </svg>\n </div>\n <h3 class=\"o-lm__mod-title\">{{ meta.label }}</h3>\n <p class=\"o-lm__mod-desc\">{{ meta.desc }}</p>\n </div>\n }\n }\n\n <!-- Custom card -->\n <div\n class=\"o-lm__mod-card o-lm__mod-card--custom\"\n [class.o-lm__mod-card--selected-custom]=\"isCustomMode()\"\n (click)=\"toggleModule('custom')\"\n role=\"button\"\n [attr.aria-pressed]=\"isCustomMode()\"\n tabindex=\"0\"\n (keydown.enter)=\"toggleModule('custom')\"\n (keydown.space)=\"$event.preventDefault(); toggleModule('custom')\"\n >\n <div class=\"o-lm__mod-check o-lm__mod-check--custom\">\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M5 12l5 5L20 7\"/></svg>\n </div>\n <div class=\"o-lm__mod-icon\">\n <svg viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M3 5h18M3 12h18M3 19h18M7 2v6M17 9v6M11 16v6\"/>\n </svg>\n </div>\n <h3 class=\"o-lm__mod-title\">Custom</h3>\n <p class=\"o-lm__mod-desc\">Pick individual features</p>\n </div>\n </div>\n\n <!-- Step 2: Features Panel -->\n <div class=\"o-lm__step-row\">\n <span class=\"o-lm__step\">2</span>\n <span class=\"o-lm__step-label\">Features included</span>\n </div>\n\n <div class=\"o-lm__panel\">\n <div class=\"o-lm__panel-header\">\n <span class=\"o-lm__panel-title\">\n @if (!isCustomMode() && selectedModules().size === 0) {\n No module selected\n } @else if (isCustomMode()) {\n Custom \u2014 all features available\n } @else {\n @if (getTotalFeaturesCount() > 0) {\n {{ getSelectedModulesTitle() }}\n } @else {\n No features found\n }\n }\n </span>\n @if (isCustomMode() || selectedModules().size > 0) {\n <span\n class=\"o-lm__badge\"\n [class.o-lm__badge--green]=\"isCustomMode()\"\n [class.o-lm__badge--blue]=\"!isCustomMode()\"\n >\n {{ getTotalFeaturesCount() }} features\n </span>\n }\n </div>\n\n @if (isCustomMode() || selectedModules().size > 0) {\n <div class=\"o-lm__search-row\">\n <svg viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/>\n </svg>\n <input\n type=\"text\"\n class=\"o-lm__search-input\"\n placeholder=\"Filter features...\"\n [value]=\"searchText()\"\n (input)=\"filterFeatures($any($event.target).value)\"\n aria-label=\"Filter features\"\n />\n </div>\n\n <div class=\"o-lm__panel-body\">\n @let groups = getDisplayedGroups();\n @if (groups.length === 0) {\n <div class=\"o-lm__empty-state\">\n <svg viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/>\n </svg>\n <p>No features match \"{{ searchText() }}\"</p>\n </div>\n } @else {\n @for (group of groups; track group.groupName) {\n <div class=\"o-lm__module-section\">\n <h4 class=\"o-lm__section-title\">\n {{ group.groupName }}\n <span class=\"o-lm__section-count\">({{ group.features.length }})</span>\n </h4>\n @for (feature of group.features; track feature.screen) {\n <div\n class=\"o-lm__feature-item\"\n [class.o-lm__feature-item--disabled]=\"disabled || feature.isDisabled\"\n >\n <input\n type=\"checkbox\"\n class=\"o-lm__feature-checkbox\"\n [formControl]=\"getFormControl(feature.productName, feature.screen)\"\n [attr.disabled]=\"(disabled || feature.isDisabled) ? '' : null\"\n [id]=\"'feat-' + feature.productName + '-' + feature.screen\"\n />\n <label\n class=\"o-lm__feature-label\"\n [for]=\"'feat-' + feature.productName + '-' + feature.screen\"\n >\n {{ feature.name }}\n </label>\n </div>\n }\n </div>\n }\n }\n </div>\n } @else {\n <div class=\"o-lm__panel-body\">\n <div class=\"o-lm__empty-state\">\n <svg viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\"/>\n <rect x=\"14\" y=\"3\" width=\"7\" height=\"7\"/>\n <rect x=\"14\" y=\"14\" width=\"7\" height=\"7\"/>\n <rect x=\"3\" y=\"14\" width=\"7\" height=\"7\"/>\n </svg>\n <p>Select a module above to view included features</p>\n </div>\n </div>\n }\n </div>\n\n <!-- Actions -->\n <div class=\"o-lm__action-row\">\n <button\n type=\"button\"\n class=\"o-lm__btn-clear\"\n [disabled]=\"disabled\"\n (click)=\"clearAll()\"\n >\n Clear all\n </button>\n </div>\n\n</div>\n", styles: [".o-lm__step-row{display:flex;align-items:center;gap:8px;margin-top:1.5rem;margin-bottom:1.25rem}.o-lm__step{width:22px;height:22px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:500;background:#e6f1fb;color:#185fa5;flex-shrink:0}.o-lm__step-label{font-size:13px;font-weight:500;color:#5a5a5a}.o-lm__modules-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;margin-bottom:1.5rem}@media(max-width:900px){.o-lm__modules-grid{grid-template-columns:repeat(3,1fr)}}@media(max-width:600px){.o-lm__modules-grid{grid-template-columns:repeat(2,1fr)}}.o-lm__mod-card{border:1.5px solid #e1e1e1;border-radius:8px;padding:14px 12px;cursor:pointer;transition:border-color .15s,background .15s;background:#fff;position:relative;-webkit-user-select:none;user-select:none;outline:none}.o-lm__mod-card:hover{border-color:#b9b9b9}.o-lm__mod-card:focus-visible{box-shadow:0 0 0 3px #15104e2e}.o-lm__mod-card--selected{border-color:#378add;background:#e6f1fb}.o-lm__mod-card--selected .o-lm__mod-icon{color:#185fa5}.o-lm__mod-card--selected .o-lm__mod-check{display:flex}.o-lm__mod-card--custom:hover{border-color:#7aaa3c}.o-lm__mod-card--selected-custom{border-color:#639922;background:#eaf3de}.o-lm__mod-card--selected-custom .o-lm__mod-icon{color:#3b6d11}.o-lm__mod-card--selected-custom .o-lm__mod-check--custom{display:flex}.o-lm__mod-check{position:absolute;top:8px;right:8px;width:16px;height:16px;border-radius:50%;background:#378add;display:none;align-items:center;justify-content:center}.o-lm__mod-check svg{width:10px;height:10px;stroke:#fff;stroke-width:3;fill:none}.o-lm__mod-check--custom{background:#639922}.o-lm__mod-icon{font-size:22px;margin-bottom:8px;color:#5a5a5a;display:flex;align-items:center}.o-lm__mod-icon svg{width:22px;height:22px}.o-lm__mod-title{font-size:13px;font-weight:500;color:#333;margin:0 0 4px}.o-lm__mod-desc{font-size:11px;color:#5a5a5a;margin:0}.o-lm__panel{border:.5px solid #e1e1e1;border-radius:8px;overflow:hidden;min-height:200px;background:#fff}.o-lm__panel-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:.5px solid #e1e1e1;background:#fafafa}.o-lm__panel-title{font-size:13px;font-weight:500;color:#333}.o-lm__badge{font-size:11px;padding:2px 8px;border-radius:20px;font-weight:500}.o-lm__badge--blue{background:#e6f1fb;color:#185fa5}.o-lm__badge--green{background:#eaf3de;color:#3b6d11}.o-lm__search-row{display:flex;align-items:center;gap:8px;padding:8px 12px;border-bottom:.5px solid #e1e1e1;background:#fff}.o-lm__search-row svg{width:14px;height:14px;color:#767676;flex-shrink:0}.o-lm__search-input{flex:1;border:none;background:transparent;font-size:12px;color:#333;outline:none}.o-lm__search-input::placeholder{color:#767676}.o-lm__panel-body{display:grid;grid-template-columns:repeat(3,1fr);gap:0}@media(max-width:700px){.o-lm__panel-body{grid-template-columns:1fr}}.o-lm__module-section{border-right:.5px solid #e1e1e1;padding:12px}.o-lm__module-section:last-child{border-right:none}.o-lm__section-title{font-size:11px;font-weight:500;color:#5a5a5a;text-transform:uppercase;letter-spacing:.5px;margin:0 0 8px;padding-bottom:6px;border-bottom:.5px solid #e1e1e1}.o-lm__section-count{font-size:10px;font-weight:400;text-transform:none;letter-spacing:0}.o-lm__feature-item{display:flex;align-items:center;gap:8px;padding:5px 0;font-size:12px;color:#333}.o-lm__feature-item--disabled{opacity:.6;cursor:not-allowed}.o-lm__feature-item--disabled .o-lm__feature-checkbox,.o-lm__feature-item--disabled .o-lm__feature-label{cursor:not-allowed}.o-lm__feature-checkbox{width:14px;height:14px;cursor:pointer;flex-shrink:0;accent-color:#15104e}.o-lm__feature-label{cursor:pointer;font-size:12px;color:#333;line-height:1.3}.o-lm__empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:3rem;color:#767676;grid-column:1/-1}.o-lm__empty-state svg{width:36px;height:36px;margin-bottom:8px;opacity:.4}.o-lm__empty-state p{font-size:13px;margin:0}.o-lm__action-row{display:flex;justify-content:flex-end;margin-top:1rem}.o-lm__btn-clear{padding:7px 18px;border-radius:8px;font-size:13px;font-weight:500;cursor:pointer;border:.5px solid #b9b9b9;background:#fff;color:#333;transition:background .12s,border-color .12s}.o-lm__btn-clear:hover:not(:disabled){background:#f5f5f5;border-color:#999}.o-lm__btn-clear:disabled{opacity:.5;cursor:not-allowed}.o-lm--disabled{pointer-events:none;opacity:.85}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
551
+ }
552
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OLicenseModulesComponent, decorators: [{
553
+ type: Component,
554
+ args: [{ selector: 'o-license-modules', standalone: true, imports: [CommonModule, ReactiveFormsModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
555
+ {
556
+ provide: NG_VALUE_ACCESSOR,
557
+ useExisting: forwardRef(() => OLicenseModulesComponent),
558
+ multi: true
559
+ }
560
+ ], template: "<div class=\"o-lm\" [class.o-lm--disabled]=\"disabled\" [formGroup]=\"form\">\n\n <!-- Step 1: Select Modules -->\n <div class=\"o-lm__step-row\">\n <span class=\"o-lm__step\">1</span>\n <span class=\"o-lm__step-label\">Select modules</span>\n </div>\n\n <div class=\"o-lm__modules-grid\">\n @for (planData of allData(); track planData.productName) {\n @let meta = getModuleMetadata(planData.productName);\n @if (meta) {\n <div\n class=\"o-lm__mod-card\"\n [class.o-lm__mod-card--selected]=\"selectedModules().has(planData.productName.toLowerCase()) && !isCustomMode()\"\n (click)=\"toggleModule(planData.productName)\"\n role=\"button\"\n [attr.aria-pressed]=\"selectedModules().has(planData.productName.toLowerCase())\"\n tabindex=\"0\"\n (keydown.enter)=\"toggleModule(planData.productName)\"\n (keydown.space)=\"$event.preventDefault(); toggleModule(planData.productName)\"\n >\n <div class=\"o-lm__mod-check\">\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M5 12l5 5L20 7\"/></svg>\n </div>\n <div class=\"o-lm__mod-icon\">\n <svg viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path [attr.d]=\"meta.icon\"/>\n </svg>\n </div>\n <h3 class=\"o-lm__mod-title\">{{ meta.label }}</h3>\n <p class=\"o-lm__mod-desc\">{{ meta.desc }}</p>\n </div>\n }\n }\n\n <!-- Custom card -->\n <div\n class=\"o-lm__mod-card o-lm__mod-card--custom\"\n [class.o-lm__mod-card--selected-custom]=\"isCustomMode()\"\n (click)=\"toggleModule('custom')\"\n role=\"button\"\n [attr.aria-pressed]=\"isCustomMode()\"\n tabindex=\"0\"\n (keydown.enter)=\"toggleModule('custom')\"\n (keydown.space)=\"$event.preventDefault(); toggleModule('custom')\"\n >\n <div class=\"o-lm__mod-check o-lm__mod-check--custom\">\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M5 12l5 5L20 7\"/></svg>\n </div>\n <div class=\"o-lm__mod-icon\">\n <svg viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M3 5h18M3 12h18M3 19h18M7 2v6M17 9v6M11 16v6\"/>\n </svg>\n </div>\n <h3 class=\"o-lm__mod-title\">Custom</h3>\n <p class=\"o-lm__mod-desc\">Pick individual features</p>\n </div>\n </div>\n\n <!-- Step 2: Features Panel -->\n <div class=\"o-lm__step-row\">\n <span class=\"o-lm__step\">2</span>\n <span class=\"o-lm__step-label\">Features included</span>\n </div>\n\n <div class=\"o-lm__panel\">\n <div class=\"o-lm__panel-header\">\n <span class=\"o-lm__panel-title\">\n @if (!isCustomMode() && selectedModules().size === 0) {\n No module selected\n } @else if (isCustomMode()) {\n Custom \u2014 all features available\n } @else {\n @if (getTotalFeaturesCount() > 0) {\n {{ getSelectedModulesTitle() }}\n } @else {\n No features found\n }\n }\n </span>\n @if (isCustomMode() || selectedModules().size > 0) {\n <span\n class=\"o-lm__badge\"\n [class.o-lm__badge--green]=\"isCustomMode()\"\n [class.o-lm__badge--blue]=\"!isCustomMode()\"\n >\n {{ getTotalFeaturesCount() }} features\n </span>\n }\n </div>\n\n @if (isCustomMode() || selectedModules().size > 0) {\n <div class=\"o-lm__search-row\">\n <svg viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/>\n </svg>\n <input\n type=\"text\"\n class=\"o-lm__search-input\"\n placeholder=\"Filter features...\"\n [value]=\"searchText()\"\n (input)=\"filterFeatures($any($event.target).value)\"\n aria-label=\"Filter features\"\n />\n </div>\n\n <div class=\"o-lm__panel-body\">\n @let groups = getDisplayedGroups();\n @if (groups.length === 0) {\n <div class=\"o-lm__empty-state\">\n <svg viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/>\n </svg>\n <p>No features match \"{{ searchText() }}\"</p>\n </div>\n } @else {\n @for (group of groups; track group.groupName) {\n <div class=\"o-lm__module-section\">\n <h4 class=\"o-lm__section-title\">\n {{ group.groupName }}\n <span class=\"o-lm__section-count\">({{ group.features.length }})</span>\n </h4>\n @for (feature of group.features; track feature.screen) {\n <div\n class=\"o-lm__feature-item\"\n [class.o-lm__feature-item--disabled]=\"disabled || feature.isDisabled\"\n >\n <input\n type=\"checkbox\"\n class=\"o-lm__feature-checkbox\"\n [formControl]=\"getFormControl(feature.productName, feature.screen)\"\n [attr.disabled]=\"(disabled || feature.isDisabled) ? '' : null\"\n [id]=\"'feat-' + feature.productName + '-' + feature.screen\"\n />\n <label\n class=\"o-lm__feature-label\"\n [for]=\"'feat-' + feature.productName + '-' + feature.screen\"\n >\n {{ feature.name }}\n </label>\n </div>\n }\n </div>\n }\n }\n </div>\n } @else {\n <div class=\"o-lm__panel-body\">\n <div class=\"o-lm__empty-state\">\n <svg viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\"/>\n <rect x=\"14\" y=\"3\" width=\"7\" height=\"7\"/>\n <rect x=\"14\" y=\"14\" width=\"7\" height=\"7\"/>\n <rect x=\"3\" y=\"14\" width=\"7\" height=\"7\"/>\n </svg>\n <p>Select a module above to view included features</p>\n </div>\n </div>\n }\n </div>\n\n <!-- Actions -->\n <div class=\"o-lm__action-row\">\n <button\n type=\"button\"\n class=\"o-lm__btn-clear\"\n [disabled]=\"disabled\"\n (click)=\"clearAll()\"\n >\n Clear all\n </button>\n </div>\n\n</div>\n", styles: [".o-lm__step-row{display:flex;align-items:center;gap:8px;margin-top:1.5rem;margin-bottom:1.25rem}.o-lm__step{width:22px;height:22px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:500;background:#e6f1fb;color:#185fa5;flex-shrink:0}.o-lm__step-label{font-size:13px;font-weight:500;color:#5a5a5a}.o-lm__modules-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;margin-bottom:1.5rem}@media(max-width:900px){.o-lm__modules-grid{grid-template-columns:repeat(3,1fr)}}@media(max-width:600px){.o-lm__modules-grid{grid-template-columns:repeat(2,1fr)}}.o-lm__mod-card{border:1.5px solid #e1e1e1;border-radius:8px;padding:14px 12px;cursor:pointer;transition:border-color .15s,background .15s;background:#fff;position:relative;-webkit-user-select:none;user-select:none;outline:none}.o-lm__mod-card:hover{border-color:#b9b9b9}.o-lm__mod-card:focus-visible{box-shadow:0 0 0 3px #15104e2e}.o-lm__mod-card--selected{border-color:#378add;background:#e6f1fb}.o-lm__mod-card--selected .o-lm__mod-icon{color:#185fa5}.o-lm__mod-card--selected .o-lm__mod-check{display:flex}.o-lm__mod-card--custom:hover{border-color:#7aaa3c}.o-lm__mod-card--selected-custom{border-color:#639922;background:#eaf3de}.o-lm__mod-card--selected-custom .o-lm__mod-icon{color:#3b6d11}.o-lm__mod-card--selected-custom .o-lm__mod-check--custom{display:flex}.o-lm__mod-check{position:absolute;top:8px;right:8px;width:16px;height:16px;border-radius:50%;background:#378add;display:none;align-items:center;justify-content:center}.o-lm__mod-check svg{width:10px;height:10px;stroke:#fff;stroke-width:3;fill:none}.o-lm__mod-check--custom{background:#639922}.o-lm__mod-icon{font-size:22px;margin-bottom:8px;color:#5a5a5a;display:flex;align-items:center}.o-lm__mod-icon svg{width:22px;height:22px}.o-lm__mod-title{font-size:13px;font-weight:500;color:#333;margin:0 0 4px}.o-lm__mod-desc{font-size:11px;color:#5a5a5a;margin:0}.o-lm__panel{border:.5px solid #e1e1e1;border-radius:8px;overflow:hidden;min-height:200px;background:#fff}.o-lm__panel-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:.5px solid #e1e1e1;background:#fafafa}.o-lm__panel-title{font-size:13px;font-weight:500;color:#333}.o-lm__badge{font-size:11px;padding:2px 8px;border-radius:20px;font-weight:500}.o-lm__badge--blue{background:#e6f1fb;color:#185fa5}.o-lm__badge--green{background:#eaf3de;color:#3b6d11}.o-lm__search-row{display:flex;align-items:center;gap:8px;padding:8px 12px;border-bottom:.5px solid #e1e1e1;background:#fff}.o-lm__search-row svg{width:14px;height:14px;color:#767676;flex-shrink:0}.o-lm__search-input{flex:1;border:none;background:transparent;font-size:12px;color:#333;outline:none}.o-lm__search-input::placeholder{color:#767676}.o-lm__panel-body{display:grid;grid-template-columns:repeat(3,1fr);gap:0}@media(max-width:700px){.o-lm__panel-body{grid-template-columns:1fr}}.o-lm__module-section{border-right:.5px solid #e1e1e1;padding:12px}.o-lm__module-section:last-child{border-right:none}.o-lm__section-title{font-size:11px;font-weight:500;color:#5a5a5a;text-transform:uppercase;letter-spacing:.5px;margin:0 0 8px;padding-bottom:6px;border-bottom:.5px solid #e1e1e1}.o-lm__section-count{font-size:10px;font-weight:400;text-transform:none;letter-spacing:0}.o-lm__feature-item{display:flex;align-items:center;gap:8px;padding:5px 0;font-size:12px;color:#333}.o-lm__feature-item--disabled{opacity:.6;cursor:not-allowed}.o-lm__feature-item--disabled .o-lm__feature-checkbox,.o-lm__feature-item--disabled .o-lm__feature-label{cursor:not-allowed}.o-lm__feature-checkbox{width:14px;height:14px;cursor:pointer;flex-shrink:0;accent-color:#15104e}.o-lm__feature-label{cursor:pointer;font-size:12px;color:#333;line-height:1.3}.o-lm__empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:3rem;color:#767676;grid-column:1/-1}.o-lm__empty-state svg{width:36px;height:36px;margin-bottom:8px;opacity:.4}.o-lm__empty-state p{font-size:13px;margin:0}.o-lm__action-row{display:flex;justify-content:flex-end;margin-top:1rem}.o-lm__btn-clear{padding:7px 18px;border-radius:8px;font-size:13px;font-weight:500;cursor:pointer;border:.5px solid #b9b9b9;background:#fff;color:#333;transition:background .12s,border-color .12s}.o-lm__btn-clear:hover:not(:disabled){background:#f5f5f5;border-color:#999}.o-lm__btn-clear:disabled{opacity:.5;cursor:not-allowed}.o-lm--disabled{pointer-events:none;opacity:.85}\n"] }]
561
+ }], ctorParameters: () => [{ type: i1.HttpClient }, { type: i0.ChangeDetectorRef }], propDecorators: { configPath: [{
562
+ type: Input
563
+ }], disabled: [{
564
+ type: Input
565
+ }], value: [{
566
+ type: Input
567
+ }], valueChange: [{
568
+ type: Output
569
+ }] } });
570
+
571
+ class OStatusToggleComponent {
572
+ labels = ['Active', 'Inactive'];
573
+ value = true;
574
+ disabled = false;
575
+ valueChange = new EventEmitter();
576
+ onChange = () => { };
577
+ onTouched = () => { };
578
+ get activeLabel() {
579
+ return this.labels[0];
580
+ }
581
+ get inactiveLabel() {
582
+ return this.labels[1];
583
+ }
584
+ toggle() {
585
+ if (this.disabled)
586
+ return;
587
+ this.value = !this.value;
588
+ this.onChange(this.value);
589
+ this.onTouched();
590
+ this.valueChange.emit(this.value);
591
+ }
592
+ writeValue(val) {
593
+ this.value = !!val;
594
+ }
595
+ registerOnChange(fn) {
596
+ this.onChange = fn;
597
+ }
598
+ registerOnTouched(fn) {
599
+ this.onTouched = fn;
600
+ }
601
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OStatusToggleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
602
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: OStatusToggleComponent, isStandalone: true, selector: "o-status-toggle", inputs: { labels: "labels", value: "value", disabled: "disabled" }, outputs: { valueChange: "valueChange" }, providers: [
603
+ {
604
+ provide: NG_VALUE_ACCESSOR,
605
+ useExisting: forwardRef(() => OStatusToggleComponent),
606
+ multi: true
607
+ }
608
+ ], ngImport: i0, template: "<label class=\"o-status-toggle\" [class.o-status-toggle--active]=\"value\" [class.o-status-toggle--inactive]=\"!value\" [class.o-status-toggle--disabled]=\"disabled\">\n <input\n type=\"checkbox\"\n class=\"o-status-toggle__input\"\n [checked]=\"value\"\n [disabled]=\"disabled\"\n (change)=\"toggle()\"\n aria-label=\"Status toggle\"\n />\n <span class=\"o-status-toggle__track\">\n <span class=\"o-status-toggle__thumb\"></span>\n </span>\n @if (value) {\n <span class=\"o-status-toggle__label o-status-toggle__label--left\">{{ activeLabel }}</span>\n }\n @if (!value) {\n <span class=\"o-status-toggle__label o-status-toggle__label--right\">{{ inactiveLabel }}</span>\n }\n</label>\n", styles: [".o-status-toggle{position:relative;display:inline-flex;align-items:center;cursor:pointer;-webkit-user-select:none;user-select:none;width:80px;height:28px;border-radius:999px;transition:background .2s}.o-status-toggle--active{background:#16a34a}.o-status-toggle--active .o-status-toggle__thumb{transform:translate(2px);right:auto;left:2px}.o-status-toggle--inactive{background:#ef4444}.o-status-toggle--inactive .o-status-toggle__thumb{transform:translate(0);left:auto;right:2px}.o-status-toggle--disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.o-status-toggle__input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}.o-status-toggle__track{position:absolute;inset:0;border-radius:999px}.o-status-toggle__thumb{position:absolute;top:2px;width:24px;height:24px;border-radius:50%;background:#f3f4f6;box-shadow:0 1px 3px #00000040;transition:left .2s,right .2s}.o-status-toggle__label{position:absolute;font-size:.68rem;font-weight:700;text-transform:uppercase;color:#fff;pointer-events:none}.o-status-toggle__label--left{left:6px}.o-status-toggle__label--right{right:6px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }] });
609
+ }
610
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OStatusToggleComponent, decorators: [{
611
+ type: Component,
612
+ args: [{ selector: 'o-status-toggle', standalone: true, imports: [CommonModule, FormsModule], providers: [
613
+ {
614
+ provide: NG_VALUE_ACCESSOR,
615
+ useExisting: forwardRef(() => OStatusToggleComponent),
616
+ multi: true
617
+ }
618
+ ], template: "<label class=\"o-status-toggle\" [class.o-status-toggle--active]=\"value\" [class.o-status-toggle--inactive]=\"!value\" [class.o-status-toggle--disabled]=\"disabled\">\n <input\n type=\"checkbox\"\n class=\"o-status-toggle__input\"\n [checked]=\"value\"\n [disabled]=\"disabled\"\n (change)=\"toggle()\"\n aria-label=\"Status toggle\"\n />\n <span class=\"o-status-toggle__track\">\n <span class=\"o-status-toggle__thumb\"></span>\n </span>\n @if (value) {\n <span class=\"o-status-toggle__label o-status-toggle__label--left\">{{ activeLabel }}</span>\n }\n @if (!value) {\n <span class=\"o-status-toggle__label o-status-toggle__label--right\">{{ inactiveLabel }}</span>\n }\n</label>\n", styles: [".o-status-toggle{position:relative;display:inline-flex;align-items:center;cursor:pointer;-webkit-user-select:none;user-select:none;width:80px;height:28px;border-radius:999px;transition:background .2s}.o-status-toggle--active{background:#16a34a}.o-status-toggle--active .o-status-toggle__thumb{transform:translate(2px);right:auto;left:2px}.o-status-toggle--inactive{background:#ef4444}.o-status-toggle--inactive .o-status-toggle__thumb{transform:translate(0);left:auto;right:2px}.o-status-toggle--disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.o-status-toggle__input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}.o-status-toggle__track{position:absolute;inset:0;border-radius:999px}.o-status-toggle__thumb{position:absolute;top:2px;width:24px;height:24px;border-radius:50%;background:#f3f4f6;box-shadow:0 1px 3px #00000040;transition:left .2s,right .2s}.o-status-toggle__label{position:absolute;font-size:.68rem;font-weight:700;text-transform:uppercase;color:#fff;pointer-events:none}.o-status-toggle__label--left{left:6px}.o-status-toggle__label--right{right:6px}\n"] }]
619
+ }], propDecorators: { labels: [{
620
+ type: Input
621
+ }], value: [{
622
+ type: Input
623
+ }], disabled: [{
624
+ type: Input
625
+ }], valueChange: [{
626
+ type: Output
627
+ }] } });
628
+
629
+ class FormDrawerComponent {
630
+ cdr;
631
+ open = false;
632
+ title = 'New Request';
633
+ steps = [];
634
+ rowData = null;
635
+ showSubmit = true;
636
+ readOnly = false;
637
+ actionType = 'new';
638
+ closeDrawer = new EventEmitter();
639
+ save = new EventEmitter();
640
+ submit = new EventEmitter();
641
+ formData = {};
642
+ selectedProducts = [];
643
+ constructor(cdr) {
644
+ this.cdr = cdr;
645
+ }
646
+ ngOnChanges(changes) {
647
+ if (changes['rowData'] || changes['open']) {
648
+ this.formData = this.rowData ? { ...this.rowData } : {};
649
+ this.initializeProductDetails();
650
+ this.selectedProducts = this.getSelectedProducts();
651
+ this.cdr.detectChanges();
652
+ }
653
+ }
654
+ initializeProductDetails() {
655
+ if (!this.formData.productDetails) {
656
+ this.formData.productDetails = {};
657
+ }
658
+ if (this.formData.licenseDetails && Array.isArray(this.formData.licenseDetails)) {
659
+ this.formData.licenseDetails.forEach((prod) => {
660
+ const pName = prod.productName.toLowerCase();
661
+ this.formData.productDetails[pName] = {
662
+ userLimit: prod.userLimit || 80,
663
+ concurrentLimit: prod.concurrentLimit || 15,
664
+ startDate: prod.startDate || '',
665
+ endDate: prod.endDate || '',
666
+ gracePeriod: prod.gracePeriod || 30
667
+ };
668
+ });
669
+ }
670
+ else {
671
+ this.syncProductDetails();
672
+ }
673
+ }
674
+ getSelectedProducts() {
675
+ const modules = this.formData.modules || {};
676
+ return Object.keys(modules).filter(key => {
677
+ const screens = modules[key];
678
+ return Array.isArray(screens) && screens.length > 0;
679
+ });
680
+ }
681
+ onModulesChange(value) {
682
+ this.formData.modules = value || {};
683
+ this.syncProductDetails();
684
+ this.selectedProducts = this.getSelectedProducts();
685
+ this.cdr.detectChanges();
686
+ }
687
+ syncProductDetails() {
688
+ if (!this.formData.productDetails) {
689
+ this.formData.productDetails = {};
690
+ }
691
+ const selectedProds = this.getSelectedProducts();
692
+ selectedProds.forEach(pName => {
693
+ const key = pName.toLowerCase();
694
+ if (!this.formData.productDetails[key]) {
695
+ this.formData.productDetails[key] = {
696
+ userLimit: 80,
697
+ concurrentLimit: 15,
698
+ startDate: '',
699
+ endDate: '',
700
+ gracePeriod: 30
701
+ };
702
+ }
703
+ });
704
+ }
705
+ /**
706
+ * Returns all fields for rendering, including hidden section-header fields and
707
+ * special component-type fields like licenseModules.
708
+ */
709
+ visibleFields(step) {
710
+ return (step.fields || []).filter(f => {
711
+ // Always include hidden fields that carry a sectionName (section headers)
712
+ if (f.type === 'hidden' && f.sectionName) {
713
+ return true;
714
+ }
715
+ // Exclude purely hidden fields with no sectionName
716
+ if (f.type === 'hidden') {
717
+ return false;
718
+ }
719
+ // Include all renderable types
720
+ return (!f.type ||
721
+ f.type === 'input' ||
722
+ f.type === 'select' ||
723
+ f.type === 'textarea' ||
724
+ f.type === 'date' ||
725
+ f.type === 'licenseModules' ||
726
+ f.type === 'roleActions');
727
+ });
728
+ }
729
+ isRoleActionEnabled(action) {
730
+ const actions = this.formData.actions || [];
731
+ return actions.includes(action);
732
+ }
733
+ toggleRoleAction(action, enabled) {
734
+ if (!this.formData.actions) {
735
+ this.formData.actions = [];
736
+ }
737
+ if (enabled) {
738
+ if (!this.formData.actions.includes(action)) {
739
+ this.formData.actions.push(action);
740
+ }
741
+ }
742
+ else {
743
+ this.formData.actions = this.formData.actions.filter((a) => a !== action);
744
+ }
745
+ this.cdr.detectChanges();
746
+ }
747
+ close() {
748
+ this.closeDrawer.emit();
749
+ }
750
+ onSave() {
751
+ this.save.emit({ ...this.formData });
752
+ }
753
+ onSubmit() {
754
+ this.submit.emit({ ...this.formData });
755
+ }
756
+ onOverlayClick(event) {
757
+ if (event.target.classList.contains('o-drawer-overlay')) {
758
+ this.close();
759
+ }
760
+ }
761
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: FormDrawerComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
762
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: FormDrawerComponent, isStandalone: true, selector: "o-form-drawer", inputs: { open: "open", title: "title", steps: "steps", rowData: "rowData", showSubmit: "showSubmit", readOnly: "readOnly", actionType: "actionType" }, outputs: { closeDrawer: "closeDrawer", save: "save", submit: "submit" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"o-drawer-overlay\" [class.open]=\"open\" (click)=\"onOverlayClick($event)\">\n <div class=\"o-drawer\" [class.open]=\"open\">\n\n <div class=\"o-drawer-header\">\n <h3 class=\"o-drawer-title\">\n {{ title }}\n @if (formData.requestId) {\n <span class=\"o-drawer-id\">({{ formData.requestId }})</span>\n }\n </h3>\n <button class=\"o-drawer-close\" type=\"button\" (click)=\"close()\" aria-label=\"Close drawer\">&#x2715;</button>\n </div>\n\n <div class=\"o-drawer-body\">\n @for (step of steps; track step.stepName) {\n @if (step.stepName === 'step3') {\n <div class=\"o-section-header span-3\">{{ step.label || 'License details' }}</div>\n \n <div class=\"o-license-details-table-wrapper\">\n <table class=\"o-license-details-table\">\n <thead>\n <tr>\n <th>Product</th>\n <th>Users</th>\n <th>Grace Period (Days)</th>\n <th>Start Date</th>\n <th>End Date</th>\n </tr>\n </thead>\n <tbody>\n @if (selectedProducts.length === 0) {\n <tr>\n <td colspan=\"5\" style=\"text-align: center; color: #94a3b8; padding: 16px;\">\n No modules selected. Select modules in Step 2 to configure license details.\n </td>\n </tr>\n } @else {\n @for (prod of selectedProducts; track prod) {\n @if (formData.productDetails && formData.productDetails[prod.toLowerCase()]) {\n <tr>\n <td class=\"o-module-name-cell\">{{ prod.toUpperCase() }}</td>\n <td>\n <input type=\"number\" class=\"o-input\" \n [(ngModel)]=\"formData.productDetails[prod.toLowerCase()].userLimit\" \n [disabled]=\"readOnly\"\n placeholder=\"e.g. 80\">\n </td>\n <td>\n <input type=\"number\" class=\"o-input\" \n [(ngModel)]=\"formData.productDetails[prod.toLowerCase()].gracePeriod\" \n [disabled]=\"readOnly || actionType === 'renew'\"\n placeholder=\"e.g. 30\">\n </td>\n <td>\n <input type=\"date\" class=\"o-input\" \n [(ngModel)]=\"formData.productDetails[prod.toLowerCase()].startDate\"\n [disabled]=\"readOnly\">\n </td>\n <td>\n <input type=\"date\" class=\"o-input\" \n [(ngModel)]=\"formData.productDetails[prod.toLowerCase()].endDate\"\n [disabled]=\"readOnly\">\n </td>\n </tr>\n }\n }\n }\n </tbody>\n </table>\n </div>\n } @else {\n @for (item of visibleFields(step); track item.name) {\n\n @if (item.type === 'hidden' && item.sectionName) {\n <div class=\"o-section-header\">{{ item.sectionName }}</div>\n } @else if (item.type === 'licenseModules') {\n <div class=\"o-field span-3\">\n @if (item.label) {\n <label class=\"o-field-label\">{{ item.label }}</label>\n }\n <o-license-modules\n [configPath]=\"item['configPath'] || 'assets/application/pages/license-modules.json'\"\n [value]=\"formData[item.name]\"\n [disabled]=\"readOnly || actionType === 'renew'\"\n (valueChange)=\"onModulesChange($event)\"\n ></o-license-modules>\n </div>\n\n } @else if (item.type === 'roleActions') {\n <div class=\"o-field span-3\">\n @if (item.label) {\n <label class=\"o-field-label\">{{ item.label }}</label>\n }\n \n <div class=\"o-permissions-grid\">\n <div class=\"o-permission-toggle-item\">\n <span class=\"o-permission-label\">Edit Draft</span>\n <o-status-toggle \n [labels]=\"['On', 'Off']\" \n [value]=\"isRoleActionEnabled('edit')\"\n (valueChange)=\"toggleRoleAction('edit', $event)\"\n [disabled]=\"readOnly\">\n </o-status-toggle>\n </div>\n <div class=\"o-permission-toggle-item\">\n <span class=\"o-permission-label\">Save Request</span>\n <o-status-toggle \n [labels]=\"['On', 'Off']\" \n [value]=\"isRoleActionEnabled('save')\"\n (valueChange)=\"toggleRoleAction('save', $event)\"\n [disabled]=\"readOnly\">\n </o-status-toggle>\n </div>\n <div class=\"o-permission-toggle-item\">\n <span class=\"o-permission-label\">Send for Approval</span>\n <o-status-toggle \n [labels]=\"['On', 'Off']\" \n [value]=\"isRoleActionEnabled('submit')\"\n (valueChange)=\"toggleRoleAction('submit', $event)\"\n [disabled]=\"readOnly\">\n </o-status-toggle>\n </div>\n <div class=\"o-permission-toggle-item\">\n <span class=\"o-permission-label\">Approve</span>\n <o-status-toggle \n [labels]=\"['On', 'Off']\" \n [value]=\"isRoleActionEnabled('approve')\"\n (valueChange)=\"toggleRoleAction('approve', $event)\"\n [disabled]=\"readOnly\">\n </o-status-toggle>\n </div>\n <div class=\"o-permission-toggle-item\">\n <span class=\"o-permission-label\">Reject</span>\n <o-status-toggle \n [labels]=\"['On', 'Off']\" \n [value]=\"isRoleActionEnabled('reject')\"\n (valueChange)=\"toggleRoleAction('reject', $event)\"\n [disabled]=\"readOnly\">\n </o-status-toggle>\n </div>\n <div class=\"o-permission-toggle-item\">\n <span class=\"o-permission-label\">Return for Revision</span>\n <o-status-toggle \n [labels]=\"['On', 'Off']\" \n [value]=\"isRoleActionEnabled('return')\"\n (valueChange)=\"toggleRoleAction('return', $event)\"\n [disabled]=\"readOnly\">\n </o-status-toggle>\n </div>\n </div>\n </div>\n\n } @else {\n <div class=\"o-field\"\n [class.span-1]=\"!item.colSpan || item.colSpan === 'col-1'\"\n [class.span-2]=\"item.colSpan === 'col-2'\"\n [class.span-3]=\"item.colSpan === 'col-3'\">\n\n <label class=\"o-field-label\">\n {{ item.label }}\n @if (item.required) {\n <span class=\"o-required\">*</span>\n }\n </label>\n\n @if (item.type === 'select') {\n <select class=\"o-input\"\n [(ngModel)]=\"formData[item.name]\"\n [disabled]=\"readOnly || actionType === 'renew' || item.isDisabled || false\">\n <option value=\"\">&#8212; Select &#8212;</option>\n @for (opt of (item.options || []); track opt.value) {\n <option [value]=\"opt.value\">{{ opt.label }}</option>\n }\n </select>\n }\n\n @if (item.type === 'textarea') {\n <textarea class=\"o-input o-textarea\"\n [placeholder]=\"item.placeholder || ''\"\n [(ngModel)]=\"formData[item.name]\"\n [disabled]=\"readOnly || actionType === 'renew' || item.isDisabled || false\">\n </textarea>\n }\n\n @if (!item.type || item.type === 'input' || item.type === 'date') {\n <input class=\"o-input\"\n [type]=\"item.inputType || 'text'\"\n [placeholder]=\"item.placeholder || ''\"\n [(ngModel)]=\"formData[item.name]\"\n [disabled]=\"readOnly || actionType === 'renew' || item.isDisabled || false\">\n }\n\n </div>\n }\n\n }\n }\n }\n </div>\n\n <div class=\"o-drawer-footer\">\n @if (readOnly) {\n <button class=\"o-btn-secondary\" type=\"button\" (click)=\"close()\">Close</button>\n } @else {\n <button class=\"o-btn-secondary\" type=\"button\" (click)=\"close()\">Cancel</button>\n <button class=\"o-btn-primary\" type=\"button\" (click)=\"onSave()\">Save</button>\n }\n </div>\n\n </div>\n</div>\n", styles: [".o-drawer-overlay{position:fixed;inset:0;background:#0000;z-index:200;pointer-events:none;transition:background .25s}.o-drawer-overlay.open{background:#0f172a59;pointer-events:all}.o-drawer{position:fixed;top:0;right:-720px;width:680px;max-width:96vw;height:100vh;background:#fff;box-shadow:-8px 0 32px #0f172a2e;display:flex;flex-direction:column;transition:right .28s cubic-bezier(.4,0,.2,1);z-index:201}.o-drawer.open{right:0}.o-drawer-header{display:flex;align-items:center;justify-content:space-between;padding:18px 24px;border-bottom:1px solid #e2e8f0;background:#f8fafc;flex-shrink:0}.o-drawer-title{font-size:1rem;font-weight:700;color:#1e293b;margin:0}.o-drawer-close{background:none;border:none;font-size:1.1rem;cursor:pointer;color:#94a3b8;padding:4px 8px;border-radius:4px;transition:background .15s}.o-drawer-close:hover{background:#f1f5f9;color:#334155}.o-drawer-body{flex:1;overflow-y:auto;padding:20px 24px;display:grid;grid-template-columns:repeat(3,1fr);gap:12px 16px;align-content:start}.o-section-header{grid-column:1/-1;font-size:.72rem;font-weight:700;text-transform:uppercase;color:#94a3b8;letter-spacing:.08em;margin-top:8px;padding-bottom:4px;border-bottom:1px solid #f1f5f9}.o-field{display:flex;flex-direction:column;gap:5px}.o-field.span-1{grid-column:span 1}.o-field.span-2{grid-column:span 2}.o-field.span-3{grid-column:1/-1}.o-field-label{font-size:.78rem;font-weight:600;color:#475569}.o-required{color:#ef4444;margin-left:2px}.o-input{padding:8px 12px;border:1px solid #e2e8f0;border-radius:7px;font-size:.85rem;color:#1e293b;outline:none;transition:border-color .15s,box-shadow .15s;background:#fff;width:100%;box-sizing:border-box}.o-input:focus{border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f61f}.o-input:disabled{background:#f8fafc;color:#94a3b8;cursor:not-allowed}.o-textarea{resize:vertical;min-height:80px}.o-drawer-footer{display:flex;gap:10px;padding:16px 24px;border-top:1px solid #e2e8f0;background:#f8fafc;flex-shrink:0}.o-btn-secondary{padding:8px 18px;border-radius:7px;border:1px solid #e2e8f0;background:#fff;color:#64748b;font-size:.82rem;cursor:pointer;transition:background .15s}.o-btn-secondary:hover{background:#f1f5f9}.o-btn-primary{padding:8px 18px;border-radius:7px;border:none;background:#3b82f6;color:#fff;font-size:.82rem;font-weight:600;cursor:pointer;transition:background .15s}.o-btn-primary:hover{background:#2563eb}.o-btn-action{padding:8px 18px;border-radius:7px;border:none;background:#10b981;color:#fff;font-size:.82rem;font-weight:600;cursor:pointer;transition:background .15s}.o-btn-action:hover{background:#059669}.o-drawer-id{font-size:.85rem;font-weight:500;color:#64748b;margin-left:8px}.o-license-details-table-wrapper{grid-column:1/-1;width:100%;overflow-x:auto;border:1px solid #e2e8f0;border-radius:8px;margin-top:10px;background:#fff}.o-license-details-table{width:100%;border-collapse:collapse;font-size:.85rem}.o-license-details-table th{background:#f8fafc;color:#475569;font-weight:600;text-align:left;padding:10px 12px;border-bottom:1px solid #e2e8f0;font-size:.78rem}.o-license-details-table td{padding:8px 12px;border-bottom:1px solid #f1f5f9;vertical-align:middle}.o-license-details-table tr:last-child td{border-bottom:none}.o-license-details-table .o-module-name-cell{font-weight:700;color:#15104e}.o-license-details-table .o-input{padding:6px 10px;font-size:.8rem;height:32px}.o-permissions-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px 16px;background:#f8fafc;padding:16px;border-radius:8px;border:1px solid #e2e8f0;margin-top:10px}.o-permission-toggle-item{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;background:#fff;border:1px solid #e2e8f0;border-radius:7px;box-shadow:0 1px 2px #0f172a08}.o-permission-label{font-size:.8rem;font-weight:600;color:#334155}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NumberValueAccessor, selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1$1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: OLicenseModulesComponent, selector: "o-license-modules", inputs: ["configPath", "disabled", "value"], outputs: ["valueChange"] }, { kind: "component", type: OStatusToggleComponent, selector: "o-status-toggle", inputs: ["labels", "value", "disabled"], outputs: ["valueChange"] }] });
763
+ }
764
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: FormDrawerComponent, decorators: [{
765
+ type: Component,
766
+ args: [{ selector: 'o-form-drawer', standalone: true, imports: [CommonModule, FormsModule, OLicenseModulesComponent, OStatusToggleComponent], template: "<div class=\"o-drawer-overlay\" [class.open]=\"open\" (click)=\"onOverlayClick($event)\">\n <div class=\"o-drawer\" [class.open]=\"open\">\n\n <div class=\"o-drawer-header\">\n <h3 class=\"o-drawer-title\">\n {{ title }}\n @if (formData.requestId) {\n <span class=\"o-drawer-id\">({{ formData.requestId }})</span>\n }\n </h3>\n <button class=\"o-drawer-close\" type=\"button\" (click)=\"close()\" aria-label=\"Close drawer\">&#x2715;</button>\n </div>\n\n <div class=\"o-drawer-body\">\n @for (step of steps; track step.stepName) {\n @if (step.stepName === 'step3') {\n <div class=\"o-section-header span-3\">{{ step.label || 'License details' }}</div>\n \n <div class=\"o-license-details-table-wrapper\">\n <table class=\"o-license-details-table\">\n <thead>\n <tr>\n <th>Product</th>\n <th>Users</th>\n <th>Grace Period (Days)</th>\n <th>Start Date</th>\n <th>End Date</th>\n </tr>\n </thead>\n <tbody>\n @if (selectedProducts.length === 0) {\n <tr>\n <td colspan=\"5\" style=\"text-align: center; color: #94a3b8; padding: 16px;\">\n No modules selected. Select modules in Step 2 to configure license details.\n </td>\n </tr>\n } @else {\n @for (prod of selectedProducts; track prod) {\n @if (formData.productDetails && formData.productDetails[prod.toLowerCase()]) {\n <tr>\n <td class=\"o-module-name-cell\">{{ prod.toUpperCase() }}</td>\n <td>\n <input type=\"number\" class=\"o-input\" \n [(ngModel)]=\"formData.productDetails[prod.toLowerCase()].userLimit\" \n [disabled]=\"readOnly\"\n placeholder=\"e.g. 80\">\n </td>\n <td>\n <input type=\"number\" class=\"o-input\" \n [(ngModel)]=\"formData.productDetails[prod.toLowerCase()].gracePeriod\" \n [disabled]=\"readOnly || actionType === 'renew'\"\n placeholder=\"e.g. 30\">\n </td>\n <td>\n <input type=\"date\" class=\"o-input\" \n [(ngModel)]=\"formData.productDetails[prod.toLowerCase()].startDate\"\n [disabled]=\"readOnly\">\n </td>\n <td>\n <input type=\"date\" class=\"o-input\" \n [(ngModel)]=\"formData.productDetails[prod.toLowerCase()].endDate\"\n [disabled]=\"readOnly\">\n </td>\n </tr>\n }\n }\n }\n </tbody>\n </table>\n </div>\n } @else {\n @for (item of visibleFields(step); track item.name) {\n\n @if (item.type === 'hidden' && item.sectionName) {\n <div class=\"o-section-header\">{{ item.sectionName }}</div>\n } @else if (item.type === 'licenseModules') {\n <div class=\"o-field span-3\">\n @if (item.label) {\n <label class=\"o-field-label\">{{ item.label }}</label>\n }\n <o-license-modules\n [configPath]=\"item['configPath'] || 'assets/application/pages/license-modules.json'\"\n [value]=\"formData[item.name]\"\n [disabled]=\"readOnly || actionType === 'renew'\"\n (valueChange)=\"onModulesChange($event)\"\n ></o-license-modules>\n </div>\n\n } @else if (item.type === 'roleActions') {\n <div class=\"o-field span-3\">\n @if (item.label) {\n <label class=\"o-field-label\">{{ item.label }}</label>\n }\n \n <div class=\"o-permissions-grid\">\n <div class=\"o-permission-toggle-item\">\n <span class=\"o-permission-label\">Edit Draft</span>\n <o-status-toggle \n [labels]=\"['On', 'Off']\" \n [value]=\"isRoleActionEnabled('edit')\"\n (valueChange)=\"toggleRoleAction('edit', $event)\"\n [disabled]=\"readOnly\">\n </o-status-toggle>\n </div>\n <div class=\"o-permission-toggle-item\">\n <span class=\"o-permission-label\">Save Request</span>\n <o-status-toggle \n [labels]=\"['On', 'Off']\" \n [value]=\"isRoleActionEnabled('save')\"\n (valueChange)=\"toggleRoleAction('save', $event)\"\n [disabled]=\"readOnly\">\n </o-status-toggle>\n </div>\n <div class=\"o-permission-toggle-item\">\n <span class=\"o-permission-label\">Send for Approval</span>\n <o-status-toggle \n [labels]=\"['On', 'Off']\" \n [value]=\"isRoleActionEnabled('submit')\"\n (valueChange)=\"toggleRoleAction('submit', $event)\"\n [disabled]=\"readOnly\">\n </o-status-toggle>\n </div>\n <div class=\"o-permission-toggle-item\">\n <span class=\"o-permission-label\">Approve</span>\n <o-status-toggle \n [labels]=\"['On', 'Off']\" \n [value]=\"isRoleActionEnabled('approve')\"\n (valueChange)=\"toggleRoleAction('approve', $event)\"\n [disabled]=\"readOnly\">\n </o-status-toggle>\n </div>\n <div class=\"o-permission-toggle-item\">\n <span class=\"o-permission-label\">Reject</span>\n <o-status-toggle \n [labels]=\"['On', 'Off']\" \n [value]=\"isRoleActionEnabled('reject')\"\n (valueChange)=\"toggleRoleAction('reject', $event)\"\n [disabled]=\"readOnly\">\n </o-status-toggle>\n </div>\n <div class=\"o-permission-toggle-item\">\n <span class=\"o-permission-label\">Return for Revision</span>\n <o-status-toggle \n [labels]=\"['On', 'Off']\" \n [value]=\"isRoleActionEnabled('return')\"\n (valueChange)=\"toggleRoleAction('return', $event)\"\n [disabled]=\"readOnly\">\n </o-status-toggle>\n </div>\n </div>\n </div>\n\n } @else {\n <div class=\"o-field\"\n [class.span-1]=\"!item.colSpan || item.colSpan === 'col-1'\"\n [class.span-2]=\"item.colSpan === 'col-2'\"\n [class.span-3]=\"item.colSpan === 'col-3'\">\n\n <label class=\"o-field-label\">\n {{ item.label }}\n @if (item.required) {\n <span class=\"o-required\">*</span>\n }\n </label>\n\n @if (item.type === 'select') {\n <select class=\"o-input\"\n [(ngModel)]=\"formData[item.name]\"\n [disabled]=\"readOnly || actionType === 'renew' || item.isDisabled || false\">\n <option value=\"\">&#8212; Select &#8212;</option>\n @for (opt of (item.options || []); track opt.value) {\n <option [value]=\"opt.value\">{{ opt.label }}</option>\n }\n </select>\n }\n\n @if (item.type === 'textarea') {\n <textarea class=\"o-input o-textarea\"\n [placeholder]=\"item.placeholder || ''\"\n [(ngModel)]=\"formData[item.name]\"\n [disabled]=\"readOnly || actionType === 'renew' || item.isDisabled || false\">\n </textarea>\n }\n\n @if (!item.type || item.type === 'input' || item.type === 'date') {\n <input class=\"o-input\"\n [type]=\"item.inputType || 'text'\"\n [placeholder]=\"item.placeholder || ''\"\n [(ngModel)]=\"formData[item.name]\"\n [disabled]=\"readOnly || actionType === 'renew' || item.isDisabled || false\">\n }\n\n </div>\n }\n\n }\n }\n }\n </div>\n\n <div class=\"o-drawer-footer\">\n @if (readOnly) {\n <button class=\"o-btn-secondary\" type=\"button\" (click)=\"close()\">Close</button>\n } @else {\n <button class=\"o-btn-secondary\" type=\"button\" (click)=\"close()\">Cancel</button>\n <button class=\"o-btn-primary\" type=\"button\" (click)=\"onSave()\">Save</button>\n }\n </div>\n\n </div>\n</div>\n", styles: [".o-drawer-overlay{position:fixed;inset:0;background:#0000;z-index:200;pointer-events:none;transition:background .25s}.o-drawer-overlay.open{background:#0f172a59;pointer-events:all}.o-drawer{position:fixed;top:0;right:-720px;width:680px;max-width:96vw;height:100vh;background:#fff;box-shadow:-8px 0 32px #0f172a2e;display:flex;flex-direction:column;transition:right .28s cubic-bezier(.4,0,.2,1);z-index:201}.o-drawer.open{right:0}.o-drawer-header{display:flex;align-items:center;justify-content:space-between;padding:18px 24px;border-bottom:1px solid #e2e8f0;background:#f8fafc;flex-shrink:0}.o-drawer-title{font-size:1rem;font-weight:700;color:#1e293b;margin:0}.o-drawer-close{background:none;border:none;font-size:1.1rem;cursor:pointer;color:#94a3b8;padding:4px 8px;border-radius:4px;transition:background .15s}.o-drawer-close:hover{background:#f1f5f9;color:#334155}.o-drawer-body{flex:1;overflow-y:auto;padding:20px 24px;display:grid;grid-template-columns:repeat(3,1fr);gap:12px 16px;align-content:start}.o-section-header{grid-column:1/-1;font-size:.72rem;font-weight:700;text-transform:uppercase;color:#94a3b8;letter-spacing:.08em;margin-top:8px;padding-bottom:4px;border-bottom:1px solid #f1f5f9}.o-field{display:flex;flex-direction:column;gap:5px}.o-field.span-1{grid-column:span 1}.o-field.span-2{grid-column:span 2}.o-field.span-3{grid-column:1/-1}.o-field-label{font-size:.78rem;font-weight:600;color:#475569}.o-required{color:#ef4444;margin-left:2px}.o-input{padding:8px 12px;border:1px solid #e2e8f0;border-radius:7px;font-size:.85rem;color:#1e293b;outline:none;transition:border-color .15s,box-shadow .15s;background:#fff;width:100%;box-sizing:border-box}.o-input:focus{border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f61f}.o-input:disabled{background:#f8fafc;color:#94a3b8;cursor:not-allowed}.o-textarea{resize:vertical;min-height:80px}.o-drawer-footer{display:flex;gap:10px;padding:16px 24px;border-top:1px solid #e2e8f0;background:#f8fafc;flex-shrink:0}.o-btn-secondary{padding:8px 18px;border-radius:7px;border:1px solid #e2e8f0;background:#fff;color:#64748b;font-size:.82rem;cursor:pointer;transition:background .15s}.o-btn-secondary:hover{background:#f1f5f9}.o-btn-primary{padding:8px 18px;border-radius:7px;border:none;background:#3b82f6;color:#fff;font-size:.82rem;font-weight:600;cursor:pointer;transition:background .15s}.o-btn-primary:hover{background:#2563eb}.o-btn-action{padding:8px 18px;border-radius:7px;border:none;background:#10b981;color:#fff;font-size:.82rem;font-weight:600;cursor:pointer;transition:background .15s}.o-btn-action:hover{background:#059669}.o-drawer-id{font-size:.85rem;font-weight:500;color:#64748b;margin-left:8px}.o-license-details-table-wrapper{grid-column:1/-1;width:100%;overflow-x:auto;border:1px solid #e2e8f0;border-radius:8px;margin-top:10px;background:#fff}.o-license-details-table{width:100%;border-collapse:collapse;font-size:.85rem}.o-license-details-table th{background:#f8fafc;color:#475569;font-weight:600;text-align:left;padding:10px 12px;border-bottom:1px solid #e2e8f0;font-size:.78rem}.o-license-details-table td{padding:8px 12px;border-bottom:1px solid #f1f5f9;vertical-align:middle}.o-license-details-table tr:last-child td{border-bottom:none}.o-license-details-table .o-module-name-cell{font-weight:700;color:#15104e}.o-license-details-table .o-input{padding:6px 10px;font-size:.8rem;height:32px}.o-permissions-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px 16px;background:#f8fafc;padding:16px;border-radius:8px;border:1px solid #e2e8f0;margin-top:10px}.o-permission-toggle-item{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;background:#fff;border:1px solid #e2e8f0;border-radius:7px;box-shadow:0 1px 2px #0f172a08}.o-permission-label{font-size:.8rem;font-weight:600;color:#334155}\n"] }]
767
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { open: [{
768
+ type: Input
769
+ }], title: [{
770
+ type: Input
771
+ }], steps: [{
772
+ type: Input
773
+ }], rowData: [{
774
+ type: Input
775
+ }], showSubmit: [{
776
+ type: Input
777
+ }], readOnly: [{
778
+ type: Input
779
+ }], actionType: [{
780
+ type: Input
781
+ }], closeDrawer: [{
782
+ type: Output
783
+ }], save: [{
784
+ type: Output
785
+ }], submit: [{
786
+ type: Output
787
+ }] } });
788
+
789
+ class ContextMenuComponent {
790
+ el;
791
+ open = false;
792
+ position = { top: 0, left: 0 };
793
+ actions = [];
794
+ row = null;
795
+ menuType = '';
796
+ actionTriggered = new EventEmitter();
797
+ closed = new EventEmitter();
798
+ hoveredAction = null;
799
+ constructor(el) {
800
+ this.el = el;
801
+ }
802
+ visibleActions() {
803
+ if (!this.row)
804
+ return [];
805
+ return this.actions.filter(a => {
806
+ const typeMatch = !a.type || a.type === 'any' || a.type === this.menuType;
807
+ const statusMatch = !a.showWhen || a.showWhen.includes(this.row.status);
808
+ return typeMatch && statusMatch;
809
+ });
810
+ }
811
+ trigger(action) {
812
+ if (action.subActions && action.subActions.length > 0)
813
+ return;
814
+ this.actionTriggered.emit({ action: action.action, row: this.row });
815
+ this.hoveredAction = null;
816
+ this.closed.emit();
817
+ }
818
+ triggerSub(subAction) {
819
+ this.actionTriggered.emit({ action: subAction.action, row: this.row });
820
+ this.hoveredAction = null;
821
+ this.closed.emit();
822
+ }
823
+ setHovered(action) {
824
+ this.hoveredAction = action;
825
+ }
826
+ isDangerAction(action) {
827
+ return action === 'reject' || action === 'terminate';
828
+ }
829
+ onClickOutside() {
830
+ if (this.open) {
831
+ this.hoveredAction = null;
832
+ this.closed.emit();
833
+ }
834
+ }
835
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ContextMenuComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
836
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ContextMenuComponent, isStandalone: true, selector: "o-context-menu", inputs: { open: "open", position: "position", actions: "actions", row: "row", menuType: "menuType" }, outputs: { actionTriggered: "actionTriggered", closed: "closed" }, host: { listeners: { "document:click": "onClickOutside()" } }, ngImport: i0, template: "@if (open) {\n<div class=\"o-ctx-menu\"\n [style.top.px]=\"position.top\"\n [style.left.px]=\"position.left\">\n @for (action of visibleActions(); track action.action) {\n @if (action.subActions && action.subActions.length > 0) {\n <div class=\"o-ctx-item o-ctx-item--has-sub\"\n (mouseenter)=\"setHovered(action.action)\"\n (mouseleave)=\"setHovered(null)\">\n <span class=\"o-ctx-item-label\">{{ action.label }}</span>\n <span class=\"o-ctx-arrow\">&#9654;</span>\n @if (hoveredAction === action.action) {\n <div class=\"o-ctx-submenu\">\n @for (sub of action.subActions; track sub.action) {\n <button type=\"button\"\n class=\"o-ctx-item\"\n [class.o-ctx-item--danger]=\"isDangerAction(sub.action)\"\n (click)=\"triggerSub(sub)\">\n {{ sub.label }}\n </button>\n }\n </div>\n }\n </div>\n } @else {\n <button type=\"button\"\n class=\"o-ctx-item\"\n [class.o-ctx-item--danger]=\"isDangerAction(action.action)\"\n (click)=\"trigger(action)\">\n {{ action.label }}\n </button>\n }\n }\n @if (visibleActions().length === 0) {\n <div class=\"o-ctx-empty\">No actions</div>\n }\n</div>\n}\n", styles: [".o-ctx-menu{position:fixed;background:#fff;border:1px solid #e2e8f0;border-radius:10px;box-shadow:0 8px 24px #0f172a24;min-width:190px;z-index:999;padding:4px 0;animation:o-ctx-fade-in .12s ease}@keyframes o-ctx-fade-in{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}.o-ctx-item{display:flex;align-items:center;gap:8px;padding:9px 16px;font-size:.84rem;color:#334155;cursor:pointer;transition:background .12s,color .12s;width:100%;background:none;border:none;text-align:left}.o-ctx-item:hover{background:#f1f5f9}.o-ctx-item--danger{color:#dc2626}.o-ctx-item--danger:hover{background:#fef2f2;color:#b91c1c}.o-ctx-item--has-sub{position:relative;justify-content:space-between}.o-ctx-item--has-sub:hover{background:#f1f5f9}.o-ctx-item-label{flex:1}.o-ctx-arrow{font-size:.65rem;color:#94a3b8;margin-left:auto}.o-ctx-submenu{position:absolute;left:100%;top:0;background:#fff;border:1px solid #e2e8f0;border-radius:10px;box-shadow:0 8px 24px #0f172a24;min-width:160px;z-index:1000;padding:4px 0;animation:o-ctx-fade-in .1s ease}.o-ctx-empty{padding:10px 16px;color:#94a3b8;font-size:.82rem}\n"] });
837
+ }
838
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ContextMenuComponent, decorators: [{
839
+ type: Component,
840
+ args: [{ selector: 'o-context-menu', standalone: true, imports: [], template: "@if (open) {\n<div class=\"o-ctx-menu\"\n [style.top.px]=\"position.top\"\n [style.left.px]=\"position.left\">\n @for (action of visibleActions(); track action.action) {\n @if (action.subActions && action.subActions.length > 0) {\n <div class=\"o-ctx-item o-ctx-item--has-sub\"\n (mouseenter)=\"setHovered(action.action)\"\n (mouseleave)=\"setHovered(null)\">\n <span class=\"o-ctx-item-label\">{{ action.label }}</span>\n <span class=\"o-ctx-arrow\">&#9654;</span>\n @if (hoveredAction === action.action) {\n <div class=\"o-ctx-submenu\">\n @for (sub of action.subActions; track sub.action) {\n <button type=\"button\"\n class=\"o-ctx-item\"\n [class.o-ctx-item--danger]=\"isDangerAction(sub.action)\"\n (click)=\"triggerSub(sub)\">\n {{ sub.label }}\n </button>\n }\n </div>\n }\n </div>\n } @else {\n <button type=\"button\"\n class=\"o-ctx-item\"\n [class.o-ctx-item--danger]=\"isDangerAction(action.action)\"\n (click)=\"trigger(action)\">\n {{ action.label }}\n </button>\n }\n }\n @if (visibleActions().length === 0) {\n <div class=\"o-ctx-empty\">No actions</div>\n }\n</div>\n}\n", styles: [".o-ctx-menu{position:fixed;background:#fff;border:1px solid #e2e8f0;border-radius:10px;box-shadow:0 8px 24px #0f172a24;min-width:190px;z-index:999;padding:4px 0;animation:o-ctx-fade-in .12s ease}@keyframes o-ctx-fade-in{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}.o-ctx-item{display:flex;align-items:center;gap:8px;padding:9px 16px;font-size:.84rem;color:#334155;cursor:pointer;transition:background .12s,color .12s;width:100%;background:none;border:none;text-align:left}.o-ctx-item:hover{background:#f1f5f9}.o-ctx-item--danger{color:#dc2626}.o-ctx-item--danger:hover{background:#fef2f2;color:#b91c1c}.o-ctx-item--has-sub{position:relative;justify-content:space-between}.o-ctx-item--has-sub:hover{background:#f1f5f9}.o-ctx-item-label{flex:1}.o-ctx-arrow{font-size:.65rem;color:#94a3b8;margin-left:auto}.o-ctx-submenu{position:absolute;left:100%;top:0;background:#fff;border:1px solid #e2e8f0;border-radius:10px;box-shadow:0 8px 24px #0f172a24;min-width:160px;z-index:1000;padding:4px 0;animation:o-ctx-fade-in .1s ease}.o-ctx-empty{padding:10px 16px;color:#94a3b8;font-size:.82rem}\n"] }]
841
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { open: [{
842
+ type: Input
843
+ }], position: [{
844
+ type: Input
845
+ }], actions: [{
846
+ type: Input
847
+ }], row: [{
848
+ type: Input
849
+ }], menuType: [{
850
+ type: Input
851
+ }], actionTriggered: [{
852
+ type: Output
853
+ }], closed: [{
854
+ type: Output
855
+ }], onClickOutside: [{
856
+ type: HostListener,
857
+ args: ['document:click']
858
+ }] } });
859
+
860
+ class OSidePanelComponent {
861
+ open = false;
862
+ title = '';
863
+ row = null;
864
+ columns = [];
865
+ closed = new EventEmitter();
866
+ onClose() {
867
+ this.closed.emit();
868
+ }
869
+ getBadgeClass(value) {
870
+ if (!value)
871
+ return { 'o-side-panel__badge': true, 'o-badge--gray': true };
872
+ const v = value.toLowerCase();
873
+ const isGreen = ['active', 'approved', 'enabled'].includes(v);
874
+ const isRed = ['rejected', 'expired', 'inactive', 'terminated'].includes(v);
875
+ const isYellow = ['pending', 'in progress', 'submitted', 'pending_approval'].includes(v);
876
+ const isBlue = ['draft', 'returned'].includes(v);
877
+ return {
878
+ 'o-side-panel__badge': true,
879
+ 'o-badge--green': isGreen,
880
+ 'o-badge--red': isRed,
881
+ 'o-badge--yellow': isYellow,
882
+ 'o-badge--blue': isBlue,
883
+ 'o-badge--gray': !isGreen && !isRed && !isYellow && !isBlue
884
+ };
885
+ }
886
+ formatDate(value) {
887
+ if (!value)
888
+ return '—';
889
+ const d = new Date(value);
890
+ if (isNaN(d.getTime()))
891
+ return value;
892
+ return d.toLocaleDateString('en-US', {
893
+ year: 'numeric',
894
+ month: 'short',
895
+ day: '2-digit'
896
+ });
897
+ }
898
+ formatValue(val) {
899
+ if (val === null || val === undefined)
900
+ return '—';
901
+ if (typeof val === 'object') {
902
+ if (Array.isArray(val)) {
903
+ return val.join(', ') || '—';
904
+ }
905
+ const parts = [];
906
+ Object.keys(val).forEach(k => {
907
+ const list = val[k];
908
+ if (Array.isArray(list) && list.length > 0) {
909
+ parts.push(`${k.toUpperCase()}: ${list.map(f => f.substring(f.lastIndexOf('/') + 1)).join(', ')}`);
910
+ }
911
+ });
912
+ return parts.join(' | ') || '—';
913
+ }
914
+ return val;
915
+ }
916
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OSidePanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
917
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: OSidePanelComponent, isStandalone: true, selector: "o-side-panel", inputs: { open: "open", title: "title", row: "row", columns: "columns" }, outputs: { closed: "closed" }, ngImport: i0, template: "@if (open) {\n<div class=\"o-side-panel__backdrop\" (click)=\"onClose()\" aria-hidden=\"true\"></div>\n}\n\n<div class=\"o-side-panel\" [class.o-side-panel--open]=\"open\" role=\"dialog\" [attr.aria-modal]=\"open\" [attr.aria-label]=\"title\">\n\n <div class=\"o-side-panel__header\">\n <span class=\"o-side-panel__title\">{{ title }}</span>\n <button class=\"o-side-panel__close\" (click)=\"onClose()\" type=\"button\" aria-label=\"Close panel\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n\n <div class=\"o-side-panel__body\">\n @if (row) {\n @for (col of columns; track col.name) {\n @if (col.pipe === 'section') {\n <div class=\"o-side-panel__section-header\" style=\"font-weight: 700; font-size: 15px; margin: 16px 0 8px 0; color: #15104E; border-bottom: 2px solid #CDCDCD; padding-bottom: 4px;\">\n {{ col.label }}\n </div>\n } @else {\n <div class=\"o-side-panel__field\" style=\"margin-bottom: 12px; display: flex; flex-direction: column;\">\n <span class=\"o-side-panel__field-label\" style=\"font-size: 12px; color: #737373; font-weight: 500; margin-bottom: 2px;\">{{ col.label }}</span>\n <span class=\"o-side-panel__field-value\" style=\"font-size: 14px; color: #333333; font-weight: 600;\">\n @if (col.pipe === 'status') {\n <span class=\"o-side-panel__badge\" [ngClass]=\"getBadgeClass(row[col.name])\">\n {{ row[col.name] || '\u2014' }}\n </span>\n } @else if (col.pipe === 'date') {\n {{ formatDate(row[col.name]) }}\n } @else {\n {{ formatValue(row[col.name]) }}\n }\n </span>\n\n @if (col.name === 'modules' && row.licenseDetails?.length) {\n <span class=\"o-side-panel__field-label\" style=\"font-size: 12px; color: #737373; font-weight: 500; margin-top: 12px; margin-bottom: 4px;\">License Details per Product</span>\n <div class=\"o-license-details-table-wrapper\" style=\"margin-top: 4px; border: 1px solid #e2e8f0; border-radius: 8px; overflow: hidden; background: #fff; width: 100%;\">\n <table class=\"o-license-details-table\" style=\"width: 100%; border-collapse: collapse; font-size: 0.8rem;\">\n <thead>\n <tr style=\"background: #f8fafc; border-bottom: 1px solid #e2e8f0;\">\n <th style=\"padding: 8px 10px; text-align: left; font-weight: 600; color: #475569; font-size: 0.75rem;\">Product</th>\n <th style=\"padding: 8px 10px; text-align: left; font-weight: 600; color: #475569; font-size: 0.75rem;\">Users</th>\n <th style=\"padding: 8px 10px; text-align: left; font-weight: 600; color: #475569; font-size: 0.75rem;\">Grace Period (Days)</th>\n <th style=\"padding: 8px 10px; text-align: left; font-weight: 600; color: #475569; font-size: 0.75rem;\">Start Date</th>\n <th style=\"padding: 8px 10px; text-align: left; font-weight: 600; color: #475569; font-size: 0.75rem;\">End Date</th>\n </tr>\n </thead>\n <tbody>\n @for (prod of row.licenseDetails; track prod.productName) {\n <tr style=\"border-bottom: 1px solid #f1f5f9;\">\n <td style=\"padding: 8px 10px; font-weight: 700; color: #15104e; text-transform: uppercase;\">{{ prod.productName }}</td>\n <td style=\"padding: 8px 10px;\">{{ prod.userLimit || '\u2014' }}</td>\n <td style=\"padding: 8px 10px;\">{{ prod.gracePeriod || '\u2014' }}</td>\n <td style=\"padding: 8px 10px;\">{{ formatDate(prod.startDate) }}</td>\n <td style=\"padding: 8px 10px;\">{{ formatDate(prod.endDate) }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </div>\n }\n }\n }\n </div>\n\n <div class=\"o-side-panel__footer\">\n <button class=\"o-side-panel__btn-close\" (click)=\"onClose()\" type=\"button\">Close</button>\n </div>\n\n</div>\n", styles: [".o-side-panel__backdrop{position:fixed;inset:0;background:#0f172a47;z-index:400;animation:o-fade-in .2s ease}.o-side-panel{position:fixed;top:0;right:0;height:100vh;width:480px;max-width:100vw;background:#fff;box-shadow:-4px 0 32px #0f172a29;z-index:401;display:flex;flex-direction:column;transform:translate(100%);transition:transform .28s cubic-bezier(.4,0,.2,1)}.o-side-panel--open{transform:translate(0)}.o-side-panel__header{display:flex;align-items:center;justify-content:space-between;padding:18px 24px;border-bottom:1px solid #e2e8f0;background:#f8fafc;flex-shrink:0}.o-side-panel__title{font-size:.95rem;font-weight:700;color:#1e293b;letter-spacing:-.01em}.o-side-panel__close{background:none;border:none;cursor:pointer;color:#94a3b8;padding:4px;border-radius:4px;display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s}.o-side-panel__close:hover{background:#f1f5f9;color:#334155}.o-side-panel__body{flex:1;overflow-y:auto;padding:20px 24px;display:flex;flex-direction:column;gap:0}.o-side-panel__field{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:12px 0;border-bottom:1px solid #f1f5f9}.o-side-panel__field:last-child{border-bottom:none}.o-side-panel__field-label{font-size:.78rem;font-weight:600;color:#64748b;flex-shrink:0;min-width:140px;padding-top:1px;text-transform:uppercase;letter-spacing:.04em}.o-side-panel__field-value{font-size:.875rem;color:#1e293b;text-align:right;word-break:break-word}.o-side-panel__badge{display:inline-flex;align-items:center;padding:2px 10px;border-radius:999px;font-size:.75rem;font-weight:600;letter-spacing:.02em}.o-badge--green{background:#dcfce7;color:#166534}.o-badge--red{background:#fee2e2;color:#991b1b}.o-badge--yellow{background:#fef9c3;color:#854d0e}.o-badge--gray{background:#f1f5f9;color:#475569}.o-badge--blue{background:#dbeafe;color:#1e40af}.o-side-panel__footer{padding:14px 24px;border-top:1px solid #e2e8f0;background:#f8fafc;display:flex;justify-content:flex-end;flex-shrink:0}.o-side-panel__btn-close{padding:8px 22px;background:#1e293b;color:#fff;border:none;border-radius:6px;font-size:.85rem;font-weight:600;cursor:pointer;font-family:inherit;transition:background .15s}.o-side-panel__btn-close:hover{background:#0f172a}@keyframes o-fade-in{0%{opacity:0}to{opacity:1}}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
918
+ }
919
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OSidePanelComponent, decorators: [{
920
+ type: Component,
921
+ args: [{ selector: 'o-side-panel', standalone: true, imports: [NgClass], template: "@if (open) {\n<div class=\"o-side-panel__backdrop\" (click)=\"onClose()\" aria-hidden=\"true\"></div>\n}\n\n<div class=\"o-side-panel\" [class.o-side-panel--open]=\"open\" role=\"dialog\" [attr.aria-modal]=\"open\" [attr.aria-label]=\"title\">\n\n <div class=\"o-side-panel__header\">\n <span class=\"o-side-panel__title\">{{ title }}</span>\n <button class=\"o-side-panel__close\" (click)=\"onClose()\" type=\"button\" aria-label=\"Close panel\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n\n <div class=\"o-side-panel__body\">\n @if (row) {\n @for (col of columns; track col.name) {\n @if (col.pipe === 'section') {\n <div class=\"o-side-panel__section-header\" style=\"font-weight: 700; font-size: 15px; margin: 16px 0 8px 0; color: #15104E; border-bottom: 2px solid #CDCDCD; padding-bottom: 4px;\">\n {{ col.label }}\n </div>\n } @else {\n <div class=\"o-side-panel__field\" style=\"margin-bottom: 12px; display: flex; flex-direction: column;\">\n <span class=\"o-side-panel__field-label\" style=\"font-size: 12px; color: #737373; font-weight: 500; margin-bottom: 2px;\">{{ col.label }}</span>\n <span class=\"o-side-panel__field-value\" style=\"font-size: 14px; color: #333333; font-weight: 600;\">\n @if (col.pipe === 'status') {\n <span class=\"o-side-panel__badge\" [ngClass]=\"getBadgeClass(row[col.name])\">\n {{ row[col.name] || '\u2014' }}\n </span>\n } @else if (col.pipe === 'date') {\n {{ formatDate(row[col.name]) }}\n } @else {\n {{ formatValue(row[col.name]) }}\n }\n </span>\n\n @if (col.name === 'modules' && row.licenseDetails?.length) {\n <span class=\"o-side-panel__field-label\" style=\"font-size: 12px; color: #737373; font-weight: 500; margin-top: 12px; margin-bottom: 4px;\">License Details per Product</span>\n <div class=\"o-license-details-table-wrapper\" style=\"margin-top: 4px; border: 1px solid #e2e8f0; border-radius: 8px; overflow: hidden; background: #fff; width: 100%;\">\n <table class=\"o-license-details-table\" style=\"width: 100%; border-collapse: collapse; font-size: 0.8rem;\">\n <thead>\n <tr style=\"background: #f8fafc; border-bottom: 1px solid #e2e8f0;\">\n <th style=\"padding: 8px 10px; text-align: left; font-weight: 600; color: #475569; font-size: 0.75rem;\">Product</th>\n <th style=\"padding: 8px 10px; text-align: left; font-weight: 600; color: #475569; font-size: 0.75rem;\">Users</th>\n <th style=\"padding: 8px 10px; text-align: left; font-weight: 600; color: #475569; font-size: 0.75rem;\">Grace Period (Days)</th>\n <th style=\"padding: 8px 10px; text-align: left; font-weight: 600; color: #475569; font-size: 0.75rem;\">Start Date</th>\n <th style=\"padding: 8px 10px; text-align: left; font-weight: 600; color: #475569; font-size: 0.75rem;\">End Date</th>\n </tr>\n </thead>\n <tbody>\n @for (prod of row.licenseDetails; track prod.productName) {\n <tr style=\"border-bottom: 1px solid #f1f5f9;\">\n <td style=\"padding: 8px 10px; font-weight: 700; color: #15104e; text-transform: uppercase;\">{{ prod.productName }}</td>\n <td style=\"padding: 8px 10px;\">{{ prod.userLimit || '\u2014' }}</td>\n <td style=\"padding: 8px 10px;\">{{ prod.gracePeriod || '\u2014' }}</td>\n <td style=\"padding: 8px 10px;\">{{ formatDate(prod.startDate) }}</td>\n <td style=\"padding: 8px 10px;\">{{ formatDate(prod.endDate) }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </div>\n }\n }\n }\n </div>\n\n <div class=\"o-side-panel__footer\">\n <button class=\"o-side-panel__btn-close\" (click)=\"onClose()\" type=\"button\">Close</button>\n </div>\n\n</div>\n", styles: [".o-side-panel__backdrop{position:fixed;inset:0;background:#0f172a47;z-index:400;animation:o-fade-in .2s ease}.o-side-panel{position:fixed;top:0;right:0;height:100vh;width:480px;max-width:100vw;background:#fff;box-shadow:-4px 0 32px #0f172a29;z-index:401;display:flex;flex-direction:column;transform:translate(100%);transition:transform .28s cubic-bezier(.4,0,.2,1)}.o-side-panel--open{transform:translate(0)}.o-side-panel__header{display:flex;align-items:center;justify-content:space-between;padding:18px 24px;border-bottom:1px solid #e2e8f0;background:#f8fafc;flex-shrink:0}.o-side-panel__title{font-size:.95rem;font-weight:700;color:#1e293b;letter-spacing:-.01em}.o-side-panel__close{background:none;border:none;cursor:pointer;color:#94a3b8;padding:4px;border-radius:4px;display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s}.o-side-panel__close:hover{background:#f1f5f9;color:#334155}.o-side-panel__body{flex:1;overflow-y:auto;padding:20px 24px;display:flex;flex-direction:column;gap:0}.o-side-panel__field{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:12px 0;border-bottom:1px solid #f1f5f9}.o-side-panel__field:last-child{border-bottom:none}.o-side-panel__field-label{font-size:.78rem;font-weight:600;color:#64748b;flex-shrink:0;min-width:140px;padding-top:1px;text-transform:uppercase;letter-spacing:.04em}.o-side-panel__field-value{font-size:.875rem;color:#1e293b;text-align:right;word-break:break-word}.o-side-panel__badge{display:inline-flex;align-items:center;padding:2px 10px;border-radius:999px;font-size:.75rem;font-weight:600;letter-spacing:.02em}.o-badge--green{background:#dcfce7;color:#166534}.o-badge--red{background:#fee2e2;color:#991b1b}.o-badge--yellow{background:#fef9c3;color:#854d0e}.o-badge--gray{background:#f1f5f9;color:#475569}.o-badge--blue{background:#dbeafe;color:#1e40af}.o-side-panel__footer{padding:14px 24px;border-top:1px solid #e2e8f0;background:#f8fafc;display:flex;justify-content:flex-end;flex-shrink:0}.o-side-panel__btn-close{padding:8px 22px;background:#1e293b;color:#fff;border:none;border-radius:6px;font-size:.85rem;font-weight:600;cursor:pointer;font-family:inherit;transition:background .15s}.o-side-panel__btn-close:hover{background:#0f172a}@keyframes o-fade-in{0%{opacity:0}to{opacity:1}}\n"] }]
922
+ }], propDecorators: { open: [{
923
+ type: Input
924
+ }], title: [{
925
+ type: Input
926
+ }], row: [{
927
+ type: Input
928
+ }], columns: [{
929
+ type: Input
930
+ }], closed: [{
931
+ type: Output
932
+ }] } });
933
+
934
+ class OTableToolsComponent {
935
+ config = { add: true };
936
+ toolAction = new EventEmitter();
937
+ emit(action) {
938
+ this.toolAction.emit(action);
939
+ }
940
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OTableToolsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
941
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: OTableToolsComponent, isStandalone: true, selector: "o-table-tools", inputs: { config: "config" }, outputs: { toolAction: "toolAction" }, ngImport: i0, template: "<div class=\"o-table-tools\">\n <div class=\"o-table-tools__left\">\n @if (config.add !== false) {\n <button type=\"button\" class=\"o-table-tools__btn\" (click)=\"emit('add')\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M12 6.85715H6.85715V12H5.14286V6.85715H0V5.14286H5.14286V0H6.85715V5.14286H12V6.85715Z\" fill=\"#737373\"/>\n </svg>\n <span>Add</span>\n </button>\n }\n\n @if (config.edit) {\n <button type=\"button\" class=\"o-table-tools__btn\" (click)=\"emit('edit')\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M0 9.50034V12H2.49965L9.87196 4.62768L7.37231 2.12803L0 9.50034ZM11.805 2.69462C12.065 2.43466 12.065 2.01471 11.805 1.75475L10.2453 0.19497C9.98529-0.06499 9.56534-0.06499 9.30538 0.19497L8.08555 1.4148L10.5852 3.91445L11.805 2.69462Z\" fill=\"#737373\"/>\n </svg>\n <span>Edit</span>\n </button>\n }\n\n @if (config.duplicate) {\n <button type=\"button\" class=\"o-table-tools__btn\" (click)=\"emit('duplicate')\">\n <svg width=\"11\" height=\"12\" viewBox=\"0 0 11 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M7.5 10.5V11.4375C7.5 11.7482 7.24816 12 6.9375 12H0.5625C0.25184 12 0 11.7482 0 11.4375V2.8125C0 2.50184 0.25184 2.25 0.5625 2.25H2.25V9.1875C2.25 9.91123 2.83877 10.5 3.5625 10.5H7.5ZM7.5 2.4375V0H3.5625C3.25184 0 3 0.25184 3 0.5625V9.1875C3 9.49816 3.25184 9.75 3.5625 9.75H9.9375C10.2482 9.75 10.5 9.49816 10.5 9.1875V3H8.0625C7.75313 3 7.5 2.74688 7.5 2.4375ZM10.3353 1.71026L8.78974 0.16474C8.68425 0.05926 8.54118 0 8.39201 0H8.25V2.25H10.5V2.10799C10.5 1.95881 10.4407 1.81575 10.3353 1.71026Z\" fill=\"#737373\"/>\n </svg>\n <span>Duplicate</span>\n </button>\n }\n\n <button type=\"button\" class=\"o-table-tools__btn\" (click)=\"emit('refresh')\">\n <svg width=\"13\" height=\"12\" viewBox=\"0 0 13 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M10.836 1.7625C9.74783 0.675 8.2544 0 6.59586 0C3.27879 0 0.599609 2.685 0.599609 6C0.599609 9.315 3.27878 12 6.59586 12C9.39511 12 11.7291 10.0875 12.397 7.5H10.836C10.2206 9.2475 8.55458 10.5 6.59586 10.5C4.11181 10.5 2.09305 8.4825 2.09305 6C2.09305 3.5175 4.11181 1.5 6.59586 1.5C7.84164 1.5 8.95233 2.0175 9.76284 2.835L7.34633 5.25H12.5996V0L10.836 1.7625Z\" fill=\"#737373\"/>\n </svg>\n <span>Refresh</span>\n </button>\n\n @if (config.clear) {\n <button type=\"button\" class=\"o-table-tools__btn\" (click)=\"emit('clear')\">\n <svg width=\"14\" height=\"12\" viewBox=\"0 0 14 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <!-- brush / clear icon design -->\n <path d=\"M2.5 10H11.5M4 8.5L2 6.5L3.5 5L9 10.5M4.5 3L11.5 10M5.5 2L12.5 9\" stroke=\"#737373\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n </svg>\n <span>Clear</span>\n </button>\n }\n </div>\n\n <div class=\"o-table-tools__right\">\n @if (config.import) {\n <button type=\"button\" class=\"o-table-tools__btn\" (click)=\"emit('import')\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M0.375 6.75C0.16875 6.75 0 6.91875 0 7.125V7.875C0 8.08125 0.16875 8.25 0.375 8.25H3V6.75H0.375ZM11.8359 2.46094L9.54141 0.16407C9.43594 0.0586 9.29297 1e-05 9.14297 1e-05H9V3.00001H12V2.85704C12 2.70938 11.9414 2.56641 11.8359 2.46095V2.46094ZM8.25 3.1875V0H3.5625C3.25078 0 3 0.25078 3 0.5625V6.75H6V5.22188C6 4.88672 6.40547 4.72032 6.64219 4.95704L8.88281 7.21876C9.0375 7.37579 9.0375 7.62657 8.88281 7.78126L6.63984 10.0406C6.40312 10.2774 5.99765 10.1109 5.99765 9.7758V8.25002H2.99999V11.4375C2.99999 11.7492 3.25077 12 3.56249 12H11.4375C11.7492 12 12 11.7492 12 11.4375V3.75002H8.81249C8.50312 3.75002 8.24999 3.4969 8.24999 3.18752L8.25 3.1875Z\" fill=\"#737373\"/>\n </svg>\n <span>Import</span>\n </button>\n }\n\n @if (config.export) {\n <button type=\"button\" class=\"o-table-tools__btn\" (click)=\"emit('export')\">\n <svg width=\"14\" height=\"12\" viewBox=\"0 0 14 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M9.14062 2.85703C9.14062 2.70937 9.08204 2.56641 8.97656 2.46094L6.68203 0.16407C6.57656 0.0586 6.4336 1e-05 6.2836 1e-05H6.14062V3.00001H9.14062V2.85703ZM13.5234 7.21875L11.2805 4.95937C11.0437 4.72265 10.6383 4.88906 10.6383 5.22421V6.74999H9.13828V8.24999H10.6383V9.77811C10.6383 10.1133 11.0437 10.2797 11.2805 10.0429L13.5234 7.78123C13.6781 7.62654 13.6781 7.37342 13.5234 7.21873V7.21875ZM4.64062 7.875V7.125C4.64062 6.91875 4.80937 6.75 5.01562 6.75H9.14062V3.75H5.95312C5.64374 3.75 5.39062 3.49688 5.39062 3.1875V0H0.703125C0.391405 0 0.140625 0.25078 0.140625 0.5625V11.4375C0.140625 11.7492 0.391405 12 0.703125 12H8.57812C8.88984 12 9.14062 11.7492 9.14062 11.4375V8.25H5.01562C4.80937 8.25 4.64062 8.08125 4.64062 7.875Z\" fill=\"#737373\"/>\n </svg>\n <span>Export</span>\n </button>\n }\n\n @if (config.print) {\n <button type=\"button\" class=\"o-table-tools__btn\" (click)=\"emit('print')\">\n <svg width=\"13\" height=\"11\" viewBox=\"0 0 13 11\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M10.8484 3H2.44844C1.45244 3 0.648438 3.804 0.648438 4.8V8.4H3.04844V10.8H10.2484V8.4H12.6484V4.8C12.6484 3.804 11.8444 3 10.8484 3ZM9.04844 9.6H4.24844V6.6H9.04844V9.6ZM10.8484 5.4C10.5184 5.4 10.2484 5.13 10.2484 4.8C10.2484 4.47 10.5184 4.2 10.8484 4.2C11.1784 4.2 11.4484 4.47 11.4484 4.8C11.4484 5.13 11.1784 5.4 10.8484 5.4ZM10.2484 0H3.04844V2.4H10.2484V0Z\" fill=\"#737373\"/>\n </svg>\n <span>Print</span>\n </button>\n }\n </div>\n</div>\n", styles: [":host{display:block;width:100%}.o-table-tools{display:flex;align-items:center;height:44px;border-bottom:1px solid #e5e7eb;background:#f9fafb;box-sizing:border-box;overflow:hidden}.o-table-tools__left{display:flex;align-items:center;flex:1}.o-table-tools__right{display:flex;align-items:center;justify-content:flex-end}.o-table-tools__btn{display:inline-flex;align-items:center;gap:6px;padding:8px 12px;font-size:12px;font-weight:500;color:#374151;background:none;border:none;cursor:pointer;white-space:nowrap;transition:background .12s;border-right:1px solid #e5e7eb;height:44px;box-sizing:border-box}.o-table-tools__btn:hover{background:#f5f5f5}.o-table-tools__btn:last-child{border-right:none}.o-table-tools__btn svg{flex-shrink:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
942
+ }
943
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OTableToolsComponent, decorators: [{
944
+ type: Component,
945
+ args: [{ selector: 'o-table-tools', standalone: true, imports: [CommonModule], template: "<div class=\"o-table-tools\">\n <div class=\"o-table-tools__left\">\n @if (config.add !== false) {\n <button type=\"button\" class=\"o-table-tools__btn\" (click)=\"emit('add')\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M12 6.85715H6.85715V12H5.14286V6.85715H0V5.14286H5.14286V0H6.85715V5.14286H12V6.85715Z\" fill=\"#737373\"/>\n </svg>\n <span>Add</span>\n </button>\n }\n\n @if (config.edit) {\n <button type=\"button\" class=\"o-table-tools__btn\" (click)=\"emit('edit')\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M0 9.50034V12H2.49965L9.87196 4.62768L7.37231 2.12803L0 9.50034ZM11.805 2.69462C12.065 2.43466 12.065 2.01471 11.805 1.75475L10.2453 0.19497C9.98529-0.06499 9.56534-0.06499 9.30538 0.19497L8.08555 1.4148L10.5852 3.91445L11.805 2.69462Z\" fill=\"#737373\"/>\n </svg>\n <span>Edit</span>\n </button>\n }\n\n @if (config.duplicate) {\n <button type=\"button\" class=\"o-table-tools__btn\" (click)=\"emit('duplicate')\">\n <svg width=\"11\" height=\"12\" viewBox=\"0 0 11 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M7.5 10.5V11.4375C7.5 11.7482 7.24816 12 6.9375 12H0.5625C0.25184 12 0 11.7482 0 11.4375V2.8125C0 2.50184 0.25184 2.25 0.5625 2.25H2.25V9.1875C2.25 9.91123 2.83877 10.5 3.5625 10.5H7.5ZM7.5 2.4375V0H3.5625C3.25184 0 3 0.25184 3 0.5625V9.1875C3 9.49816 3.25184 9.75 3.5625 9.75H9.9375C10.2482 9.75 10.5 9.49816 10.5 9.1875V3H8.0625C7.75313 3 7.5 2.74688 7.5 2.4375ZM10.3353 1.71026L8.78974 0.16474C8.68425 0.05926 8.54118 0 8.39201 0H8.25V2.25H10.5V2.10799C10.5 1.95881 10.4407 1.81575 10.3353 1.71026Z\" fill=\"#737373\"/>\n </svg>\n <span>Duplicate</span>\n </button>\n }\n\n <button type=\"button\" class=\"o-table-tools__btn\" (click)=\"emit('refresh')\">\n <svg width=\"13\" height=\"12\" viewBox=\"0 0 13 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M10.836 1.7625C9.74783 0.675 8.2544 0 6.59586 0C3.27879 0 0.599609 2.685 0.599609 6C0.599609 9.315 3.27878 12 6.59586 12C9.39511 12 11.7291 10.0875 12.397 7.5H10.836C10.2206 9.2475 8.55458 10.5 6.59586 10.5C4.11181 10.5 2.09305 8.4825 2.09305 6C2.09305 3.5175 4.11181 1.5 6.59586 1.5C7.84164 1.5 8.95233 2.0175 9.76284 2.835L7.34633 5.25H12.5996V0L10.836 1.7625Z\" fill=\"#737373\"/>\n </svg>\n <span>Refresh</span>\n </button>\n\n @if (config.clear) {\n <button type=\"button\" class=\"o-table-tools__btn\" (click)=\"emit('clear')\">\n <svg width=\"14\" height=\"12\" viewBox=\"0 0 14 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <!-- brush / clear icon design -->\n <path d=\"M2.5 10H11.5M4 8.5L2 6.5L3.5 5L9 10.5M4.5 3L11.5 10M5.5 2L12.5 9\" stroke=\"#737373\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n </svg>\n <span>Clear</span>\n </button>\n }\n </div>\n\n <div class=\"o-table-tools__right\">\n @if (config.import) {\n <button type=\"button\" class=\"o-table-tools__btn\" (click)=\"emit('import')\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M0.375 6.75C0.16875 6.75 0 6.91875 0 7.125V7.875C0 8.08125 0.16875 8.25 0.375 8.25H3V6.75H0.375ZM11.8359 2.46094L9.54141 0.16407C9.43594 0.0586 9.29297 1e-05 9.14297 1e-05H9V3.00001H12V2.85704C12 2.70938 11.9414 2.56641 11.8359 2.46095V2.46094ZM8.25 3.1875V0H3.5625C3.25078 0 3 0.25078 3 0.5625V6.75H6V5.22188C6 4.88672 6.40547 4.72032 6.64219 4.95704L8.88281 7.21876C9.0375 7.37579 9.0375 7.62657 8.88281 7.78126L6.63984 10.0406C6.40312 10.2774 5.99765 10.1109 5.99765 9.7758V8.25002H2.99999V11.4375C2.99999 11.7492 3.25077 12 3.56249 12H11.4375C11.7492 12 12 11.7492 12 11.4375V3.75002H8.81249C8.50312 3.75002 8.24999 3.4969 8.24999 3.18752L8.25 3.1875Z\" fill=\"#737373\"/>\n </svg>\n <span>Import</span>\n </button>\n }\n\n @if (config.export) {\n <button type=\"button\" class=\"o-table-tools__btn\" (click)=\"emit('export')\">\n <svg width=\"14\" height=\"12\" viewBox=\"0 0 14 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M9.14062 2.85703C9.14062 2.70937 9.08204 2.56641 8.97656 2.46094L6.68203 0.16407C6.57656 0.0586 6.4336 1e-05 6.2836 1e-05H6.14062V3.00001H9.14062V2.85703ZM13.5234 7.21875L11.2805 4.95937C11.0437 4.72265 10.6383 4.88906 10.6383 5.22421V6.74999H9.13828V8.24999H10.6383V9.77811C10.6383 10.1133 11.0437 10.2797 11.2805 10.0429L13.5234 7.78123C13.6781 7.62654 13.6781 7.37342 13.5234 7.21873V7.21875ZM4.64062 7.875V7.125C4.64062 6.91875 4.80937 6.75 5.01562 6.75H9.14062V3.75H5.95312C5.64374 3.75 5.39062 3.49688 5.39062 3.1875V0H0.703125C0.391405 0 0.140625 0.25078 0.140625 0.5625V11.4375C0.140625 11.7492 0.391405 12 0.703125 12H8.57812C8.88984 12 9.14062 11.7492 9.14062 11.4375V8.25H5.01562C4.80937 8.25 4.64062 8.08125 4.64062 7.875Z\" fill=\"#737373\"/>\n </svg>\n <span>Export</span>\n </button>\n }\n\n @if (config.print) {\n <button type=\"button\" class=\"o-table-tools__btn\" (click)=\"emit('print')\">\n <svg width=\"13\" height=\"11\" viewBox=\"0 0 13 11\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M10.8484 3H2.44844C1.45244 3 0.648438 3.804 0.648438 4.8V8.4H3.04844V10.8H10.2484V8.4H12.6484V4.8C12.6484 3.804 11.8444 3 10.8484 3ZM9.04844 9.6H4.24844V6.6H9.04844V9.6ZM10.8484 5.4C10.5184 5.4 10.2484 5.13 10.2484 4.8C10.2484 4.47 10.5184 4.2 10.8484 4.2C11.1784 4.2 11.4484 4.47 11.4484 4.8C11.4484 5.13 11.1784 5.4 10.8484 5.4ZM10.2484 0H3.04844V2.4H10.2484V0Z\" fill=\"#737373\"/>\n </svg>\n <span>Print</span>\n </button>\n }\n </div>\n</div>\n", styles: [":host{display:block;width:100%}.o-table-tools{display:flex;align-items:center;height:44px;border-bottom:1px solid #e5e7eb;background:#f9fafb;box-sizing:border-box;overflow:hidden}.o-table-tools__left{display:flex;align-items:center;flex:1}.o-table-tools__right{display:flex;align-items:center;justify-content:flex-end}.o-table-tools__btn{display:inline-flex;align-items:center;gap:6px;padding:8px 12px;font-size:12px;font-weight:500;color:#374151;background:none;border:none;cursor:pointer;white-space:nowrap;transition:background .12s;border-right:1px solid #e5e7eb;height:44px;box-sizing:border-box}.o-table-tools__btn:hover{background:#f5f5f5}.o-table-tools__btn:last-child{border-right:none}.o-table-tools__btn svg{flex-shrink:0}\n"] }]
946
+ }], propDecorators: { config: [{
947
+ type: Input
948
+ }], toolAction: [{
949
+ type: Output
950
+ }] } });
951
+
952
+ /** Context-menu actions hidden for each role */
953
+ const ROLE_HIDDEN_ACTIONS = {
954
+ SYSTEM_ADMIN: ['edit'],
955
+ APPROVER: ['edit', 'save'],
956
+ REQUESTER: ['approve', 'reject', 'return', 'edit'],
957
+ VIEWER: ['approve', 'reject', 'return', 'edit', 'save', 'submit']
958
+ };
959
+ class PageRendererComponent {
960
+ cdr;
961
+ http;
962
+ page = null;
963
+ data = [];
964
+ userRole = 'SYSTEM_ADMIN';
965
+ /**
966
+ * If provided, forces this tab to be active.
967
+ * Parent sets this from route params.
968
+ */
969
+ activeTab = '';
970
+ actionTriggered = new EventEmitter();
971
+ /**
972
+ * Emitted when the user clicks a tab button so the parent
973
+ * can update the URL. The value is the tab's `.value` string.
974
+ */
975
+ tabChange = new EventEmitter();
976
+ filteredRows = [];
977
+ selectedRows = [];
978
+ drawerReadOnly = false;
979
+ drawerAction = 'new';
980
+ dynamicContextActions = [];
981
+ // Share dialog state
982
+ shareDialogOpen = false;
983
+ shareRow = null;
984
+ shareForm = { to: '', cc: '', subject: '', body: '' };
985
+ shareSending = false;
986
+ shareSuccess = false;
987
+ constructor(cdr, http) {
988
+ this.cdr = cdr;
989
+ this.http = http;
990
+ }
991
+ onSelectionChange(rows) {
992
+ this.selectedRows = rows;
993
+ this.cdr.detectChanges();
994
+ }
995
+ onTableToolAction(action) {
996
+ if (action === 'add') {
997
+ this.openFormNew();
998
+ }
999
+ else if (action === 'edit') {
1000
+ if (this.selectedRows.length === 1) {
1001
+ this.onContextAction({ action: 'edit', row: this.selectedRows[0] });
1002
+ }
1003
+ }
1004
+ else if (action === 'duplicate') {
1005
+ if (this.selectedRows.length === 1) {
1006
+ const rowClone = { ...this.selectedRows[0] };
1007
+ const idField = this.page?.tableUniqueFieldName || 'uuid';
1008
+ delete rowClone[idField];
1009
+ delete rowClone['requestId'];
1010
+ this.drawerTitle = `New ${this.page?.title || 'Request'} (Clone)`;
1011
+ this.drawerRowData = rowClone;
1012
+ this.showSubmitBtn = true;
1013
+ this.drawerOpen = true;
1014
+ this.cdr.detectChanges();
1015
+ }
1016
+ }
1017
+ else if (action === 'refresh') {
1018
+ this.actionTriggered.emit({ action: 'refresh', row: null });
1019
+ }
1020
+ else if (action === 'clear') {
1021
+ this.selectedRows = [];
1022
+ this.actionTriggered.emit({ action: 'clear', row: null });
1023
+ }
1024
+ else if (action === 'export') {
1025
+ this.actionTriggered.emit({ action: 'export', row: null, payload: this.selectedRows });
1026
+ }
1027
+ else if (action === 'import') {
1028
+ this.actionTriggered.emit({ action: 'import', row: null });
1029
+ }
1030
+ this.cdr.detectChanges();
1031
+ }
1032
+ menuOpen = false;
1033
+ menuPosition = { top: 0, left: 0 };
1034
+ menuRow = null;
1035
+ drawerOpen = false;
1036
+ drawerTitle = '';
1037
+ drawerRowData = null;
1038
+ showSubmitBtn = true;
1039
+ sidePanelOpen = false;
1040
+ sidePanelTitle = '';
1041
+ sidePanelRow = null;
1042
+ sidePanelColumns = [];
1043
+ // ── Lifecycle ──────────────────────────────────────────────────────────────
1044
+ ngOnChanges(changes) {
1045
+ // When page config loads, resolve initial active tab
1046
+ if (changes['page'] && this.page?.toggleButton?.elements?.length) {
1047
+ if (!this.activeTab) {
1048
+ const defaultTab = this.page.toggleButton.elements.find(t => t.isDefault) ||
1049
+ this.page.toggleButton.elements[0];
1050
+ this.activeTab = defaultTab.value;
1051
+ }
1052
+ }
1053
+ this.applyTabFilter();
1054
+ }
1055
+ // ── Tab Handling ───────────────────────────────────────────────────────────
1056
+ onTabChange(tab) {
1057
+ this.activeTab = tab;
1058
+ this.applyTabFilter();
1059
+ this.tabChange.emit(tab);
1060
+ }
1061
+ applyTabFilter() {
1062
+ if (!this.page?.toggleButton?.statusMap || !this.activeTab) {
1063
+ this.filteredRows = this.data;
1064
+ this.cdr.detectChanges();
1065
+ return;
1066
+ }
1067
+ const statuses = this.page.toggleButton.statusMap[this.activeTab] || [];
1068
+ const filtered = statuses.length
1069
+ ? this.data.filter(r => statuses.includes(r.status))
1070
+ : this.data;
1071
+ this.filteredRows = filtered;
1072
+ this.cdr.detectChanges();
1073
+ }
1074
+ // ── Role Visibility ────────────────────────────────────────────────────────
1075
+ showAddButton() {
1076
+ if (!this.page?.tableTool?.add)
1077
+ return false;
1078
+ return this.userRole !== 'APPROVER' && this.userRole !== 'VIEWER';
1079
+ }
1080
+ get computedTableToolConfig() {
1081
+ if (!this.page?.tableTool)
1082
+ return null;
1083
+ // Pages without workflow tabs (system settings: users, roles, sessions, audits)
1084
+ // show the toolbar as-is — no tab restriction needed
1085
+ const hasWorkflowTabs = this.page.toggleButton?.elements?.some(t => t.value === 'Request' || t.value === 'Requests');
1086
+ if (!hasWorkflowTabs) {
1087
+ return { ...this.page.tableTool };
1088
+ }
1089
+ // Workflow pages (tenant, license): restrict Add/Edit/Duplicate to Request tab only
1090
+ const isRequestTab = this.activeTab === 'Request' || this.activeTab === 'Requests';
1091
+ return {
1092
+ ...this.page.tableTool,
1093
+ add: isRequestTab ? this.page.tableTool.add : false,
1094
+ edit: isRequestTab ? this.page.tableTool.edit : false,
1095
+ duplicate: isRequestTab ? this.page.tableTool.duplicate : false
1096
+ };
1097
+ }
1098
+ get filteredContextActions() {
1099
+ const hidden = ROLE_HIDDEN_ACTIONS[this.userRole] || [];
1100
+ return (this.page?.contextMenuActions || []).filter(a => !hidden.includes(a.action));
1101
+ }
1102
+ // ── Menu Handling ──────────────────────────────────────────────────────────
1103
+ onMenuOpen(event) {
1104
+ this.menuRow = event.row;
1105
+ const rect = event.event.target.getBoundingClientRect();
1106
+ this.menuPosition = {
1107
+ top: rect.bottom + window.scrollY,
1108
+ left: Math.min(rect.left + window.scrollX - 100, window.innerWidth - 210)
1109
+ };
1110
+ this.menuOpen = true;
1111
+ this.dynamicContextActions = [];
1112
+ this.cdr.detectChanges();
1113
+ if (this.menuRow && this.page) {
1114
+ const uuid = this.menuRow[this.page.tableUniqueFieldName || 'uuid'];
1115
+ const status = this.menuRow.status;
1116
+ const storeName = this.page.store || '';
1117
+ const type = (storeName.toLowerCase().includes('license')) ? 'license' : 'tenant';
1118
+ this.http.post('http://localhost:8082/api/workflow-actions', { uuid, status, type }).subscribe({
1119
+ next: (actions) => {
1120
+ const hidden = ROLE_HIDDEN_ACTIONS[this.userRole] || [];
1121
+ // Filter out 'edit' entirely — no edit from context menu
1122
+ const filtered = (actions || []).filter(a => !hidden.includes(a.action) && a.action !== 'edit');
1123
+ // Group approve + reject into a single hover-submenu item for In-Progress
1124
+ const hasApprove = filtered.some(a => a.action === 'approve');
1125
+ const hasReject = filtered.some(a => a.action === 'reject');
1126
+ if (hasApprove && hasReject) {
1127
+ const rest = filtered.filter(a => a.action !== 'approve' && a.action !== 'reject');
1128
+ this.dynamicContextActions = [
1129
+ {
1130
+ label: 'Approve',
1131
+ action: 'approve-group',
1132
+ subActions: [
1133
+ { label: 'Approve', action: 'approve' },
1134
+ { label: 'Reject', action: 'reject' }
1135
+ ]
1136
+ },
1137
+ ...rest
1138
+ ];
1139
+ }
1140
+ else {
1141
+ this.dynamicContextActions = filtered;
1142
+ }
1143
+ this.cdr.detectChanges();
1144
+ },
1145
+ error: (err) => {
1146
+ console.error('Failed to fetch dynamic actions:', err);
1147
+ this.dynamicContextActions = [];
1148
+ this.cdr.detectChanges();
1149
+ }
1150
+ });
1151
+ }
1152
+ }
1153
+ onRowClick(row) {
1154
+ this.actionTriggered.emit({ action: 'rowClick', row });
1155
+ }
1156
+ onContextAction(event) {
1157
+ this.menuOpen = false;
1158
+ if (event.action === 'share') {
1159
+ this.openShareDialog(event.row);
1160
+ return;
1161
+ }
1162
+ if (event.action === 'view') {
1163
+ this.drawerTitle = 'View Request';
1164
+ let rowData = { ...event.row };
1165
+ this.populateLicenseDetails(rowData);
1166
+ this.drawerRowData = rowData;
1167
+ this.drawerReadOnly = true;
1168
+ this.drawerAction = 'view';
1169
+ this.showSubmitBtn = false;
1170
+ this.drawerOpen = true;
1171
+ }
1172
+ else if (event.action === 'edit') {
1173
+ this.drawerTitle = 'Edit Draft';
1174
+ let rowData = { ...event.row };
1175
+ this.populateLicenseDetails(rowData);
1176
+ this.drawerRowData = rowData;
1177
+ this.drawerReadOnly = false;
1178
+ this.drawerAction = 'edit';
1179
+ this.showSubmitBtn = true;
1180
+ this.drawerOpen = true;
1181
+ }
1182
+ else if (event.action === 'renew') {
1183
+ this.drawerTitle = 'Renew License';
1184
+ let rowData = { ...event.row };
1185
+ this.populateLicenseDetails(rowData);
1186
+ this.drawerRowData = rowData;
1187
+ this.drawerReadOnly = false;
1188
+ this.drawerAction = 'renew';
1189
+ this.showSubmitBtn = false;
1190
+ this.drawerOpen = true;
1191
+ }
1192
+ else if (event.action === 'upgrade') {
1193
+ this.drawerTitle = 'Upgrade License';
1194
+ let rowData = { ...event.row };
1195
+ this.populateLicenseDetails(rowData);
1196
+ this.drawerRowData = rowData;
1197
+ this.drawerReadOnly = false;
1198
+ this.drawerAction = 'upgrade';
1199
+ this.showSubmitBtn = false;
1200
+ this.drawerOpen = true;
1201
+ }
1202
+ else {
1203
+ this.actionTriggered.emit({ action: event.action, row: event.row });
1204
+ }
1205
+ this.cdr.detectChanges();
1206
+ }
1207
+ populateLicenseDetails(rowData) {
1208
+ if (rowData.licenseDetails) {
1209
+ const modulesMap = {};
1210
+ const prodDetails = {};
1211
+ rowData.licenseDetails.forEach((prod) => {
1212
+ const pName = prod.productName.toLowerCase();
1213
+ modulesMap[pName] = prod.features || [];
1214
+ prodDetails[pName] = {
1215
+ userLimit: prod.userLimit || 80,
1216
+ concurrentLimit: prod.concurrentLimit || 15,
1217
+ startDate: prod.startDate || '',
1218
+ endDate: prod.endDate || '',
1219
+ gracePeriod: prod.gracePeriod || 30
1220
+ };
1221
+ });
1222
+ rowData['modules'] = modulesMap;
1223
+ rowData['productDetails'] = prodDetails;
1224
+ }
1225
+ }
1226
+ // ── Share Dialog ───────────────────────────────────────────────────────────
1227
+ openShareDialog(row) {
1228
+ this.shareRow = row;
1229
+ const name = row.companyName || row.tenantName || row.requestId || '';
1230
+ this.shareForm = {
1231
+ to: row.adminEmail || row.email || '',
1232
+ cc: '',
1233
+ subject: `OPAC — ${name} is now Active`,
1234
+ body: `Dear Team,\n\nPlease be informed that the record for "${name}" has been activated on the OPAC platform.\n\nBest regards,\nOPAC System`
1235
+ };
1236
+ this.shareSending = false;
1237
+ this.shareSuccess = false;
1238
+ this.shareDialogOpen = true;
1239
+ this.cdr.detectChanges();
1240
+ }
1241
+ closeShareDialog() {
1242
+ this.shareDialogOpen = false;
1243
+ this.shareRow = null;
1244
+ this.cdr.detectChanges();
1245
+ }
1246
+ sendShareEmail() {
1247
+ if (!this.shareForm.to)
1248
+ return;
1249
+ this.shareSending = true;
1250
+ this.cdr.detectChanges();
1251
+ const uuid = this.shareRow?.[this.page?.tableUniqueFieldName || 'uuid'];
1252
+ const storeName = this.page?.store || '';
1253
+ const type = storeName.toLowerCase().includes('license') ? 'license' : 'tenant';
1254
+ this.http.post('http://localhost:8082/api/email/send', {
1255
+ to: this.shareForm.to,
1256
+ cc: this.shareForm.cc,
1257
+ subject: this.shareForm.subject,
1258
+ body: this.shareForm.body,
1259
+ uuid,
1260
+ type
1261
+ }).subscribe({
1262
+ next: () => {
1263
+ this.shareSending = false;
1264
+ this.shareSuccess = true;
1265
+ this.cdr.detectChanges();
1266
+ setTimeout(() => this.closeShareDialog(), 2000);
1267
+ },
1268
+ error: () => {
1269
+ this.shareSending = false;
1270
+ this.cdr.detectChanges();
1271
+ }
1272
+ });
1273
+ }
1274
+ // ── Side Panel ─────────────────────────────────────────────────────────────
1275
+ openSidePanel(row) {
1276
+ let sidePanelRow = { ...row };
1277
+ if (sidePanelRow.licenseDetails) {
1278
+ const modulesMap = {};
1279
+ sidePanelRow.licenseDetails.forEach((prod) => {
1280
+ const pName = prod.productName.toLowerCase();
1281
+ modulesMap[pName] = prod.features || [];
1282
+ });
1283
+ sidePanelRow['modules'] = modulesMap;
1284
+ }
1285
+ this.sidePanelRow = sidePanelRow;
1286
+ this.sidePanelTitle = `${this.page?.title || 'Record'} — Details`;
1287
+ const fields = [];
1288
+ if (this.page?.steps) {
1289
+ this.page.steps.forEach(step => {
1290
+ if (step.fields) {
1291
+ step.fields.forEach(f => {
1292
+ if (f.type !== 'hidden' || f.sectionName) {
1293
+ fields.push(f);
1294
+ }
1295
+ });
1296
+ }
1297
+ });
1298
+ }
1299
+ if (fields.length > 0) {
1300
+ this.sidePanelColumns = fields.map(f => ({
1301
+ name: f.name,
1302
+ label: f.label || f.sectionName || '',
1303
+ pipe: f.type === 'hidden' ? 'section' : (f.pipe || '')
1304
+ }));
1305
+ }
1306
+ else {
1307
+ this.sidePanelColumns = (this.page?.tableList || []).map((col) => ({
1308
+ name: col.name,
1309
+ label: col.label,
1310
+ pipe: col.pipe
1311
+ }));
1312
+ }
1313
+ this.sidePanelOpen = true;
1314
+ this.actionTriggered.emit({ action: 'view', row });
1315
+ this.cdr.detectChanges();
1316
+ }
1317
+ // ── Form Drawer ────────────────────────────────────────────────────────────
1318
+ openFormNew() {
1319
+ this.drawerTitle = `New ${this.page?.title || 'Request'}`;
1320
+ this.drawerRowData = null;
1321
+ this.drawerReadOnly = false;
1322
+ this.showSubmitBtn = true;
1323
+ this.drawerOpen = true;
1324
+ this.cdr.detectChanges();
1325
+ }
1326
+ onSave(payload) {
1327
+ this.drawerOpen = false;
1328
+ const finalPayload = this.buildLicensePayload(payload);
1329
+ const emittedAction = (this.drawerAction === 'renew' || this.drawerAction === 'upgrade')
1330
+ ? this.drawerAction
1331
+ : 'save';
1332
+ this.actionTriggered.emit({ action: emittedAction, row: this.drawerRowData, payload: finalPayload });
1333
+ this.cdr.detectChanges();
1334
+ }
1335
+ onSubmitForApproval(payload) {
1336
+ this.drawerOpen = false;
1337
+ const finalPayload = this.buildLicensePayload(payload);
1338
+ this.actionTriggered.emit({ action: 'submit', row: this.drawerRowData, payload: finalPayload });
1339
+ this.cdr.detectChanges();
1340
+ }
1341
+ buildLicensePayload(payload) {
1342
+ let finalPayload = { ...payload };
1343
+ if (this.page?.name === 'LicenseRequest' || finalPayload.modules) {
1344
+ const licenseDetails = [];
1345
+ const modules = finalPayload.modules || {};
1346
+ const prodDetails = finalPayload.productDetails || {};
1347
+ Object.keys(modules).forEach(prodKey => {
1348
+ const features = modules[prodKey];
1349
+ if (features && features.length > 0) {
1350
+ const detail = prodDetails[prodKey.toLowerCase()] || {};
1351
+ licenseDetails.push({
1352
+ productName: prodKey,
1353
+ startDate: detail.startDate || null,
1354
+ endDate: detail.endDate || null,
1355
+ userLimit: detail.userLimit ? Number(detail.userLimit) : null,
1356
+ concurrentLimit: detail.concurrentLimit ? Number(detail.concurrentLimit) : null,
1357
+ gracePeriod: detail.gracePeriod ? Number(detail.gracePeriod) : null,
1358
+ features: features
1359
+ });
1360
+ }
1361
+ });
1362
+ finalPayload.licenseDetails = licenseDetails;
1363
+ if (licenseDetails.length > 0) {
1364
+ finalPayload.gracePeriod = Math.max(...licenseDetails.map(d => d.gracePeriod || 0));
1365
+ finalPayload.totalUsers = Math.max(...licenseDetails.map(d => d.userLimit || 0));
1366
+ finalPayload.maxConcurrent = Math.max(...licenseDetails.map(d => d.concurrentLimit || 0));
1367
+ const startDates = licenseDetails.map(d => d.startDate).filter(Boolean);
1368
+ if (startDates.length > 0) {
1369
+ finalPayload.startDate = startDates.reduce((min, d) => d < min ? d : min, startDates[0]);
1370
+ }
1371
+ const endDates = licenseDetails.map(d => d.endDate).filter(Boolean);
1372
+ if (endDates.length > 0) {
1373
+ finalPayload.endDate = endDates.reduce((max, d) => d > max ? d : max, endDates[0]);
1374
+ }
1375
+ }
1376
+ }
1377
+ return finalPayload;
1378
+ }
1379
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: PageRendererComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i1.HttpClient }], target: i0.ɵɵFactoryTarget.Component });
1380
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: PageRendererComponent, isStandalone: true, selector: "o-page-renderer", inputs: { page: "page", data: "data", userRole: "userRole", activeTab: "activeTab" }, outputs: { actionTriggered: "actionTriggered", tabChange: "tabChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"o-page\">\n\n <!-- Page Header -->\n <div class=\"o-page-header\">\n <div class=\"o-page-title-row\">\n <h2 class=\"o-page-title\">{{ page?.title }}</h2>\n </div>\n\n @if (page?.toggleButton?.elements?.length) {\n <o-toggle-tabs\n [tabs]=\"page!.toggleButton!.elements\"\n [activeTab]=\"activeTab\"\n (tabChange)=\"onTabChange($event)\">\n </o-toggle-tabs>\n }\n </div>\n\n @if (computedTableToolConfig) {\n <o-table-tools\n [config]=\"computedTableToolConfig\"\n (toolAction)=\"onTableToolAction($event)\"\n ></o-table-tools>\n }\n\n <!-- Data Table -->\n <o-data-table\n [columns]=\"page?.tableList || []\"\n [rows]=\"filteredRows\"\n (selectionChange)=\"onSelectionChange($event)\"\n (menuOpen)=\"onMenuOpen($event)\"\n (rowClick)=\"onRowClick($event)\"\n (actionClick)=\"onContextAction($event)\">\n </o-data-table>\n\n <!-- Context Menu -->\n <o-context-menu\n [open]=\"menuOpen\"\n [position]=\"menuPosition\"\n [actions]=\"dynamicContextActions\"\n [row]=\"menuRow\"\n [menuType]=\"page?.store || ''\"\n (actionTriggered)=\"onContextAction($event)\"\n (closed)=\"menuOpen = false\">\n </o-context-menu>\n\n <!-- Form Drawer -->\n <o-form-drawer\n [open]=\"drawerOpen\"\n [title]=\"drawerTitle\"\n [steps]=\"page?.steps || []\"\n [rowData]=\"drawerRowData\"\n [showSubmit]=\"showSubmitBtn\"\n [readOnly]=\"drawerReadOnly\"\n [actionType]=\"drawerAction\"\n (closeDrawer)=\"drawerOpen = false\"\n (save)=\"onSave($event)\"\n (submit)=\"onSubmitForApproval($event)\">\n </o-form-drawer>\n\n <!-- Share / Email Dialog -->\n @if (shareDialogOpen) {\n <div class=\"o-share-overlay\"\n (click)=\"closeShareDialog()\"\n (keydown.escape)=\"closeShareDialog()\">\n <div class=\"o-share-dialog\"\n (click)=\"$event.stopPropagation()\"\n (keydown.escape)=\"closeShareDialog()\">\n <div class=\"o-share-header\">\n <h3 class=\"o-share-title\">Share via Email</h3>\n <button type=\"button\" class=\"o-share-close\" (click)=\"closeShareDialog()\" aria-label=\"Close dialog\">&#x2715;</button>\n </div>\n\n @if (shareSuccess) {\n <div class=\"o-share-success\">\n <svg width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#16a34a\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\"/>\n </svg>\n <p>Email sent successfully!</p>\n </div>\n } @else {\n <div class=\"o-share-body\">\n <div class=\"o-share-field\">\n <label class=\"o-share-label\" for=\"share-to\">To <span class=\"o-required\">*</span></label>\n <input id=\"share-to\" class=\"o-share-input\" type=\"email\" placeholder=\"recipient@email.com\" [(ngModel)]=\"shareForm.to\" />\n </div>\n <div class=\"o-share-field\">\n <label class=\"o-share-label\" for=\"share-cc\">CC</label>\n <input id=\"share-cc\" class=\"o-share-input\" type=\"text\" placeholder=\"cc@email.com (comma-separated)\" [(ngModel)]=\"shareForm.cc\" />\n </div>\n <div class=\"o-share-field\">\n <label class=\"o-share-label\" for=\"share-subject\">Subject</label>\n <input id=\"share-subject\" class=\"o-share-input\" type=\"text\" [(ngModel)]=\"shareForm.subject\" />\n </div>\n <div class=\"o-share-field\">\n <label class=\"o-share-label\" for=\"share-body\">Message</label>\n <textarea id=\"share-body\" class=\"o-share-input o-share-textarea\" rows=\"5\" [(ngModel)]=\"shareForm.body\"></textarea>\n </div>\n </div>\n <div class=\"o-share-footer\">\n <button type=\"button\" class=\"o-btn-secondary\" (click)=\"closeShareDialog()\">Cancel</button>\n <button type=\"button\" class=\"o-btn-primary\" [disabled]=\"shareSending || !shareForm.to\" (click)=\"sendShareEmail()\">\n @if (shareSending) { Sending\u2026 } @else { Send Email }\n </button>\n </div>\n }\n </div>\n </div>\n }\n\n</div>\n", styles: [".o-page{display:flex;flex-direction:column;gap:16px;height:100%}.o-page-header{display:flex;flex-direction:column;gap:12px}.o-page-title-row{display:flex;align-items:center;justify-content:space-between}.o-page-title{font-size:1.1rem;font-weight:700;color:#1e293b;margin:0}.o-add-btn{padding:8px 18px;background:#3b82f6;color:#fff;border:none;border-radius:8px;font-size:.82rem;font-weight:600;cursor:pointer;font-family:inherit;transition:background .15s}.o-add-btn:hover{background:#2563eb}.o-share-overlay{position:fixed;inset:0;background:#0f172a73;z-index:1100;display:flex;align-items:center;justify-content:center}.o-share-dialog{background:#fff;border-radius:14px;box-shadow:0 20px 60px #0f172a33;width:480px;max-width:96vw;display:flex;flex-direction:column;animation:o-share-in .18s ease}@keyframes o-share-in{0%{opacity:0;transform:scale(.96) translateY(-8px)}to{opacity:1;transform:scale(1) translateY(0)}}.o-share-header{display:flex;align-items:center;justify-content:space-between;padding:18px 22px 14px;border-bottom:1px solid #e2e8f0}.o-share-title{margin:0;font-size:1rem;font-weight:700;color:#1e293b}.o-share-close{background:none;border:none;font-size:1rem;color:#64748b;cursor:pointer;padding:4px 8px;border-radius:6px;transition:background .12s}.o-share-close:hover{background:#f1f5f9}.o-share-body{padding:18px 22px;display:flex;flex-direction:column;gap:14px}.o-share-field{display:flex;flex-direction:column;gap:5px}.o-share-label{font-size:.82rem;font-weight:600;color:#374151}.o-share-input{padding:8px 12px;border:1px solid #d1d5db;border-radius:8px;font-size:.86rem;font-family:inherit;color:#1e293b;outline:none;transition:border-color .15s}.o-share-input:focus{border-color:#6366f1}.o-share-textarea{resize:vertical;min-height:90px}.o-share-footer{display:flex;justify-content:flex-end;gap:10px;padding:14px 22px 18px;border-top:1px solid #e2e8f0}.o-share-success{display:flex;flex-direction:column;align-items:center;gap:12px;padding:40px 22px;color:#16a34a;font-weight:600;font-size:.95rem}.o-btn-primary{padding:8px 20px;background:#4f46e5;color:#fff;border:none;border-radius:8px;font-size:.85rem;font-weight:600;cursor:pointer;font-family:inherit;transition:background .15s}.o-btn-primary:hover:not(:disabled){background:#4338ca}.o-btn-primary:disabled{opacity:.5;cursor:not-allowed}.o-btn-secondary{padding:8px 20px;background:#f1f5f9;color:#334155;border:1px solid #e2e8f0;border-radius:8px;font-size:.85rem;font-weight:600;cursor:pointer;font-family:inherit;transition:background .15s}.o-btn-secondary:hover{background:#e2e8f0}.o-required{color:#ef4444;margin-left:2px}\n"], dependencies: [{ kind: "component", type: ToggleTabsComponent, selector: "o-toggle-tabs", inputs: ["tabs", "activeTab"], outputs: ["tabChange"] }, { kind: "component", type: DataTableComponent, selector: "o-data-table", inputs: ["columns", "rows", "selectable", "actions"], outputs: ["rowClick", "menuOpen", "actionClick", "selectionChange"] }, { kind: "component", type: FormDrawerComponent, selector: "o-form-drawer", inputs: ["open", "title", "steps", "rowData", "showSubmit", "readOnly", "actionType"], outputs: ["closeDrawer", "save", "submit"] }, { kind: "component", type: ContextMenuComponent, selector: "o-context-menu", inputs: ["open", "position", "actions", "row", "menuType"], outputs: ["actionTriggered", "closed"] }, { kind: "component", type: OTableToolsComponent, selector: "o-table-tools", inputs: ["config"], outputs: ["toolAction"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
1381
+ }
1382
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: PageRendererComponent, decorators: [{
1383
+ type: Component,
1384
+ args: [{ selector: 'o-page-renderer', standalone: true, imports: [
1385
+ ToggleTabsComponent,
1386
+ DataTableComponent,
1387
+ FormDrawerComponent,
1388
+ ContextMenuComponent,
1389
+ OSidePanelComponent,
1390
+ OTableToolsComponent,
1391
+ FormsModule
1392
+ ], template: "<div class=\"o-page\">\n\n <!-- Page Header -->\n <div class=\"o-page-header\">\n <div class=\"o-page-title-row\">\n <h2 class=\"o-page-title\">{{ page?.title }}</h2>\n </div>\n\n @if (page?.toggleButton?.elements?.length) {\n <o-toggle-tabs\n [tabs]=\"page!.toggleButton!.elements\"\n [activeTab]=\"activeTab\"\n (tabChange)=\"onTabChange($event)\">\n </o-toggle-tabs>\n }\n </div>\n\n @if (computedTableToolConfig) {\n <o-table-tools\n [config]=\"computedTableToolConfig\"\n (toolAction)=\"onTableToolAction($event)\"\n ></o-table-tools>\n }\n\n <!-- Data Table -->\n <o-data-table\n [columns]=\"page?.tableList || []\"\n [rows]=\"filteredRows\"\n (selectionChange)=\"onSelectionChange($event)\"\n (menuOpen)=\"onMenuOpen($event)\"\n (rowClick)=\"onRowClick($event)\"\n (actionClick)=\"onContextAction($event)\">\n </o-data-table>\n\n <!-- Context Menu -->\n <o-context-menu\n [open]=\"menuOpen\"\n [position]=\"menuPosition\"\n [actions]=\"dynamicContextActions\"\n [row]=\"menuRow\"\n [menuType]=\"page?.store || ''\"\n (actionTriggered)=\"onContextAction($event)\"\n (closed)=\"menuOpen = false\">\n </o-context-menu>\n\n <!-- Form Drawer -->\n <o-form-drawer\n [open]=\"drawerOpen\"\n [title]=\"drawerTitle\"\n [steps]=\"page?.steps || []\"\n [rowData]=\"drawerRowData\"\n [showSubmit]=\"showSubmitBtn\"\n [readOnly]=\"drawerReadOnly\"\n [actionType]=\"drawerAction\"\n (closeDrawer)=\"drawerOpen = false\"\n (save)=\"onSave($event)\"\n (submit)=\"onSubmitForApproval($event)\">\n </o-form-drawer>\n\n <!-- Share / Email Dialog -->\n @if (shareDialogOpen) {\n <div class=\"o-share-overlay\"\n (click)=\"closeShareDialog()\"\n (keydown.escape)=\"closeShareDialog()\">\n <div class=\"o-share-dialog\"\n (click)=\"$event.stopPropagation()\"\n (keydown.escape)=\"closeShareDialog()\">\n <div class=\"o-share-header\">\n <h3 class=\"o-share-title\">Share via Email</h3>\n <button type=\"button\" class=\"o-share-close\" (click)=\"closeShareDialog()\" aria-label=\"Close dialog\">&#x2715;</button>\n </div>\n\n @if (shareSuccess) {\n <div class=\"o-share-success\">\n <svg width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#16a34a\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\"/>\n </svg>\n <p>Email sent successfully!</p>\n </div>\n } @else {\n <div class=\"o-share-body\">\n <div class=\"o-share-field\">\n <label class=\"o-share-label\" for=\"share-to\">To <span class=\"o-required\">*</span></label>\n <input id=\"share-to\" class=\"o-share-input\" type=\"email\" placeholder=\"recipient@email.com\" [(ngModel)]=\"shareForm.to\" />\n </div>\n <div class=\"o-share-field\">\n <label class=\"o-share-label\" for=\"share-cc\">CC</label>\n <input id=\"share-cc\" class=\"o-share-input\" type=\"text\" placeholder=\"cc@email.com (comma-separated)\" [(ngModel)]=\"shareForm.cc\" />\n </div>\n <div class=\"o-share-field\">\n <label class=\"o-share-label\" for=\"share-subject\">Subject</label>\n <input id=\"share-subject\" class=\"o-share-input\" type=\"text\" [(ngModel)]=\"shareForm.subject\" />\n </div>\n <div class=\"o-share-field\">\n <label class=\"o-share-label\" for=\"share-body\">Message</label>\n <textarea id=\"share-body\" class=\"o-share-input o-share-textarea\" rows=\"5\" [(ngModel)]=\"shareForm.body\"></textarea>\n </div>\n </div>\n <div class=\"o-share-footer\">\n <button type=\"button\" class=\"o-btn-secondary\" (click)=\"closeShareDialog()\">Cancel</button>\n <button type=\"button\" class=\"o-btn-primary\" [disabled]=\"shareSending || !shareForm.to\" (click)=\"sendShareEmail()\">\n @if (shareSending) { Sending\u2026 } @else { Send Email }\n </button>\n </div>\n }\n </div>\n </div>\n }\n\n</div>\n", styles: [".o-page{display:flex;flex-direction:column;gap:16px;height:100%}.o-page-header{display:flex;flex-direction:column;gap:12px}.o-page-title-row{display:flex;align-items:center;justify-content:space-between}.o-page-title{font-size:1.1rem;font-weight:700;color:#1e293b;margin:0}.o-add-btn{padding:8px 18px;background:#3b82f6;color:#fff;border:none;border-radius:8px;font-size:.82rem;font-weight:600;cursor:pointer;font-family:inherit;transition:background .15s}.o-add-btn:hover{background:#2563eb}.o-share-overlay{position:fixed;inset:0;background:#0f172a73;z-index:1100;display:flex;align-items:center;justify-content:center}.o-share-dialog{background:#fff;border-radius:14px;box-shadow:0 20px 60px #0f172a33;width:480px;max-width:96vw;display:flex;flex-direction:column;animation:o-share-in .18s ease}@keyframes o-share-in{0%{opacity:0;transform:scale(.96) translateY(-8px)}to{opacity:1;transform:scale(1) translateY(0)}}.o-share-header{display:flex;align-items:center;justify-content:space-between;padding:18px 22px 14px;border-bottom:1px solid #e2e8f0}.o-share-title{margin:0;font-size:1rem;font-weight:700;color:#1e293b}.o-share-close{background:none;border:none;font-size:1rem;color:#64748b;cursor:pointer;padding:4px 8px;border-radius:6px;transition:background .12s}.o-share-close:hover{background:#f1f5f9}.o-share-body{padding:18px 22px;display:flex;flex-direction:column;gap:14px}.o-share-field{display:flex;flex-direction:column;gap:5px}.o-share-label{font-size:.82rem;font-weight:600;color:#374151}.o-share-input{padding:8px 12px;border:1px solid #d1d5db;border-radius:8px;font-size:.86rem;font-family:inherit;color:#1e293b;outline:none;transition:border-color .15s}.o-share-input:focus{border-color:#6366f1}.o-share-textarea{resize:vertical;min-height:90px}.o-share-footer{display:flex;justify-content:flex-end;gap:10px;padding:14px 22px 18px;border-top:1px solid #e2e8f0}.o-share-success{display:flex;flex-direction:column;align-items:center;gap:12px;padding:40px 22px;color:#16a34a;font-weight:600;font-size:.95rem}.o-btn-primary{padding:8px 20px;background:#4f46e5;color:#fff;border:none;border-radius:8px;font-size:.85rem;font-weight:600;cursor:pointer;font-family:inherit;transition:background .15s}.o-btn-primary:hover:not(:disabled){background:#4338ca}.o-btn-primary:disabled{opacity:.5;cursor:not-allowed}.o-btn-secondary{padding:8px 20px;background:#f1f5f9;color:#334155;border:1px solid #e2e8f0;border-radius:8px;font-size:.85rem;font-weight:600;cursor:pointer;font-family:inherit;transition:background .15s}.o-btn-secondary:hover{background:#e2e8f0}.o-required{color:#ef4444;margin-left:2px}\n"] }]
1393
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i1.HttpClient }], propDecorators: { page: [{
1394
+ type: Input
1395
+ }], data: [{
1396
+ type: Input
1397
+ }], userRole: [{
1398
+ type: Input
1399
+ }], activeTab: [{
1400
+ type: Input
1401
+ }], actionTriggered: [{
1402
+ type: Output
1403
+ }], tabChange: [{
1404
+ type: Output
1405
+ }] } });
1406
+
1407
+ class PageHeaderComponent {
1408
+ breadcrumbs = [];
1409
+ title = '';
1410
+ subtitle;
1411
+ actionLabel;
1412
+ showBack = false;
1413
+ actionClick = new EventEmitter();
1414
+ backClick = new EventEmitter();
1415
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: PageHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1416
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: PageHeaderComponent, isStandalone: true, selector: "o-page-header", inputs: { breadcrumbs: "breadcrumbs", title: "title", subtitle: "subtitle", actionLabel: "actionLabel", showBack: "showBack" }, outputs: { actionClick: "actionClick", backClick: "backClick" }, ngImport: i0, template: "<div class=\"o-page-header\">\n <!-- Breadcrumb -->\n @if (breadcrumbs && breadcrumbs.length > 0) {\n <nav class=\"o-ph-breadcrumb\">\n @for (crumb of breadcrumbs; track crumb.label; let last = $last) {\n @if (crumb.route && !last) {\n <a class=\"o-ph-crumb o-ph-crumb--link\" [routerLink]=\"crumb.route\">\n {{ crumb.label }}\n </a>\n }\n @if (!crumb.route || last) {\n <span class=\"o-ph-crumb\" [class.o-ph-crumb--active]=\"last\">\n {{ crumb.label }}\n </span>\n }\n @if (!last) {\n <span class=\"o-ph-separator\" aria-hidden=\"true\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"9 18 15 12 9 6\"/>\n </svg>\n </span>\n }\n }\n </nav>\n }\n\n <!-- Title row -->\n <div class=\"o-ph-title-row\">\n <!-- Back button -->\n @if (showBack) {\n <button class=\"o-ph-back-btn\" (click)=\"backClick.emit()\" aria-label=\"Go back\" type=\"button\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"19\" y1=\"12\" x2=\"5\" y2=\"12\"/>\n <polyline points=\"12 19 5 12 12 5\"/>\n </svg>\n </button>\n }\n\n <!-- Title + subtitle -->\n <div class=\"o-ph-text\">\n <h2 class=\"o-ph-title\">{{ title }}</h2>\n @if (subtitle) {\n <p class=\"o-ph-subtitle\">{{ subtitle }}</p>\n }\n </div>\n\n <!-- Spacer -->\n <div class=\"o-ph-spacer\"></div>\n\n <!-- Primary action button -->\n @if (actionLabel) {\n <button class=\"o-ph-action-btn\" type=\"button\" (click)=\"actionClick.emit()\">\n {{ actionLabel }}\n </button>\n }\n </div>\n</div>\n", styles: [".o-page-header{display:flex;flex-direction:column;gap:6px;padding:20px 24px 16px;border-bottom:1px solid #e2e8f0;background:#fff}.o-ph-breadcrumb{display:flex;align-items:center;gap:4px;flex-wrap:wrap}.o-ph-crumb{font-size:.75rem;color:#94a3b8;text-decoration:none;white-space:nowrap}.o-ph-crumb--link{color:#64748b;text-decoration:none;cursor:pointer;transition:color .15s}.o-ph-crumb--link:hover{color:#15104e}.o-ph-crumb--active{color:#1e293b;font-weight:500}.o-ph-separator{display:flex;align-items:center;color:#cbd5e1}.o-ph-title-row{display:flex;align-items:center;gap:12px}.o-ph-back-btn{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:1px solid #e2e8f0;border-radius:8px;background:#fff;color:#475569;cursor:pointer;flex-shrink:0;transition:background .15s,border-color .15s,color .15s}.o-ph-back-btn:hover{background:#f1f5f9;border-color:#cbd5e1;color:#15104e}.o-ph-text{display:flex;flex-direction:column;gap:2px}.o-ph-title{margin:0;font-size:1.25rem;font-weight:700;color:#15104e;line-height:1.3}.o-ph-subtitle{margin:0;font-size:.8rem;color:#64748b}.o-ph-spacer{flex:1}.o-ph-action-btn{padding:8px 18px;background:#ff8200;color:#fff;border:none;border-radius:8px;font-size:.85rem;font-weight:600;cursor:pointer;white-space:nowrap;transition:background .15s}.o-ph-action-btn:hover{background:#e67300}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }] });
1417
+ }
1418
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: PageHeaderComponent, decorators: [{
1419
+ type: Component,
1420
+ args: [{ selector: 'o-page-header', standalone: true, imports: [CommonModule, RouterModule], template: "<div class=\"o-page-header\">\n <!-- Breadcrumb -->\n @if (breadcrumbs && breadcrumbs.length > 0) {\n <nav class=\"o-ph-breadcrumb\">\n @for (crumb of breadcrumbs; track crumb.label; let last = $last) {\n @if (crumb.route && !last) {\n <a class=\"o-ph-crumb o-ph-crumb--link\" [routerLink]=\"crumb.route\">\n {{ crumb.label }}\n </a>\n }\n @if (!crumb.route || last) {\n <span class=\"o-ph-crumb\" [class.o-ph-crumb--active]=\"last\">\n {{ crumb.label }}\n </span>\n }\n @if (!last) {\n <span class=\"o-ph-separator\" aria-hidden=\"true\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"9 18 15 12 9 6\"/>\n </svg>\n </span>\n }\n }\n </nav>\n }\n\n <!-- Title row -->\n <div class=\"o-ph-title-row\">\n <!-- Back button -->\n @if (showBack) {\n <button class=\"o-ph-back-btn\" (click)=\"backClick.emit()\" aria-label=\"Go back\" type=\"button\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"19\" y1=\"12\" x2=\"5\" y2=\"12\"/>\n <polyline points=\"12 19 5 12 12 5\"/>\n </svg>\n </button>\n }\n\n <!-- Title + subtitle -->\n <div class=\"o-ph-text\">\n <h2 class=\"o-ph-title\">{{ title }}</h2>\n @if (subtitle) {\n <p class=\"o-ph-subtitle\">{{ subtitle }}</p>\n }\n </div>\n\n <!-- Spacer -->\n <div class=\"o-ph-spacer\"></div>\n\n <!-- Primary action button -->\n @if (actionLabel) {\n <button class=\"o-ph-action-btn\" type=\"button\" (click)=\"actionClick.emit()\">\n {{ actionLabel }}\n </button>\n }\n </div>\n</div>\n", styles: [".o-page-header{display:flex;flex-direction:column;gap:6px;padding:20px 24px 16px;border-bottom:1px solid #e2e8f0;background:#fff}.o-ph-breadcrumb{display:flex;align-items:center;gap:4px;flex-wrap:wrap}.o-ph-crumb{font-size:.75rem;color:#94a3b8;text-decoration:none;white-space:nowrap}.o-ph-crumb--link{color:#64748b;text-decoration:none;cursor:pointer;transition:color .15s}.o-ph-crumb--link:hover{color:#15104e}.o-ph-crumb--active{color:#1e293b;font-weight:500}.o-ph-separator{display:flex;align-items:center;color:#cbd5e1}.o-ph-title-row{display:flex;align-items:center;gap:12px}.o-ph-back-btn{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:1px solid #e2e8f0;border-radius:8px;background:#fff;color:#475569;cursor:pointer;flex-shrink:0;transition:background .15s,border-color .15s,color .15s}.o-ph-back-btn:hover{background:#f1f5f9;border-color:#cbd5e1;color:#15104e}.o-ph-text{display:flex;flex-direction:column;gap:2px}.o-ph-title{margin:0;font-size:1.25rem;font-weight:700;color:#15104e;line-height:1.3}.o-ph-subtitle{margin:0;font-size:.8rem;color:#64748b}.o-ph-spacer{flex:1}.o-ph-action-btn{padding:8px 18px;background:#ff8200;color:#fff;border:none;border-radius:8px;font-size:.85rem;font-weight:600;cursor:pointer;white-space:nowrap;transition:background .15s}.o-ph-action-btn:hover{background:#e67300}\n"] }]
1421
+ }], propDecorators: { breadcrumbs: [{
1422
+ type: Input
1423
+ }], title: [{
1424
+ type: Input
1425
+ }], subtitle: [{
1426
+ type: Input
1427
+ }], actionLabel: [{
1428
+ type: Input
1429
+ }], showBack: [{
1430
+ type: Input
1431
+ }], actionClick: [{
1432
+ type: Output
1433
+ }], backClick: [{
1434
+ type: Output
1435
+ }] } });
1436
+
1437
+ class SideNavComponent {
1438
+ navItems = [];
1439
+ activeKey = '';
1440
+ showLogout = true;
1441
+ navChange = new EventEmitter();
1442
+ logout = new EventEmitter();
1443
+ onNavClick(key) {
1444
+ this.navChange.emit(key);
1445
+ }
1446
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: SideNavComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1447
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: SideNavComponent, isStandalone: true, selector: "o-side-nav", inputs: { navItems: "navItems", activeKey: "activeKey", showLogout: "showLogout" }, outputs: { navChange: "navChange", logout: "logout" }, ngImport: i0, template: "<nav class=\"o-side-nav\">\n <!-- Logo -->\n <div class=\"o-sn-logo\" aria-label=\"Orque Platform\">\n <span class=\"o-sn-logo-letter\">O</span>\n </div>\n\n <!-- Nav items -->\n <div class=\"o-sn-items\">\n @for (item of navItems; track item.key) {\n <button\n class=\"o-sn-item\"\n type=\"button\"\n [class.o-sn-item--active]=\"item.key === activeKey\"\n [title]=\"item.tooltip || item.label\"\n [attr.aria-label]=\"item.label\"\n [attr.aria-current]=\"item.key === activeKey ? 'page' : null\"\n (click)=\"onNavClick(item.key)\"\n >\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path [attr.d]=\"item.svgPath\"/>\n </svg>\n </button>\n }\n </div>\n\n <!-- Spacer -->\n <div class=\"o-sn-spacer\"></div>\n\n <!-- Logout -->\n @if (showLogout) {\n <button\n class=\"o-sn-item o-sn-item--logout\"\n type=\"button\"\n title=\"Logout\"\n aria-label=\"Logout\"\n (click)=\"logout.emit()\"\n >\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\"/>\n <polyline points=\"16 17 21 12 16 7\"/>\n <line x1=\"21\" y1=\"12\" x2=\"9\" y2=\"12\"/>\n </svg>\n </button>\n }\n</nav>\n", styles: [".o-side-nav{display:flex;flex-direction:column;align-items:center;width:70px;min-height:100vh;background:#15104e;padding:0;box-sizing:border-box;flex-shrink:0}.o-sn-logo{display:flex;align-items:center;justify-content:center;width:70px;height:64px;flex-shrink:0}.o-sn-logo-letter{display:flex;align-items:center;justify-content:center;width:36px;height:36px;background:#fff;color:#15104e;font-size:1.1rem;font-weight:800;border-radius:8px;letter-spacing:-.5px}.o-sn-items{display:flex;flex-direction:column;align-items:center;gap:4px;width:100%;padding:8px 0}.o-sn-item{position:relative;display:flex;align-items:center;justify-content:center;width:70px;height:52px;background:none;border:none;color:#ffffff80;cursor:pointer;transition:color .15s,background .15s;outline:none;flex-shrink:0}.o-sn-item:before{content:\"\";position:absolute;left:0;top:8px;bottom:8px;width:3px;border-radius:0 2px 2px 0;background:transparent;transition:background .15s}.o-sn-item:hover{color:#fff;background:#ffffff12}.o-sn-item--active{color:#fff;background:#ff82001f}.o-sn-item--active:before{background:#ff8200}.o-sn-spacer{flex:1}.o-sn-item--logout{margin-bottom:16px;color:#fff6}.o-sn-item--logout:before{display:none}.o-sn-item--logout:hover{color:#ff4d4f;background:#ff4d4f1a}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
1448
+ }
1449
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: SideNavComponent, decorators: [{
1450
+ type: Component,
1451
+ args: [{ selector: 'o-side-nav', standalone: true, imports: [CommonModule], template: "<nav class=\"o-side-nav\">\n <!-- Logo -->\n <div class=\"o-sn-logo\" aria-label=\"Orque Platform\">\n <span class=\"o-sn-logo-letter\">O</span>\n </div>\n\n <!-- Nav items -->\n <div class=\"o-sn-items\">\n @for (item of navItems; track item.key) {\n <button\n class=\"o-sn-item\"\n type=\"button\"\n [class.o-sn-item--active]=\"item.key === activeKey\"\n [title]=\"item.tooltip || item.label\"\n [attr.aria-label]=\"item.label\"\n [attr.aria-current]=\"item.key === activeKey ? 'page' : null\"\n (click)=\"onNavClick(item.key)\"\n >\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path [attr.d]=\"item.svgPath\"/>\n </svg>\n </button>\n }\n </div>\n\n <!-- Spacer -->\n <div class=\"o-sn-spacer\"></div>\n\n <!-- Logout -->\n @if (showLogout) {\n <button\n class=\"o-sn-item o-sn-item--logout\"\n type=\"button\"\n title=\"Logout\"\n aria-label=\"Logout\"\n (click)=\"logout.emit()\"\n >\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\"/>\n <polyline points=\"16 17 21 12 16 7\"/>\n <line x1=\"21\" y1=\"12\" x2=\"9\" y2=\"12\"/>\n </svg>\n </button>\n }\n</nav>\n", styles: [".o-side-nav{display:flex;flex-direction:column;align-items:center;width:70px;min-height:100vh;background:#15104e;padding:0;box-sizing:border-box;flex-shrink:0}.o-sn-logo{display:flex;align-items:center;justify-content:center;width:70px;height:64px;flex-shrink:0}.o-sn-logo-letter{display:flex;align-items:center;justify-content:center;width:36px;height:36px;background:#fff;color:#15104e;font-size:1.1rem;font-weight:800;border-radius:8px;letter-spacing:-.5px}.o-sn-items{display:flex;flex-direction:column;align-items:center;gap:4px;width:100%;padding:8px 0}.o-sn-item{position:relative;display:flex;align-items:center;justify-content:center;width:70px;height:52px;background:none;border:none;color:#ffffff80;cursor:pointer;transition:color .15s,background .15s;outline:none;flex-shrink:0}.o-sn-item:before{content:\"\";position:absolute;left:0;top:8px;bottom:8px;width:3px;border-radius:0 2px 2px 0;background:transparent;transition:background .15s}.o-sn-item:hover{color:#fff;background:#ffffff12}.o-sn-item--active{color:#fff;background:#ff82001f}.o-sn-item--active:before{background:#ff8200}.o-sn-spacer{flex:1}.o-sn-item--logout{margin-bottom:16px;color:#fff6}.o-sn-item--logout:before{display:none}.o-sn-item--logout:hover{color:#ff4d4f;background:#ff4d4f1a}\n"] }]
1452
+ }], propDecorators: { navItems: [{
1453
+ type: Input
1454
+ }], activeKey: [{
1455
+ type: Input
1456
+ }], showLogout: [{
1457
+ type: Input
1458
+ }], navChange: [{
1459
+ type: Output
1460
+ }], logout: [{
1461
+ type: Output
1462
+ }] } });
1463
+
1464
+ class SearchFilterComponent {
1465
+ placeholder;
1466
+ value;
1467
+ search = new EventEmitter();
1468
+ searchInputRef;
1469
+ inputValue = '';
1470
+ subject = new Subject();
1471
+ sub;
1472
+ ngOnInit() {
1473
+ if (this.value !== undefined) {
1474
+ this.inputValue = this.value;
1475
+ }
1476
+ this.sub = this.subject.pipe(debounceTime(300), distinctUntilChanged()).subscribe(val => this.search.emit(val));
1477
+ }
1478
+ ngOnChanges(changes) {
1479
+ if (changes['value'] && changes['value'].currentValue !== this.inputValue) {
1480
+ this.inputValue = changes['value'].currentValue ?? '';
1481
+ }
1482
+ }
1483
+ ngOnDestroy() {
1484
+ this.sub?.unsubscribe();
1485
+ this.subject.complete();
1486
+ }
1487
+ onInput() {
1488
+ this.subject.next(this.inputValue);
1489
+ }
1490
+ clear() {
1491
+ this.inputValue = '';
1492
+ this.subject.next('');
1493
+ this.searchInputRef?.nativeElement.focus();
1494
+ }
1495
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: SearchFilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1496
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: SearchFilterComponent, isStandalone: true, selector: "o-search-filter", inputs: { placeholder: "placeholder", value: "value" }, outputs: { search: "search" }, viewQueries: [{ propertyName: "searchInputRef", first: true, predicate: ["searchInput"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"o-search-filter\">\n <span class=\"o-sf-icon\" aria-hidden=\"true\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/>\n </svg>\n </span>\n <input\n #searchInput\n class=\"o-sf-input\"\n type=\"text\"\n [placeholder]=\"placeholder || 'Search...'\"\n [(ngModel)]=\"inputValue\"\n (input)=\"onInput()\"\n (keydown.escape)=\"clear()\"\n [attr.aria-label]=\"placeholder || 'Search'\"\n autocomplete=\"off\"\n />\n @if (inputValue) {\n <button\n class=\"o-sf-clear\"\n type=\"button\"\n (click)=\"clear()\"\n aria-label=\"Clear search\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n }\n</div>\n", styles: [".o-search-filter{display:flex;align-items:center;gap:0;background:#fff;border:1px solid #e2e8f0;border-radius:8px;padding:0 10px;height:38px;transition:border-color .15s,box-shadow .15s;box-sizing:border-box}.o-search-filter:focus-within{border-color:#15104e;box-shadow:0 0 0 3px #15104e1a}.o-sf-icon{display:flex;align-items:center;color:#94a3b8;flex-shrink:0;margin-right:8px}.o-sf-input{flex:1;border:none;outline:none;background:transparent;font-size:.85rem;color:#1e293b;min-width:0}.o-sf-input::placeholder{color:#94a3b8}.o-sf-clear{display:flex;align-items:center;justify-content:center;background:none;border:none;padding:2px;margin-left:4px;color:#94a3b8;cursor:pointer;border-radius:4px;flex-shrink:0;transition:color .12s,background .12s}.o-sf-clear:hover{color:#475569;background:#f1f5f9}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
1497
+ }
1498
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: SearchFilterComponent, decorators: [{
1499
+ type: Component,
1500
+ args: [{ selector: 'o-search-filter', standalone: true, imports: [CommonModule, FormsModule], template: "<div class=\"o-search-filter\">\n <span class=\"o-sf-icon\" aria-hidden=\"true\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/>\n </svg>\n </span>\n <input\n #searchInput\n class=\"o-sf-input\"\n type=\"text\"\n [placeholder]=\"placeholder || 'Search...'\"\n [(ngModel)]=\"inputValue\"\n (input)=\"onInput()\"\n (keydown.escape)=\"clear()\"\n [attr.aria-label]=\"placeholder || 'Search'\"\n autocomplete=\"off\"\n />\n @if (inputValue) {\n <button\n class=\"o-sf-clear\"\n type=\"button\"\n (click)=\"clear()\"\n aria-label=\"Clear search\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n }\n</div>\n", styles: [".o-search-filter{display:flex;align-items:center;gap:0;background:#fff;border:1px solid #e2e8f0;border-radius:8px;padding:0 10px;height:38px;transition:border-color .15s,box-shadow .15s;box-sizing:border-box}.o-search-filter:focus-within{border-color:#15104e;box-shadow:0 0 0 3px #15104e1a}.o-sf-icon{display:flex;align-items:center;color:#94a3b8;flex-shrink:0;margin-right:8px}.o-sf-input{flex:1;border:none;outline:none;background:transparent;font-size:.85rem;color:#1e293b;min-width:0}.o-sf-input::placeholder{color:#94a3b8}.o-sf-clear{display:flex;align-items:center;justify-content:center;background:none;border:none;padding:2px;margin-left:4px;color:#94a3b8;cursor:pointer;border-radius:4px;flex-shrink:0;transition:color .12s,background .12s}.o-sf-clear:hover{color:#475569;background:#f1f5f9}\n"] }]
1501
+ }], propDecorators: { placeholder: [{
1502
+ type: Input
1503
+ }], value: [{
1504
+ type: Input
1505
+ }], search: [{
1506
+ type: Output
1507
+ }], searchInputRef: [{
1508
+ type: ViewChild,
1509
+ args: ['searchInput']
1510
+ }] } });
1511
+
1512
+ class ConfirmationDialogComponent {
1513
+ open = false;
1514
+ title = '';
1515
+ message = '';
1516
+ confirmLabel = 'Confirm';
1517
+ cancelLabel = 'Cancel';
1518
+ type = 'info';
1519
+ confirmed = new EventEmitter();
1520
+ cancelled = new EventEmitter();
1521
+ onConfirm() {
1522
+ this.confirmed.emit();
1523
+ }
1524
+ onCancel() {
1525
+ this.cancelled.emit();
1526
+ }
1527
+ onOverlayClick() {
1528
+ this.cancelled.emit();
1529
+ }
1530
+ onEscape() {
1531
+ if (this.open) {
1532
+ this.cancelled.emit();
1533
+ }
1534
+ }
1535
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ConfirmationDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1536
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ConfirmationDialogComponent, isStandalone: true, selector: "o-confirmation-dialog", inputs: { open: "open", title: "title", message: "message", confirmLabel: "confirmLabel", cancelLabel: "cancelLabel", type: "type" }, outputs: { confirmed: "confirmed", cancelled: "cancelled" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "@if (open) {\n<div class=\"o-cd-overlay\" (click)=\"onOverlayClick()\" role=\"dialog\" aria-modal=\"true\"\n [attr.aria-label]=\"title\">\n <div class=\"o-cd-card\" (click)=\"$event.stopPropagation()\">\n\n <div class=\"o-cd-header\">\n <span class=\"o-cd-icon\" [class]=\"'o-cd-icon--' + (type || 'info')\">\n @if (type === 'danger') {\n <svg width=\"22\" height=\"22\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/>\n <line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"/>\n <line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"/>\n </svg>\n } @else if (type === 'warning') {\n <svg width=\"22\" height=\"22\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/>\n <line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/>\n <line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>\n </svg>\n } @else {\n <svg width=\"22\" height=\"22\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/>\n <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\"/>\n <line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\"/>\n </svg>\n }\n </span>\n <h3 class=\"o-cd-title\">{{ title }}</h3>\n </div>\n\n <div class=\"o-cd-body\">\n <p class=\"o-cd-message\">{{ message }}</p>\n </div>\n\n <div class=\"o-cd-footer\">\n <button class=\"o-cd-btn o-cd-btn--cancel\" type=\"button\" (click)=\"onCancel()\">\n {{ cancelLabel || 'Cancel' }}\n </button>\n <button class=\"o-cd-btn o-cd-btn--confirm\"\n [class]=\"'o-cd-btn o-cd-btn--confirm o-cd-btn--confirm--' + (type || 'info')\"\n type=\"button\"\n (click)=\"onConfirm()\">\n {{ confirmLabel || 'Confirm' }}\n </button>\n </div>\n\n </div>\n</div>\n}\n", styles: [".o-cd-overlay{position:fixed;inset:0;background:#0f172a73;z-index:1000;display:flex;align-items:center;justify-content:center;padding:16px;animation:o-cd-fade-in .15s ease}@keyframes o-cd-fade-in{0%{opacity:0}to{opacity:1}}.o-cd-card{background:#fff;border-radius:14px;box-shadow:0 20px 60px #0f172a38;width:420px;max-width:95vw;display:flex;flex-direction:column;animation:o-cd-slide-up .18s ease;overflow:hidden}@keyframes o-cd-slide-up{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}.o-cd-header{display:flex;align-items:center;gap:12px;padding:20px 24px 12px}.o-cd-icon{display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:10px;flex-shrink:0}.o-cd-icon--danger{background:#fef2f2;color:#dc2626}.o-cd-icon--warning{background:#fffbeb;color:#d97706}.o-cd-icon--info{background:#eff6ff;color:#2563eb}.o-cd-title{margin:0;font-size:1rem;font-weight:700;color:#1e293b;line-height:1.3}.o-cd-body{padding:4px 24px 20px}.o-cd-message{margin:0;font-size:.875rem;color:#64748b;line-height:1.55}.o-cd-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;padding:16px 24px;border-top:1px solid #f1f5f9;background:#f8fafc}.o-cd-btn{padding:8px 18px;border-radius:8px;font-size:.85rem;font-weight:600;cursor:pointer;border:none;transition:background .15s,color .15s;outline:none}.o-cd-btn--cancel{background:#fff;color:#475569;border:1px solid #e2e8f0}.o-cd-btn--cancel:hover{background:#f1f5f9;border-color:#cbd5e1}.o-cd-btn--confirm{color:#fff}.o-cd-btn--confirm--danger{background:#dc2626}.o-cd-btn--confirm--danger:hover{background:#b91c1c}.o-cd-btn--confirm--warning{background:#d97706}.o-cd-btn--confirm--warning:hover{background:#b45309}.o-cd-btn--confirm--info{background:#2563eb}.o-cd-btn--confirm--info:hover{background:#1d4ed8}\n"] });
1537
+ }
1538
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ConfirmationDialogComponent, decorators: [{
1539
+ type: Component,
1540
+ args: [{ selector: 'o-confirmation-dialog', standalone: true, imports: [], template: "@if (open) {\n<div class=\"o-cd-overlay\" (click)=\"onOverlayClick()\" role=\"dialog\" aria-modal=\"true\"\n [attr.aria-label]=\"title\">\n <div class=\"o-cd-card\" (click)=\"$event.stopPropagation()\">\n\n <div class=\"o-cd-header\">\n <span class=\"o-cd-icon\" [class]=\"'o-cd-icon--' + (type || 'info')\">\n @if (type === 'danger') {\n <svg width=\"22\" height=\"22\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/>\n <line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"/>\n <line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"/>\n </svg>\n } @else if (type === 'warning') {\n <svg width=\"22\" height=\"22\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/>\n <line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/>\n <line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>\n </svg>\n } @else {\n <svg width=\"22\" height=\"22\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/>\n <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\"/>\n <line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\"/>\n </svg>\n }\n </span>\n <h3 class=\"o-cd-title\">{{ title }}</h3>\n </div>\n\n <div class=\"o-cd-body\">\n <p class=\"o-cd-message\">{{ message }}</p>\n </div>\n\n <div class=\"o-cd-footer\">\n <button class=\"o-cd-btn o-cd-btn--cancel\" type=\"button\" (click)=\"onCancel()\">\n {{ cancelLabel || 'Cancel' }}\n </button>\n <button class=\"o-cd-btn o-cd-btn--confirm\"\n [class]=\"'o-cd-btn o-cd-btn--confirm o-cd-btn--confirm--' + (type || 'info')\"\n type=\"button\"\n (click)=\"onConfirm()\">\n {{ confirmLabel || 'Confirm' }}\n </button>\n </div>\n\n </div>\n</div>\n}\n", styles: [".o-cd-overlay{position:fixed;inset:0;background:#0f172a73;z-index:1000;display:flex;align-items:center;justify-content:center;padding:16px;animation:o-cd-fade-in .15s ease}@keyframes o-cd-fade-in{0%{opacity:0}to{opacity:1}}.o-cd-card{background:#fff;border-radius:14px;box-shadow:0 20px 60px #0f172a38;width:420px;max-width:95vw;display:flex;flex-direction:column;animation:o-cd-slide-up .18s ease;overflow:hidden}@keyframes o-cd-slide-up{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}.o-cd-header{display:flex;align-items:center;gap:12px;padding:20px 24px 12px}.o-cd-icon{display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:10px;flex-shrink:0}.o-cd-icon--danger{background:#fef2f2;color:#dc2626}.o-cd-icon--warning{background:#fffbeb;color:#d97706}.o-cd-icon--info{background:#eff6ff;color:#2563eb}.o-cd-title{margin:0;font-size:1rem;font-weight:700;color:#1e293b;line-height:1.3}.o-cd-body{padding:4px 24px 20px}.o-cd-message{margin:0;font-size:.875rem;color:#64748b;line-height:1.55}.o-cd-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;padding:16px 24px;border-top:1px solid #f1f5f9;background:#f8fafc}.o-cd-btn{padding:8px 18px;border-radius:8px;font-size:.85rem;font-weight:600;cursor:pointer;border:none;transition:background .15s,color .15s;outline:none}.o-cd-btn--cancel{background:#fff;color:#475569;border:1px solid #e2e8f0}.o-cd-btn--cancel:hover{background:#f1f5f9;border-color:#cbd5e1}.o-cd-btn--confirm{color:#fff}.o-cd-btn--confirm--danger{background:#dc2626}.o-cd-btn--confirm--danger:hover{background:#b91c1c}.o-cd-btn--confirm--warning{background:#d97706}.o-cd-btn--confirm--warning:hover{background:#b45309}.o-cd-btn--confirm--info{background:#2563eb}.o-cd-btn--confirm--info:hover{background:#1d4ed8}\n"] }]
1541
+ }], propDecorators: { open: [{
1542
+ type: Input
1543
+ }], title: [{
1544
+ type: Input
1545
+ }], message: [{
1546
+ type: Input
1547
+ }], confirmLabel: [{
1548
+ type: Input
1549
+ }], cancelLabel: [{
1550
+ type: Input
1551
+ }], type: [{
1552
+ type: Input
1553
+ }], confirmed: [{
1554
+ type: Output
1555
+ }], cancelled: [{
1556
+ type: Output
1557
+ }], onEscape: [{
1558
+ type: HostListener,
1559
+ args: ['document:keydown.escape']
1560
+ }] } });
1561
+
1562
+ class OTableComponent {
1563
+ columns = [];
1564
+ rows = [];
1565
+ showActions = true;
1566
+ showAdd = true;
1567
+ addLabel = 'Add';
1568
+ pagination = {
1569
+ page: 1,
1570
+ total: 0,
1571
+ total_pages: 1,
1572
+ startRow: 1,
1573
+ endRow: 0,
1574
+ pageLinks: []
1575
+ };
1576
+ openForm = new EventEmitter();
1577
+ paginationIndex = new EventEmitter();
1578
+ rowSelect = new EventEmitter();
1579
+ selectedRows = [];
1580
+ allSelected = false;
1581
+ ngOnChanges(changes) {
1582
+ if (changes['rows']) {
1583
+ this.selectedRows = [];
1584
+ this.allSelected = false;
1585
+ }
1586
+ }
1587
+ onRowClick(row) {
1588
+ this.rowSelect.emit(row);
1589
+ }
1590
+ toggleAll(event) {
1591
+ const checked = event.target.checked;
1592
+ this.allSelected = checked;
1593
+ this.selectedRows = checked ? [...this.rows] : [];
1594
+ }
1595
+ toggleRow(row) {
1596
+ const idx = this.selectedRows.indexOf(row);
1597
+ if (idx >= 0) {
1598
+ this.selectedRows.splice(idx, 1);
1599
+ }
1600
+ else {
1601
+ this.selectedRows.push(row);
1602
+ }
1603
+ this.allSelected = this.selectedRows.length === this.rows.length;
1604
+ }
1605
+ isSelected(row) {
1606
+ return this.selectedRows.includes(row);
1607
+ }
1608
+ prevPage() {
1609
+ if (this.pagination.page > 1) {
1610
+ this.paginationIndex.emit(this.pagination.page - 1);
1611
+ }
1612
+ }
1613
+ nextPage() {
1614
+ if (this.pagination.page < this.pagination.total_pages) {
1615
+ this.paginationIndex.emit(this.pagination.page + 1);
1616
+ }
1617
+ }
1618
+ showPage(page) {
1619
+ this.paginationIndex.emit(page);
1620
+ }
1621
+ onAddClick() {
1622
+ this.openForm.emit({ status: 'new' });
1623
+ }
1624
+ getCellValue(row, field) {
1625
+ return field.split('.').reduce((obj, key) => obj?.[key], row);
1626
+ }
1627
+ formatDate(value) {
1628
+ if (!value)
1629
+ return '';
1630
+ const d = new Date(value);
1631
+ if (isNaN(d.getTime()))
1632
+ return value;
1633
+ return d.toLocaleDateString('en-GB', {
1634
+ day: '2-digit',
1635
+ month: 'short',
1636
+ year: 'numeric'
1637
+ });
1638
+ }
1639
+ statusClass(status) {
1640
+ if (!status)
1641
+ return '';
1642
+ switch (status.toLowerCase()) {
1643
+ case 'draft':
1644
+ return 'o-status-badge--draft';
1645
+ case 'active':
1646
+ case 'approved':
1647
+ return 'o-status-badge--active';
1648
+ case 'pending':
1649
+ case 'returned':
1650
+ case 'in progress':
1651
+ return 'o-status-badge--progress';
1652
+ case 'rejected':
1653
+ case 'cancelled':
1654
+ case 'expired':
1655
+ return 'o-status-badge--rejected';
1656
+ default:
1657
+ return 'o-status-badge--inactive';
1658
+ }
1659
+ }
1660
+ columnWidth(col) {
1661
+ return col.width ?? '10rem';
1662
+ }
1663
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1664
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: OTableComponent, isStandalone: true, selector: "o-table", inputs: { columns: "columns", rows: "rows", showActions: "showActions", showAdd: "showAdd", addLabel: "addLabel", pagination: "pagination" }, outputs: { openForm: "openForm", paginationIndex: "paginationIndex", rowSelect: "rowSelect" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"o-table-wrapper\">\n <div class=\"o-table-scroll\">\n <table class=\"o-table\">\n <thead class=\"o-table__head\">\n <tr>\n <!-- Checkbox column -->\n <th class=\"o-table__th o-table__th--checkbox\">\n <input\n type=\"checkbox\"\n class=\"o-table__checkbox\"\n [checked]=\"allSelected\"\n (change)=\"toggleAll($event)\"\n aria-label=\"Select all rows\"\n />\n </th>\n\n <!-- Actions column -->\n @if (showActions) {\n <th class=\"o-table__th o-table__th--actions\">Actions</th>\n }\n\n <!-- Data columns -->\n @for (col of columns; track col.field) {\n <th\n class=\"o-table__th\"\n [style.min-width]=\"columnWidth(col)\"\n >\n {{ col.header }}\n </th>\n }\n </tr>\n </thead>\n\n <tbody class=\"o-table__body\">\n @if (rows && rows.length > 0) {\n @for (row of rows; track $index) {\n <tr\n class=\"o-table__row\"\n [class.o-table__row--selected]=\"isSelected(row)\"\n (click)=\"onRowClick(row)\"\n >\n <!-- Checkbox cell -->\n <td class=\"o-table__td o-table__td--checkbox\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"checkbox\"\n class=\"o-table__checkbox\"\n [checked]=\"isSelected(row)\"\n (change)=\"toggleRow(row)\"\n aria-label=\"Select row\"\n />\n </td>\n\n <!-- Actions cell -->\n @if (showActions) {\n <td class=\"o-table__td o-table__td--actions\" (click)=\"$event.stopPropagation()\">\n <ng-content select=\"[slot=actions]\"></ng-content>\n </td>\n }\n\n <!-- Data cells -->\n @for (col of columns; track col.field) {\n <td class=\"o-table__td\">\n @if (col.pipe === 'status') {\n <span\n class=\"o-status-badge\"\n [ngClass]=\"statusClass(getCellValue(row, col.field))\"\n >\n {{ getCellValue(row, col.field) }}\n </span>\n } @else if (col.pipe === 'date') {\n {{ formatDate(getCellValue(row, col.field)) }}\n } @else {\n {{ getCellValue(row, col.field) }}\n }\n </td>\n }\n </tr>\n }\n }\n </tbody>\n </table>\n </div>\n\n <!-- Empty state -->\n @if (!rows || rows.length === 0) {\n <div class=\"o-table__empty\">\n <svg\n class=\"o-table__empty-icon\"\n width=\"58\"\n height=\"59\"\n viewBox=\"0 0 58 59\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M8.0001 58.389H7.9991H3.9996C1.79421 58.389 0 56.5944 0 54.3885V29.3883C0 27.1829 1.79421 25.3887 3.9996 25.3887V27.3885C3.46577 27.3894 2.96412 27.5976 2.58606 27.9757C2.20801 28.3537 1.9998 28.8554 1.9998 29.3883V54.3885C1.9998 55.4912 2.89691 56.3883 3.9996 56.3883H8.0001V58.388V58.389ZM50.0004 38.3892H49.9994H47.9997V29.3883C47.9997 28.2856 47.1026 27.3885 45.9999 27.3885H44.0001V25.3887L45.9999 25.3887C48.2058 25.3887 50.0004 27.1829 50.0004 29.3883V38.3882V38.3892Z\"\n fill=\"#14034E\"\n />\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M8.25974 44.3889H8.25774V44.3879L6.63794 44.3889V3.68279V1.96918C6.63974 1.44823 6.84654 0.96047 7.22024 0.59578C7.58294 0.24118 8.06238 0.0459 8.57024 0.0459H8.60714H31.1341C31.161 0.0459 31.1899 0.04192 31.213 0.03873C31.2393 0.03511 31.2657 0.0315 31.2916 0.0315C31.2988 0.0315 31.3069 0.0315 31.3141 0.03239C31.3518 0.01895 31.391 0.00804 31.4302 0C31.605 0 31.7785 0.0585 31.9189 0.16472C32.1548 0.26776 32.365 0.41191 32.5435 0.5931L40.6003 8.8371C40.6778 8.91638 40.7505 9.00572 40.8163 9.1026C41.0875 9.29115 41.2151 9.62155 41.1412 9.94409C41.1541 10.0369 41.1611 10.1279 41.1619 10.2141V32.3892H39.5401V10.5894H32.5885C31.5027 10.5813 30.6193 9.69185 30.6193 8.60669V1.66859L8.60714 1.66861C8.39935 1.66861 8.25974 1.78941 8.25974 1.96919V6.16769V44.3889ZM32.2411 2.60459V8.60668C32.2402 8.80225 32.3928 8.96039 32.5885 8.96668H38.4475L32.2411 2.60459Z\"\n fill=\"#32BCF3\"\n />\n </svg>\n <p class=\"o-table__empty-text\">No data available</p>\n @if (showAdd) {\n <button class=\"o-table__empty-btn\" type=\"button\" (click)=\"onAddClick()\">\n {{ addLabel }}\n </button>\n }\n </div>\n }\n\n <!-- Pagination -->\n @if (rows && rows.length > 0) {\n <div class=\"o-table__pagination\">\n <p class=\"o-table__pagination-info\">\n Showing\n <span class=\"o-table__pagination-info--bold\">{{ pagination.startRow }}</span>\n to\n <span class=\"o-table__pagination-info--bold\">{{ pagination.endRow }}</span>\n of\n <span class=\"o-table__pagination-info--bold\">{{ pagination.total }}</span>\n results\n </p>\n\n <nav class=\"o-table__pagination-nav\" aria-label=\"Pagination\">\n <button\n class=\"o-table__page-btn o-table__page-btn--prev\"\n type=\"button\"\n [disabled]=\"pagination.page <= 1\"\n (click)=\"prevPage()\"\n aria-label=\"Previous page\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path\n fill-rule=\"evenodd\"\n d=\"M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n </button>\n\n @for (pageNum of pagination.pageLinks; track pageNum) {\n <button\n class=\"o-table__page-btn\"\n type=\"button\"\n [class.o-table__page-btn--active]=\"pageNum === pagination.page\"\n (click)=\"showPage(pageNum)\"\n [attr.aria-current]=\"pageNum === pagination.page ? 'page' : null\"\n >\n {{ pageNum }}\n </button>\n }\n\n <button\n class=\"o-table__page-btn o-table__page-btn--next\"\n type=\"button\"\n [disabled]=\"pagination.page >= pagination.total_pages\"\n (click)=\"nextPage()\"\n aria-label=\"Next page\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path\n fill-rule=\"evenodd\"\n d=\"M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n </button>\n </nav>\n </div>\n }\n</div>\n", styles: [".o-table-wrapper{display:flex;flex-direction:column;width:100%;border:1px solid #e2e8f0;border-radius:8px;overflow:hidden;background:#fff}.o-table-scroll{overflow-x:auto;overflow-y:auto}.o-table{width:100%;border-collapse:collapse;table-layout:fixed;box-sizing:border-box}.o-table__head{position:sticky;top:0;z-index:10;background:#d9dbde}.o-table__th{padding:10px 14px;font-size:.75rem;font-weight:600;color:#374151;text-align:left;white-space:nowrap;min-width:10rem;border-right:1px solid #c9cdd3;border-bottom:2px solid #c9cdd3;box-sizing:border-box}.o-table__th:last-child{border-right:none}.o-table__th--checkbox{width:40px;min-width:40px;max-width:40px;text-align:center;padding:10px 8px}.o-table__th--actions{width:128px;min-width:128px;max-width:128px}.o-table__body{background:#fff}.o-table__row{cursor:pointer;transition:background .12s;border-bottom:1px solid #f1f5f9}.o-table__row:hover{background:#f8fafc}.o-table__row--selected{background:#eff6ff}.o-table__row--selected:hover{background:#dbeafe}.o-table__td{padding:10px 14px;font-size:.82rem;color:#374151;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;border-right:1px solid #f1f5f9;box-sizing:border-box}.o-table__td:last-child{border-right:none}.o-table__td--checkbox{width:40px;min-width:40px;max-width:40px;text-align:center;padding:10px 8px}.o-table__td--actions{width:128px;min-width:128px;max-width:128px}.o-table__checkbox{width:16px;height:16px;cursor:pointer;accent-color:#15104e}.o-status-badge{display:inline-flex;align-items:center;padding:2px 10px;border-radius:99px;font-size:.72rem;font-weight:600;text-transform:capitalize;white-space:nowrap}.o-status-badge--active{background:#dcfce7;color:#166534}.o-status-badge--inactive{background:#f1f5f9;color:#64748b}.o-status-badge--draft{background:#fef9c3;color:#854d0e}.o-status-badge--progress{background:#fef3c7;color:#92400e}.o-status-badge--rejected{background:#fee2e2;color:#991b1b}.o-table__empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:48px 24px;gap:12px}.o-table__empty-icon{opacity:.6}.o-table__empty-text{margin:0;font-size:.875rem;color:#64748b}.o-table__empty-btn{padding:8px 24px;background:#ff8200;color:#fff;border:none;border-radius:6px;font-size:.85rem;font-weight:600;cursor:pointer;transition:background .15s}.o-table__empty-btn:hover{background:#e67300}.o-table__pagination{display:flex;align-items:center;justify-content:space-between;padding:10px 16px;border-top:1px solid #e2e8f0;background:#f9fafb;flex-wrap:wrap;gap:8px}.o-table__pagination-info{font-size:.75rem;color:#6b7280;margin:0}.o-table__pagination-info--bold{font-weight:600;color:#374151}.o-table__pagination-nav{display:flex;align-items:center;gap:2px}.o-table__page-btn{display:inline-flex;align-items:center;justify-content:center;min-width:32px;height:32px;padding:0 8px;border:1px solid #e2e8f0;background:#fff;color:#374151;font-size:.78rem;cursor:pointer;transition:background .12s,border-color .12s;border-radius:4px}.o-table__page-btn:hover:not(:disabled){background:#f1f5f9;border-color:#cbd5e1}.o-table__page-btn:disabled{opacity:.4;cursor:default}.o-table__page-btn--active{background:#15104e;border-color:#15104e;color:#fff;font-weight:600}.o-table__page-btn--active:hover{background:#15104e}.o-table__page-btn--prev,.o-table__page-btn--next{padding:0 6px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
1665
+ }
1666
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OTableComponent, decorators: [{
1667
+ type: Component,
1668
+ args: [{ selector: 'o-table', standalone: true, imports: [CommonModule], template: "<div class=\"o-table-wrapper\">\n <div class=\"o-table-scroll\">\n <table class=\"o-table\">\n <thead class=\"o-table__head\">\n <tr>\n <!-- Checkbox column -->\n <th class=\"o-table__th o-table__th--checkbox\">\n <input\n type=\"checkbox\"\n class=\"o-table__checkbox\"\n [checked]=\"allSelected\"\n (change)=\"toggleAll($event)\"\n aria-label=\"Select all rows\"\n />\n </th>\n\n <!-- Actions column -->\n @if (showActions) {\n <th class=\"o-table__th o-table__th--actions\">Actions</th>\n }\n\n <!-- Data columns -->\n @for (col of columns; track col.field) {\n <th\n class=\"o-table__th\"\n [style.min-width]=\"columnWidth(col)\"\n >\n {{ col.header }}\n </th>\n }\n </tr>\n </thead>\n\n <tbody class=\"o-table__body\">\n @if (rows && rows.length > 0) {\n @for (row of rows; track $index) {\n <tr\n class=\"o-table__row\"\n [class.o-table__row--selected]=\"isSelected(row)\"\n (click)=\"onRowClick(row)\"\n >\n <!-- Checkbox cell -->\n <td class=\"o-table__td o-table__td--checkbox\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"checkbox\"\n class=\"o-table__checkbox\"\n [checked]=\"isSelected(row)\"\n (change)=\"toggleRow(row)\"\n aria-label=\"Select row\"\n />\n </td>\n\n <!-- Actions cell -->\n @if (showActions) {\n <td class=\"o-table__td o-table__td--actions\" (click)=\"$event.stopPropagation()\">\n <ng-content select=\"[slot=actions]\"></ng-content>\n </td>\n }\n\n <!-- Data cells -->\n @for (col of columns; track col.field) {\n <td class=\"o-table__td\">\n @if (col.pipe === 'status') {\n <span\n class=\"o-status-badge\"\n [ngClass]=\"statusClass(getCellValue(row, col.field))\"\n >\n {{ getCellValue(row, col.field) }}\n </span>\n } @else if (col.pipe === 'date') {\n {{ formatDate(getCellValue(row, col.field)) }}\n } @else {\n {{ getCellValue(row, col.field) }}\n }\n </td>\n }\n </tr>\n }\n }\n </tbody>\n </table>\n </div>\n\n <!-- Empty state -->\n @if (!rows || rows.length === 0) {\n <div class=\"o-table__empty\">\n <svg\n class=\"o-table__empty-icon\"\n width=\"58\"\n height=\"59\"\n viewBox=\"0 0 58 59\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M8.0001 58.389H7.9991H3.9996C1.79421 58.389 0 56.5944 0 54.3885V29.3883C0 27.1829 1.79421 25.3887 3.9996 25.3887V27.3885C3.46577 27.3894 2.96412 27.5976 2.58606 27.9757C2.20801 28.3537 1.9998 28.8554 1.9998 29.3883V54.3885C1.9998 55.4912 2.89691 56.3883 3.9996 56.3883H8.0001V58.388V58.389ZM50.0004 38.3892H49.9994H47.9997V29.3883C47.9997 28.2856 47.1026 27.3885 45.9999 27.3885H44.0001V25.3887L45.9999 25.3887C48.2058 25.3887 50.0004 27.1829 50.0004 29.3883V38.3882V38.3892Z\"\n fill=\"#14034E\"\n />\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M8.25974 44.3889H8.25774V44.3879L6.63794 44.3889V3.68279V1.96918C6.63974 1.44823 6.84654 0.96047 7.22024 0.59578C7.58294 0.24118 8.06238 0.0459 8.57024 0.0459H8.60714H31.1341C31.161 0.0459 31.1899 0.04192 31.213 0.03873C31.2393 0.03511 31.2657 0.0315 31.2916 0.0315C31.2988 0.0315 31.3069 0.0315 31.3141 0.03239C31.3518 0.01895 31.391 0.00804 31.4302 0C31.605 0 31.7785 0.0585 31.9189 0.16472C32.1548 0.26776 32.365 0.41191 32.5435 0.5931L40.6003 8.8371C40.6778 8.91638 40.7505 9.00572 40.8163 9.1026C41.0875 9.29115 41.2151 9.62155 41.1412 9.94409C41.1541 10.0369 41.1611 10.1279 41.1619 10.2141V32.3892H39.5401V10.5894H32.5885C31.5027 10.5813 30.6193 9.69185 30.6193 8.60669V1.66859L8.60714 1.66861C8.39935 1.66861 8.25974 1.78941 8.25974 1.96919V6.16769V44.3889ZM32.2411 2.60459V8.60668C32.2402 8.80225 32.3928 8.96039 32.5885 8.96668H38.4475L32.2411 2.60459Z\"\n fill=\"#32BCF3\"\n />\n </svg>\n <p class=\"o-table__empty-text\">No data available</p>\n @if (showAdd) {\n <button class=\"o-table__empty-btn\" type=\"button\" (click)=\"onAddClick()\">\n {{ addLabel }}\n </button>\n }\n </div>\n }\n\n <!-- Pagination -->\n @if (rows && rows.length > 0) {\n <div class=\"o-table__pagination\">\n <p class=\"o-table__pagination-info\">\n Showing\n <span class=\"o-table__pagination-info--bold\">{{ pagination.startRow }}</span>\n to\n <span class=\"o-table__pagination-info--bold\">{{ pagination.endRow }}</span>\n of\n <span class=\"o-table__pagination-info--bold\">{{ pagination.total }}</span>\n results\n </p>\n\n <nav class=\"o-table__pagination-nav\" aria-label=\"Pagination\">\n <button\n class=\"o-table__page-btn o-table__page-btn--prev\"\n type=\"button\"\n [disabled]=\"pagination.page <= 1\"\n (click)=\"prevPage()\"\n aria-label=\"Previous page\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path\n fill-rule=\"evenodd\"\n d=\"M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n </button>\n\n @for (pageNum of pagination.pageLinks; track pageNum) {\n <button\n class=\"o-table__page-btn\"\n type=\"button\"\n [class.o-table__page-btn--active]=\"pageNum === pagination.page\"\n (click)=\"showPage(pageNum)\"\n [attr.aria-current]=\"pageNum === pagination.page ? 'page' : null\"\n >\n {{ pageNum }}\n </button>\n }\n\n <button\n class=\"o-table__page-btn o-table__page-btn--next\"\n type=\"button\"\n [disabled]=\"pagination.page >= pagination.total_pages\"\n (click)=\"nextPage()\"\n aria-label=\"Next page\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path\n fill-rule=\"evenodd\"\n d=\"M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n </button>\n </nav>\n </div>\n }\n</div>\n", styles: [".o-table-wrapper{display:flex;flex-direction:column;width:100%;border:1px solid #e2e8f0;border-radius:8px;overflow:hidden;background:#fff}.o-table-scroll{overflow-x:auto;overflow-y:auto}.o-table{width:100%;border-collapse:collapse;table-layout:fixed;box-sizing:border-box}.o-table__head{position:sticky;top:0;z-index:10;background:#d9dbde}.o-table__th{padding:10px 14px;font-size:.75rem;font-weight:600;color:#374151;text-align:left;white-space:nowrap;min-width:10rem;border-right:1px solid #c9cdd3;border-bottom:2px solid #c9cdd3;box-sizing:border-box}.o-table__th:last-child{border-right:none}.o-table__th--checkbox{width:40px;min-width:40px;max-width:40px;text-align:center;padding:10px 8px}.o-table__th--actions{width:128px;min-width:128px;max-width:128px}.o-table__body{background:#fff}.o-table__row{cursor:pointer;transition:background .12s;border-bottom:1px solid #f1f5f9}.o-table__row:hover{background:#f8fafc}.o-table__row--selected{background:#eff6ff}.o-table__row--selected:hover{background:#dbeafe}.o-table__td{padding:10px 14px;font-size:.82rem;color:#374151;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;border-right:1px solid #f1f5f9;box-sizing:border-box}.o-table__td:last-child{border-right:none}.o-table__td--checkbox{width:40px;min-width:40px;max-width:40px;text-align:center;padding:10px 8px}.o-table__td--actions{width:128px;min-width:128px;max-width:128px}.o-table__checkbox{width:16px;height:16px;cursor:pointer;accent-color:#15104e}.o-status-badge{display:inline-flex;align-items:center;padding:2px 10px;border-radius:99px;font-size:.72rem;font-weight:600;text-transform:capitalize;white-space:nowrap}.o-status-badge--active{background:#dcfce7;color:#166534}.o-status-badge--inactive{background:#f1f5f9;color:#64748b}.o-status-badge--draft{background:#fef9c3;color:#854d0e}.o-status-badge--progress{background:#fef3c7;color:#92400e}.o-status-badge--rejected{background:#fee2e2;color:#991b1b}.o-table__empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:48px 24px;gap:12px}.o-table__empty-icon{opacity:.6}.o-table__empty-text{margin:0;font-size:.875rem;color:#64748b}.o-table__empty-btn{padding:8px 24px;background:#ff8200;color:#fff;border:none;border-radius:6px;font-size:.85rem;font-weight:600;cursor:pointer;transition:background .15s}.o-table__empty-btn:hover{background:#e67300}.o-table__pagination{display:flex;align-items:center;justify-content:space-between;padding:10px 16px;border-top:1px solid #e2e8f0;background:#f9fafb;flex-wrap:wrap;gap:8px}.o-table__pagination-info{font-size:.75rem;color:#6b7280;margin:0}.o-table__pagination-info--bold{font-weight:600;color:#374151}.o-table__pagination-nav{display:flex;align-items:center;gap:2px}.o-table__page-btn{display:inline-flex;align-items:center;justify-content:center;min-width:32px;height:32px;padding:0 8px;border:1px solid #e2e8f0;background:#fff;color:#374151;font-size:.78rem;cursor:pointer;transition:background .12s,border-color .12s;border-radius:4px}.o-table__page-btn:hover:not(:disabled){background:#f1f5f9;border-color:#cbd5e1}.o-table__page-btn:disabled{opacity:.4;cursor:default}.o-table__page-btn--active{background:#15104e;border-color:#15104e;color:#fff;font-weight:600}.o-table__page-btn--active:hover{background:#15104e}.o-table__page-btn--prev,.o-table__page-btn--next{padding:0 6px}\n"] }]
1669
+ }], propDecorators: { columns: [{
1670
+ type: Input
1671
+ }], rows: [{
1672
+ type: Input
1673
+ }], showActions: [{
1674
+ type: Input
1675
+ }], showAdd: [{
1676
+ type: Input
1677
+ }], addLabel: [{
1678
+ type: Input
1679
+ }], pagination: [{
1680
+ type: Input
1681
+ }], openForm: [{
1682
+ type: Output
1683
+ }], paginationIndex: [{
1684
+ type: Output
1685
+ }], rowSelect: [{
1686
+ type: Output
1687
+ }] } });
1688
+
1689
+ class OToggleButtonComponent {
1690
+ elements = [];
1691
+ selectedValue;
1692
+ toggleChange = new EventEmitter();
1693
+ activeIndex = 0;
1694
+ ngOnChanges(changes) {
1695
+ if (changes['selectedValue'] || changes['elements']) {
1696
+ const idx = this.elements.findIndex(e => e.value === this.selectedValue);
1697
+ this.activeIndex = idx >= 0 ? idx : 0;
1698
+ }
1699
+ }
1700
+ select(index, element) {
1701
+ this.activeIndex = index;
1702
+ this.toggleChange.emit(element);
1703
+ }
1704
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OToggleButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1705
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: OToggleButtonComponent, isStandalone: true, selector: "o-toggle-button", inputs: { elements: "elements", selectedValue: "selectedValue" }, outputs: { toggleChange: "toggleChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"o-toggle-button\" role=\"group\" aria-label=\"Toggle options\">\n @for (element of elements; track element.value; let i = $index) {\n <button\n type=\"button\"\n class=\"o-toggle-button__item\"\n [class.o-toggle-button__item--active]=\"i === activeIndex\"\n (click)=\"select(i, element)\"\n [attr.aria-pressed]=\"i === activeIndex\"\n >\n {{ element.label }}\n </button>\n }\n</div>\n", styles: [".o-toggle-button{display:inline-flex;align-items:center}.o-toggle-button__item{display:inline-flex;align-items:center;justify-content:center;height:40px;min-width:100px;padding:0 16px;font-size:.82rem;font-weight:500;color:#374151;background:#fff;border:1px solid #9ca3af;cursor:pointer;transition:background .12s,color .12s,border-color .12s;white-space:nowrap;margin-right:-1px}.o-toggle-button__item:first-child{border-radius:6px 0 0 6px}.o-toggle-button__item:last-child{border-radius:0 6px 6px 0;margin-right:0}.o-toggle-button__item:only-child{border-radius:6px}.o-toggle-button__item:hover:not(.o-toggle-button__item--active){background:#f3f4f6;border-color:#6b7280;z-index:1}.o-toggle-button__item--active{background:#1d4ed8;border-color:#1d4ed8;color:#fff;z-index:2}.o-toggle-button__item--active:hover{background:#1e40af;border-color:#1e40af}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
1706
+ }
1707
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OToggleButtonComponent, decorators: [{
1708
+ type: Component,
1709
+ args: [{ selector: 'o-toggle-button', standalone: true, imports: [CommonModule], template: "<div class=\"o-toggle-button\" role=\"group\" aria-label=\"Toggle options\">\n @for (element of elements; track element.value; let i = $index) {\n <button\n type=\"button\"\n class=\"o-toggle-button__item\"\n [class.o-toggle-button__item--active]=\"i === activeIndex\"\n (click)=\"select(i, element)\"\n [attr.aria-pressed]=\"i === activeIndex\"\n >\n {{ element.label }}\n </button>\n }\n</div>\n", styles: [".o-toggle-button{display:inline-flex;align-items:center}.o-toggle-button__item{display:inline-flex;align-items:center;justify-content:center;height:40px;min-width:100px;padding:0 16px;font-size:.82rem;font-weight:500;color:#374151;background:#fff;border:1px solid #9ca3af;cursor:pointer;transition:background .12s,color .12s,border-color .12s;white-space:nowrap;margin-right:-1px}.o-toggle-button__item:first-child{border-radius:6px 0 0 6px}.o-toggle-button__item:last-child{border-radius:0 6px 6px 0;margin-right:0}.o-toggle-button__item:only-child{border-radius:6px}.o-toggle-button__item:hover:not(.o-toggle-button__item--active){background:#f3f4f6;border-color:#6b7280;z-index:1}.o-toggle-button__item--active{background:#1d4ed8;border-color:#1d4ed8;color:#fff;z-index:2}.o-toggle-button__item--active:hover{background:#1e40af;border-color:#1e40af}\n"] }]
1710
+ }], propDecorators: { elements: [{
1711
+ type: Input
1712
+ }], selectedValue: [{
1713
+ type: Input
1714
+ }], toggleChange: [{
1715
+ type: Output
1716
+ }] } });
1717
+
1718
+ class ODialogComponent {
1719
+ open = false;
1720
+ title = '';
1721
+ width = '600px';
1722
+ valid = true;
1723
+ showFooter = true;
1724
+ submitLabel = 'Submit';
1725
+ onClose = new EventEmitter();
1726
+ onSubmit = new EventEmitter();
1727
+ close() {
1728
+ this.onClose.emit();
1729
+ }
1730
+ submit() {
1731
+ if (this.valid) {
1732
+ this.onSubmit.emit();
1733
+ }
1734
+ }
1735
+ onOverlayClick(event) {
1736
+ if (event.target.classList.contains('o-dialog__overlay')) {
1737
+ this.close();
1738
+ }
1739
+ }
1740
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ODialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1741
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ODialogComponent, isStandalone: true, selector: "o-dialog", inputs: { open: "open", title: "title", width: "width", valid: "valid", showFooter: "showFooter", submitLabel: "submitLabel" }, outputs: { onClose: "onClose", onSubmit: "onSubmit" }, ngImport: i0, template: "@if (open) {\n <div\n class=\"o-dialog__overlay\"\n role=\"dialog\"\n [attr.aria-label]=\"title\"\n aria-modal=\"true\"\n (click)=\"onOverlayClick($event)\"\n >\n <div class=\"o-dialog__card\" [style.width]=\"width\">\n <!-- Header -->\n <div class=\"o-dialog__header\">\n <h3 class=\"o-dialog__title\">{{ title }}</h3>\n <button\n type=\"button\"\n class=\"o-dialog__close-btn\"\n (click)=\"close()\"\n aria-label=\"Close dialog\"\n >\n <svg width=\"22\" height=\"22\" viewBox=\"0 0 28 28\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path\n d=\"M15.6449 13.9999L20.6616 8.99489C20.8813 8.7752 21.0047 8.47724 21.0047 8.16656C21.0047 7.85587 20.8813 7.55791 20.6616 7.33822C20.4419 7.11854 20.1439 6.99512 19.8333 6.99512C19.5226 6.99512 19.2246 7.11854 19.0049 7.33822L13.9999 12.3549L8.99493 7.33822C8.77524 7.11854 8.47728 6.99512 8.1666 6.99512C7.85591 6.99512 7.55795 7.11854 7.33826 7.33822C7.11857 7.55791 6.99516 7.85587 6.99516 8.16656C6.99516 8.47724 7.11857 8.7752 7.33826 8.99489L12.3549 13.9999L7.33826 19.0049C7.22891 19.1133 7.14212 19.2424 7.08289 19.3846C7.02366 19.5267 6.99316 19.6792 6.99316 19.8332C6.99316 19.9872 7.02366 20.1397 7.08289 20.2819C7.14212 20.4241 7.22891 20.5531 7.33826 20.6616C7.44672 20.7709 7.57575 20.8577 7.71792 20.9169C7.86009 20.9762 8.01258 21.0067 8.1666 21.0067C8.32061 21.0067 8.4731 20.9762 8.61527 20.9169C8.75744 20.8577 8.88647 20.7709 8.99493 20.6616L13.9999 15.6449L19.0049 20.6616C19.1134 20.7709 19.2424 20.8577 19.3846 20.9169C19.5268 20.9762 19.6792 21.0067 19.8333 21.0067C19.9873 21.0067 20.1398 20.9762 20.2819 20.9169C20.4241 20.8577 20.5531 20.7709 20.6616 20.6616C20.7709 20.5531 20.8577 20.4241 20.917 20.2819C20.9762 20.1397 21.0067 19.9872 21.0067 19.8332C21.0067 19.6792 20.9762 19.5267 20.917 19.3846C20.8577 19.2424 20.7709 19.1133 20.6616 19.0049L15.6449 13.9999Z\"\n fill=\"#374151\"\n />\n </svg>\n </button>\n </div>\n\n <!-- Body -->\n <div class=\"o-dialog__body\">\n <ng-content></ng-content>\n </div>\n\n <!-- Footer -->\n @if (showFooter) {\n <div class=\"o-dialog__footer\">\n <button type=\"button\" class=\"o-dialog__btn o-dialog__btn--close\" (click)=\"close()\">\n Close\n </button>\n <button\n type=\"button\"\n class=\"o-dialog__btn o-dialog__btn--submit\"\n [disabled]=\"!valid\"\n [class.o-dialog__btn--submit-disabled]=\"!valid\"\n (click)=\"submit()\"\n >\n {{ submitLabel }}\n </button>\n </div>\n }\n </div>\n </div>\n}\n", styles: [".o-dialog__overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:#00000080;z-index:1000;padding:24px;box-sizing:border-box}.o-dialog__card{position:relative;display:flex;flex-direction:column;background:#fff;border-radius:10px;box-shadow:0 20px 60px #0003;max-height:calc(100vh - 48px);overflow:hidden;z-index:1001}.o-dialog__header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid #e2e8f0;flex-shrink:0}.o-dialog__title{margin:0;font-size:1.1rem;font-weight:600;color:#15104e;line-height:1.3}.o-dialog__close-btn{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border:none;background:none;cursor:pointer;border-radius:6px;color:#374151;flex-shrink:0;transition:background .12s}.o-dialog__close-btn:hover{background:#f1f5f9}.o-dialog__body{flex:1;overflow-y:auto;padding:24px}.o-dialog__footer{display:flex;align-items:center;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid #e2e8f0;flex-shrink:0}.o-dialog__btn{padding:8px 24px;border-radius:6px;font-size:.875rem;font-weight:500;cursor:pointer;transition:background .12s,border-color .12s;border:1px solid transparent}.o-dialog__btn--close{background:#f1f5f9;color:#374151;border-color:#e2e8f0}.o-dialog__btn--close:hover{background:#e2e8f0}.o-dialog__btn--submit{background:#ff8200;color:#fff;border-color:#ff8200}.o-dialog__btn--submit:hover:not(:disabled){background:#e67300;border-color:#e67300}.o-dialog__btn--submit:disabled,.o-dialog__btn--submit--submit-disabled{background:#fbb47a;border-color:#fbb47a;cursor:not-allowed}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
1742
+ }
1743
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ODialogComponent, decorators: [{
1744
+ type: Component,
1745
+ args: [{ selector: 'o-dialog', standalone: true, imports: [CommonModule], template: "@if (open) {\n <div\n class=\"o-dialog__overlay\"\n role=\"dialog\"\n [attr.aria-label]=\"title\"\n aria-modal=\"true\"\n (click)=\"onOverlayClick($event)\"\n >\n <div class=\"o-dialog__card\" [style.width]=\"width\">\n <!-- Header -->\n <div class=\"o-dialog__header\">\n <h3 class=\"o-dialog__title\">{{ title }}</h3>\n <button\n type=\"button\"\n class=\"o-dialog__close-btn\"\n (click)=\"close()\"\n aria-label=\"Close dialog\"\n >\n <svg width=\"22\" height=\"22\" viewBox=\"0 0 28 28\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path\n d=\"M15.6449 13.9999L20.6616 8.99489C20.8813 8.7752 21.0047 8.47724 21.0047 8.16656C21.0047 7.85587 20.8813 7.55791 20.6616 7.33822C20.4419 7.11854 20.1439 6.99512 19.8333 6.99512C19.5226 6.99512 19.2246 7.11854 19.0049 7.33822L13.9999 12.3549L8.99493 7.33822C8.77524 7.11854 8.47728 6.99512 8.1666 6.99512C7.85591 6.99512 7.55795 7.11854 7.33826 7.33822C7.11857 7.55791 6.99516 7.85587 6.99516 8.16656C6.99516 8.47724 7.11857 8.7752 7.33826 8.99489L12.3549 13.9999L7.33826 19.0049C7.22891 19.1133 7.14212 19.2424 7.08289 19.3846C7.02366 19.5267 6.99316 19.6792 6.99316 19.8332C6.99316 19.9872 7.02366 20.1397 7.08289 20.2819C7.14212 20.4241 7.22891 20.5531 7.33826 20.6616C7.44672 20.7709 7.57575 20.8577 7.71792 20.9169C7.86009 20.9762 8.01258 21.0067 8.1666 21.0067C8.32061 21.0067 8.4731 20.9762 8.61527 20.9169C8.75744 20.8577 8.88647 20.7709 8.99493 20.6616L13.9999 15.6449L19.0049 20.6616C19.1134 20.7709 19.2424 20.8577 19.3846 20.9169C19.5268 20.9762 19.6792 21.0067 19.8333 21.0067C19.9873 21.0067 20.1398 20.9762 20.2819 20.9169C20.4241 20.8577 20.5531 20.7709 20.6616 20.6616C20.7709 20.5531 20.8577 20.4241 20.917 20.2819C20.9762 20.1397 21.0067 19.9872 21.0067 19.8332C21.0067 19.6792 20.9762 19.5267 20.917 19.3846C20.8577 19.2424 20.7709 19.1133 20.6616 19.0049L15.6449 13.9999Z\"\n fill=\"#374151\"\n />\n </svg>\n </button>\n </div>\n\n <!-- Body -->\n <div class=\"o-dialog__body\">\n <ng-content></ng-content>\n </div>\n\n <!-- Footer -->\n @if (showFooter) {\n <div class=\"o-dialog__footer\">\n <button type=\"button\" class=\"o-dialog__btn o-dialog__btn--close\" (click)=\"close()\">\n Close\n </button>\n <button\n type=\"button\"\n class=\"o-dialog__btn o-dialog__btn--submit\"\n [disabled]=\"!valid\"\n [class.o-dialog__btn--submit-disabled]=\"!valid\"\n (click)=\"submit()\"\n >\n {{ submitLabel }}\n </button>\n </div>\n }\n </div>\n </div>\n}\n", styles: [".o-dialog__overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:#00000080;z-index:1000;padding:24px;box-sizing:border-box}.o-dialog__card{position:relative;display:flex;flex-direction:column;background:#fff;border-radius:10px;box-shadow:0 20px 60px #0003;max-height:calc(100vh - 48px);overflow:hidden;z-index:1001}.o-dialog__header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid #e2e8f0;flex-shrink:0}.o-dialog__title{margin:0;font-size:1.1rem;font-weight:600;color:#15104e;line-height:1.3}.o-dialog__close-btn{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border:none;background:none;cursor:pointer;border-radius:6px;color:#374151;flex-shrink:0;transition:background .12s}.o-dialog__close-btn:hover{background:#f1f5f9}.o-dialog__body{flex:1;overflow-y:auto;padding:24px}.o-dialog__footer{display:flex;align-items:center;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid #e2e8f0;flex-shrink:0}.o-dialog__btn{padding:8px 24px;border-radius:6px;font-size:.875rem;font-weight:500;cursor:pointer;transition:background .12s,border-color .12s;border:1px solid transparent}.o-dialog__btn--close{background:#f1f5f9;color:#374151;border-color:#e2e8f0}.o-dialog__btn--close:hover{background:#e2e8f0}.o-dialog__btn--submit{background:#ff8200;color:#fff;border-color:#ff8200}.o-dialog__btn--submit:hover:not(:disabled){background:#e67300;border-color:#e67300}.o-dialog__btn--submit:disabled,.o-dialog__btn--submit--submit-disabled{background:#fbb47a;border-color:#fbb47a;cursor:not-allowed}\n"] }]
1746
+ }], propDecorators: { open: [{
1747
+ type: Input
1748
+ }], title: [{
1749
+ type: Input
1750
+ }], width: [{
1751
+ type: Input
1752
+ }], valid: [{
1753
+ type: Input
1754
+ }], showFooter: [{
1755
+ type: Input
1756
+ }], submitLabel: [{
1757
+ type: Input
1758
+ }], onClose: [{
1759
+ type: Output
1760
+ }], onSubmit: [{
1761
+ type: Output
1762
+ }] } });
1763
+
1764
+ class ODotsActionComponent {
1765
+ el;
1766
+ items = [];
1767
+ open = false;
1768
+ itemClick = new EventEmitter();
1769
+ toggled = new EventEmitter();
1770
+ constructor(el) {
1771
+ this.el = el;
1772
+ }
1773
+ toggle(event) {
1774
+ event.stopPropagation();
1775
+ this.open = !this.open;
1776
+ this.toggled.emit(this.open);
1777
+ }
1778
+ onItemClick(event, item) {
1779
+ event.stopPropagation();
1780
+ this.itemClick.emit(item);
1781
+ this.open = false;
1782
+ this.toggled.emit(false);
1783
+ }
1784
+ onDocumentClick(event) {
1785
+ if (!this.el.nativeElement.contains(event.target)) {
1786
+ if (this.open) {
1787
+ this.open = false;
1788
+ this.toggled.emit(false);
1789
+ }
1790
+ }
1791
+ }
1792
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ODotsActionComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
1793
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ODotsActionComponent, isStandalone: true, selector: "o-dots-action", inputs: { items: "items", open: "open" }, outputs: { itemClick: "itemClick", toggled: "toggled" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, ngImport: i0, template: "<div class=\"o-dots-action\">\n <button\n type=\"button\"\n class=\"o-dots-action__trigger\"\n (click)=\"toggle($event)\"\n [class.o-dots-action__trigger--open]=\"open\"\n aria-label=\"Actions menu\"\n [attr.aria-expanded]=\"open\"\n >\n <svg width=\"4\" height=\"16\" viewBox=\"0 0 4 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <circle cx=\"2\" cy=\"2\" r=\"2\" fill=\"#6B7280\"/>\n <circle cx=\"2\" cy=\"9\" r=\"2\" fill=\"#6B7280\"/>\n <circle cx=\"2\" cy=\"16\" r=\"2\" fill=\"#6B7280\"/>\n </svg>\n </button>\n\n @if (open) {\n <div class=\"o-dots-action__menu\" role=\"menu\">\n @for (item of items; track item.action) {\n <button\n type=\"button\"\n class=\"o-dots-action__item\"\n [class.o-dots-action__item--danger]=\"item.isDanger\"\n (click)=\"onItemClick($event, item)\"\n role=\"menuitem\"\n >\n {{ item.label }}\n </button>\n }\n </div>\n }\n</div>\n", styles: [".o-dots-action{position:relative;display:inline-flex}.o-dots-action__trigger{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:none;background:none;border-radius:6px;cursor:pointer;transition:background .12s}.o-dots-action__trigger:hover,.o-dots-action__trigger--open{background:#f1f5f9}.o-dots-action__menu{position:absolute;top:calc(100% + 4px);right:0;z-index:50;min-width:160px;background:#fff;border:1px solid #e2e8f0;border-radius:8px;box-shadow:0 8px 24px #0000001f;overflow:hidden;padding:4px 0}.o-dots-action__item{display:block;width:100%;padding:8px 16px;font-size:.8rem;font-weight:400;color:#374151;background:none;border:none;text-align:left;cursor:pointer;white-space:nowrap;transition:background .1s}.o-dots-action__item:hover{background:#f8fafc;color:#15104e}.o-dots-action__item--danger{color:#dc2626}.o-dots-action__item--danger:hover{background:#fef2f2;color:#b91c1c}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
1794
+ }
1795
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ODotsActionComponent, decorators: [{
1796
+ type: Component,
1797
+ args: [{ selector: 'o-dots-action', standalone: true, imports: [CommonModule], template: "<div class=\"o-dots-action\">\n <button\n type=\"button\"\n class=\"o-dots-action__trigger\"\n (click)=\"toggle($event)\"\n [class.o-dots-action__trigger--open]=\"open\"\n aria-label=\"Actions menu\"\n [attr.aria-expanded]=\"open\"\n >\n <svg width=\"4\" height=\"16\" viewBox=\"0 0 4 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <circle cx=\"2\" cy=\"2\" r=\"2\" fill=\"#6B7280\"/>\n <circle cx=\"2\" cy=\"9\" r=\"2\" fill=\"#6B7280\"/>\n <circle cx=\"2\" cy=\"16\" r=\"2\" fill=\"#6B7280\"/>\n </svg>\n </button>\n\n @if (open) {\n <div class=\"o-dots-action__menu\" role=\"menu\">\n @for (item of items; track item.action) {\n <button\n type=\"button\"\n class=\"o-dots-action__item\"\n [class.o-dots-action__item--danger]=\"item.isDanger\"\n (click)=\"onItemClick($event, item)\"\n role=\"menuitem\"\n >\n {{ item.label }}\n </button>\n }\n </div>\n }\n</div>\n", styles: [".o-dots-action{position:relative;display:inline-flex}.o-dots-action__trigger{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:none;background:none;border-radius:6px;cursor:pointer;transition:background .12s}.o-dots-action__trigger:hover,.o-dots-action__trigger--open{background:#f1f5f9}.o-dots-action__menu{position:absolute;top:calc(100% + 4px);right:0;z-index:50;min-width:160px;background:#fff;border:1px solid #e2e8f0;border-radius:8px;box-shadow:0 8px 24px #0000001f;overflow:hidden;padding:4px 0}.o-dots-action__item{display:block;width:100%;padding:8px 16px;font-size:.8rem;font-weight:400;color:#374151;background:none;border:none;text-align:left;cursor:pointer;white-space:nowrap;transition:background .1s}.o-dots-action__item:hover{background:#f8fafc;color:#15104e}.o-dots-action__item--danger{color:#dc2626}.o-dots-action__item--danger:hover{background:#fef2f2;color:#b91c1c}\n"] }]
1798
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { items: [{
1799
+ type: Input
1800
+ }], open: [{
1801
+ type: Input
1802
+ }], itemClick: [{
1803
+ type: Output
1804
+ }], toggled: [{
1805
+ type: Output
1806
+ }], onDocumentClick: [{
1807
+ type: HostListener,
1808
+ args: ['document:click', ['$event']]
1809
+ }] } });
1810
+
1811
+ class OToastService {
1812
+ _messages$ = new BehaviorSubject([]);
1813
+ get messages$() {
1814
+ return this._messages$.asObservable();
1815
+ }
1816
+ add(heading, message, type, autoHide = true, delay = 5000) {
1817
+ const id = `toast-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
1818
+ const toast = { id, heading, message, type, autoHide, delay };
1819
+ const current = this._messages$.getValue();
1820
+ this._messages$.next([...current, toast]);
1821
+ if (autoHide) {
1822
+ setTimeout(() => this.remove(id), delay);
1823
+ }
1824
+ }
1825
+ addSuccess(heading, message, autoHide = true, delay = 5000) {
1826
+ this.add(heading, message, 'success', autoHide, delay);
1827
+ }
1828
+ addError(heading, message, autoHide = true, delay = 5000) {
1829
+ this.add(heading, message, 'error', autoHide, delay);
1830
+ }
1831
+ addWarning(heading, message, autoHide = true, delay = 5000) {
1832
+ this.add(heading, message, 'warning', autoHide, delay);
1833
+ }
1834
+ addInfo(heading, message, autoHide = true, delay = 5000) {
1835
+ this.add(heading, message, 'info', autoHide, delay);
1836
+ }
1837
+ remove(id) {
1838
+ const current = this._messages$.getValue();
1839
+ this._messages$.next(current.filter(m => m.id !== id));
1840
+ }
1841
+ clear() {
1842
+ this._messages$.next([]);
1843
+ }
1844
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OToastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1845
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OToastService, providedIn: 'root' });
1846
+ }
1847
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OToastService, decorators: [{
1848
+ type: Injectable,
1849
+ args: [{ providedIn: 'root' }]
1850
+ }] });
1851
+
1852
+ class OToastComponent {
1853
+ toastService;
1854
+ cdr;
1855
+ messages = [];
1856
+ sub;
1857
+ constructor(toastService, cdr) {
1858
+ this.toastService = toastService;
1859
+ this.cdr = cdr;
1860
+ }
1861
+ ngOnInit() {
1862
+ this.sub = this.toastService.messages$.subscribe(msgs => {
1863
+ this.messages = msgs;
1864
+ this.cdr.markForCheck();
1865
+ });
1866
+ }
1867
+ ngOnDestroy() {
1868
+ this.sub?.unsubscribe();
1869
+ }
1870
+ remove(id) {
1871
+ this.toastService.remove(id);
1872
+ }
1873
+ typeColor(type) {
1874
+ switch (type) {
1875
+ case 'success': return '#16a34a';
1876
+ case 'error': return '#dc2626';
1877
+ case 'warning': return '#d97706';
1878
+ case 'info': return '#2563eb';
1879
+ default: return '#2563eb';
1880
+ }
1881
+ }
1882
+ typeLabel(type) {
1883
+ switch (type) {
1884
+ case 'success': return 'Success';
1885
+ case 'error': return 'Error';
1886
+ case 'warning': return 'Warning';
1887
+ case 'info': return 'Info';
1888
+ default: return '';
1889
+ }
1890
+ }
1891
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OToastComponent, deps: [{ token: OToastService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
1892
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: OToastComponent, isStandalone: true, selector: "o-toast", ngImport: i0, template: "<div class=\"o-toast-container\" aria-live=\"polite\" aria-atomic=\"false\">\n @for (msg of messages; track msg.id) {\n <div class=\"o-toast\" [class]=\"'o-toast--' + msg.type\" role=\"alert\">\n <!-- Type bar -->\n <div class=\"o-toast__bar\" [style.background]=\"typeColor(msg.type)\"></div>\n\n <div class=\"o-toast__content\">\n <!-- Icon area -->\n <div class=\"o-toast__icon-wrap\" [style.background]=\"typeColor(msg.type) + '18'\">\n @if (msg.type === 'success') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" [style.color]=\"typeColor(msg.type)\" aria-hidden=\"true\">\n <polyline points=\"20 6 9 17 4 12\"/>\n </svg>\n }\n @if (msg.type === 'error') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" [style.color]=\"typeColor(msg.type)\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/>\n <line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"/>\n <line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"/>\n </svg>\n }\n @if (msg.type === 'warning') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" [style.color]=\"typeColor(msg.type)\" aria-hidden=\"true\">\n <path d=\"M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z\"/>\n <line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/>\n <line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>\n </svg>\n }\n @if (msg.type === 'info') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" [style.color]=\"typeColor(msg.type)\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/>\n <line x1=\"12\" y1=\"16\" x2=\"12\" y2=\"12\"/>\n <line x1=\"12\" y1=\"8\" x2=\"12.01\" y2=\"8\"/>\n </svg>\n }\n </div>\n\n <!-- Text -->\n <div class=\"o-toast__text\">\n @if (msg.heading) {\n <p class=\"o-toast__heading\">{{ msg.heading }}</p>\n }\n <p class=\"o-toast__message\">{{ msg.message }}</p>\n </div>\n\n <!-- Close -->\n <button\n type=\"button\"\n class=\"o-toast__close\"\n (click)=\"remove(msg.id)\"\n aria-label=\"Dismiss notification\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\" width=\"12\" height=\"12\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z\"/>\n </svg>\n </button>\n </div>\n </div>\n }\n</div>\n", styles: ["@keyframes o-toast-slide-in{0%{transform:translate(110%);opacity:0}to{transform:translate(0);opacity:1}}.o-toast-container{position:fixed;top:20px;right:20px;width:360px;z-index:9999;display:flex;flex-direction:column;gap:10px;pointer-events:none}.o-toast{display:flex;flex-direction:column;background:#fff;border-radius:8px;box-shadow:0 8px 24px #00000024;overflow:hidden;pointer-events:all;animation:o-toast-slide-in .22s ease-out forwards}.o-toast__bar{height:4px;width:100%;flex-shrink:0}.o-toast__content{display:flex;align-items:flex-start;gap:12px;padding:14px 14px 14px 16px}.o-toast__icon-wrap{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:50%;flex-shrink:0;margin-top:1px}.o-toast__text{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.o-toast__heading{margin:0;font-size:.85rem;font-weight:600;color:#1e293b;line-height:1.3}.o-toast__message{margin:0;font-size:.8rem;color:#475569;line-height:1.4}.o-toast__close{display:flex;align-items:center;justify-content:center;width:24px;height:24px;flex-shrink:0;background:none;border:none;cursor:pointer;color:#94a3b8;border-radius:4px;margin-top:2px;transition:color .12s,background .12s}.o-toast__close:hover{color:#64748b;background:#f1f5f9}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1893
+ }
1894
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OToastComponent, decorators: [{
1895
+ type: Component,
1896
+ args: [{ selector: 'o-toast', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"o-toast-container\" aria-live=\"polite\" aria-atomic=\"false\">\n @for (msg of messages; track msg.id) {\n <div class=\"o-toast\" [class]=\"'o-toast--' + msg.type\" role=\"alert\">\n <!-- Type bar -->\n <div class=\"o-toast__bar\" [style.background]=\"typeColor(msg.type)\"></div>\n\n <div class=\"o-toast__content\">\n <!-- Icon area -->\n <div class=\"o-toast__icon-wrap\" [style.background]=\"typeColor(msg.type) + '18'\">\n @if (msg.type === 'success') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" [style.color]=\"typeColor(msg.type)\" aria-hidden=\"true\">\n <polyline points=\"20 6 9 17 4 12\"/>\n </svg>\n }\n @if (msg.type === 'error') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" [style.color]=\"typeColor(msg.type)\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/>\n <line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"/>\n <line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"/>\n </svg>\n }\n @if (msg.type === 'warning') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" [style.color]=\"typeColor(msg.type)\" aria-hidden=\"true\">\n <path d=\"M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z\"/>\n <line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/>\n <line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>\n </svg>\n }\n @if (msg.type === 'info') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" [style.color]=\"typeColor(msg.type)\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/>\n <line x1=\"12\" y1=\"16\" x2=\"12\" y2=\"12\"/>\n <line x1=\"12\" y1=\"8\" x2=\"12.01\" y2=\"8\"/>\n </svg>\n }\n </div>\n\n <!-- Text -->\n <div class=\"o-toast__text\">\n @if (msg.heading) {\n <p class=\"o-toast__heading\">{{ msg.heading }}</p>\n }\n <p class=\"o-toast__message\">{{ msg.message }}</p>\n </div>\n\n <!-- Close -->\n <button\n type=\"button\"\n class=\"o-toast__close\"\n (click)=\"remove(msg.id)\"\n aria-label=\"Dismiss notification\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\" width=\"12\" height=\"12\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z\"/>\n </svg>\n </button>\n </div>\n </div>\n }\n</div>\n", styles: ["@keyframes o-toast-slide-in{0%{transform:translate(110%);opacity:0}to{transform:translate(0);opacity:1}}.o-toast-container{position:fixed;top:20px;right:20px;width:360px;z-index:9999;display:flex;flex-direction:column;gap:10px;pointer-events:none}.o-toast{display:flex;flex-direction:column;background:#fff;border-radius:8px;box-shadow:0 8px 24px #00000024;overflow:hidden;pointer-events:all;animation:o-toast-slide-in .22s ease-out forwards}.o-toast__bar{height:4px;width:100%;flex-shrink:0}.o-toast__content{display:flex;align-items:flex-start;gap:12px;padding:14px 14px 14px 16px}.o-toast__icon-wrap{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:50%;flex-shrink:0;margin-top:1px}.o-toast__text{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.o-toast__heading{margin:0;font-size:.85rem;font-weight:600;color:#1e293b;line-height:1.3}.o-toast__message{margin:0;font-size:.8rem;color:#475569;line-height:1.4}.o-toast__close{display:flex;align-items:center;justify-content:center;width:24px;height:24px;flex-shrink:0;background:none;border:none;cursor:pointer;color:#94a3b8;border-radius:4px;margin-top:2px;transition:color .12s,background .12s}.o-toast__close:hover{color:#64748b;background:#f1f5f9}\n"] }]
1897
+ }], ctorParameters: () => [{ type: OToastService }, { type: i0.ChangeDetectorRef }] });
1898
+
1899
+ class OSelectComponent {
1900
+ options = [];
1901
+ placeholder = 'Select';
1902
+ value = null;
1903
+ disabled = false;
1904
+ valueChange = new EventEmitter();
1905
+ onChange = () => { };
1906
+ onTouched = () => { };
1907
+ onSelectChange(event) {
1908
+ const el = event.target;
1909
+ const selected = this.options.find(o => String(o.value) === el.value);
1910
+ const val = selected ? selected.value : null;
1911
+ this.value = val;
1912
+ this.onChange(val);
1913
+ this.onTouched();
1914
+ this.valueChange.emit(val);
1915
+ }
1916
+ optionStringValue(val) {
1917
+ return val == null ? '' : String(val);
1918
+ }
1919
+ writeValue(val) {
1920
+ this.value = val ?? null;
1921
+ }
1922
+ registerOnChange(fn) {
1923
+ this.onChange = fn;
1924
+ }
1925
+ registerOnTouched(fn) {
1926
+ this.onTouched = fn; // eslint-disable-line @typescript-eslint/no-empty-function
1927
+ }
1928
+ setDisabledState(isDisabled) {
1929
+ this.disabled = isDisabled;
1930
+ }
1931
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1932
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: OSelectComponent, isStandalone: true, selector: "o-select", inputs: { options: "options", placeholder: "placeholder", value: "value", disabled: "disabled" }, outputs: { valueChange: "valueChange" }, providers: [
1933
+ {
1934
+ provide: NG_VALUE_ACCESSOR,
1935
+ useExisting: forwardRef(() => OSelectComponent),
1936
+ multi: true
1937
+ }
1938
+ ], ngImport: i0, template: "<div class=\"o-select-wrapper\" [class.o-select-wrapper--disabled]=\"disabled\">\n <select\n class=\"o-select\"\n [disabled]=\"disabled\"\n [value]=\"optionStringValue(value)\"\n (change)=\"onSelectChange($event)\"\n (blur)=\"onTouched()\"\n [attr.aria-label]=\"placeholder\"\n >\n <option value=\"\" disabled [selected]=\"value === null || value === undefined || value === ''\">\n {{ placeholder }}\n </option>\n @for (option of options; track option.value) {\n <option\n [value]=\"optionStringValue(option.value)\"\n [selected]=\"optionStringValue(option.value) === optionStringValue(value)\"\n >\n {{ option.label }}\n </option>\n }\n </select>\n</div>\n", styles: [".o-select-wrapper{position:relative;display:block;width:100%}.o-select-wrapper:after{content:\"\";position:absolute;right:12px;top:50%;transform:translateY(-50%);width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #6b7280;pointer-events:none}.o-select-wrapper--disabled{opacity:.6;cursor:not-allowed}.o-select{display:block;width:100%;height:40px;padding:0 36px 0 12px;border:1px solid #d1d5db;border-radius:6px;background:#fff;font-size:.875rem;color:#374151;cursor:pointer;appearance:none;-webkit-appearance:none;outline:none;box-sizing:border-box;transition:border-color .15s,box-shadow .15s}.o-select:focus{border-color:#15104e;box-shadow:0 0 0 3px #15104e1a}.o-select:disabled{cursor:not-allowed;background:#f9fafb}.o-select option[value=\"\"][disabled]{color:#9ca3af}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }] });
1939
+ }
1940
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OSelectComponent, decorators: [{
1941
+ type: Component,
1942
+ args: [{ selector: 'o-select', standalone: true, imports: [CommonModule, FormsModule], providers: [
1943
+ {
1944
+ provide: NG_VALUE_ACCESSOR,
1945
+ useExisting: forwardRef(() => OSelectComponent),
1946
+ multi: true
1947
+ }
1948
+ ], template: "<div class=\"o-select-wrapper\" [class.o-select-wrapper--disabled]=\"disabled\">\n <select\n class=\"o-select\"\n [disabled]=\"disabled\"\n [value]=\"optionStringValue(value)\"\n (change)=\"onSelectChange($event)\"\n (blur)=\"onTouched()\"\n [attr.aria-label]=\"placeholder\"\n >\n <option value=\"\" disabled [selected]=\"value === null || value === undefined || value === ''\">\n {{ placeholder }}\n </option>\n @for (option of options; track option.value) {\n <option\n [value]=\"optionStringValue(option.value)\"\n [selected]=\"optionStringValue(option.value) === optionStringValue(value)\"\n >\n {{ option.label }}\n </option>\n }\n </select>\n</div>\n", styles: [".o-select-wrapper{position:relative;display:block;width:100%}.o-select-wrapper:after{content:\"\";position:absolute;right:12px;top:50%;transform:translateY(-50%);width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #6b7280;pointer-events:none}.o-select-wrapper--disabled{opacity:.6;cursor:not-allowed}.o-select{display:block;width:100%;height:40px;padding:0 36px 0 12px;border:1px solid #d1d5db;border-radius:6px;background:#fff;font-size:.875rem;color:#374151;cursor:pointer;appearance:none;-webkit-appearance:none;outline:none;box-sizing:border-box;transition:border-color .15s,box-shadow .15s}.o-select:focus{border-color:#15104e;box-shadow:0 0 0 3px #15104e1a}.o-select:disabled{cursor:not-allowed;background:#f9fafb}.o-select option[value=\"\"][disabled]{color:#9ca3af}\n"] }]
1949
+ }], propDecorators: { options: [{
1950
+ type: Input
1951
+ }], placeholder: [{
1952
+ type: Input
1953
+ }], value: [{
1954
+ type: Input
1955
+ }], disabled: [{
1956
+ type: Input
1957
+ }], valueChange: [{
1958
+ type: Output
1959
+ }] } });
1960
+
1961
+ /*
1962
+ * Public API Surface of o-ui
1963
+ */
1964
+
1965
+ /**
1966
+ * Generated bundle index. Do not edit.
1967
+ */
1968
+
1969
+ export { ConfirmationDialogComponent, ContextMenuComponent, ContextSwitcherComponent, DataTableComponent, FormDrawerComponent, ODialogComponent, ODotsActionComponent, OLicenseModulesComponent, OSelectComponent, OSidePanelComponent, OStatusToggleComponent, OTableComponent, OTableToolsComponent, OToastComponent, OToastService, OToggleButtonComponent, OUi, PageHeaderComponent, PageRendererComponent, SearchFilterComponent, SideNavComponent, ToggleTabsComponent };
1970
+ //# sourceMappingURL=orque-ui.mjs.map