@nettyapps/ntyux 21.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1083 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Component, NgModule, inject, Injectable, input, output, ViewContainerRef, ViewChild, Input, ViewEncapsulation, signal, effect, computed, forwardRef, ChangeDetectionStrategy, ElementRef, model } from '@angular/core';
3
+ import * as i1$1 from '@angular/material/form-field';
4
+ import { MatFormFieldModule } from '@angular/material/form-field';
5
+ import * as i2$1 from '@angular/material/autocomplete';
6
+ import { MatAutocompleteModule, MatAutocompleteTrigger } from '@angular/material/autocomplete';
7
+ import * as i3$1 from '@angular/forms';
8
+ import { ReactiveFormsModule, FormControl, Validators, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
9
+ import { ErrorStateMatcher } from '@angular/material/core';
10
+ import * as i4 from '@angular/material/input';
11
+ import { MatInputModule } from '@angular/material/input';
12
+ import * as i2 from '@angular/material/icon';
13
+ import { MatIconModule } from '@angular/material/icon';
14
+ import { UiBase, NettyUISearchInput } from '@nettyapps/ntyui';
15
+ import { HttpClient } from '@angular/common/http';
16
+ import { EnvironmentProxy } from '@nettyapps/ntycontract';
17
+ import { MAT_DIALOG_DATA, MatDialog, MatDialogConfig, MatDialogModule } from '@angular/material/dialog';
18
+ import * as i1 from '@angular/common';
19
+ import { CommonModule, Location, TitleCasePipe } from '@angular/common';
20
+ import * as i3 from '@ngx-translate/core';
21
+ import { TranslateModule } from '@ngx-translate/core';
22
+ import { Clipboard } from '@angular/cdk/clipboard';
23
+ import { AlertService, CommonService, AuthenticationService, NettyMenuService, CredentialsService, NettyImageService } from '@nettyapps/ntybase';
24
+ import * as i5 from '@angular/router';
25
+ import { Router, ActivatedRoute, RouterLink, RouterModule } from '@angular/router';
26
+ import * as i3$2 from '@angular/material/list';
27
+ import { MatListModule } from '@angular/material/list';
28
+ import * as i4$1 from '@angular/material/menu';
29
+ import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu';
30
+ import * as i3$3 from '@angular/material/toolbar';
31
+ import { MatToolbarModule } from '@angular/material/toolbar';
32
+ import { I18nService } from '@nettyapps/ntyi18n';
33
+
34
+ class Ntyux {
35
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: Ntyux, deps: [], target: i0.ɵɵFactoryTarget.Component });
36
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.3", type: Ntyux, isStandalone: false, selector: "lib-ntyux", ngImport: i0, template: `
37
+ <p>
38
+ ntyux works!
39
+ </p>
40
+ `, isInline: true, styles: [""] });
41
+ }
42
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: Ntyux, decorators: [{
43
+ type: Component,
44
+ args: [{ selector: 'lib-ntyux', standalone: false, template: `
45
+ <p>
46
+ ntyux works!
47
+ </p>
48
+ ` }]
49
+ }] });
50
+
51
+ class NtyuxModule {
52
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NtyuxModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
53
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.0.3", ngImport: i0, type: NtyuxModule, declarations: [Ntyux], exports: [Ntyux] });
54
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NtyuxModule });
55
+ }
56
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NtyuxModule, decorators: [{
57
+ type: NgModule,
58
+ args: [{
59
+ declarations: [
60
+ Ntyux
61
+ ],
62
+ imports: [],
63
+ exports: [
64
+ Ntyux
65
+ ]
66
+ }]
67
+ }] });
68
+
69
+ class Guid {
70
+ value = this.empty;
71
+ constructor(value) {
72
+ if (value) {
73
+ if (Guid.isValid(value)) {
74
+ this.value = value;
75
+ }
76
+ }
77
+ }
78
+ static newGuid() {
79
+ return new Guid('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
80
+ const r = (Math.random() * 16) | 0;
81
+ const v = c == 'x' ? r : (r & 0x3) | 0x8;
82
+ return v.toString(16);
83
+ }));
84
+ }
85
+ /**
86
+ * return all zeros '00000000-0000-0000-0000-000000000000'
87
+ */
88
+ static get empty() {
89
+ return '00000000-0000-0000-0000-000000000000';
90
+ }
91
+ get empty() {
92
+ return Guid.empty;
93
+ }
94
+ static isValid(str) {
95
+ const validRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
96
+ return validRegex.test(str);
97
+ }
98
+ toString() {
99
+ return this.value;
100
+ }
101
+ toJSON() {
102
+ return this.value;
103
+ }
104
+ /**
105
+ * True is guid is empty or not valid
106
+ * @param str
107
+ * @returns
108
+ */
109
+ static isNullOrEmpty(str) {
110
+ if (str == null || str == undefined) {
111
+ return true;
112
+ }
113
+ if (!Guid.isValid(str)) {
114
+ return true;
115
+ }
116
+ if (str == Guid.empty) {
117
+ return true;
118
+ }
119
+ return false;
120
+ }
121
+ /**
122
+ * True if the guid is valid and not all zeros (empty)
123
+ * @param str
124
+ * @returns
125
+ */
126
+ static isValidAndNotEmpty(str) {
127
+ return !Guid.isNullOrEmpty(str);
128
+ }
129
+ /**
130
+ * Return empty guid if the given guid is not valid
131
+ * @param guid
132
+ * @returns
133
+ */
134
+ static emptyWhenNull(guid) {
135
+ if (Guid.isValidAndNotEmpty(guid)) {
136
+ return guid;
137
+ }
138
+ return Guid.empty;
139
+ }
140
+ }
141
+
142
+ class AutoCompleteProxy {
143
+ http = inject(HttpClient);
144
+ environmentProxy = inject(EnvironmentProxy);
145
+ getAutocompleteDataFilter(searchTable, searchData) {
146
+ let functionUrl = this.environmentProxy.getServerLink(searchTable + '/AutoCompleteFilter');
147
+ return this.http.post(functionUrl, searchData);
148
+ }
149
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: AutoCompleteProxy, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
150
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: AutoCompleteProxy, providedIn: 'root' });
151
+ }
152
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: AutoCompleteProxy, decorators: [{
153
+ type: Injectable,
154
+ args: [{
155
+ providedIn: 'root',
156
+ }]
157
+ }] });
158
+
159
+ class NtyAutoCompleteFilter {
160
+ coid = null;
161
+ searchString = null;
162
+ fields = null;
163
+ }
164
+
165
+ /**
166
+ * This component can load and display any container which is provided dynamically during run time
167
+ * The only requirement is the component that will be displayed should extend AgGridBaseComponent
168
+ */
169
+ class AutoCompleteLookup {
170
+ data = inject(MAT_DIALOG_DATA);
171
+ container;
172
+ component = input(...(ngDevMode ? [undefined, { debugName: "component" }] : []));
173
+ filterField = input(null, ...(ngDevMode ? [{ debugName: "filterField" }] : []));
174
+ filterFieldValue = input(null, ...(ngDevMode ? [{ debugName: "filterFieldValue" }] : []));
175
+ filterFieldNumeric = input(false, ...(ngDevMode ? [{ debugName: "filterFieldNumeric" }] : []));
176
+ filterFieldEquality = input('=', ...(ngDevMode ? [{ debugName: "filterFieldEquality" }] : []));
177
+ // Output
178
+ selectedElement = output();
179
+ ngOnInit() {
180
+ this.initializeComponent();
181
+ }
182
+ ngOnChanges(changes) {
183
+ if (this.isComponentConfigChanged(changes)) {
184
+ this.initializeComponent();
185
+ }
186
+ }
187
+ isComponentConfigChanged(changes) {
188
+ return (!!changes['component'] ||
189
+ !!changes['filterField'] ||
190
+ !!changes['filterFieldValue'] ||
191
+ !!changes['filterFieldNumeric'] ||
192
+ !!changes['filterFieldEquality']);
193
+ }
194
+ initializeComponent() {
195
+ const config = this.data || this.getInputConfig();
196
+ this.loadComponent(config);
197
+ }
198
+ getInputConfig() {
199
+ return {
200
+ component: this.component(),
201
+ filterField: this.filterField(),
202
+ filterFieldValue: this.filterFieldValue(),
203
+ filterFieldNumeric: this.filterFieldNumeric(),
204
+ filterFieldEquality: this.filterFieldEquality(),
205
+ };
206
+ }
207
+ loadComponent(config) {
208
+ if (!config?.component)
209
+ return;
210
+ this.container.clear();
211
+ const componentRef = this.container.createComponent(config.component);
212
+ componentRef.setInput('popupValid', true);
213
+ if (config.filterField !== undefined) {
214
+ componentRef.setInput('filterField', config.filterField);
215
+ }
216
+ if (config.filterFieldValue !== undefined) {
217
+ componentRef.setInput('filterFieldValue', config.filterFieldValue);
218
+ }
219
+ if (config.filterFieldEquality !== undefined) {
220
+ componentRef.setInput('filterFieldEquality', config.filterFieldEquality);
221
+ }
222
+ if (config.filterFieldNumeric !== undefined) {
223
+ componentRef.setInput('filterFieldNumeric', config.filterFieldNumeric);
224
+ }
225
+ const sub = componentRef.instance.selectedElement.subscribe((data) => {
226
+ this.selectedElement.emit(data);
227
+ });
228
+ componentRef.onDestroy(() => sub.unsubscribe());
229
+ }
230
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: AutoCompleteLookup, deps: [], target: i0.ɵɵFactoryTarget.Component });
231
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.3", type: AutoCompleteLookup, isStandalone: true, selector: "ntybase-auto-complete-lookup", inputs: { component: { classPropertyName: "component", publicName: "component", isSignal: true, isRequired: false, transformFunction: null }, filterField: { classPropertyName: "filterField", publicName: "filterField", isSignal: true, isRequired: false, transformFunction: null }, filterFieldValue: { classPropertyName: "filterFieldValue", publicName: "filterFieldValue", isSignal: true, isRequired: false, transformFunction: null }, filterFieldNumeric: { classPropertyName: "filterFieldNumeric", publicName: "filterFieldNumeric", isSignal: true, isRequired: false, transformFunction: null }, filterFieldEquality: { classPropertyName: "filterFieldEquality", publicName: "filterFieldEquality", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedElement: "selectedElement" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, read: ViewContainerRef, static: true }], usesOnChanges: true, ngImport: i0, template: ` <ng-template #container></ng-template> `, isInline: true });
232
+ }
233
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: AutoCompleteLookup, decorators: [{
234
+ type: Component,
235
+ args: [{
236
+ selector: 'ntybase-auto-complete-lookup',
237
+ imports: [],
238
+ template: ` <ng-template #container></ng-template> `,
239
+ }]
240
+ }], propDecorators: { container: [{
241
+ type: ViewChild,
242
+ args: ['container', { read: ViewContainerRef, static: true }]
243
+ }], component: [{ type: i0.Input, args: [{ isSignal: true, alias: "component", required: false }] }], filterField: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterField", required: false }] }], filterFieldValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterFieldValue", required: false }] }], filterFieldNumeric: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterFieldNumeric", required: false }] }], filterFieldEquality: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterFieldEquality", required: false }] }], selectedElement: [{ type: i0.Output, args: ["selectedElement"] }] } });
244
+
245
+ class AutoCompletePopupMenu {
246
+ field = '';
247
+ recordGuid;
248
+ fieldDisabled = false;
249
+ componentPath = '';
250
+ inputValue = '';
251
+ Result = output();
252
+ clipboard = inject(Clipboard);
253
+ alertService = inject(AlertService);
254
+ ngOnInit() { }
255
+ gotoNew() {
256
+ if (!this.componentPath)
257
+ return;
258
+ // Split the component path into segments
259
+ const pathSegments = this.componentPath.split('/').filter((p) => p);
260
+ this.gotoNewTab(pathSegments, '', 'new');
261
+ this.Result.emit('New');
262
+ }
263
+ gotoMain() {
264
+ if (!this.componentPath || !this.recordGuid)
265
+ return;
266
+ // Split the component path into segments
267
+ const pathSegments = this.componentPath.split('/').filter((p) => p);
268
+ this.gotoNewTab(pathSegments, this.recordGuid, 'edit');
269
+ this.Result.emit('Main');
270
+ }
271
+ gotoLookup() {
272
+ if (!this.fieldDisabled) {
273
+ this.Result.emit('Lookup');
274
+ }
275
+ }
276
+ gotoNewTab(target, params, type) {
277
+ if (!target || target.length === 0)
278
+ return;
279
+ // Construct the URL directly in the format you want
280
+ let url = `/${target.join('/')}`;
281
+ // Add query parameters
282
+ const queryParams = [];
283
+ if (params) {
284
+ queryParams.push(`parameters=${encodeURIComponent(JSON.stringify(params))}`);
285
+ }
286
+ if (type) {
287
+ queryParams.push(`type=${type}`);
288
+ }
289
+ queryParams.push('isNewTab=true');
290
+ if (queryParams.length > 0) {
291
+ url += `?${queryParams.join('&')}`;
292
+ }
293
+ // Open in new tab
294
+ window.open(url, '_blank');
295
+ }
296
+ copyToClipboard() {
297
+ if (this.inputValue) {
298
+ this.clipboard.copy(this.inputValue);
299
+ this.alertService.showAlert('@copiedToClipboard');
300
+ this.Result.emit('Copy');
301
+ }
302
+ }
303
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: AutoCompletePopupMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
304
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.3", type: AutoCompletePopupMenu, isStandalone: true, selector: "ntybase-auto-complete-popup-menu", inputs: { field: "field", recordGuid: "recordGuid", fieldDisabled: "fieldDisabled", componentPath: "componentPath", inputValue: "inputValue" }, outputs: { Result: "Result" }, ngImport: i0, template: "<div class=\"dialogbase\">\n <div class=\"menu\">\n <ul class=\"list-group\">\n <li\n class=\"list-group-item list-group-item-action d-flex justify-content-between align-items-center menu-item\"\n >\n <span (click)=\"copyToClipboard()\" class=\"align-line\"\n ><mat-icon>content_copy</mat-icon>{{'@copy'| translate}}</span\n >\n </li>\n <li\n class=\"list-group-item list-group-item-action d-flex justify-content-between align-items-center menu-item\"\n >\n <span (click)=\"gotoMain()\" class=\"align-line\"\n ><mat-icon>edit</mat-icon>{{'@popupGotoRecordDefinition'|\n translate}}</span\n >\n </li>\n <li\n class=\"list-group-item list-group-item-action d-flex justify-content-between align-items-center menu-item\"\n >\n <span (click)=\"gotoNew()\" class=\"align-line\"\n ><mat-icon>add</mat-icon>{{'@popupNewRecord'| translate}}</span\n >\n </li>\n <li\n class=\"list-group-item list-group-item-action d-flex justify-content-between align-items-center\"\n [ngClass]=\"{'menu-item':!fieldDisabled , 'menu-item-disabled':fieldDisabled }\"\n >\n <span (click)=\"gotoLookup()\" class=\"align-line\"\n ><mat-icon>search</mat-icon>{{'@popupSelectFromList'|translate}}</span\n >\n </li>\n </ul>\n </div>\n</div>\n", styles: [".dialogbase{background:#0000001a;padding:0;border-radius:12px;overflow:hidden;box-shadow:0 4px 20px #00000026}.menu{background:var(--mat-sys-primary-container);padding:0;display:flex;border-radius:inherit}.align-line{display:flex;vertical-align:middle;align-items:center;gap:12px;padding:0 8px}.menu-item{padding:12px 16px;display:flex;vertical-align:middle;cursor:pointer;transition:all .2s ease-out;border-radius:8px;margin:4px;color:var(--mat-sys-on-primary-container)}.menu-item:hover{background:var(--mat-sys-on-primary-fixed);color:var(--mat-sys-on-primary);transform:translateY(-2px);box-shadow:0 2px 8px #0000001a}.menu-item:hover .mat-icon{color:var(--mat-sys-on-primary);transform:scale(1.05)}.menu-item-disabled,.menu-item-disabled:hover{background:var(--mat-sys-on-primary-fixed);color:var(--mat-sys-on-primary);font-style:italic;opacity:.7;cursor:not-allowed}.menu-item-disabled .mat-icon,.menu-item-disabled:hover .mat-icon{color:var(--mat-sys-on-primary)}.mat-icon{transition:all .2s ease-out;font-size:20px;width:20px;height:20px}.list-group{list-style:none;padding:8px;margin:0;width:100%}.list-group-item{background-position:bottom;background-size:100% 1px;background-repeat:no-repeat;padding-bottom:12px;margin-bottom:4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }], encapsulation: i0.ViewEncapsulation.None });
305
+ }
306
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: AutoCompletePopupMenu, decorators: [{
307
+ type: Component,
308
+ args: [{ selector: 'ntybase-auto-complete-popup-menu', imports: [CommonModule, ReactiveFormsModule, MatIconModule, TranslateModule], encapsulation: ViewEncapsulation.None, template: "<div class=\"dialogbase\">\n <div class=\"menu\">\n <ul class=\"list-group\">\n <li\n class=\"list-group-item list-group-item-action d-flex justify-content-between align-items-center menu-item\"\n >\n <span (click)=\"copyToClipboard()\" class=\"align-line\"\n ><mat-icon>content_copy</mat-icon>{{'@copy'| translate}}</span\n >\n </li>\n <li\n class=\"list-group-item list-group-item-action d-flex justify-content-between align-items-center menu-item\"\n >\n <span (click)=\"gotoMain()\" class=\"align-line\"\n ><mat-icon>edit</mat-icon>{{'@popupGotoRecordDefinition'|\n translate}}</span\n >\n </li>\n <li\n class=\"list-group-item list-group-item-action d-flex justify-content-between align-items-center menu-item\"\n >\n <span (click)=\"gotoNew()\" class=\"align-line\"\n ><mat-icon>add</mat-icon>{{'@popupNewRecord'| translate}}</span\n >\n </li>\n <li\n class=\"list-group-item list-group-item-action d-flex justify-content-between align-items-center\"\n [ngClass]=\"{'menu-item':!fieldDisabled , 'menu-item-disabled':fieldDisabled }\"\n >\n <span (click)=\"gotoLookup()\" class=\"align-line\"\n ><mat-icon>search</mat-icon>{{'@popupSelectFromList'|translate}}</span\n >\n </li>\n </ul>\n </div>\n</div>\n", styles: [".dialogbase{background:#0000001a;padding:0;border-radius:12px;overflow:hidden;box-shadow:0 4px 20px #00000026}.menu{background:var(--mat-sys-primary-container);padding:0;display:flex;border-radius:inherit}.align-line{display:flex;vertical-align:middle;align-items:center;gap:12px;padding:0 8px}.menu-item{padding:12px 16px;display:flex;vertical-align:middle;cursor:pointer;transition:all .2s ease-out;border-radius:8px;margin:4px;color:var(--mat-sys-on-primary-container)}.menu-item:hover{background:var(--mat-sys-on-primary-fixed);color:var(--mat-sys-on-primary);transform:translateY(-2px);box-shadow:0 2px 8px #0000001a}.menu-item:hover .mat-icon{color:var(--mat-sys-on-primary);transform:scale(1.05)}.menu-item-disabled,.menu-item-disabled:hover{background:var(--mat-sys-on-primary-fixed);color:var(--mat-sys-on-primary);font-style:italic;opacity:.7;cursor:not-allowed}.menu-item-disabled .mat-icon,.menu-item-disabled:hover .mat-icon{color:var(--mat-sys-on-primary)}.mat-icon{transition:all .2s ease-out;font-size:20px;width:20px;height:20px}.list-group{list-style:none;padding:8px;margin:0;width:100%}.list-group-item{background-position:bottom;background-size:100% 1px;background-repeat:no-repeat;padding-bottom:12px;margin-bottom:4px}\n"] }]
309
+ }], propDecorators: { field: [{
310
+ type: Input
311
+ }], recordGuid: [{
312
+ type: Input
313
+ }], fieldDisabled: [{
314
+ type: Input
315
+ }], componentPath: [{
316
+ type: Input
317
+ }], inputValue: [{
318
+ type: Input
319
+ }], Result: [{ type: i0.Output, args: ["Result"] }] } });
320
+
321
+ class SelectionItem {
322
+ name = null;
323
+ value = null;
324
+ constructor(_value = null, _name = null) {
325
+ this.name = _name;
326
+ this.value = _value;
327
+ }
328
+ }
329
+ class AutoComplete extends UiBase {
330
+ // Input parameters
331
+ tableName = input('', ...(ngDevMode ? [{ debugName: "tableName" }] : []));
332
+ searchAfter = input(3, ...(ngDevMode ? [{ debugName: "searchAfter" }] : []));
333
+ fieldCode = input(null, ...(ngDevMode ? [{ debugName: "fieldCode" }] : []));
334
+ fieldName = input(null, ...(ngDevMode ? [{ debugName: "fieldName" }] : []));
335
+ lookupComponent = input(null, ...(ngDevMode ? [{ debugName: "lookupComponent" }] : []));
336
+ lookupComponentPath = input(null, ...(ngDevMode ? [{ debugName: "lookupComponentPath" }] : []));
337
+ filterField = input(null, ...(ngDevMode ? [{ debugName: "filterField" }] : []));
338
+ filterFieldValue = input(null, ...(ngDevMode ? [{ debugName: "filterFieldValue" }] : []));
339
+ filterFieldNumeric = input(false, ...(ngDevMode ? [{ debugName: "filterFieldNumeric" }] : []));
340
+ filterFieldEquality = input('=', ...(ngDevMode ? [{ debugName: "filterFieldEquality" }] : []));
341
+ debounceTime = input(500, ...(ngDevMode ? [{ debugName: "debounceTime" }] : []));
342
+ // Inject dependencies
343
+ alertService = inject(AlertService);
344
+ autoCompleteService = inject(AutoCompleteProxy);
345
+ dialog = inject(MatDialog);
346
+ autoCompleteTrigger;
347
+ inputField;
348
+ control = new FormControl();
349
+ matcher = new ErrorStateMatcher();
350
+ // Signal parameters
351
+ searchText = signal('', ...(ngDevMode ? [{ debugName: "searchText" }] : []));
352
+ selectedOption = signal(null, ...(ngDevMode ? [{ debugName: "selectedOption" }] : []));
353
+ loadedOptions = signal([], ...(ngDevMode ? [{ debugName: "loadedOptions" }] : []));
354
+ searchSignal = signal('', ...(ngDevMode ? [{ debugName: "searchSignal" }] : []));
355
+ debounceTimeout;
356
+ constructor() {
357
+ super();
358
+ // Sync form control with selected option
359
+ effect(() => {
360
+ const option = this.selectedOption();
361
+ if (option) {
362
+ this.searchText.set(option.name || '');
363
+ }
364
+ else if (this.control.value && !this.selectedOption()) {
365
+ const found = this.loadedOptions().find((opt) => opt.value === this.control.value);
366
+ if (found) {
367
+ this.selectedOption.set(found);
368
+ this.searchText.set(found.name || '');
369
+ }
370
+ }
371
+ });
372
+ // Then watch the debounced signal:
373
+ effect((onCleanup) => {
374
+ const searchValue = this.searchSignal();
375
+ clearTimeout(this.debounceTimeout);
376
+ this.debounceTimeout = setTimeout(() => {
377
+ if (this.shouldTriggerSearch(searchValue)) {
378
+ this.loadAutoCompleteData(searchValue);
379
+ }
380
+ else {
381
+ this.loadedOptions.set([]);
382
+ }
383
+ }, this.debounceTime());
384
+ onCleanup(() => {
385
+ clearTimeout(this.debounceTimeout);
386
+ });
387
+ });
388
+ effect(() => {
389
+ this.onChange(this.value());
390
+ });
391
+ effect(() => {
392
+ this.updateValidators();
393
+ });
394
+ }
395
+ shouldTriggerSearch(searchValue) {
396
+ return searchValue.length >= this.searchAfter();
397
+ }
398
+ filteredData = computed(() => {
399
+ return this.loadedOptions();
400
+ }, ...(ngDevMode ? [{ debugName: "filteredData" }] : []));
401
+ onInputChanged(event) {
402
+ const value = event.target.value;
403
+ this.searchText.set(value);
404
+ this.onTouched();
405
+ // Clear selection if text doesn't match
406
+ if (this.selectedOption() && this.selectedOption()?.name !== value) {
407
+ this.selectedOption.set(null);
408
+ }
409
+ if (value.length === 0) {
410
+ this.clearInput();
411
+ return;
412
+ }
413
+ this.searchSignal.set(value);
414
+ this.autoCompleteTrigger.closePanel();
415
+ }
416
+ loadAutoCompleteData(searchValue) {
417
+ this.loadedOptions.set([]);
418
+ const filter = new NtyAutoCompleteFilter();
419
+ filter.searchString =
420
+ searchValue.trim() === '' && searchValue.length >= this.searchAfter()
421
+ ? ''
422
+ : searchValue;
423
+ filter.fields = [];
424
+ if (this.filterField() && this.filterField().trim().length > 0) {
425
+ const fields = {
426
+ equalitySign: this.filterFieldEquality(),
427
+ fieldName: this.filterField(),
428
+ fieldValue: this.filterFieldValue(),
429
+ numeric: this.filterFieldNumeric(),
430
+ };
431
+ filter.fields?.push(fields);
432
+ }
433
+ this.autoCompleteService
434
+ .getAutocompleteDataFilter(this.tableName(), filter)
435
+ .subscribe({
436
+ next: (data) => {
437
+ if (data && Array.isArray(data)) {
438
+ const options = data.map((item) => ({
439
+ name: item.text,
440
+ value: item.guidValue,
441
+ }));
442
+ this.loadedOptions.set(options);
443
+ if (options.length === 1) {
444
+ this.selectedOption.set(options[0]);
445
+ this.control.setValue(options[0].value);
446
+ this.searchText.set(options[0].name || '');
447
+ }
448
+ else if (options.length > 1) {
449
+ this.autoCompleteTrigger.openPanel();
450
+ }
451
+ }
452
+ },
453
+ error: (err) => {
454
+ this.alertService.showAlert(err.message);
455
+ },
456
+ });
457
+ }
458
+ clearInput() {
459
+ this.selectedOption.set(new SelectionItem(Guid.empty, ''));
460
+ this.control.reset();
461
+ this.searchText.set('');
462
+ this.loadedOptions.set([]);
463
+ this.inputField.nativeElement.value = '';
464
+ }
465
+ onPaste(event) {
466
+ const clipboardData = event.clipboardData;
467
+ if (!clipboardData)
468
+ return;
469
+ const pastedText = clipboardData.getData('text');
470
+ this.searchText.set(pastedText);
471
+ this.searchSignal.set(pastedText);
472
+ }
473
+ displayFn(item) {
474
+ return this.searchText();
475
+ }
476
+ optionSelected(event) {
477
+ const selectedValue = event.option.value;
478
+ const selected = this.loadedOptions().find((opt) => opt.value === selectedValue);
479
+ if (selected) {
480
+ this.selectedOption.set(selected);
481
+ this.control.setValue(selected.value);
482
+ this.searchText.set(selected.name || '');
483
+ }
484
+ }
485
+ writeValue(value) {
486
+ this.value.set(value);
487
+ if (!value)
488
+ return;
489
+ if (this.fieldCode()) {
490
+ this.selectedOption.set({ value, name: this.fieldCode() });
491
+ }
492
+ }
493
+ onLookup() {
494
+ if (!this.lookupComponent())
495
+ return;
496
+ const dialogRef = this.dialog.open(AutoCompleteLookup, {
497
+ height: 'auto',
498
+ width: '100%',
499
+ maxWidth: '100%',
500
+ maxHeight: 'auto',
501
+ data: {
502
+ component: this.lookupComponent(),
503
+ ...(this.filterField() && {
504
+ filterField: this.filterField(),
505
+ filterFieldValue: this.filterFieldValue(),
506
+ filterFieldEquality: this.filterFieldEquality(),
507
+ filterFieldNumeric: this.filterFieldNumeric(),
508
+ }),
509
+ },
510
+ });
511
+ dialogRef.componentInstance.selectedElement.subscribe((data) => {
512
+ if (data) {
513
+ const selection = new SelectionItem(data.value, data.text);
514
+ this.selectedOption.set(selection);
515
+ this.control.setValue(selection.value);
516
+ this.searchText.set(selection.name || '');
517
+ }
518
+ dialogRef.close();
519
+ });
520
+ }
521
+ /** Popup screen to goto main table
522
+ *
523
+ * @param event
524
+ * @returns
525
+ */
526
+ rightClick(event) {
527
+ event.stopPropagation();
528
+ const dialogConfig = new MatDialogConfig();
529
+ dialogConfig.position = {
530
+ top: event.clientY + 'px',
531
+ left: event.clientX + 'px',
532
+ };
533
+ dialogConfig.height = 'auto';
534
+ dialogConfig.width = 'auto';
535
+ dialogConfig.panelClass = 'foo';
536
+ const dialogRef = this.dialog.open(AutoCompletePopupMenu, dialogConfig);
537
+ dialogRef.componentInstance.field = this.tableName();
538
+ dialogRef.componentInstance.recordGuid = this.value();
539
+ dialogRef.componentInstance.fieldDisabled = this.disabled();
540
+ dialogRef.componentInstance.componentPath = this.lookupComponentPath();
541
+ dialogRef.componentInstance.inputValue = this.searchText();
542
+ dialogRef.componentInstance.Result.subscribe((result) => {
543
+ switch (result) {
544
+ case 'Lookup':
545
+ dialogRef.close();
546
+ this.onLookup();
547
+ break;
548
+ default:
549
+ dialogRef.close();
550
+ break;
551
+ }
552
+ });
553
+ return false;
554
+ }
555
+ // Validation
556
+ getErrorMessage() {
557
+ if (this.control.hasError('required') && this.errorMessages()['required']) {
558
+ return this.errorMessages()['required'];
559
+ }
560
+ return '';
561
+ }
562
+ updateValidators() {
563
+ const validators = [];
564
+ if (this.required()) {
565
+ validators.push(Validators.required);
566
+ }
567
+ this.control.setValidators(validators);
568
+ this.control.updateValueAndValidity();
569
+ }
570
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: AutoComplete, deps: [], target: i0.ɵɵFactoryTarget.Component });
571
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: AutoComplete, isStandalone: true, selector: "ntybase-auto-complete", inputs: { tableName: { classPropertyName: "tableName", publicName: "tableName", isSignal: true, isRequired: false, transformFunction: null }, searchAfter: { classPropertyName: "searchAfter", publicName: "searchAfter", isSignal: true, isRequired: false, transformFunction: null }, fieldCode: { classPropertyName: "fieldCode", publicName: "fieldCode", isSignal: true, isRequired: false, transformFunction: null }, fieldName: { classPropertyName: "fieldName", publicName: "fieldName", isSignal: true, isRequired: false, transformFunction: null }, lookupComponent: { classPropertyName: "lookupComponent", publicName: "lookupComponent", isSignal: true, isRequired: false, transformFunction: null }, lookupComponentPath: { classPropertyName: "lookupComponentPath", publicName: "lookupComponentPath", isSignal: true, isRequired: false, transformFunction: null }, filterField: { classPropertyName: "filterField", publicName: "filterField", isSignal: true, isRequired: false, transformFunction: null }, filterFieldValue: { classPropertyName: "filterFieldValue", publicName: "filterFieldValue", isSignal: true, isRequired: false, transformFunction: null }, filterFieldNumeric: { classPropertyName: "filterFieldNumeric", publicName: "filterFieldNumeric", isSignal: true, isRequired: false, transformFunction: null }, filterFieldEquality: { classPropertyName: "filterFieldEquality", publicName: "filterFieldEquality", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
572
+ {
573
+ provide: NG_VALUE_ACCESSOR,
574
+ useExisting: forwardRef(() => AutoComplete),
575
+ multi: true,
576
+ },
577
+ ], viewQueries: [{ propertyName: "autoCompleteTrigger", first: true, predicate: MatAutocompleteTrigger, descendants: true }, { propertyName: "inputField", first: true, predicate: ["autoCompleteInput"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<mat-form-field\n class=\"ui-full-width\"\n [appearance]=\"appearance()\"\n [class.required-field]=\"required()\"\n [class.required-with-value]=\"required() && value()\"\n>\n @if (label()) {\n <mat-label>{{ label() }}</mat-label>\n }\n\n <input\n type=\"text\"\n #autoCompleteInput\n matInput\n [(ngModel)]=\"value\"\n [matAutocomplete]=\"auto\"\n (input)=\"onInputChanged($event)\"\n (paste)=\"onPaste($event)\"\n [errorStateMatcher]=\"matcher\"\n [formControl]=\"control\"\n (contextmenu)=\"rightClick($event)\"\n />\n\n @if (value() && !disabled()) {\n <button\n mat-icon-button\n matSuffix\n (click)=\"clearInput()\"\n aria-label=\"Clear\"\n class=\"clear-btn number-clear-btn\"\n >\n <mat-icon>cancel</mat-icon>\n </button>\n }\n\n <button\n mat-icon-button\n matSuffix\n aria-label=\"Search\"\n (click)=\"onLookup()\"\n class=\"search-btn\"\n >\n <mat-icon>search</mat-icon>\n </button>\n <mat-autocomplete\n #auto=\"matAutocomplete\"\n [displayWith]=\"displayFn.bind(this)\"\n (optionSelected)=\"optionSelected($event)\"\n >\n @for (option of filteredData(); track option.value) {\n <mat-option [value]=\"option.value\"> {{ option.name }} </mat-option>\n }\n </mat-autocomplete>\n\n <mat-error>{{ getErrorMessage() }}</mat-error>\n</mat-form-field>\n", styles: ["::ng-deep .ui-full-width{width:100%;max-width:500px}::ng-deep .mat-mdc-form-field-subscript-wrapper{width:auto;height:0}::ng-deep .clear-btn{background:none;border:none;box-shadow:none;cursor:pointer}::ng-deep .search-icon{background:none;border:none;box-shadow:none;opacity:1;cursor:pointer}::ng-deep .search-icon:hover,::ng-deep .clear-btn:hover{color:#f97a00}\n"], dependencies: [{ kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i1$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i1$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i1$1.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i1$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "component", type: i2$1.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "component", type: i2$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i2$1.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatDialogModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
578
+ }
579
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: AutoComplete, decorators: [{
580
+ type: Component,
581
+ args: [{ selector: 'ntybase-auto-complete', imports: [
582
+ MatFormFieldModule,
583
+ MatAutocompleteModule,
584
+ FormsModule,
585
+ MatInputModule,
586
+ MatIconModule,
587
+ ReactiveFormsModule,
588
+ MatDialogModule,
589
+ ], providers: [
590
+ {
591
+ provide: NG_VALUE_ACCESSOR,
592
+ useExisting: forwardRef(() => AutoComplete),
593
+ multi: true,
594
+ },
595
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<mat-form-field\n class=\"ui-full-width\"\n [appearance]=\"appearance()\"\n [class.required-field]=\"required()\"\n [class.required-with-value]=\"required() && value()\"\n>\n @if (label()) {\n <mat-label>{{ label() }}</mat-label>\n }\n\n <input\n type=\"text\"\n #autoCompleteInput\n matInput\n [(ngModel)]=\"value\"\n [matAutocomplete]=\"auto\"\n (input)=\"onInputChanged($event)\"\n (paste)=\"onPaste($event)\"\n [errorStateMatcher]=\"matcher\"\n [formControl]=\"control\"\n (contextmenu)=\"rightClick($event)\"\n />\n\n @if (value() && !disabled()) {\n <button\n mat-icon-button\n matSuffix\n (click)=\"clearInput()\"\n aria-label=\"Clear\"\n class=\"clear-btn number-clear-btn\"\n >\n <mat-icon>cancel</mat-icon>\n </button>\n }\n\n <button\n mat-icon-button\n matSuffix\n aria-label=\"Search\"\n (click)=\"onLookup()\"\n class=\"search-btn\"\n >\n <mat-icon>search</mat-icon>\n </button>\n <mat-autocomplete\n #auto=\"matAutocomplete\"\n [displayWith]=\"displayFn.bind(this)\"\n (optionSelected)=\"optionSelected($event)\"\n >\n @for (option of filteredData(); track option.value) {\n <mat-option [value]=\"option.value\"> {{ option.name }} </mat-option>\n }\n </mat-autocomplete>\n\n <mat-error>{{ getErrorMessage() }}</mat-error>\n</mat-form-field>\n", styles: ["::ng-deep .ui-full-width{width:100%;max-width:500px}::ng-deep .mat-mdc-form-field-subscript-wrapper{width:auto;height:0}::ng-deep .clear-btn{background:none;border:none;box-shadow:none;cursor:pointer}::ng-deep .search-icon{background:none;border:none;box-shadow:none;opacity:1;cursor:pointer}::ng-deep .search-icon:hover,::ng-deep .clear-btn:hover{color:#f97a00}\n"] }]
596
+ }], ctorParameters: () => [], propDecorators: { tableName: [{ type: i0.Input, args: [{ isSignal: true, alias: "tableName", required: false }] }], searchAfter: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchAfter", required: false }] }], fieldCode: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldCode", required: false }] }], fieldName: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldName", required: false }] }], lookupComponent: [{ type: i0.Input, args: [{ isSignal: true, alias: "lookupComponent", required: false }] }], lookupComponentPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "lookupComponentPath", required: false }] }], filterField: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterField", required: false }] }], filterFieldValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterFieldValue", required: false }] }], filterFieldNumeric: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterFieldNumeric", required: false }] }], filterFieldEquality: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterFieldEquality", required: false }] }], debounceTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceTime", required: false }] }], autoCompleteTrigger: [{
597
+ type: ViewChild,
598
+ args: [MatAutocompleteTrigger]
599
+ }], inputField: [{
600
+ type: ViewChild,
601
+ args: ['autoCompleteInput']
602
+ }] } });
603
+
604
+ class HttpError403 {
605
+ attemptedUrl = '';
606
+ routeSubscription;
607
+ router = inject(Router);
608
+ route = inject(ActivatedRoute);
609
+ location = inject(Location);
610
+ commonService = inject(CommonService);
611
+ constructor() {
612
+ // Router'dan state'i al
613
+ const navigation = this.router.currentNavigation();
614
+ this.attemptedUrl = navigation?.extras.state?.['attemptedUrl'] || '';
615
+ // Eğer state'ten gelmediyse, mevcut URL'i temizleyerek kullan
616
+ if (!this.attemptedUrl) {
617
+ this.attemptedUrl = this.getCleanUrlPath();
618
+ }
619
+ this.routeSubscription = this.router.events.subscribe(() => {
620
+ this.updateAttemptedUrl();
621
+ });
622
+ }
623
+ ngOnInit() {
624
+ // 1. URL'deki secondary outlet'leri temizle (örneğin: "(rightSidenav:...)")
625
+ this.cleanUrlFromFragments();
626
+ // 2. Hata mesajı için attemptedUrl'i güncelle
627
+ this.updateAttemptedUrl();
628
+ // 3. Right sidenav'ı kapat
629
+ this.commonService.toggleRightSidenav(false);
630
+ }
631
+ ngOnDestroy() {
632
+ if (this.routeSubscription) {
633
+ this.routeSubscription.unsubscribe();
634
+ }
635
+ }
636
+ /**
637
+ * URL'deki fragment ve outlet'leri temizler.
638
+ * Örnek: "/sys/NettyUserGroup(rightSidenav:...)" → "/sys/NettyUserGroup"
639
+ */
640
+ cleanUrlFromFragments() {
641
+ const currentUrl = this.router.url;
642
+ const cleanUrl = currentUrl.split('(')[0]; // "(" sonrasını sil
643
+ if (currentUrl !== cleanUrl) {
644
+ // URL'yi temizlenmiş haliyle değiştir (tarayıcı geçmişine eklemeden)
645
+ this.router.navigateByUrl(cleanUrl, {
646
+ replaceUrl: true,
647
+ skipLocationChange: false,
648
+ });
649
+ }
650
+ }
651
+ /**
652
+ * attemptedUrl'i güncel router URL'ine göre günceller
653
+ */
654
+ updateAttemptedUrl() {
655
+ this.attemptedUrl = this.getCleanUrlPath();
656
+ }
657
+ /**
658
+ * Temizlenmiş URL yolunu döndürür (queryParams ve fragment olmadan).
659
+ */
660
+ getCleanUrlPath() {
661
+ return this.router.url.split('?')[0].split('(')[0];
662
+ }
663
+ goHome() {
664
+ this.router.navigate(['/'], { replaceUrl: true });
665
+ this.commonService.toggleRightSidenav(false);
666
+ }
667
+ goBack() {
668
+ this.location.back();
669
+ }
670
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: HttpError403, deps: [], target: i0.ɵɵFactoryTarget.Component });
671
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.3", type: HttpError403, isStandalone: true, selector: "ntybase-http-error403", ngImport: i0, template: "<div class=\"error-container\">\n <div class=\"error-page\">\n <h1>403 - Eri\u015Fim Engellendi</h1>\n\n <p>\n <strong>\u00DCzg\u00FCn\u00FCz, {{ attemptedUrl }}</strong> sayfas\u0131na eri\u015Fim izniniz\n bulunmamaktad\u0131r.\n </p>\n\n <p class=\"error-detail\">\n Bu sayfay\u0131 g\u00F6r\u00FCnt\u00FClemek i\u00E7in gerekli yetkilere sahip de\u011Filsiniz. E\u011Fer bu\n bir hata oldu\u011Funu d\u00FC\u015F\u00FCn\u00FCyorsan\u0131z, l\u00FCtfen sistem y\u00F6neticinizle ileti\u015Fime\n ge\u00E7in.\n </p>\n <button mat-raised-button (click)=\"goHome()\" color=\"primary\" routerLink=\"/\">\n Ana Sayfaya D\u00F6n\n </button>\n <button mat-raised-button (click)=\"goBack()\" color=\"primary\" routerLink=\"/\">\n Geri D\u00F6n\n </button>\n </div>\n</div>\n", styles: [".error-container{background-image:var(--httperror403-bg-image, linear-gradient(rgba(0, 0, 0, .6), rgba(0, 0, 0, .6)), url(https://images.unsplash.com/photo-1519681393784-d120267933ba?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1124&q=100));background-blend-mode:overlay;height:100vh;width:100%;background-position:center;background-repeat:no-repeat;background-size:cover;display:flex;justify-content:center;align-items:center;color:#fff;font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif}.error-page{text-align:center;padding:3rem;max-width:650px;margin:0 auto;background-color:#000000b3;border-radius:16px;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);box-shadow:0 8px 32px #0000004d;animation:fadeIn .8s ease-in-out}.error-page h1{font-size:6rem;margin:0;color:var(--mat-sys-primary-container);text-shadow:0 4px 8px rgba(0,0,0,.3);font-weight:700}.error-page h3{font-size:1.8rem;margin-top:0;margin-bottom:1.5rem;color:#f8f9fa}.error-page p{margin:1.5rem 0;font-size:1.1rem;line-height:1.6;color:#e9ecef}.error-page strong{word-break:break-all;color:#ff6b6b;font-weight:500}button{margin:.5rem;padding:.8rem 2rem;font-size:1rem;font-weight:500;border-radius:50px;transition:all .3s ease;background-color:var(--mat-sys-primary);color:var(--mat-sys-on-primary)}button:hover{transform:translateY(-2px);box-shadow:0 6px 12px #00000026}button:active{transform:translateY(0)}@keyframes fadeIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@media(max-width:600px){.error-page{padding:2rem;width:90%}.error-page h1{font-size:4rem}.error-page h3{font-size:1.5rem}button{display:block;width:100%;margin:.5rem 0}}\n"] });
672
+ }
673
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: HttpError403, decorators: [{
674
+ type: Component,
675
+ args: [{ selector: 'ntybase-http-error403', imports: [], template: "<div class=\"error-container\">\n <div class=\"error-page\">\n <h1>403 - Eri\u015Fim Engellendi</h1>\n\n <p>\n <strong>\u00DCzg\u00FCn\u00FCz, {{ attemptedUrl }}</strong> sayfas\u0131na eri\u015Fim izniniz\n bulunmamaktad\u0131r.\n </p>\n\n <p class=\"error-detail\">\n Bu sayfay\u0131 g\u00F6r\u00FCnt\u00FClemek i\u00E7in gerekli yetkilere sahip de\u011Filsiniz. E\u011Fer bu\n bir hata oldu\u011Funu d\u00FC\u015F\u00FCn\u00FCyorsan\u0131z, l\u00FCtfen sistem y\u00F6neticinizle ileti\u015Fime\n ge\u00E7in.\n </p>\n <button mat-raised-button (click)=\"goHome()\" color=\"primary\" routerLink=\"/\">\n Ana Sayfaya D\u00F6n\n </button>\n <button mat-raised-button (click)=\"goBack()\" color=\"primary\" routerLink=\"/\">\n Geri D\u00F6n\n </button>\n </div>\n</div>\n", styles: [".error-container{background-image:var(--httperror403-bg-image, linear-gradient(rgba(0, 0, 0, .6), rgba(0, 0, 0, .6)), url(https://images.unsplash.com/photo-1519681393784-d120267933ba?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1124&q=100));background-blend-mode:overlay;height:100vh;width:100%;background-position:center;background-repeat:no-repeat;background-size:cover;display:flex;justify-content:center;align-items:center;color:#fff;font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif}.error-page{text-align:center;padding:3rem;max-width:650px;margin:0 auto;background-color:#000000b3;border-radius:16px;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);box-shadow:0 8px 32px #0000004d;animation:fadeIn .8s ease-in-out}.error-page h1{font-size:6rem;margin:0;color:var(--mat-sys-primary-container);text-shadow:0 4px 8px rgba(0,0,0,.3);font-weight:700}.error-page h3{font-size:1.8rem;margin-top:0;margin-bottom:1.5rem;color:#f8f9fa}.error-page p{margin:1.5rem 0;font-size:1.1rem;line-height:1.6;color:#e9ecef}.error-page strong{word-break:break-all;color:#ff6b6b;font-weight:500}button{margin:.5rem;padding:.8rem 2rem;font-size:1rem;font-weight:500;border-radius:50px;transition:all .3s ease;background-color:var(--mat-sys-primary);color:var(--mat-sys-on-primary)}button:hover{transform:translateY(-2px);box-shadow:0 6px 12px #00000026}button:active{transform:translateY(0)}@keyframes fadeIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@media(max-width:600px){.error-page{padding:2rem;width:90%}.error-page h1{font-size:4rem}.error-page h3{font-size:1.5rem}button{display:block;width:100%;margin:.5rem 0}}\n"] }]
676
+ }], ctorParameters: () => [] });
677
+
678
+ class HttpError404 {
679
+ attemptedUrl = '';
680
+ routeSubscription;
681
+ router = inject(Router);
682
+ route = inject(ActivatedRoute);
683
+ location = inject(Location);
684
+ commonService = inject(CommonService);
685
+ constructor() {
686
+ this.routeSubscription = this.router.events.subscribe(() => {
687
+ this.updateAttemptedUrl();
688
+ });
689
+ }
690
+ ngOnInit() {
691
+ // 1. URL'deki secondary outlet'leri temizle (örneğin: "(rightSidenav:...)")
692
+ this.cleanUrlFromFragments();
693
+ // 2. Hata mesajı için attemptedUrl'i güncelle
694
+ this.updateAttemptedUrl();
695
+ // 3. Right sidenav'ı kapat
696
+ this.commonService.toggleRightSidenav(false);
697
+ }
698
+ ngOnDestroy() {
699
+ if (this.routeSubscription) {
700
+ this.routeSubscription.unsubscribe();
701
+ }
702
+ }
703
+ /**
704
+ * URL'deki fragment ve outlet'leri temizler.
705
+ * Örnek: "/sys/NettyUserGroup(rightSidenav:...)" → "/sys/NettyUserGroup"
706
+ */
707
+ cleanUrlFromFragments() {
708
+ const currentUrl = this.router.url;
709
+ const cleanUrl = currentUrl.split('(')[0]; // "(" sonrasını sil
710
+ if (currentUrl !== cleanUrl) {
711
+ // URL'yi temizlenmiş haliyle değiştir (tarayıcı geçmişine eklemeden)
712
+ this.router.navigateByUrl(cleanUrl, {
713
+ replaceUrl: true,
714
+ skipLocationChange: false,
715
+ });
716
+ }
717
+ }
718
+ /**
719
+ * Temizlenmiş URL yolunu döndürür (queryParams ve fragment olmadan).
720
+ */
721
+ getCleanUrlPath() {
722
+ return this.router.url.split('?')[0].split('(')[0];
723
+ }
724
+ /**
725
+ * attemptedUrl'i güncel router URL'ine göre günceller
726
+ */
727
+ updateAttemptedUrl() {
728
+ this.attemptedUrl = this.getCleanUrlPath();
729
+ }
730
+ goHome() {
731
+ this.router.navigate(['/'], { replaceUrl: true });
732
+ this.commonService.toggleRightSidenav(false);
733
+ }
734
+ goBack() {
735
+ this.location.back();
736
+ }
737
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: HttpError404, deps: [], target: i0.ɵɵFactoryTarget.Component });
738
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.3", type: HttpError404, isStandalone: true, selector: "ntybase-http-error404", ngImport: i0, template: "<div class=\"error-container\">\n <div class=\"error-page\">\n <h1>404</h1>\n <h3>Sayfa Bulunamad\u0131</h3>\n <p><strong>{{ attemptedUrl }}</strong> adresi bulunamad\u0131.</p>\n <p>Bu i\u00E7erik ta\u015F\u0131nm\u0131\u015F veya silinmi\u015F olabilir.</p>\n <button mat-raised-button (click)=\"goHome()\" color=\"primary\" routerLink=\"/\">\n Ana Sayfaya D\u00F6n\n </button>\n <button mat-raised-button (click)=\"goBack()\" color=\"primary\" routerLink=\"/\">\n Geri D\u00F6n\n </button>\n </div>\n</div>\n", styles: [".error-container{background-image:var(--httperror404-bg-image, linear-gradient(rgba(0, 0, 0, .6), rgba(0, 0, 0, .6)), url(https://images.unsplash.com/photo-1519681393784-d120267933ba?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1124&q=100));background-blend-mode:overlay;height:100vh;width:100%;background-position:center;background-repeat:no-repeat;background-size:cover;display:flex;justify-content:center;align-items:center;color:#fff;font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif}.error-page{text-align:center;padding:3rem;max-width:650px;margin:0 auto;background-color:#000000b3;border-radius:16px;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);box-shadow:0 8px 32px #0000004d;animation:fadeIn .8s ease-in-out}.error-page h1{font-size:6rem;margin:0;color:var(--mat-sys-primary-container);text-shadow:0 4px 8px rgba(0,0,0,.3);font-weight:700}.error-page h3{font-size:1.8rem;margin-top:0;margin-bottom:1.5rem;color:#f8f9fa}.error-page p{margin:1.5rem 0;font-size:1.1rem;line-height:1.6;color:#e9ecef}.error-page strong{word-break:break-all;color:#ff6b6b;font-weight:500}button{margin:.5rem;padding:.8rem 2rem;font-size:1rem;font-weight:500;border-radius:50px;transition:all .3s ease;background-color:var(--mat-sys-primary);color:var(--mat-sys-on-primary)}button:hover{transform:translateY(-2px);box-shadow:0 6px 12px #00000026}button:active{transform:translateY(0)}@keyframes fadeIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@media(max-width:600px){.error-page{padding:2rem;width:90%}.error-page h1{font-size:4rem}.error-page h3{font-size:1.5rem}button{display:block;width:100%;margin:.5rem 0}}\n"] });
739
+ }
740
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: HttpError404, decorators: [{
741
+ type: Component,
742
+ args: [{ selector: 'ntybase-http-error404', imports: [], template: "<div class=\"error-container\">\n <div class=\"error-page\">\n <h1>404</h1>\n <h3>Sayfa Bulunamad\u0131</h3>\n <p><strong>{{ attemptedUrl }}</strong> adresi bulunamad\u0131.</p>\n <p>Bu i\u00E7erik ta\u015F\u0131nm\u0131\u015F veya silinmi\u015F olabilir.</p>\n <button mat-raised-button (click)=\"goHome()\" color=\"primary\" routerLink=\"/\">\n Ana Sayfaya D\u00F6n\n </button>\n <button mat-raised-button (click)=\"goBack()\" color=\"primary\" routerLink=\"/\">\n Geri D\u00F6n\n </button>\n </div>\n</div>\n", styles: [".error-container{background-image:var(--httperror404-bg-image, linear-gradient(rgba(0, 0, 0, .6), rgba(0, 0, 0, .6)), url(https://images.unsplash.com/photo-1519681393784-d120267933ba?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1124&q=100));background-blend-mode:overlay;height:100vh;width:100%;background-position:center;background-repeat:no-repeat;background-size:cover;display:flex;justify-content:center;align-items:center;color:#fff;font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif}.error-page{text-align:center;padding:3rem;max-width:650px;margin:0 auto;background-color:#000000b3;border-radius:16px;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);box-shadow:0 8px 32px #0000004d;animation:fadeIn .8s ease-in-out}.error-page h1{font-size:6rem;margin:0;color:var(--mat-sys-primary-container);text-shadow:0 4px 8px rgba(0,0,0,.3);font-weight:700}.error-page h3{font-size:1.8rem;margin-top:0;margin-bottom:1.5rem;color:#f8f9fa}.error-page p{margin:1.5rem 0;font-size:1.1rem;line-height:1.6;color:#e9ecef}.error-page strong{word-break:break-all;color:#ff6b6b;font-weight:500}button{margin:.5rem;padding:.8rem 2rem;font-size:1rem;font-weight:500;border-radius:50px;transition:all .3s ease;background-color:var(--mat-sys-primary);color:var(--mat-sys-on-primary)}button:hover{transform:translateY(-2px);box-shadow:0 6px 12px #00000026}button:active{transform:translateY(0)}@keyframes fadeIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@media(max-width:600px){.error-page{padding:2rem;width:90%}.error-page h1{font-size:4rem}.error-page h3{font-size:1.5rem}button{display:block;width:100%;margin:.5rem 0}}\n"] }]
743
+ }], ctorParameters: () => [] });
744
+
745
+ class LeftSidenav {
746
+ authService = inject(AuthenticationService);
747
+ router = inject(Router);
748
+ menuService = inject(NettyMenuService);
749
+ commonService = inject(CommonService);
750
+ environmentProxy = inject(EnvironmentProxy);
751
+ credentialsService = inject(CredentialsService);
752
+ imageService = inject(NettyImageService);
753
+ elementRef = inject(ElementRef);
754
+ username = signal('', ...(ngDevMode ? [{ debugName: "username" }] : []));
755
+ profileImage = signal('', ...(ngDevMode ? [{ debugName: "profileImage" }] : []));
756
+ version = this.environmentProxy.version();
757
+ isMinimized = input(false, ...(ngDevMode ? [{ debugName: "isMinimized" }] : []));
758
+ toggleMinimize = output();
759
+ onToggleMinimize() {
760
+ this.toggleMinimize.emit();
761
+ }
762
+ logout() {
763
+ this.authService.logout().subscribe({
764
+ next: (success) => {
765
+ if (success) {
766
+ const cleanUrl = this.commonService.getCleanUrlPath();
767
+ this.router.navigateByUrl(cleanUrl, { replaceUrl: true }).then(() => {
768
+ this.router.navigate(['/login']);
769
+ });
770
+ }
771
+ },
772
+ error: (err) => {
773
+ console.error('Logout failed:', err);
774
+ },
775
+ });
776
+ }
777
+ // Menu Search
778
+ menuItems = signal([], ...(ngDevMode ? [{ debugName: "menuItems" }] : []));
779
+ searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
780
+ originalMenuItems = signal([], ...(ngDevMode ? [{ debugName: "originalMenuItems" }] : []));
781
+ constructor() {
782
+ effect(() => {
783
+ const fetchedMenu = this.menuService.menu();
784
+ if (fetchedMenu) {
785
+ const processedItems = this.processMenuItems(fetchedMenu);
786
+ this.originalMenuItems.set(processedItems);
787
+ }
788
+ else {
789
+ this.originalMenuItems.set([]);
790
+ }
791
+ });
792
+ this.loadUserInfo();
793
+ }
794
+ ngOnInit() {
795
+ // Application name gelene kadar 3 kez dene
796
+ this.trySetMenuName(0);
797
+ }
798
+ trySetMenuName(attempt) {
799
+ const appName = this.environmentProxy.getApplicationName();
800
+ if (appName && appName.trim()) {
801
+ // Application name geldi, menüyü yükle
802
+ this.menuService.setMenuName(appName);
803
+ }
804
+ else if (attempt < 3) {
805
+ // Henüz gelmedi, 300ms sonra tekrar dene
806
+ setTimeout(() => {
807
+ this.trySetMenuName(attempt + 1);
808
+ }, 300);
809
+ }
810
+ else {
811
+ // 3 denemeden sonra hala gelmedi, boş string ile dene
812
+ console.warn('Application name not found, using empty string');
813
+ this.menuService.setMenuName('');
814
+ }
815
+ }
816
+ ngAfterViewInit() {
817
+ setTimeout(() => {
818
+ this.loadUserInfo();
819
+ }, 0);
820
+ }
821
+ loadUserInfo() {
822
+ const credentials = this.credentialsService.getCredentials();
823
+ if (credentials && credentials.username) {
824
+ this.username.set(credentials.username);
825
+ this.generateProfileImage(credentials.username);
826
+ }
827
+ }
828
+ generateProfileImage(username) {
829
+ const initials = this.getInitials(username);
830
+ const primaryColor = this.getCssVariableValue('--mat-sys-primary');
831
+ const imageUrl = this.imageService.generateTextImage(initials, 40, 40, primaryColor || '#4ECDC4', '#FFFFFF', 'rgba(0, 0, 0, 0.2)');
832
+ this.profileImage.set(imageUrl);
833
+ }
834
+ getCssVariableValue(variableName) {
835
+ if (typeof window === 'undefined')
836
+ return null;
837
+ const element = this.elementRef.nativeElement;
838
+ return (getComputedStyle(element).getPropertyValue(variableName).trim() || null);
839
+ }
840
+ getInitials(username) {
841
+ if (!username)
842
+ return 'U';
843
+ const names = username.trim().split(' ');
844
+ if (names.length === 1) {
845
+ return names[0].charAt(0).toUpperCase();
846
+ }
847
+ else {
848
+ return (names[0].charAt(0) + names[names.length - 1].charAt(0)).toUpperCase();
849
+ }
850
+ }
851
+ processMenuItems(apiResponse) {
852
+ if (!apiResponse || !Array.isArray(apiResponse)) {
853
+ return [];
854
+ }
855
+ return apiResponse.map((item) => ({
856
+ name: item.label || item.name,
857
+ icon: item.icon || '',
858
+ link: item.route || item.link || '#',
859
+ expanded: false,
860
+ children: item.children ? this.processMenuItems(item.children) : [],
861
+ }));
862
+ }
863
+ filteredMenuItems = computed(() => {
864
+ const term = this.searchTerm().toLowerCase();
865
+ if (!term)
866
+ return this.originalMenuItems();
867
+ return this.filterMenuItems(this.originalMenuItems(), term);
868
+ }, ...(ngDevMode ? [{ debugName: "filteredMenuItems" }] : []));
869
+ filterMenuItems(items, term) {
870
+ const normalizedTerm = this.commonService.normalizeTurkish(term);
871
+ return items
872
+ .map((item) => {
873
+ const filteredChildren = item.children
874
+ ? this.filterMenuItems(item.children, term)
875
+ : [];
876
+ const normalizedName = this.commonService.normalizeTurkish(item.name);
877
+ const matches = normalizedName.includes(normalizedTerm) ||
878
+ filteredChildren.length > 0;
879
+ return matches
880
+ ? {
881
+ ...item,
882
+ children: filteredChildren,
883
+ expanded: matches || filteredChildren.length > 0,
884
+ }
885
+ : null;
886
+ })
887
+ .filter((item) => item !== null);
888
+ }
889
+ toggleSubMenu(item) {
890
+ item.expanded = !item.expanded;
891
+ }
892
+ onSearch(term) {
893
+ this.searchTerm.set(term);
894
+ }
895
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: LeftSidenav, deps: [], target: i0.ɵɵFactoryTarget.Component });
896
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: LeftSidenav, isStandalone: true, selector: "ntybase-left-sidenav", inputs: { isMinimized: { classPropertyName: "isMinimized", publicName: "isMinimized", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { toggleMinimize: "toggleMinimize" }, ngImport: i0, template: "<div class=\"sidenav-content-wrapper\">\n <!-- Minimize Icon -->\n <button mat-icon-button (click)=\"onToggleMinimize()\" class=\"minimize-button\">\n <mat-icon class=\"minimize-icon\">\n {{ isMinimized() ? \"chevron_right\" : \"chevron_left\" }}\n </mat-icon>\n </button>\n\n <!-- Profile -->\n <div class=\"profile-section\">\n <button mat-button [matMenuTriggerFor]=\"profileMenu\" class=\"profile-button\">\n <img [src]=\"profileImage()\" [alt]=\"username()\" class=\"profile-image\" />\n @if(!isMinimized()){\n <div class=\"profile-info\">\n <p class=\"profile-name\">{{ username() }}</p>\n </div>\n }\n </button>\n\n <mat-menu #profileMenu=\"matMenu\" xPosition=\"before\">\n <button mat-menu-item disabled>\n <mat-icon>account_circle</mat-icon>\n <span>{{ '@profile' | translate }}</span>\n </button>\n <button mat-menu-item mat-button disabled>\n <mat-icon>swap_horiz</mat-icon>\n <span>{{ '@changeCompany' | translate }}</span>\n </button>\n <button mat-menu-item mat-button (click)=\"logout()\">\n <mat-icon>logout</mat-icon>\n <span>{{ '@logout' | translate }}</span>\n </button>\n </mat-menu>\n </div>\n\n <!-- Search Input -->\n @if (!isMinimized()){\n <ntyui-search-input\n class=\"search-input\"\n [label]=\"'@search' | translate\"\n [placeholder]=\"'@placeholderSearch' | translate\"\n [appearance]=\"'outline'\"\n (search)=\"onSearch($event)\"\n >\n </ntyui-search-input>\n }\n\n <!-- Menu -->\n @if (!isMinimized()) {\n <mat-nav-list>\n <div class=\"sidebar\">\n <ul>\n <!-- Recursive menu render -->\n @let items = filteredMenuItems(); @let level = 0;\n <ng-container\n [ngTemplateOutlet]=\"menuItemsTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: items, level: level }\"\n ></ng-container>\n </ul>\n </div>\n </mat-nav-list>\n }\n\n <!-- Footer -->\n <div class=\"sidenav-footer\">\n <button mat-button [matMenuTriggerFor]=\"footerMenu\" class=\"footer-button\">\n @if (!isMinimized()) {\n <div class=\"footer-text\">\n <div class=\"company-name\">\n <p>&copy; 2025 AXIS</p>\n </div>\n </div>\n }\n </button>\n <div class=\"version\">\n <span class=\"version\">v{{version}}</span>\n </div>\n </div>\n\n <mat-menu #footerMenu=\"matMenu\" xPosition=\"before\">\n <button mat-menu-item disabled>\n <mat-icon>swap_horiz</mat-icon>\n {{ '@changeCompany' | translate }}\n </button>\n <button mat-menu-item mat-button (click)=\"logout()\">\n <mat-icon>logout</mat-icon>\n <span>{{ '@logout' | translate }}</span>\n </button>\n </mat-menu>\n</div>\n\n<!-- Recursive Menu Template -->\n<ng-template #menuItemsTemplate let-items let-level=\"level\">\n @for (item of items; track item.name) {\n <li [class]=\"'menu-level-' + level\">\n <!-- Leaf node (no children) -->\n @if (!item.children || item.children.length === 0) {\n <a\n [routerLink]=\"item.link\"\n routerLinkActive=\"active\"\n [class]=\"'menu-link level-' + level\"\n >\n <mat-icon>{{ item.icon }}</mat-icon>\n <span>{{ item.name }}</span>\n </a>\n }\n\n <!-- Parent node with children -->\n @if (item.children && item.children.length > 0) {\n <a\n (click)=\"toggleSubMenu(item)\"\n [class.expanded]=\"item.expanded\"\n [class]=\"'menu-link has-children level-' + level\"\n >\n <mat-icon>{{ item.icon }}</mat-icon>\n <span>{{ item.name }}</span>\n <mat-icon [class.rotated]=\"item.expanded\" class=\"expand-icon\">\n {{ item.expanded ? 'expand_less' : 'expand_more' }}\n </mat-icon>\n </a>\n }\n\n <!-- Recursive submenu -->\n @if (item.children && item.children.length > 0 && item.expanded) {\n <ul [class]=\"'submenu level-' + (level + 1)\">\n <ng-container\n *ngTemplateOutlet=\"menuItemsTemplate; context: { $implicit: item.children, level: level + 1 }\"\n ></ng-container>\n </ul>\n }\n </li>\n }\n</ng-template>\n", styles: [".profile-section{text-align:center;cursor:pointer;transition:all .3s ease;display:flex;align-items:center;flex-direction:column;position:relative}.profile-section:hover{transform:scale(.98)}.profile-section .profile-button{display:flex;flex-direction:column;align-items:center;background:none;border:none;padding:0;cursor:pointer;width:100%}.profile-image{width:60px;height:60px;border-radius:50%;object-fit:cover;margin:0 auto 8px;display:block;border:2px solid white;box-shadow:0 2px 4px #0000001a;transition:all .3s ease}.profile-name{font-weight:500;font-size:1rem;color:inherit;transition:opacity .3s ease;margin-top:8px}:host-context(.minimized) .profile-section{padding:10px 0}:host-context(.minimized) .profile-image{width:40px;height:40px;margin:0 auto}.sidenav-footer{position:static;bottom:0;left:0;right:0;padding:16px;color:inherit;font-size:12px;border-top:1px solid rgba(0,0,0,.1);transition:all .3s ease;display:flex;align-items:center;justify-content:space-between;margin-top:auto}.sidenav-footer .version{font-size:10px;margin-top:4px}.footer-button{border:none;background:none;cursor:pointer}:host-context(.minimized) .version{margin-right:7px}:host-context(.minimized) .sidenav-content-wrapper{overflow:hidden}.sidenav-content-wrapper{position:relative;height:100%;display:flex;flex-direction:column;overflow-x:hidden}.sidebar{flex:1;overflow:auto}.sidebar ul{list-style:none;padding:0;margin:0}.sidebar li{margin-bottom:4px;position:relative}.sidebar li a{display:flex;align-items:center;padding:12px 0 0;border-radius:6px;color:var(--mat-sys-primary);text-decoration:none;transition:all .2s ease;position:relative;overflow:hidden}.sidebar li a:hover{background-color:var(--mat-nty-save-record-header-bar);color:var(--mat-nty-save-record-identifier)}.sidebar li a.active{background-color:var(--mat-nty-save-record-header-bar);color:var(--mat-nty-save-record-identifier);font-weight:500}.sidebar li a.active:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background-color:var(--mat-nty-save-record-header-bar);border-radius:3px 0 0 3px}.sidebar mat-icon{font-size:20px;width:20px;height:20px;color:inherit}.sidebar span{font-size:.875rem;white-space:nowrap}.menu-link.level-2{margin-left:20px;font-size:.82rem}.menu-link.level-3{margin-left:20px;font-size:.8rem}.menu-link.level-4{margin-left:20px;font-size:.78rem}.submenu{padding-left:12px;margin-top:4px}.submenu li a{padding:10px 16px 0 20px}.submenu mat-icon{font-size:18px}.sidebar li a mat-icon:last-child{margin-left:auto;margin-right:0;font-size:18px;color:#5f6368}:host-context(.minimized) .sidebar li a{justify-content:center;padding:12px 0}:host-context(.minimized) .sidenav-content-wrapper button[mat-icon-button]{position:absolute;right:-8px;top:50%;z-index:999}:host-context(.minimized) .sidebar mat-icon{margin-right:0}:host-context(.minimized) .sidebar span,:host-context(.minimized) .sidebar li a mat-icon:last-child{display:none}:host-context(.minimized) .submenu{display:none}.sidenav-content-wrapper button[mat-icon-button]{position:absolute;right:-8px;top:1px;z-index:999}.search-input{margin-top:10px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i3$2.MatNavList, selector: "mat-nav-list", exportAs: ["matNavList"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i4$1.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i4$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i4$1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i5.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "component", type: NettyUISearchInput, selector: "ntyui-search-input", outputs: ["search"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] });
897
+ }
898
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: LeftSidenav, decorators: [{
899
+ type: Component,
900
+ args: [{ selector: 'ntybase-left-sidenav', imports: [
901
+ CommonModule,
902
+ MatIconModule,
903
+ MatListModule,
904
+ MatMenuModule,
905
+ MatMenuTrigger,
906
+ RouterLink,
907
+ RouterModule,
908
+ NettyUISearchInput,
909
+ TranslateModule,
910
+ ], template: "<div class=\"sidenav-content-wrapper\">\n <!-- Minimize Icon -->\n <button mat-icon-button (click)=\"onToggleMinimize()\" class=\"minimize-button\">\n <mat-icon class=\"minimize-icon\">\n {{ isMinimized() ? \"chevron_right\" : \"chevron_left\" }}\n </mat-icon>\n </button>\n\n <!-- Profile -->\n <div class=\"profile-section\">\n <button mat-button [matMenuTriggerFor]=\"profileMenu\" class=\"profile-button\">\n <img [src]=\"profileImage()\" [alt]=\"username()\" class=\"profile-image\" />\n @if(!isMinimized()){\n <div class=\"profile-info\">\n <p class=\"profile-name\">{{ username() }}</p>\n </div>\n }\n </button>\n\n <mat-menu #profileMenu=\"matMenu\" xPosition=\"before\">\n <button mat-menu-item disabled>\n <mat-icon>account_circle</mat-icon>\n <span>{{ '@profile' | translate }}</span>\n </button>\n <button mat-menu-item mat-button disabled>\n <mat-icon>swap_horiz</mat-icon>\n <span>{{ '@changeCompany' | translate }}</span>\n </button>\n <button mat-menu-item mat-button (click)=\"logout()\">\n <mat-icon>logout</mat-icon>\n <span>{{ '@logout' | translate }}</span>\n </button>\n </mat-menu>\n </div>\n\n <!-- Search Input -->\n @if (!isMinimized()){\n <ntyui-search-input\n class=\"search-input\"\n [label]=\"'@search' | translate\"\n [placeholder]=\"'@placeholderSearch' | translate\"\n [appearance]=\"'outline'\"\n (search)=\"onSearch($event)\"\n >\n </ntyui-search-input>\n }\n\n <!-- Menu -->\n @if (!isMinimized()) {\n <mat-nav-list>\n <div class=\"sidebar\">\n <ul>\n <!-- Recursive menu render -->\n @let items = filteredMenuItems(); @let level = 0;\n <ng-container\n [ngTemplateOutlet]=\"menuItemsTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: items, level: level }\"\n ></ng-container>\n </ul>\n </div>\n </mat-nav-list>\n }\n\n <!-- Footer -->\n <div class=\"sidenav-footer\">\n <button mat-button [matMenuTriggerFor]=\"footerMenu\" class=\"footer-button\">\n @if (!isMinimized()) {\n <div class=\"footer-text\">\n <div class=\"company-name\">\n <p>&copy; 2025 AXIS</p>\n </div>\n </div>\n }\n </button>\n <div class=\"version\">\n <span class=\"version\">v{{version}}</span>\n </div>\n </div>\n\n <mat-menu #footerMenu=\"matMenu\" xPosition=\"before\">\n <button mat-menu-item disabled>\n <mat-icon>swap_horiz</mat-icon>\n {{ '@changeCompany' | translate }}\n </button>\n <button mat-menu-item mat-button (click)=\"logout()\">\n <mat-icon>logout</mat-icon>\n <span>{{ '@logout' | translate }}</span>\n </button>\n </mat-menu>\n</div>\n\n<!-- Recursive Menu Template -->\n<ng-template #menuItemsTemplate let-items let-level=\"level\">\n @for (item of items; track item.name) {\n <li [class]=\"'menu-level-' + level\">\n <!-- Leaf node (no children) -->\n @if (!item.children || item.children.length === 0) {\n <a\n [routerLink]=\"item.link\"\n routerLinkActive=\"active\"\n [class]=\"'menu-link level-' + level\"\n >\n <mat-icon>{{ item.icon }}</mat-icon>\n <span>{{ item.name }}</span>\n </a>\n }\n\n <!-- Parent node with children -->\n @if (item.children && item.children.length > 0) {\n <a\n (click)=\"toggleSubMenu(item)\"\n [class.expanded]=\"item.expanded\"\n [class]=\"'menu-link has-children level-' + level\"\n >\n <mat-icon>{{ item.icon }}</mat-icon>\n <span>{{ item.name }}</span>\n <mat-icon [class.rotated]=\"item.expanded\" class=\"expand-icon\">\n {{ item.expanded ? 'expand_less' : 'expand_more' }}\n </mat-icon>\n </a>\n }\n\n <!-- Recursive submenu -->\n @if (item.children && item.children.length > 0 && item.expanded) {\n <ul [class]=\"'submenu level-' + (level + 1)\">\n <ng-container\n *ngTemplateOutlet=\"menuItemsTemplate; context: { $implicit: item.children, level: level + 1 }\"\n ></ng-container>\n </ul>\n }\n </li>\n }\n</ng-template>\n", styles: [".profile-section{text-align:center;cursor:pointer;transition:all .3s ease;display:flex;align-items:center;flex-direction:column;position:relative}.profile-section:hover{transform:scale(.98)}.profile-section .profile-button{display:flex;flex-direction:column;align-items:center;background:none;border:none;padding:0;cursor:pointer;width:100%}.profile-image{width:60px;height:60px;border-radius:50%;object-fit:cover;margin:0 auto 8px;display:block;border:2px solid white;box-shadow:0 2px 4px #0000001a;transition:all .3s ease}.profile-name{font-weight:500;font-size:1rem;color:inherit;transition:opacity .3s ease;margin-top:8px}:host-context(.minimized) .profile-section{padding:10px 0}:host-context(.minimized) .profile-image{width:40px;height:40px;margin:0 auto}.sidenav-footer{position:static;bottom:0;left:0;right:0;padding:16px;color:inherit;font-size:12px;border-top:1px solid rgba(0,0,0,.1);transition:all .3s ease;display:flex;align-items:center;justify-content:space-between;margin-top:auto}.sidenav-footer .version{font-size:10px;margin-top:4px}.footer-button{border:none;background:none;cursor:pointer}:host-context(.minimized) .version{margin-right:7px}:host-context(.minimized) .sidenav-content-wrapper{overflow:hidden}.sidenav-content-wrapper{position:relative;height:100%;display:flex;flex-direction:column;overflow-x:hidden}.sidebar{flex:1;overflow:auto}.sidebar ul{list-style:none;padding:0;margin:0}.sidebar li{margin-bottom:4px;position:relative}.sidebar li a{display:flex;align-items:center;padding:12px 0 0;border-radius:6px;color:var(--mat-sys-primary);text-decoration:none;transition:all .2s ease;position:relative;overflow:hidden}.sidebar li a:hover{background-color:var(--mat-nty-save-record-header-bar);color:var(--mat-nty-save-record-identifier)}.sidebar li a.active{background-color:var(--mat-nty-save-record-header-bar);color:var(--mat-nty-save-record-identifier);font-weight:500}.sidebar li a.active:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background-color:var(--mat-nty-save-record-header-bar);border-radius:3px 0 0 3px}.sidebar mat-icon{font-size:20px;width:20px;height:20px;color:inherit}.sidebar span{font-size:.875rem;white-space:nowrap}.menu-link.level-2{margin-left:20px;font-size:.82rem}.menu-link.level-3{margin-left:20px;font-size:.8rem}.menu-link.level-4{margin-left:20px;font-size:.78rem}.submenu{padding-left:12px;margin-top:4px}.submenu li a{padding:10px 16px 0 20px}.submenu mat-icon{font-size:18px}.sidebar li a mat-icon:last-child{margin-left:auto;margin-right:0;font-size:18px;color:#5f6368}:host-context(.minimized) .sidebar li a{justify-content:center;padding:12px 0}:host-context(.minimized) .sidenav-content-wrapper button[mat-icon-button]{position:absolute;right:-8px;top:50%;z-index:999}:host-context(.minimized) .sidebar mat-icon{margin-right:0}:host-context(.minimized) .sidebar span,:host-context(.minimized) .sidebar li a mat-icon:last-child{display:none}:host-context(.minimized) .submenu{display:none}.sidenav-content-wrapper button[mat-icon-button]{position:absolute;right:-8px;top:1px;z-index:999}.search-input{margin-top:10px}\n"] }]
911
+ }], ctorParameters: () => [], propDecorators: { isMinimized: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMinimized", required: false }] }], toggleMinimize: [{ type: i0.Output, args: ["toggleMinimize"] }] } });
912
+
913
+ class Theme {
914
+ THEME_STORAGE_KEY = 'app-theme';
915
+ appTheme = signal('light', ...(ngDevMode ? [{ debugName: "appTheme" }] : []));
916
+ themes = [
917
+ { name: 'light', icon: 'light_mode' },
918
+ { name: 'dark', icon: 'dark_mode' },
919
+ ];
920
+ selectedTheme = computed(() => this.themes.find((theme) => theme.name === this.appTheme()), ...(ngDevMode ? [{ debugName: "selectedTheme" }] : []));
921
+ constructor() {
922
+ const savedTheme = this.getSavedTheme();
923
+ if (savedTheme) {
924
+ this.appTheme.set(savedTheme);
925
+ }
926
+ effect(() => {
927
+ const appTheme = this.appTheme();
928
+ document.body.style.setProperty('color-scheme', appTheme);
929
+ document.body.setAttribute('data-ag-theme-mode', appTheme);
930
+ });
931
+ }
932
+ getThemes() {
933
+ return this.themes;
934
+ }
935
+ setTheme(theme) {
936
+ this.appTheme.set(theme);
937
+ this.saveTheme(theme);
938
+ }
939
+ getSavedTheme() {
940
+ if (typeof window !== 'undefined' && window.localStorage) {
941
+ const saved = localStorage.getItem(this.THEME_STORAGE_KEY);
942
+ return saved === 'light' || saved === 'dark' ? saved : null;
943
+ }
944
+ return null;
945
+ }
946
+ saveTheme(theme) {
947
+ if (typeof window !== 'undefined' && window.localStorage) {
948
+ localStorage.setItem(this.THEME_STORAGE_KEY, theme);
949
+ }
950
+ }
951
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: Theme, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
952
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: Theme, providedIn: 'root' });
953
+ }
954
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: Theme, decorators: [{
955
+ type: Injectable,
956
+ args: [{
957
+ providedIn: 'root',
958
+ }]
959
+ }], ctorParameters: () => [] });
960
+
961
+ class ColorPalette {
962
+ THEME_STORAGE_KEY = 'selected-theme';
963
+ themes = [];
964
+ currentTheme = signal(null, ...(ngDevMode ? [{ debugName: "currentTheme" }] : []));
965
+ setThemes(customThemes) {
966
+ this.themes = customThemes;
967
+ const savedThemeId = this.getSavedTheme();
968
+ const themeToSet = savedThemeId
969
+ ? this.themes.find((t) => t.id === savedThemeId)
970
+ : this.themes[0];
971
+ if (themeToSet) {
972
+ this.currentTheme.set(themeToSet);
973
+ }
974
+ else if (this.themes.length > 0) {
975
+ this.currentTheme.set(this.themes[0]);
976
+ }
977
+ }
978
+ getThemes() {
979
+ return this.themes;
980
+ }
981
+ setTheme(themeId) {
982
+ const theme = this.themes.find((t) => t.id === themeId);
983
+ if (theme) {
984
+ this.currentTheme.set(theme);
985
+ this.saveTheme(themeId);
986
+ }
987
+ }
988
+ getSavedTheme() {
989
+ if (typeof window !== 'undefined' && window.localStorage) {
990
+ return localStorage.getItem(this.THEME_STORAGE_KEY);
991
+ }
992
+ return null;
993
+ }
994
+ saveTheme(themeId) {
995
+ if (typeof window !== 'undefined' && window.localStorage) {
996
+ localStorage.setItem(this.THEME_STORAGE_KEY, themeId);
997
+ }
998
+ }
999
+ updateThemeClass = effect(() => {
1000
+ const theme = this.currentTheme();
1001
+ if (theme) {
1002
+ document.body.classList.remove(...this.themes.map((t) => `${t.id}-theme`));
1003
+ document.body.classList.add(`${theme.id}-theme`);
1004
+ }
1005
+ }, ...(ngDevMode ? [{ debugName: "updateThemeClass" }] : []));
1006
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ColorPalette, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1007
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ColorPalette, providedIn: 'root' });
1008
+ }
1009
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ColorPalette, decorators: [{
1010
+ type: Injectable,
1011
+ args: [{
1012
+ providedIn: 'root',
1013
+ }]
1014
+ }] });
1015
+
1016
+ class Toolbar {
1017
+ themeService = inject(Theme);
1018
+ colorPaletteService = inject(ColorPalette);
1019
+ i18nService = inject(I18nService);
1020
+ router = inject(Router);
1021
+ toggleSidenav = output();
1022
+ onToggleSidenav() {
1023
+ this.toggleSidenav.emit();
1024
+ }
1025
+ // Language
1026
+ icon = model(false, ...(ngDevMode ? [{ debugName: "icon" }] : []));
1027
+ get currentLanguage() {
1028
+ return this.i18nService.language;
1029
+ }
1030
+ get languages() {
1031
+ return this.i18nService.supportedLanguages;
1032
+ }
1033
+ setLanguage(language) {
1034
+ this.i18nService.language = language;
1035
+ setTimeout(() => {
1036
+ window.location.reload();
1037
+ }, 100);
1038
+ }
1039
+ getCurrentLanguageIcon() {
1040
+ switch (this.i18nService.language) {
1041
+ case 'Türkçe':
1042
+ return 'fi fi-tr';
1043
+ case 'English':
1044
+ return 'fi fi-us';
1045
+ default:
1046
+ return 'fi fi-tr';
1047
+ }
1048
+ }
1049
+ getLanguageIcon(language) {
1050
+ switch (language) {
1051
+ case 'Türkçe':
1052
+ return 'fi fi-tr';
1053
+ case 'English':
1054
+ return 'fi fi-us';
1055
+ default:
1056
+ return 'fi fi-tr';
1057
+ }
1058
+ }
1059
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: Toolbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
1060
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: Toolbar, isStandalone: true, selector: "ntybase-toolbar", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { toggleSidenav: "toggleSidenav", icon: "iconChange" }, ngImport: i0, template: "<mat-toolbar class=\"sidenav-header\">\n <div class=\"left-section\">\n <button mat-icon-button (click)=\"onToggleSidenav()\" class=\"menu-button\">\n <mat-icon>menu</mat-icon>\n </button>\n <span>{{'app_name' | translate }}</span>\n\n <!-- Language Selection -->\n <div class=\"language-toggle\">\n @if (icon()) {\n <button mat-icon-button [matMenuTriggerFor]=\"languageMenu\">\n <span class=\"{{ getCurrentLanguageIcon() }}\"></span>\n </button>\n } @else {\n <button\n mat-raised-button\n color=\"primary\"\n [matMenuTriggerFor]=\"languageMenu\"\n >\n {{ currentLanguage }}\n </button>\n }\n </div>\n\n <!-- Language Menu -->\n <mat-menu #languageMenu=\"matMenu\">\n @for (language of languages; track language) {\n <button mat-menu-item (click)=\"setLanguage(language)\">\n <span class=\"{{ getLanguageIcon(language) }}\"></span>\n {{ language }}\n </button>\n }\n </mat-menu>\n </div>\n\n <div class=\"spacer\"></div>\n\n <div class=\"flex-stretch\"></div>\n <button\n mat-icon-button\n [mat-menu-trigger-for]=\"themeMenu\"\n class=\"theme-button\"\n >\n <mat-icon>{{ themeService.selectedTheme()?.icon }}</mat-icon>\n </button>\n <mat-menu #themeMenu=\"matMenu\">\n @for (theme of themeService.getThemes(); track theme.name) {\n <button\n [class.selected-theme]=\"themeService.selectedTheme()?.name === theme.name\"\n mat-menu-item\n (click)=\"themeService.setTheme(theme.name)\"\n >\n <mat-icon>{{ theme.icon }}</mat-icon>\n <span>{{ theme.name | titlecase }}</span>\n </button>\n }\n </mat-menu>\n <button\n mat-icon-button\n [matMenuTriggerFor]=\"customThemeMenu\"\n class=\"custom-theme-button\"\n >\n <mat-icon>format_color_fill</mat-icon>\n </button>\n <mat-menu #customThemeMenu=\"matMenu\">\n @for (customTheme of colorPaletteService.getThemes(); track customTheme.id)\n {\n <button\n mat-menu-item\n (click)=\"colorPaletteService.setTheme(customTheme.id)\"\n >\n <div class=\"theme-menu-item\">\n <div\n class=\"color-preview\"\n [style.background-color]=\"customTheme.primary\"\n ></div>\n <span>{{ customTheme.displayName }}</span>\n </div>\n </button>\n }\n </mat-menu>\n</mat-toolbar>\n", styles: ["mat-toolbar{position:fixed;top:0;left:0;right:0;z-index:3;box-shadow:0 1px 5px #0000001a;display:flex;align-items:center}.sidenav-header{display:flex;justify-content:space-between;align-items:center;padding:16px;margin:0;font-weight:500}.sidenav-header .left-section{display:flex;align-items:center;gap:16px}.sidenav-header .spacer{flex:1 1 auto}.flex-stretch{flex:1 0 auto}.theme-menu-item{display:flex;align-items:center;gap:12px}.color-preview{width:24px;height:24px;border-radius:50%}::ng-deep .theme-button{border:none;background:none;margin-right:15px;cursor:pointer}::ng-deep .custom-theme-button{border:none;background:none;cursor:pointer}::ng-deep .menu-button{border:none;background:none;cursor:pointer}.language-button{margin:0 auto}::ng-deep button{border:none;background:none;cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i4$1.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i4$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i4$1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatToolbarModule }, { kind: "component", type: i3$3.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] });
1061
+ }
1062
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: Toolbar, decorators: [{
1063
+ type: Component,
1064
+ args: [{ selector: 'ntybase-toolbar', imports: [
1065
+ MatIconModule,
1066
+ MatMenuModule,
1067
+ MatToolbarModule,
1068
+ CommonModule,
1069
+ TitleCasePipe,
1070
+ TranslateModule,
1071
+ ], template: "<mat-toolbar class=\"sidenav-header\">\n <div class=\"left-section\">\n <button mat-icon-button (click)=\"onToggleSidenav()\" class=\"menu-button\">\n <mat-icon>menu</mat-icon>\n </button>\n <span>{{'app_name' | translate }}</span>\n\n <!-- Language Selection -->\n <div class=\"language-toggle\">\n @if (icon()) {\n <button mat-icon-button [matMenuTriggerFor]=\"languageMenu\">\n <span class=\"{{ getCurrentLanguageIcon() }}\"></span>\n </button>\n } @else {\n <button\n mat-raised-button\n color=\"primary\"\n [matMenuTriggerFor]=\"languageMenu\"\n >\n {{ currentLanguage }}\n </button>\n }\n </div>\n\n <!-- Language Menu -->\n <mat-menu #languageMenu=\"matMenu\">\n @for (language of languages; track language) {\n <button mat-menu-item (click)=\"setLanguage(language)\">\n <span class=\"{{ getLanguageIcon(language) }}\"></span>\n {{ language }}\n </button>\n }\n </mat-menu>\n </div>\n\n <div class=\"spacer\"></div>\n\n <div class=\"flex-stretch\"></div>\n <button\n mat-icon-button\n [mat-menu-trigger-for]=\"themeMenu\"\n class=\"theme-button\"\n >\n <mat-icon>{{ themeService.selectedTheme()?.icon }}</mat-icon>\n </button>\n <mat-menu #themeMenu=\"matMenu\">\n @for (theme of themeService.getThemes(); track theme.name) {\n <button\n [class.selected-theme]=\"themeService.selectedTheme()?.name === theme.name\"\n mat-menu-item\n (click)=\"themeService.setTheme(theme.name)\"\n >\n <mat-icon>{{ theme.icon }}</mat-icon>\n <span>{{ theme.name | titlecase }}</span>\n </button>\n }\n </mat-menu>\n <button\n mat-icon-button\n [matMenuTriggerFor]=\"customThemeMenu\"\n class=\"custom-theme-button\"\n >\n <mat-icon>format_color_fill</mat-icon>\n </button>\n <mat-menu #customThemeMenu=\"matMenu\">\n @for (customTheme of colorPaletteService.getThemes(); track customTheme.id)\n {\n <button\n mat-menu-item\n (click)=\"colorPaletteService.setTheme(customTheme.id)\"\n >\n <div class=\"theme-menu-item\">\n <div\n class=\"color-preview\"\n [style.background-color]=\"customTheme.primary\"\n ></div>\n <span>{{ customTheme.displayName }}</span>\n </div>\n </button>\n }\n </mat-menu>\n</mat-toolbar>\n", styles: ["mat-toolbar{position:fixed;top:0;left:0;right:0;z-index:3;box-shadow:0 1px 5px #0000001a;display:flex;align-items:center}.sidenav-header{display:flex;justify-content:space-between;align-items:center;padding:16px;margin:0;font-weight:500}.sidenav-header .left-section{display:flex;align-items:center;gap:16px}.sidenav-header .spacer{flex:1 1 auto}.flex-stretch{flex:1 0 auto}.theme-menu-item{display:flex;align-items:center;gap:12px}.color-preview{width:24px;height:24px;border-radius:50%}::ng-deep .theme-button{border:none;background:none;margin-right:15px;cursor:pointer}::ng-deep .custom-theme-button{border:none;background:none;cursor:pointer}::ng-deep .menu-button{border:none;background:none;cursor:pointer}.language-button{margin:0 auto}::ng-deep button{border:none;background:none;cursor:pointer}\n"] }]
1072
+ }], propDecorators: { toggleSidenav: [{ type: i0.Output, args: ["toggleSidenav"] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }, { type: i0.Output, args: ["iconChange"] }] } });
1073
+
1074
+ /*
1075
+ * Public API Surface of ntyux
1076
+ */
1077
+
1078
+ /**
1079
+ * Generated bundle index. Do not edit.
1080
+ */
1081
+
1082
+ export { AutoComplete, ColorPalette, HttpError403, HttpError404, LeftSidenav, Ntyux, NtyuxModule, SelectionItem, Theme, Toolbar };
1083
+ //# sourceMappingURL=nettyapps-ntyux.mjs.map