aril 1.2.4 → 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,373 +1,373 @@
1
- import { NgClass } from '@angular/common';
2
- import { HttpClient } from '@angular/common/http';
3
- import { Component, HostListener, OnDestroy, OnInit, Signal } from '@angular/core';
4
- import { toSignal } from '@angular/core/rxjs-interop';
5
- import { NavigationEnd, Router, RouterLink } from '@angular/router';
6
-
7
- import { TooltipModule } from 'primeng/tooltip';
8
-
9
- import { TranslocoService } from '@ngneat/transloco';
10
- import { KeycloakService } from 'keycloak-angular';
11
- import { Subscription, filter } from 'rxjs';
12
-
13
- import { bridge } from 'aril/boot/bridge';
14
- import { PluginMenuItem } from 'aril/boot/config/apps';
15
- import { TranslateJsonPipe } from 'aril/util/pipes';
16
-
17
- import { LayoutService } from '../../service/app.layout.service';
18
- import { AppMenuService } from '../../service/app.menu.service';
19
-
20
- @Component({
21
- standalone: true,
22
- selector: 'app-expandable-menu',
23
- imports: [RouterLink, NgClass, TooltipModule],
24
- templateUrl: 'expandable-menu.component.html',
25
- providers: [TranslateJsonPipe]
26
- })
27
- export class ExpandableMenuComponent implements OnInit, OnDestroy {
28
- activeLang: Signal<string>;
29
- overlayMenuOpenSubscription: Subscription | null = null;
30
- routerSubscription: Subscription | null = null;
31
- username: string;
32
-
33
- cachedMenuItems: PluginMenuItem[] = [];
34
-
35
- selectedMainMenuItem: PluginMenuItem | null = null;
36
-
37
- get sidebarExpanded(): boolean {
38
- return this.layoutService.state.secondarySidebarExpanded;
39
- }
40
-
41
- set sidebarExpanded(value: boolean) {
42
- this.layoutService.setSecondarySidebarExpanded(value);
43
- }
44
-
45
- tooltipText = {
46
- toggleButton: {
47
- expanded: {
48
- tr: 'Menüyü Gizle',
49
- en: 'Hide Menu'
50
- },
51
- collapsed: {
52
- tr: 'Menüyü Göster',
53
- en: 'Show Menu'
54
- }
55
- },
56
- lockButton: {
57
- locked: {
58
- tr: 'Otomatik Kapanmayı Aç',
59
- en: 'Enable Auto-Close'
60
- },
61
- unlocked: {
62
- tr: 'Menüyü Kilitle',
63
- en: 'Lock Menu'
64
- }
65
- }
66
- };
67
-
68
- private readonly MAX_LENGTH = {
69
- main: 8,
70
- secondary: 20,
71
- tertiary: 20
72
- };
73
-
74
- actionLoadingItems = new Set<string>();
75
- private actionSubscription: Subscription | null = null;
76
-
77
- constructor(
78
- private translateJsonPipe: TranslateJsonPipe,
79
- private menuService: AppMenuService,
80
- private router: Router,
81
- private keycloak: KeycloakService,
82
- private translocoService: TranslocoService,
83
- private layoutService: LayoutService,
84
- private http: HttpClient
85
- ) {
86
- this.activeLang = toSignal(this.translocoService.langChanges$, { initialValue: 'tr' });
87
- this.username = this.keycloak.getUsername().charAt(0).toUpperCase();
88
- }
89
-
90
- // Getter for layout service states
91
- get selectedMainItem(): number | null {
92
- return this.layoutService.state.selectedMainItem;
93
- }
94
-
95
- get secondarySidebarExpanded(): boolean {
96
- return this.layoutService.state.secondarySidebarExpanded;
97
- }
98
-
99
- // Check if text is truncated and needs tooltip
100
- isTextTruncated(
101
- label: any,
102
- lang: string | null,
103
- menuType: 'main' | 'secondary' | 'tertiary' | 'nested' = 'secondary'
104
- ): boolean {
105
- if (!label || !lang || !label[lang]) return false;
106
-
107
- const text = label[lang];
108
- const textLength = text.length;
109
- const maxLength = menuType === 'nested' ? this.MAX_LENGTH.tertiary : this.MAX_LENGTH[menuType];
110
- return textLength > maxLength;
111
- }
112
-
113
- // Recursively check if a menu item or its children match the current route
114
- isActiveRoute(menuItem: PluginMenuItem): boolean {
115
- if (!menuItem || !menuItem.routerLink) return false;
116
-
117
- // Get current URL path after the hash
118
- const currentUrl = this.router.url;
119
- const hashUrl = currentUrl.indexOf('#') > -1 ? currentUrl.split('#')[1] : currentUrl;
120
-
121
- // Check for exact match first
122
- if (hashUrl === menuItem.routerLink || hashUrl === '/' + menuItem.routerLink) {
123
- return true;
124
- }
125
-
126
- // Check if current URL starts with the routerLink (handles child routes)
127
- if (
128
- menuItem.routerLink !== '/' &&
129
- (hashUrl.startsWith(menuItem.routerLink + '/') || hashUrl.startsWith('/' + menuItem.routerLink + '/'))
130
- ) {
131
- return true;
132
- }
133
-
134
- // Check child items if they exist
135
- if (menuItem.items && menuItem.items.length > 0) {
136
- return menuItem.items.some((childItem) => this.isActiveRoute(childItem));
137
- }
138
-
139
- return false;
140
- }
141
-
142
- ngOnInit() {
143
- this.updateMenuItems();
144
- this.initializeMenu();
145
- this.updateActiveMenuFromCurrentRoute();
146
-
147
- // Subscribe to route changes to handle secondary sidebar closing
148
- this.routerSubscription = this.router.events
149
- .pipe(filter((event) => event instanceof NavigationEnd))
150
- .subscribe(() => {
151
- // Update active menu based on the new route
152
- this.updateActiveMenuFromCurrentRoute();
153
- });
154
-
155
- // Subscribe to overlay menu open notifications
156
- this.overlayMenuOpenSubscription = this.layoutService.overlayOpen$.subscribe(() => {
157
- if (this.layoutService.state.staticMenuMobileActive) {
158
- this.layoutService.setSecondarySidebarExpanded(true);
159
- }
160
- });
161
- }
162
-
163
- ngOnDestroy() {
164
- if (this.overlayMenuOpenSubscription) {
165
- this.overlayMenuOpenSubscription.unsubscribe();
166
- }
167
-
168
- if (this.routerSubscription) {
169
- this.routerSubscription.unsubscribe();
170
- }
171
-
172
- this.actionSubscription?.unsubscribe();
173
- }
174
-
175
- // Initialize the menu with proper active state
176
- initializeMenu() {
177
- this.layoutService.setSelectedMainItem(null);
178
- this.selectedMainMenuItem = null;
179
- this.layoutService.setSecondarySidebarExpanded(false);
180
- this.updateMenuItems();
181
- }
182
-
183
- updateMenuFromRootKey(index: number): void {
184
- this.updateMenuItems();
185
- const menuItems = this.getMenuItems();
186
-
187
- if (index >= 0 && index < menuItems.length) {
188
- const item = menuItems[index];
189
- if (!item.separator) {
190
- this.layoutService.setSelectedMainItem(index);
191
- this.selectedMainMenuItem = item;
192
- this.layoutService.setSecondarySidebarExpanded(true);
193
- }
194
- }
195
- }
196
-
197
- updateMenuItems(): void {
198
- this.cachedMenuItems = bridge.isHostMode() ? bridge.hostMenuItems() : this.menuService.menuItems();
199
- }
200
-
201
- getMenuItems(): PluginMenuItem[] {
202
- return this.cachedMenuItems;
203
- }
204
-
205
- selectMainItem(index: number, item: PluginMenuItem): void {
206
- // Handle action items at top level
207
- if (item.action && (!item.items || item.items.length === 0)) {
208
- this.onActionItemClick(item);
209
- return;
210
- }
211
-
212
- if (item.routerLink && (!item.items || item.items.length === 0)) {
213
- this.layoutService.setSecondarySidebarExpanded(false);
214
- this.layoutService.setSelectedMainItem(index);
215
- this.selectedMainMenuItem = item;
216
- this.router.navigate([item.routerLink]);
217
- return;
218
- }
219
-
220
- // Handle regular menu items with submenu
221
- if (this.selectedMainItem === index) {
222
- this.layoutService.toggleSecondarySidebar();
223
- } else {
224
- this.layoutService.setSelectedMainItem(index);
225
- this.selectedMainMenuItem = item;
226
-
227
- if (item.items && item.items.length > 0) {
228
- this.layoutService.setSecondarySidebarExpanded(true);
229
- } else {
230
- this.layoutService.setSecondarySidebarExpanded(false);
231
- }
232
- }
233
-
234
- // For mobile devices, ensure the menu stays open
235
- if (this.isMobile()) {
236
- this.layoutService.state.staticMenuMobileActive = true;
237
- }
238
- }
239
-
240
- toggleSecondarySidebar(): void {
241
- this.layoutService.toggleSecondarySidebar();
242
- }
243
-
244
- isMobile(): boolean {
245
- return this.layoutService.isMobile();
246
- }
247
-
248
- // Listen for resize events - now handled more efficiently by layout service
249
- @HostListener('window:resize')
250
- onResize() {
251
- if (!this.isMobile() && this.layoutService.state.staticMenuMobileActive) {
252
- this.layoutService.closeMobileMenu();
253
- }
254
- }
255
-
256
- updateActiveMenuFromCurrentRoute(): void {
257
- const menuItems = this.getMenuItems();
258
-
259
- for (let i = 0; i < menuItems.length; i++) {
260
- const menuItem = menuItems[i];
261
- if (this.isActiveRoute(menuItem)) {
262
- this.layoutService.setSelectedMainItem(i);
263
- this.selectedMainMenuItem = menuItem;
264
-
265
- if (menuItem.items && menuItem.items.length > 0) {
266
- if (!this.isMobile() && !this.secondarySidebarExpanded && !this.layoutService.state.staticMenuMobileActive) {
267
- this.layoutService.setSecondarySidebarExpanded(true);
268
- }
269
- } else {
270
- this.layoutService.setSecondarySidebarExpanded(false);
271
- }
272
- return;
273
- }
274
- }
275
- }
276
-
277
- isHaveSelectedChildItem(item: PluginMenuItem): boolean {
278
- if (!item || !item.items || item.items.length === 0) return false;
279
-
280
- for (const child of item.items) {
281
- if (this.isActiveRoute(child)) {
282
- return true;
283
- }
284
-
285
- if (child.items && child.items.length > 0) {
286
- if (this.isHaveSelectedChildItem(child)) {
287
- return true;
288
- }
289
- }
290
- }
291
-
292
- return false;
293
- }
294
-
295
- getLocalText(text: any): string {
296
- return this.translateJsonPipe.transform(text);
297
- }
298
-
299
- getActionItemKey(item: PluginMenuItem): string {
300
- return item.action?.url + '|' + item.action?.successRoute;
301
- }
302
-
303
- isActionLoading(item: PluginMenuItem): boolean {
304
- return this.actionLoadingItems.has(this.getActionItemKey(item));
305
- }
306
-
307
- onActionItemClick(item: PluginMenuItem, event?: Event): void {
308
- if (event) {
309
- event.preventDefault();
310
- event.stopPropagation();
311
- }
312
-
313
- if (!item.action || this.isActionLoading(item)) return;
314
-
315
- // Cancel any previous action request
316
- this.actionSubscription?.unsubscribe();
317
-
318
- const key = this.getActionItemKey(item);
319
- this.actionLoadingItems.add(key);
320
-
321
- const request$ =
322
- item.action.method === 'GET'
323
- ? this.http.get(item.action.url)
324
- : this.http.post(item.action.url, item.action.body ?? {});
325
-
326
- this.actionSubscription = request$.subscribe({
327
- next: (response: any) => {
328
- this.actionLoadingItems.delete(key);
329
- const route = this.buildSuccessRoute(item.action!, response);
330
- this.router.navigate([route]);
331
- },
332
- error: () => {
333
- this.actionLoadingItems.delete(key);
334
- }
335
- });
336
- }
337
-
338
- private buildSuccessRoute(action: PluginMenuItem['action'], response: any): string {
339
- if (!action?.successRouteResponseKey) return action?.successRoute ?? '/';
340
-
341
- const value = this.getValueByPath(response, action.successRouteResponseKey);
342
- if (value === undefined || value === null) return action.successRoute;
343
-
344
- return `${action.successRoute}/${value}`;
345
- }
346
-
347
- private getValueByPath(obj: any, path: string): any {
348
- if (!obj || !path) return undefined;
349
-
350
- return path.split('.').reduce((current, key) => {
351
- if (current === undefined || current === null) return undefined;
352
-
353
- // Handle array access like 'result[0]'
354
- const arrayMatch = key.match(/^(.+?)\[(\d+)\]$/);
355
- if (arrayMatch) {
356
- const arrayKey = arrayMatch[1];
357
- const index = parseInt(arrayMatch[2], 10);
358
- return current[arrayKey]?.[index];
359
- }
360
-
361
- return current[key];
362
- }, obj);
363
- }
364
-
365
- toggleSidebar() {
366
- // Eğer seçili bir ana menü öğesi varsa ve alt menüleri varsa toggle yapabiliriz
367
- if (this.selectedMainMenuItem && this.selectedMainMenuItem.items && this.selectedMainMenuItem.items.length > 0) {
368
- this.sidebarExpanded = !this.sidebarExpanded;
369
- } else if (this.sidebarExpanded) {
370
- this.sidebarExpanded = false;
371
- }
372
- }
373
- }
1
+ import { NgClass } from '@angular/common';
2
+ import { HttpClient } from '@angular/common/http';
3
+ import { Component, HostListener, OnDestroy, OnInit, Signal } from '@angular/core';
4
+ import { toSignal } from '@angular/core/rxjs-interop';
5
+ import { NavigationEnd, Router, RouterLink } from '@angular/router';
6
+
7
+ import { TooltipModule } from 'primeng/tooltip';
8
+
9
+ import { TranslocoService } from '@ngneat/transloco';
10
+ import { KeycloakService } from 'keycloak-angular';
11
+ import { Subscription, filter } from 'rxjs';
12
+
13
+ import { bridge } from 'aril/boot/bridge';
14
+ import { PluginMenuItem } from 'aril/boot/config/apps';
15
+ import { TranslateJsonPipe } from 'aril/util/pipes';
16
+
17
+ import { LayoutService } from '../../service/app.layout.service';
18
+ import { AppMenuService } from '../../service/app.menu.service';
19
+
20
+ @Component({
21
+ standalone: true,
22
+ selector: 'app-expandable-menu',
23
+ imports: [RouterLink, NgClass, TooltipModule],
24
+ templateUrl: 'expandable-menu.component.html',
25
+ providers: [TranslateJsonPipe]
26
+ })
27
+ export class ExpandableMenuComponent implements OnInit, OnDestroy {
28
+ activeLang: Signal<string>;
29
+ overlayMenuOpenSubscription: Subscription | null = null;
30
+ routerSubscription: Subscription | null = null;
31
+ username: string;
32
+
33
+ cachedMenuItems: PluginMenuItem[] = [];
34
+
35
+ selectedMainMenuItem: PluginMenuItem | null = null;
36
+
37
+ get sidebarExpanded(): boolean {
38
+ return this.layoutService.state.secondarySidebarExpanded;
39
+ }
40
+
41
+ set sidebarExpanded(value: boolean) {
42
+ this.layoutService.setSecondarySidebarExpanded(value);
43
+ }
44
+
45
+ tooltipText = {
46
+ toggleButton: {
47
+ expanded: {
48
+ tr: 'Menüyü Gizle',
49
+ en: 'Hide Menu'
50
+ },
51
+ collapsed: {
52
+ tr: 'Menüyü Göster',
53
+ en: 'Show Menu'
54
+ }
55
+ },
56
+ lockButton: {
57
+ locked: {
58
+ tr: 'Otomatik Kapanmayı Aç',
59
+ en: 'Enable Auto-Close'
60
+ },
61
+ unlocked: {
62
+ tr: 'Menüyü Kilitle',
63
+ en: 'Lock Menu'
64
+ }
65
+ }
66
+ };
67
+
68
+ private readonly MAX_LENGTH = {
69
+ main: 8,
70
+ secondary: 20,
71
+ tertiary: 20
72
+ };
73
+
74
+ actionLoadingItems = new Set<string>();
75
+ private actionSubscription: Subscription | null = null;
76
+
77
+ constructor(
78
+ private translateJsonPipe: TranslateJsonPipe,
79
+ private menuService: AppMenuService,
80
+ private router: Router,
81
+ private keycloak: KeycloakService,
82
+ private translocoService: TranslocoService,
83
+ private layoutService: LayoutService,
84
+ private http: HttpClient
85
+ ) {
86
+ this.activeLang = toSignal(this.translocoService.langChanges$, { initialValue: 'tr' });
87
+ this.username = this.keycloak.getUsername().charAt(0).toUpperCase();
88
+ }
89
+
90
+ // Getter for layout service states
91
+ get selectedMainItem(): number | null {
92
+ return this.layoutService.state.selectedMainItem;
93
+ }
94
+
95
+ get secondarySidebarExpanded(): boolean {
96
+ return this.layoutService.state.secondarySidebarExpanded;
97
+ }
98
+
99
+ // Check if text is truncated and needs tooltip
100
+ isTextTruncated(
101
+ label: any,
102
+ lang: string | null,
103
+ menuType: 'main' | 'secondary' | 'tertiary' | 'nested' = 'secondary'
104
+ ): boolean {
105
+ if (!label || !lang || !label[lang]) return false;
106
+
107
+ const text = label[lang];
108
+ const textLength = text.length;
109
+ const maxLength = menuType === 'nested' ? this.MAX_LENGTH.tertiary : this.MAX_LENGTH[menuType];
110
+ return textLength > maxLength;
111
+ }
112
+
113
+ // Recursively check if a menu item or its children match the current route
114
+ isActiveRoute(menuItem: PluginMenuItem): boolean {
115
+ if (!menuItem || !menuItem.routerLink) return false;
116
+
117
+ // Get current URL path after the hash
118
+ const currentUrl = this.router.url;
119
+ const hashUrl = currentUrl.indexOf('#') > -1 ? currentUrl.split('#')[1] : currentUrl;
120
+
121
+ // Check for exact match first
122
+ if (hashUrl === menuItem.routerLink || hashUrl === '/' + menuItem.routerLink) {
123
+ return true;
124
+ }
125
+
126
+ // Check if current URL starts with the routerLink (handles child routes)
127
+ if (
128
+ menuItem.routerLink !== '/' &&
129
+ (hashUrl.startsWith(menuItem.routerLink + '/') || hashUrl.startsWith('/' + menuItem.routerLink + '/'))
130
+ ) {
131
+ return true;
132
+ }
133
+
134
+ // Check child items if they exist
135
+ if (menuItem.items && menuItem.items.length > 0) {
136
+ return menuItem.items.some((childItem) => this.isActiveRoute(childItem));
137
+ }
138
+
139
+ return false;
140
+ }
141
+
142
+ ngOnInit() {
143
+ this.updateMenuItems();
144
+ this.initializeMenu();
145
+ this.updateActiveMenuFromCurrentRoute();
146
+
147
+ // Subscribe to route changes to handle secondary sidebar closing
148
+ this.routerSubscription = this.router.events
149
+ .pipe(filter((event) => event instanceof NavigationEnd))
150
+ .subscribe(() => {
151
+ // Update active menu based on the new route
152
+ this.updateActiveMenuFromCurrentRoute();
153
+ });
154
+
155
+ // Subscribe to overlay menu open notifications
156
+ this.overlayMenuOpenSubscription = this.layoutService.overlayOpen$.subscribe(() => {
157
+ if (this.layoutService.state.staticMenuMobileActive) {
158
+ this.layoutService.setSecondarySidebarExpanded(true);
159
+ }
160
+ });
161
+ }
162
+
163
+ ngOnDestroy() {
164
+ if (this.overlayMenuOpenSubscription) {
165
+ this.overlayMenuOpenSubscription.unsubscribe();
166
+ }
167
+
168
+ if (this.routerSubscription) {
169
+ this.routerSubscription.unsubscribe();
170
+ }
171
+
172
+ this.actionSubscription?.unsubscribe();
173
+ }
174
+
175
+ // Initialize the menu with proper active state
176
+ initializeMenu() {
177
+ this.layoutService.setSelectedMainItem(null);
178
+ this.selectedMainMenuItem = null;
179
+ this.layoutService.setSecondarySidebarExpanded(false);
180
+ this.updateMenuItems();
181
+ }
182
+
183
+ updateMenuFromRootKey(index: number): void {
184
+ this.updateMenuItems();
185
+ const menuItems = this.getMenuItems();
186
+
187
+ if (index >= 0 && index < menuItems.length) {
188
+ const item = menuItems[index];
189
+ if (!item.separator) {
190
+ this.layoutService.setSelectedMainItem(index);
191
+ this.selectedMainMenuItem = item;
192
+ this.layoutService.setSecondarySidebarExpanded(true);
193
+ }
194
+ }
195
+ }
196
+
197
+ updateMenuItems(): void {
198
+ this.cachedMenuItems = bridge.isHostMode() ? bridge.hostMenuItems() : this.menuService.menuItems();
199
+ }
200
+
201
+ getMenuItems(): PluginMenuItem[] {
202
+ return this.cachedMenuItems;
203
+ }
204
+
205
+ selectMainItem(index: number, item: PluginMenuItem): void {
206
+ // Handle action items at top level
207
+ if (item.action && (!item.items || item.items.length === 0)) {
208
+ this.onActionItemClick(item);
209
+ return;
210
+ }
211
+
212
+ if (item.routerLink && (!item.items || item.items.length === 0)) {
213
+ this.layoutService.setSecondarySidebarExpanded(false);
214
+ this.layoutService.setSelectedMainItem(index);
215
+ this.selectedMainMenuItem = item;
216
+ this.router.navigate([item.routerLink]);
217
+ return;
218
+ }
219
+
220
+ // Handle regular menu items with submenu
221
+ if (this.selectedMainItem === index) {
222
+ this.layoutService.toggleSecondarySidebar();
223
+ } else {
224
+ this.layoutService.setSelectedMainItem(index);
225
+ this.selectedMainMenuItem = item;
226
+
227
+ if (item.items && item.items.length > 0) {
228
+ this.layoutService.setSecondarySidebarExpanded(true);
229
+ } else {
230
+ this.layoutService.setSecondarySidebarExpanded(false);
231
+ }
232
+ }
233
+
234
+ // For mobile devices, ensure the menu stays open
235
+ if (this.isMobile()) {
236
+ this.layoutService.state.staticMenuMobileActive = true;
237
+ }
238
+ }
239
+
240
+ toggleSecondarySidebar(): void {
241
+ this.layoutService.toggleSecondarySidebar();
242
+ }
243
+
244
+ isMobile(): boolean {
245
+ return this.layoutService.isMobile();
246
+ }
247
+
248
+ // Listen for resize events - now handled more efficiently by layout service
249
+ @HostListener('window:resize')
250
+ onResize() {
251
+ if (!this.isMobile() && this.layoutService.state.staticMenuMobileActive) {
252
+ this.layoutService.closeMobileMenu();
253
+ }
254
+ }
255
+
256
+ updateActiveMenuFromCurrentRoute(): void {
257
+ const menuItems = this.getMenuItems();
258
+
259
+ for (let i = 0; i < menuItems.length; i++) {
260
+ const menuItem = menuItems[i];
261
+ if (this.isActiveRoute(menuItem)) {
262
+ this.layoutService.setSelectedMainItem(i);
263
+ this.selectedMainMenuItem = menuItem;
264
+
265
+ if (menuItem.items && menuItem.items.length > 0) {
266
+ if (!this.isMobile() && !this.secondarySidebarExpanded && !this.layoutService.state.staticMenuMobileActive) {
267
+ this.layoutService.setSecondarySidebarExpanded(true);
268
+ }
269
+ } else {
270
+ this.layoutService.setSecondarySidebarExpanded(false);
271
+ }
272
+ return;
273
+ }
274
+ }
275
+ }
276
+
277
+ isHaveSelectedChildItem(item: PluginMenuItem): boolean {
278
+ if (!item || !item.items || item.items.length === 0) return false;
279
+
280
+ for (const child of item.items) {
281
+ if (this.isActiveRoute(child)) {
282
+ return true;
283
+ }
284
+
285
+ if (child.items && child.items.length > 0) {
286
+ if (this.isHaveSelectedChildItem(child)) {
287
+ return true;
288
+ }
289
+ }
290
+ }
291
+
292
+ return false;
293
+ }
294
+
295
+ getLocalText(text: any): string {
296
+ return this.translateJsonPipe.transform(text);
297
+ }
298
+
299
+ getActionItemKey(item: PluginMenuItem): string {
300
+ return item.action?.url + '|' + item.action?.successRoute;
301
+ }
302
+
303
+ isActionLoading(item: PluginMenuItem): boolean {
304
+ return this.actionLoadingItems.has(this.getActionItemKey(item));
305
+ }
306
+
307
+ onActionItemClick(item: PluginMenuItem, event?: Event): void {
308
+ if (event) {
309
+ event.preventDefault();
310
+ event.stopPropagation();
311
+ }
312
+
313
+ if (!item.action || this.isActionLoading(item)) return;
314
+
315
+ // Cancel any previous action request
316
+ this.actionSubscription?.unsubscribe();
317
+
318
+ const key = this.getActionItemKey(item);
319
+ this.actionLoadingItems.add(key);
320
+
321
+ const request$ =
322
+ item.action.method === 'GET'
323
+ ? this.http.get(item.action.url)
324
+ : this.http.post(item.action.url, item.action.body ?? {});
325
+
326
+ this.actionSubscription = request$.subscribe({
327
+ next: (response: any) => {
328
+ this.actionLoadingItems.delete(key);
329
+ const route = this.buildSuccessRoute(item.action!, response);
330
+ this.router.navigate([route]);
331
+ },
332
+ error: () => {
333
+ this.actionLoadingItems.delete(key);
334
+ }
335
+ });
336
+ }
337
+
338
+ private buildSuccessRoute(action: PluginMenuItem['action'], response: any): string {
339
+ if (!action?.successRouteResponseKey) return action?.successRoute ?? '/';
340
+
341
+ const value = this.getValueByPath(response, action.successRouteResponseKey);
342
+ if (value === undefined || value === null) return action.successRoute;
343
+
344
+ return `${action.successRoute}/${value}`;
345
+ }
346
+
347
+ private getValueByPath(obj: any, path: string): any {
348
+ if (!obj || !path) return undefined;
349
+
350
+ return path.split('.').reduce((current, key) => {
351
+ if (current === undefined || current === null) return undefined;
352
+
353
+ // Handle array access like 'result[0]'
354
+ const arrayMatch = key.match(/^(.+?)\[(\d+)\]$/);
355
+ if (arrayMatch) {
356
+ const arrayKey = arrayMatch[1];
357
+ const index = parseInt(arrayMatch[2], 10);
358
+ return current[arrayKey]?.[index];
359
+ }
360
+
361
+ return current[key];
362
+ }, obj);
363
+ }
364
+
365
+ toggleSidebar() {
366
+ // Eğer seçili bir ana menü öğesi varsa ve alt menüleri varsa toggle yapabiliriz
367
+ if (this.selectedMainMenuItem && this.selectedMainMenuItem.items && this.selectedMainMenuItem.items.length > 0) {
368
+ this.sidebarExpanded = !this.sidebarExpanded;
369
+ } else if (this.sidebarExpanded) {
370
+ this.sidebarExpanded = false;
371
+ }
372
+ }
373
+ }