@trismegisto/spine 2.0.1 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +35 -5
  2. package/fesm2022/trismegisto-spine-audit.mjs +1 -1
  3. package/fesm2022/trismegisto-spine-audit.mjs.map +1 -1
  4. package/fesm2022/trismegisto-spine-auto-service-apps.technical-ClMKB9cK.mjs +48 -0
  5. package/fesm2022/trismegisto-spine-auto-service-apps.technical-ClMKB9cK.mjs.map +1 -0
  6. package/fesm2022/trismegisto-spine-auto-service-apps.usage-Cf5SG-oQ.mjs +55 -0
  7. package/fesm2022/trismegisto-spine-auto-service-apps.usage-Cf5SG-oQ.mjs.map +1 -0
  8. package/fesm2022/trismegisto-spine-auto-service-audit.technical-C901aIIQ.mjs +42 -0
  9. package/fesm2022/trismegisto-spine-auto-service-audit.technical-C901aIIQ.mjs.map +1 -0
  10. package/fesm2022/trismegisto-spine-auto-service-audit.usage-GIJ2RwuH.mjs +63 -0
  11. package/fesm2022/trismegisto-spine-auto-service-audit.usage-GIJ2RwuH.mjs.map +1 -0
  12. package/fesm2022/trismegisto-spine-auto-service-auto-service-client.component-C7SPavrX.mjs +784 -0
  13. package/fesm2022/trismegisto-spine-auto-service-auto-service-client.component-C7SPavrX.mjs.map +1 -0
  14. package/fesm2022/trismegisto-spine-auto-service-auto-service-delete-6Bk46MOf.mjs +39 -0
  15. package/fesm2022/trismegisto-spine-auto-service-auto-service-delete-6Bk46MOf.mjs.map +1 -0
  16. package/fesm2022/{trismegisto-spine-auto-service-auto-service.component-B5tyJ2zY.mjs → trismegisto-spine-auto-service-auto-service.component-BLN49F4a.mjs} +32 -7
  17. package/fesm2022/trismegisto-spine-auto-service-auto-service.component-BLN49F4a.mjs.map +1 -0
  18. package/fesm2022/trismegisto-spine-auto-service-client.technical-BHWhpyWv.mjs +46 -0
  19. package/fesm2022/trismegisto-spine-auto-service-client.technical-BHWhpyWv.mjs.map +1 -0
  20. package/fesm2022/trismegisto-spine-auto-service-client.usage-CM2WB2e2.mjs +80 -0
  21. package/fesm2022/trismegisto-spine-auto-service-client.usage-CM2WB2e2.mjs.map +1 -0
  22. package/fesm2022/trismegisto-spine-auto-service-groups.technical-jQlwaQPn.mjs +39 -0
  23. package/fesm2022/trismegisto-spine-auto-service-groups.technical-jQlwaQPn.mjs.map +1 -0
  24. package/fesm2022/trismegisto-spine-auto-service-groups.usage-Bh7ig2zZ.mjs +54 -0
  25. package/fesm2022/trismegisto-spine-auto-service-groups.usage-Bh7ig2zZ.mjs.map +1 -0
  26. package/fesm2022/trismegisto-spine-auto-service-home.technical-BrVhcgDO.mjs +35 -0
  27. package/fesm2022/trismegisto-spine-auto-service-home.technical-BrVhcgDO.mjs.map +1 -0
  28. package/fesm2022/trismegisto-spine-auto-service-home.usage-D6cb4B3Z.mjs +43 -0
  29. package/fesm2022/trismegisto-spine-auto-service-home.usage-D6cb4B3Z.mjs.map +1 -0
  30. package/fesm2022/trismegisto-spine-auto-service.mjs +82 -40
  31. package/fesm2022/trismegisto-spine-auto-service.mjs.map +1 -1
  32. package/fesm2022/trismegisto-spine-layout.mjs +988 -46
  33. package/fesm2022/trismegisto-spine-layout.mjs.map +1 -1
  34. package/fesm2022/trismegisto-spine-shell.mjs +3 -1
  35. package/fesm2022/trismegisto-spine-shell.mjs.map +1 -1
  36. package/fesm2022/trismegisto-spine.mjs +591 -16
  37. package/fesm2022/trismegisto-spine.mjs.map +1 -1
  38. package/package.json +6 -11
  39. package/types/trismegisto-spine-auto-service.d.ts +41 -2
  40. package/types/trismegisto-spine-auto-service.d.ts.map +1 -1
  41. package/types/trismegisto-spine-layout.d.ts +214 -13
  42. package/types/trismegisto-spine-layout.d.ts.map +1 -1
  43. package/types/trismegisto-spine-shell.d.ts +1 -0
  44. package/types/trismegisto-spine-shell.d.ts.map +1 -1
  45. package/types/trismegisto-spine.d.ts +214 -8
  46. package/types/trismegisto-spine.d.ts.map +1 -1
  47. package/fesm2022/trismegisto-spine-auto-service-auto-service-client.component-BYkUOwWL.mjs +0 -595
  48. package/fesm2022/trismegisto-spine-auto-service-auto-service-client.component-BYkUOwWL.mjs.map +0 -1
  49. package/fesm2022/trismegisto-spine-auto-service-auto-service.component-B5tyJ2zY.mjs.map +0 -1
@@ -0,0 +1,784 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, ChangeDetectorRef, computed, Input, ChangeDetectionStrategy, Component, ElementRef, Injector, EventEmitter, afterNextRender, Output } from '@angular/core';
3
+ import * as i2 from '@angular/common';
4
+ import { CommonModule } from '@angular/common';
5
+ import { ButtonComponent, CheckboxComponent, ComboboxComponent, IconComponent, InputComponent, TextareaComponent, ModalComponent, DataGridComponent, DataGridCellDirective, TagComponent, AlertComponent, IconButtonComponent, ReorderListComponent, DividerComponent, DrawerComponent, TabsComponent, TabComponent, TabContentDirective } from '@trismegisto/angular';
6
+ import { A as AutoServiceClientPickerComponent } from './trismegisto-spine-auto-service-auto-service-client-picker.component-Z0wCANF6.mjs';
7
+ import * as i3 from '@angular/forms';
8
+ import { FormControl, ReactiveFormsModule, FormsModule } from '@angular/forms';
9
+ import { HERMES_ICONS } from '@trismegisto/icons/catalog';
10
+ import { formatMessage, pluralMessage, moveItem, formatMessageHtml } from '@trismegisto/primitives';
11
+ import { A as AutoServiceDeleteFeedback } from './trismegisto-spine-auto-service-auto-service-delete-6Bk46MOf.mjs';
12
+ import { SPINE_MESSAGES } from '@trismegisto/spine';
13
+ import { AutoServiceService } from './trismegisto-spine-auto-service.mjs';
14
+
15
+ /**
16
+ * Caminho de UMA rota = um trecho só do endereço (`dashboard`, `ordens-servico`). O `/` entre níveis
17
+ * é o roteador que monta, juntando o caminho de cada rota da composição (header→…→page). Um `/`
18
+ * dentro do caminho (ex.: `laboratorio/dashboard` numa rota só) desalinha a cadeia que o `ViewGuard`
19
+ * resolve por nível e a tela cai no fallback (volta para o início) — achado real, 2026-10-07.
20
+ *
21
+ * Só letras minúsculas e hífen, sem hífen no início/fim nem dois seguidos. Mesma regra do admin-server
22
+ * do Argos (`route-path.ts`), que é quem de fato recusa — aqui é só para avisar antes de enviar.
23
+ */
24
+ const ROUTE_PATH_PATTERN = /^[a-z]+(?:-[a-z]+)*$/;
25
+ /** Vazio passa (rota "pasta", só agrupa o menu); preenchido precisa casar com `ROUTE_PATH_PATTERN`. */
26
+ function routePathValidator(control) {
27
+ const value = control.value ?? '';
28
+ return value === '' || ROUTE_PATH_PATTERN.test(value) ? null : { routePath: true };
29
+ }
30
+
31
+ class AutoServiceRoutesComponent {
32
+ constructor(service) {
33
+ this.service = service;
34
+ this.text = inject(SPINE_MESSAGES).autoService;
35
+ /** Telas com estado em campo comum, preenchido depois de `await`: marca a view ao terminar (app sem zone.js). */
36
+ this.cdr = inject(ChangeDetectorRef);
37
+ this.deletion = inject(AutoServiceDeleteFeedback);
38
+ this.clientId = '';
39
+ this.routes = [];
40
+ this.loading = false;
41
+ this.showRouteModal = false;
42
+ this.editingRouteId = '';
43
+ this.name = new FormControl('', { nonNullable: true });
44
+ this.displayName = new FormControl('', { nonNullable: true });
45
+ this.icon = new FormControl('', { nonNullable: true });
46
+ /** Catálogo inteiro do @trismegisto/icons, em ordem alfabética, cada opção com o próprio ícone — mesmo seletor
47
+ * da tela de rotas do Argos central. */
48
+ this.iconOptions = [...HERMES_ICONS]
49
+ .sort((a, b) => a.name.localeCompare(b.name))
50
+ .map((icon) => ({ value: icon.name, label: icon.name, icon: icon.name }));
51
+ this.path = new FormControl('', { nonNullable: true, validators: [routePathValidator] });
52
+ this.info = new FormControl('', { nonNullable: true });
53
+ /** Rota protegida (`excludable: false`) é travada aqui: o auto-serviço não libera a exclusão — o
54
+ * admin-server também recusa (403), isto só evita oferecer o que vai ser negado. */
55
+ this.excludable = new FormControl(true, { nonNullable: true });
56
+ /** Mensagem do erro `routePath` no `hm-input` (chave do validador → texto). */
57
+ this.pathMessages = computed(() => ({ routePath: this.text.pathInvalid }), ...(ngDevMode ? [{ debugName: "pathMessages" }] : /* istanbul ignore next */ []));
58
+ this.rowId = (r) => r.id;
59
+ this.columns = computed(() => [
60
+ { key: 'name', title: this.text.colName, value: (r) => r.name, sortable: true },
61
+ { key: 'display_name', title: this.text.colDisplay, value: (r) => r.display_name || '—', sortable: true },
62
+ { key: 'icon', title: this.text.colIcon, value: (r) => r.icon || '—' },
63
+ { key: 'path', title: this.text.colPath, value: (r) => r.path || '—', sortable: true },
64
+ {
65
+ key: 'excludable', title: this.text.colExcludable, sortable: true,
66
+ value: (r) => (r.excludable ? this.text.excludableYes : this.text.excludableNo),
67
+ },
68
+ ], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
69
+ // Rota de template não se edita nem se exclui aqui (403 no admin-server); excluir, além disso, só em rota
70
+ // excluível (409). Linha sem ação nenhuma fica sem o kebab.
71
+ this.rowActions = computed(() => [
72
+ { id: 'edit', icon: 'pencil', label: this.text.edit, available: (r) => !r.templated },
73
+ {
74
+ id: 'delete', icon: 'trash', label: this.text.delete, danger: true, separatorAbove: true,
75
+ available: (r) => !r.templated && r.excludable,
76
+ confirm: (r) => ({ heading: this.text.confirmDeleteRouteHeading, message: formatMessage(this.text.confirmDeleteRoute, { name: r.display_name || r.name }) }),
77
+ },
78
+ ], ...(ngDevMode ? [{ debugName: "rowActions" }] : /* istanbul ignore next */ []));
79
+ }
80
+ ngOnChanges(changes) { if (changes['clientId'])
81
+ this.load(); }
82
+ async load() {
83
+ if (!this.clientId) {
84
+ this.routes = [];
85
+ return;
86
+ }
87
+ this.loading = true;
88
+ try {
89
+ const res = await this.service.getWebRoutes(this.clientId);
90
+ this.routes = res.routes;
91
+ }
92
+ finally {
93
+ this.loading = false;
94
+ this.cdr.markForCheck();
95
+ }
96
+ }
97
+ resetForm(r) {
98
+ this.name.setValue(r?.name ?? '');
99
+ this.displayName.setValue(r?.display_name ?? '');
100
+ this.icon.setValue(r?.icon ?? '');
101
+ this.path.setValue(r?.path ?? '');
102
+ this.info.setValue(r?.info ?? '');
103
+ this.excludable.setValue(r?.excludable ?? true);
104
+ // Protegida só destrava no Argos central. Nova rota (sem `r`) sempre pode escolher.
105
+ if (r && !r.excludable)
106
+ this.excludable.disable();
107
+ else
108
+ this.excludable.enable();
109
+ // Sem isso, abrir o modal de novo mostraria o erro do caminho da vez anterior antes de digitar.
110
+ this.path.markAsPristine();
111
+ this.path.markAsUntouched();
112
+ }
113
+ /** Editando rota protegida — mostra por que o "Excluível" está travado. */
114
+ get excludableLocked() {
115
+ return this.excludable.disabled;
116
+ }
117
+ openCreateRoute() {
118
+ this.editingRouteId = '';
119
+ this.resetForm();
120
+ this.showRouteModal = true;
121
+ }
122
+ openEditRoute(r) {
123
+ if (r.templated)
124
+ return;
125
+ this.editingRouteId = r.id;
126
+ this.resetForm(r);
127
+ this.showRouteModal = true;
128
+ }
129
+ async saveRoute() {
130
+ // O botão já fica desabilitado; a guarda cobre chamadas diretas (ex.: Enter no form).
131
+ if (!this.name.value || this.path.invalid)
132
+ return;
133
+ const body = {
134
+ name: this.name.value, display_name: this.displayName.value, icon: this.icon.value,
135
+ path: this.path.value, info: this.info.value, excludable: this.excludable.value,
136
+ };
137
+ if (this.editingRouteId) {
138
+ await this.service.updateWebRoute(this.clientId, this.editingRouteId, body);
139
+ }
140
+ else {
141
+ await this.service.createWebRoute(this.clientId, body);
142
+ }
143
+ this.showRouteModal = false;
144
+ await this.load();
145
+ }
146
+ async deleteRoute(r) {
147
+ if (r.templated || !r.excludable)
148
+ return;
149
+ // Já confirmado pelo `confirm` da ação na grade.
150
+ if (await this.deletion.run(() => this.service.deleteWebRoute(this.clientId, r.id)))
151
+ await this.load();
152
+ }
153
+ onAction(e) {
154
+ if (e.action === 'edit')
155
+ this.openEditRoute(e.row);
156
+ if (e.action === 'delete')
157
+ void this.deleteRoute(e.row);
158
+ }
159
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceRoutesComponent, deps: [{ token: AutoServiceService }], target: i0.ɵɵFactoryTarget.Component }); }
160
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.24", type: AutoServiceRoutesComponent, isStandalone: true, selector: "spine-auto-service-routes", inputs: { clientId: "clientId" }, usesOnChanges: true, ngImport: i0, template: "<hm-data-grid\n [columns]=\"columns()\"\n [rows]=\"routes\"\n [rowId]=\"rowId\"\n [caption]=\"text.routesCaption\"\n [loading]=\"loading\"\n [rowActions]=\"rowActions()\"\n (actionTriggered)=\"onAction($event)\"\n [emptyMessage]=\"text.noRoutes\"\n>\n <hm-button dataGridActions icon=\"plus\" (clicked)=\"openCreateRoute()\">{{ text.newRoute }}</hm-button>\n <ng-template hmCell=\"name\" let-r>\n <span class=\"name-cell\">\n {{ r.name }}\n <hm-tag *ngIf=\"r.templated\" [name]=\"text.templateTag\" color=\"info\" [title]=\"text.templateLocked\" />\n </span>\n </ng-template>\n <ng-template hmCell=\"icon\" let-r>\n <span class=\"icon-cell\">\n <hm-icon *ngIf=\"r.icon\" [shape]=\"r.icon\"></hm-icon>\n {{ r.icon || '\u2014' }}\n </span>\n </ng-template>\n <ng-template hmCell=\"path\" let-r>\n <code *ngIf=\"r.path\">{{ r.path }}</code>\n <ng-container *ngIf=\"!r.path\">\u2014</ng-container>\n </ng-template>\n</hm-data-grid>\n\n<!-- Modal: criar/editar rota -->\n<hm-modal [open]=\"showRouteModal\" (openChange)=\"showRouteModal = $event\" [heading]=\"editingRouteId ? text.editRoute : text.newRoute\" (closed)=\"showRouteModal = false\">\n <div class=\"form-stack\">\n <hm-input [formControl]=\"name\" [label]=\"text.internalName\" />\n <hm-input [formControl]=\"displayName\" [label]=\"text.displayName\" />\n <hm-combobox [options]=\"iconOptions\" [formControl]=\"icon\" [label]=\"text.icon\" [placeholder]=\"text.iconPlaceholder\" />\n <hm-input [formControl]=\"path\" [label]=\"text.path\" [placeholder]=\"text.pathPlaceholder\" [hint]=\"text.pathHint\" [messages]=\"pathMessages()\" />\n <hm-textarea [formControl]=\"info\" [label]=\"text.info\" />\n <hm-checkbox [formControl]=\"excludable\" [label]=\"text.excludable\" />\n <p *ngIf=\"excludableLocked\" class=\"field-note\">{{ text.excludableLocked }}</p>\n </div>\n\n <ng-container footer>\n <hm-button emphasis=\"medium\" (clicked)=\"showRouteModal = false\">{{ text.cancel }}</hm-button>\n <hm-button [disabled]=\"!name.value || path.invalid\" (clicked)=\"saveRoute()\">\n {{ editingRouteId ? text.save : text.create }}\n </hm-button>\n </ng-container>\n</hm-modal>\n", styles: [".as-stack{display:flex;flex-direction:column;gap:var(--hm-space-md);min-inline-size:0}.as-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-xs)}.as-field-row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--hm-space-sm)}.as-field-row>:first-child{flex:1 1 20rem;min-inline-size:0}.as-summary{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-sm)}.as-summary__info{display:flex;flex:1 1 16rem;flex-direction:column;gap:var(--hm-space-micro);min-inline-size:0}.as-summary__title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs);margin:0;color:var(--hm-text);font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-semibold)}.as-summary__detail{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.muted{color:var(--hm-muted)}:host{display:block}.form-stack{display:flex;flex-direction:column;gap:var(--hm-space-sm)}.field-note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.name-cell{display:inline-flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.icon-cell{display:inline-flex;align-items:center;gap:var(--hm-space-micro)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "busy", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: CheckboxComponent, selector: "hm-checkbox", inputs: ["label", "labelHidden", "checked", "indeterminate"], outputs: ["checkedChange"] }, { kind: "component", type: ComboboxComponent, selector: "hm-combobox", inputs: ["label", "options", "value", "placeholder", "icon", "hint", "help", "size", "labelHidden", "clearable", "requiredManual", "loading", "allowCustomValue"], outputs: ["valueChange"] }, { kind: "component", type: IconComponent, selector: "hm-icon", inputs: ["shape", "direction", "size", "solid", "badge", "status", "flip", "inverse", "label"] }, { kind: "component", type: InputComponent, selector: "hm-input", inputs: ["value", "label", "type", "placeholder", "icon", "labelHidden", "hint", "prefix", "suffix", "prefixIcon", "mask", "maskValue", "min", "max", "step", "stepButtons", "maxLength", "autocomplete", "inputMode"], outputs: ["valueChange"] }, { kind: "component", type: TextareaComponent, selector: "hm-textarea", inputs: ["value", "label", "hint", "help", "placeholder", "maxLength", "rows", "autoGrow", "labelHidden", "loading", "maxRows", "submitKey"], outputs: ["valueChange", "submitted"] }, { kind: "component", type: ModalComponent, selector: "hm-modal", inputs: ["open", "heading", "size", "closeLabel", "closable", "beforeClose"], outputs: ["openChange", "closed"] }, { kind: "component", type: DataGridComponent, selector: "hm-data-grid", inputs: ["columns", "rows", "rowId", "caption", "rowActions", "loading", "error", "emptyMessage", "noResultsMessage", "filterable", "paginated", "pageState", "pagination", "totalItems", "sort", "selectable", "selectionMode", "selection", "rowSelectable", "rowSelectableReason", "rowDetailDisabled", "rowDetailHidden", "virtual", "rowHeight", "pinnedColumns", "columnState", "pageSizes", "fillHeight", "maxHeight", "bordered", "rowState", "rowClickable", "remoteSearch", "remoteSort", "itemsLabel", "pageSizeLabel", "exportable", "exportFileName", "groupBy", "aggregates", "aggregateFormatter", "persistKey", "persistStorage", "advancedFilter", "filterQuery", "columnManager", "columnOrder", "searchTerm"], outputs: ["pageStateChange", "sortChange", "selectionChange", "pinnedColumnsChange", "columnStateChange", "actionTriggered", "detailToggled", "rowClicked", "clearFiltersRequested", "filteredChange", "searchTermChanged", "groupByChange", "filterQueryChange", "columnOrderChange", "searchTermChange", "detailPaneToggled"] }, { kind: "directive", type: DataGridCellDirective, selector: "ng-template[hmCell]", inputs: ["hmCell"] }, { kind: "component", type: TagComponent, selector: "hm-tag", inputs: ["name", "color", "compact"] }], changeDetection: i0.ChangeDetectionStrategy.Eager }); }
161
+ }
162
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceRoutesComponent, decorators: [{
163
+ type: Component,
164
+ args: [{ selector: 'spine-auto-service-routes', standalone: true, imports: [
165
+ CommonModule, ReactiveFormsModule, ButtonComponent, CheckboxComponent, ComboboxComponent, IconComponent, InputComponent,
166
+ TextareaComponent, ModalComponent, DataGridComponent, DataGridCellDirective, TagComponent,
167
+ ], changeDetection: ChangeDetectionStrategy.Eager, template: "<hm-data-grid\n [columns]=\"columns()\"\n [rows]=\"routes\"\n [rowId]=\"rowId\"\n [caption]=\"text.routesCaption\"\n [loading]=\"loading\"\n [rowActions]=\"rowActions()\"\n (actionTriggered)=\"onAction($event)\"\n [emptyMessage]=\"text.noRoutes\"\n>\n <hm-button dataGridActions icon=\"plus\" (clicked)=\"openCreateRoute()\">{{ text.newRoute }}</hm-button>\n <ng-template hmCell=\"name\" let-r>\n <span class=\"name-cell\">\n {{ r.name }}\n <hm-tag *ngIf=\"r.templated\" [name]=\"text.templateTag\" color=\"info\" [title]=\"text.templateLocked\" />\n </span>\n </ng-template>\n <ng-template hmCell=\"icon\" let-r>\n <span class=\"icon-cell\">\n <hm-icon *ngIf=\"r.icon\" [shape]=\"r.icon\"></hm-icon>\n {{ r.icon || '\u2014' }}\n </span>\n </ng-template>\n <ng-template hmCell=\"path\" let-r>\n <code *ngIf=\"r.path\">{{ r.path }}</code>\n <ng-container *ngIf=\"!r.path\">\u2014</ng-container>\n </ng-template>\n</hm-data-grid>\n\n<!-- Modal: criar/editar rota -->\n<hm-modal [open]=\"showRouteModal\" (openChange)=\"showRouteModal = $event\" [heading]=\"editingRouteId ? text.editRoute : text.newRoute\" (closed)=\"showRouteModal = false\">\n <div class=\"form-stack\">\n <hm-input [formControl]=\"name\" [label]=\"text.internalName\" />\n <hm-input [formControl]=\"displayName\" [label]=\"text.displayName\" />\n <hm-combobox [options]=\"iconOptions\" [formControl]=\"icon\" [label]=\"text.icon\" [placeholder]=\"text.iconPlaceholder\" />\n <hm-input [formControl]=\"path\" [label]=\"text.path\" [placeholder]=\"text.pathPlaceholder\" [hint]=\"text.pathHint\" [messages]=\"pathMessages()\" />\n <hm-textarea [formControl]=\"info\" [label]=\"text.info\" />\n <hm-checkbox [formControl]=\"excludable\" [label]=\"text.excludable\" />\n <p *ngIf=\"excludableLocked\" class=\"field-note\">{{ text.excludableLocked }}</p>\n </div>\n\n <ng-container footer>\n <hm-button emphasis=\"medium\" (clicked)=\"showRouteModal = false\">{{ text.cancel }}</hm-button>\n <hm-button [disabled]=\"!name.value || path.invalid\" (clicked)=\"saveRoute()\">\n {{ editingRouteId ? text.save : text.create }}\n </hm-button>\n </ng-container>\n</hm-modal>\n", styles: [".as-stack{display:flex;flex-direction:column;gap:var(--hm-space-md);min-inline-size:0}.as-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-xs)}.as-field-row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--hm-space-sm)}.as-field-row>:first-child{flex:1 1 20rem;min-inline-size:0}.as-summary{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-sm)}.as-summary__info{display:flex;flex:1 1 16rem;flex-direction:column;gap:var(--hm-space-micro);min-inline-size:0}.as-summary__title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs);margin:0;color:var(--hm-text);font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-semibold)}.as-summary__detail{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.muted{color:var(--hm-muted)}:host{display:block}.form-stack{display:flex;flex-direction:column;gap:var(--hm-space-sm)}.field-note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.name-cell{display:inline-flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.icon-cell{display:inline-flex;align-items:center;gap:var(--hm-space-micro)}\n"] }]
168
+ }], ctorParameters: () => [{ type: AutoServiceService }], propDecorators: { clientId: [{
169
+ type: Input
170
+ }] } });
171
+
172
+ /**
173
+ * Modal "Reordenar menu" da aba Roteadores: a mesma ferramenta do Argos central (árvore do menu, ordem
174
+ * por nível de irmãos, `PATCH /routers/reorder` com um grupo `{type, router_ids}` por item), mas navegando
175
+ * pelos níveis em vez de escolher cada nível num combobox. Cada nível alterado guarda a ordem nova até o
176
+ * "Salvar ordem", que grava um nível por chamada. Reordena por arrasto (alça do `hm-reorder-list`, que
177
+ * também funciona no teclado) e pelos botões subir/descer de cada linha.
178
+ */
179
+ class AutoServiceRoutersReorderComponent {
180
+ constructor() {
181
+ this.messages = inject(SPINE_MESSAGES);
182
+ this.text = this.messages.autoService;
183
+ /** Estado em campo comum, preenchido depois de `await`: marca a view ao terminar (app sem zone.js). */
184
+ this.cdr = inject(ChangeDetectorRef);
185
+ this.host = inject(ElementRef);
186
+ this.injector = inject(Injector);
187
+ this.service = inject(AutoServiceService);
188
+ this.clientId = '';
189
+ this.open = false;
190
+ this.openChange = new EventEmitter();
191
+ /** Gravou ao menos um nível: quem abriu recarrega o que mostra. */
192
+ this.saved = new EventEmitter();
193
+ this.tree = [];
194
+ /** Nós abertos, do topo até o nível mostrado (vazio = topo do menu). */
195
+ this.path = [];
196
+ this.items = [];
197
+ this.loading = false;
198
+ this.saving = false;
199
+ this.error = '';
200
+ /** Anúncio da região viva (mover por botão). */
201
+ this.announcement = '';
202
+ /** Níveis com ordem nova ainda não gravada: o nó pai (`null` = topo) → os irmãos na ordem nova. */
203
+ this.pending = new Map();
204
+ }
205
+ ngOnChanges(changes) { if (changes['open'] && this.open)
206
+ void this.load(); }
207
+ get hasChanges() { return this.pending.size > 0; }
208
+ get parent() { return this.path[this.path.length - 1] ?? null; }
209
+ get levelName() { return this.parent ? this.nodeName(this.parent) : this.text.reorderRoot; }
210
+ get listLabel() { return formatMessage(this.text.reorderListLabel, { name: this.levelName }); }
211
+ async load() {
212
+ this.loading = true;
213
+ this.error = '';
214
+ this.pending.clear();
215
+ this.path = [];
216
+ this.tree = [];
217
+ this.items = [];
218
+ try {
219
+ this.tree = (await this.service.getRouterTree(this.clientId)).tree ?? [];
220
+ this.showLevel();
221
+ }
222
+ catch {
223
+ this.error = this.text.reorderLoadError;
224
+ }
225
+ finally {
226
+ this.loading = false;
227
+ this.cdr.markForCheck();
228
+ }
229
+ }
230
+ nodeName(n) { return n.route_display_name || n.route_name; }
231
+ typeLabel(type) {
232
+ const t = this.text;
233
+ return { header: t.levelHeader, navigation: t.levelNavigation, menu: t.levelMenu, item: t.levelItem, submenu: t.levelSubmenu, page: t.colPage }[type] ?? type;
234
+ }
235
+ childCount(n) { return pluralMessage(this.text.childCount, n.children.length, this.messages.meta.locale); }
236
+ label(key, item) { return formatMessage(this.text[key], { name: item.label }); }
237
+ /** Entra no item: passa a ordenar os filhos dele. */
238
+ enter(item) {
239
+ if (!item.node.children.length)
240
+ return;
241
+ this.path = [...this.path, item.node];
242
+ this.showLevel();
243
+ this.focusFirstHandle();
244
+ }
245
+ /** Volta a um nível da trilha (`-1` = topo do menu). */
246
+ goTo(index) {
247
+ this.path = this.path.slice(0, index + 1);
248
+ this.showLevel();
249
+ this.focusFirstHandle();
250
+ }
251
+ /** Ordem nova vinda do arrasto (ou do teclado na alça) do `hm-reorder-list`. */
252
+ onItemsChange(items) { this.apply(items.map((i) => i.node)); }
253
+ /** Botões subir/descer: move uma posição, anuncia e mantém o foco num botão da mesma linha. */
254
+ move(index, delta) {
255
+ const to = index + delta;
256
+ if (to < 0 || to >= this.items.length)
257
+ return;
258
+ const item = this.items[index];
259
+ this.apply(moveItem(this.items, index, to).map((i) => i.node));
260
+ this.announcement = formatMessage(this.text.movedTo, { name: item.label, position: to + 1, total: this.items.length });
261
+ // A linha muda de lugar no DOM: devolve o foco ao botão clicado (ou ao outro, se este ficou desabilitado).
262
+ const wanted = delta < 0 && to === 0 ? 'down' : delta > 0 && to === this.items.length - 1 ? 'up' : delta < 0 ? 'up' : 'down';
263
+ afterNextRender(() => {
264
+ this.host.nativeElement
265
+ .querySelector(`[data-move="${wanted}"][data-item="${CSS.escape(item.id)}"] button`)
266
+ ?.focus();
267
+ }, { injector: this.injector });
268
+ }
269
+ async save() {
270
+ if (!this.hasChanges || this.saving)
271
+ return;
272
+ this.saving = true;
273
+ this.error = '';
274
+ let wrote = false;
275
+ try {
276
+ for (const [parent, siblings] of [...this.pending]) {
277
+ await this.service.reorderRouters(this.clientId, siblings.map((n) => ({ type: n.route_type, router_ids: n.router_ids })));
278
+ this.pending.delete(parent);
279
+ wrote = true;
280
+ }
281
+ this.saved.emit();
282
+ this.close();
283
+ }
284
+ catch {
285
+ // O que já foi gravado sai da fila; o resto continua pendente para tentar de novo.
286
+ this.error = this.text.reorderSaveError;
287
+ if (wrote)
288
+ this.saved.emit();
289
+ }
290
+ finally {
291
+ this.saving = false;
292
+ this.cdr.markForCheck();
293
+ }
294
+ }
295
+ close() { this.open = false; this.openChange.emit(false); }
296
+ siblingsOf(parent) { return parent ? parent.children : this.tree; }
297
+ /** Troca os irmãos do nível mostrado pela ordem nova e marca o nível como pendente. */
298
+ apply(next) {
299
+ const parent = this.parent;
300
+ if (parent)
301
+ parent.children = next;
302
+ else
303
+ this.tree = next;
304
+ this.pending.set(parent, next);
305
+ this.showLevel();
306
+ }
307
+ showLevel() {
308
+ this.items = this.siblingsOf(this.parent).map((node) => ({
309
+ id: `${node.route_type}:${node.routed_id}`, label: this.nodeName(node), node,
310
+ }));
311
+ }
312
+ focusFirstHandle() {
313
+ afterNextRender(() => this.host.nativeElement.querySelector('.hm-reorder-list__handle')?.focus(), { injector: this.injector });
314
+ }
315
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceRoutersReorderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
316
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.24", type: AutoServiceRoutersReorderComponent, isStandalone: true, selector: "spine-auto-service-routers-reorder", inputs: { clientId: "clientId", open: "open" }, outputs: { openChange: "openChange", saved: "saved" }, usesOnChanges: true, ngImport: i0, template: "<hm-modal [open]=\"open\" (openChange)=\"open = $event\" [heading]=\"text.reorderTitle\" size=\"large\" (closed)=\"close()\">\n <div class=\"reorder\">\n <p class=\"muted reorder__hint\">{{ text.reorderHint }}</p>\n\n <hm-alert *ngIf=\"error\" variant=\"error\">{{ error }}</hm-alert>\n\n <div *ngIf=\"loading\" class=\"reorder__loading\">{{ text.loading }}</div>\n\n <ng-container *ngIf=\"!loading && tree.length\">\n <!-- Trilha dos n\u00EDveis abertos: cada degrau anterior volta para aquele n\u00EDvel. -->\n <nav class=\"reorder__path\" [attr.aria-label]=\"text.reorderPath\">\n <ol>\n <li>\n <hm-button *ngIf=\"path.length; else rootCurrent\" emphasis=\"minimal\" size=\"small\" (clicked)=\"goTo(-1)\">{{ text.reorderRoot }}</hm-button>\n <ng-template #rootCurrent><span aria-current=\"location\">{{ text.reorderRoot }}</span></ng-template>\n </li>\n <li *ngFor=\"let node of path; let i = index; let last = last\">\n <hm-icon shape=\"angle\" direction=\"right\" class=\"reorder__sep\" />\n <hm-button *ngIf=\"!last\" emphasis=\"minimal\" size=\"small\" (clicked)=\"goTo(i)\">{{ nodeName(node) }}</hm-button>\n <span *ngIf=\"last\" aria-current=\"location\">{{ nodeName(node) }}</span>\n </li>\n </ol>\n </nav>\n\n <hm-reorder-list [label]=\"listLabel\" [labelHidden]=\"true\" [items]=\"items\" (itemsChange)=\"onItemsChange($event)\" [disabled]=\"saving\">\n <ng-template let-item let-index=\"index\">\n <div class=\"reorder__row\">\n <hm-icon *ngIf=\"item.node.route_icon\" [shape]=\"item.node.route_icon\" class=\"reorder__icon\" />\n <span class=\"reorder__name\">\n <span class=\"reorder__label\">{{ item.label }}</span>\n <span *ngIf=\"item.node.children.length\" class=\"reorder__count\">{{ childCount(item.node) }}</span>\n </span>\n <hm-tag [name]=\"typeLabel(item.node.route_type)\" color=\"neutral\" />\n <span class=\"reorder__actions\">\n <hm-icon-button\n emphasis=\"subtle\" size=\"small\" icon=\"arrow\" iconDirection=\"up\" [label]=\"label('moveUp', item)\"\n [attr.data-move]=\"'up'\" [attr.data-item]=\"item.id\"\n [disabled]=\"saving || index === 0\" (clicked)=\"move(index, -1)\" />\n <hm-icon-button\n emphasis=\"subtle\" size=\"small\" icon=\"arrow\" iconDirection=\"down\" [label]=\"label('moveDown', item)\"\n [attr.data-move]=\"'down'\" [attr.data-item]=\"item.id\"\n [disabled]=\"saving || index === items.length - 1\" (clicked)=\"move(index, 1)\" />\n <hm-icon-button\n *ngIf=\"item.node.children.length\"\n emphasis=\"subtle\" size=\"small\" icon=\"angle\" iconDirection=\"right\" [label]=\"label('openLevel', item)\"\n (clicked)=\"enter(item)\" />\n </span>\n </div>\n </ng-template>\n </hm-reorder-list>\n </ng-container>\n\n <div *ngIf=\"!loading && !error && !tree.length\" class=\"reorder__empty\">\n <hm-icon shape=\"sort-vertical\" size=\"xl\" />\n <p>{{ text.reorderEmpty }}</p>\n </div>\n\n <div class=\"hm-visually-hidden\" role=\"status\" aria-live=\"polite\" aria-atomic=\"true\">{{ announcement }}</div>\n </div>\n\n <ng-container footer>\n <hm-button emphasis=\"medium\" (clicked)=\"close()\">{{ text.cancel }}</hm-button>\n <hm-button [disabled]=\"!hasChanges\" [loading]=\"saving\" (clicked)=\"save()\">{{ text.reorderSave }}</hm-button>\n </ng-container>\n</hm-modal>\n", styles: [".as-stack{display:flex;flex-direction:column;gap:var(--hm-space-md);min-inline-size:0}.as-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-xs)}.as-field-row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--hm-space-sm)}.as-field-row>:first-child{flex:1 1 20rem;min-inline-size:0}.as-summary{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-sm)}.as-summary__info{display:flex;flex:1 1 16rem;flex-direction:column;gap:var(--hm-space-micro);min-inline-size:0}.as-summary__title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs);margin:0;color:var(--hm-text);font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-semibold)}.as-summary__detail{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.muted{color:var(--hm-muted)}.reorder{display:flex;flex-direction:column;gap:var(--hm-space-sm)}.reorder__hint{margin:0}.reorder__loading{display:flex;justify-content:center;padding:var(--hm-space-xl);color:var(--hm-muted)}.reorder__path ol{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-micro);margin:0;padding:0;list-style:none;font-size:var(--hm-font-size-sm)}.reorder__path li{display:inline-flex;align-items:center;gap:var(--hm-space-micro)}.reorder__path [aria-current]{font-weight:var(--hm-font-weight-semibold);color:var(--hm-text)}.reorder__sep{color:var(--hm-muted)}.reorder__row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs);min-inline-size:0}.reorder__icon{flex:0 0 auto;color:var(--hm-action)}.reorder__name{display:flex;flex:1 1 10rem;flex-direction:column;min-inline-size:0}.reorder__label{overflow-wrap:anywhere}.reorder__count{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.reorder__actions{display:inline-flex;gap:var(--hm-space-micro);margin-inline-start:auto}.reorder__empty{display:flex;flex-direction:column;align-items:center;gap:var(--hm-space-xs);padding:var(--hm-space-xl);color:var(--hm-muted);text-align:center}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: AlertComponent, selector: "hm-alert", inputs: ["variant", "appearance", "heading", "closable", "size", "showIcon", "link", "linkLabel", "linkTarget", "dismissKey", "duration"], outputs: ["closed"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "busy", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: IconButtonComponent, selector: "hm-icon-button", inputs: ["label", "icon", "solid", "iconDirection", "emphasis", "intent", "size", "disabled", "toggle", "pressed", "keyShortcuts"], outputs: ["pressedChange", "clicked"] }, { kind: "component", type: IconComponent, selector: "hm-icon", inputs: ["shape", "direction", "size", "solid", "badge", "status", "flip", "inverse", "label"] }, { kind: "component", type: ModalComponent, selector: "hm-modal", inputs: ["open", "heading", "size", "closeLabel", "closable", "beforeClose"], outputs: ["openChange", "closed"] }, { kind: "component", type: ReorderListComponent, selector: "hm-reorder-list", inputs: ["items", "label", "labelHidden", "hint", "size", "removable"], outputs: ["itemsChange", "moved", "removed"] }, { kind: "component", type: TagComponent, selector: "hm-tag", inputs: ["name", "color", "compact"] }], changeDetection: i0.ChangeDetectionStrategy.Eager }); }
317
+ }
318
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceRoutersReorderComponent, decorators: [{
319
+ type: Component,
320
+ args: [{ selector: 'spine-auto-service-routers-reorder', standalone: true, imports: [CommonModule, AlertComponent, ButtonComponent, IconButtonComponent, IconComponent, ModalComponent, ReorderListComponent, TagComponent], changeDetection: ChangeDetectionStrategy.Eager, template: "<hm-modal [open]=\"open\" (openChange)=\"open = $event\" [heading]=\"text.reorderTitle\" size=\"large\" (closed)=\"close()\">\n <div class=\"reorder\">\n <p class=\"muted reorder__hint\">{{ text.reorderHint }}</p>\n\n <hm-alert *ngIf=\"error\" variant=\"error\">{{ error }}</hm-alert>\n\n <div *ngIf=\"loading\" class=\"reorder__loading\">{{ text.loading }}</div>\n\n <ng-container *ngIf=\"!loading && tree.length\">\n <!-- Trilha dos n\u00EDveis abertos: cada degrau anterior volta para aquele n\u00EDvel. -->\n <nav class=\"reorder__path\" [attr.aria-label]=\"text.reorderPath\">\n <ol>\n <li>\n <hm-button *ngIf=\"path.length; else rootCurrent\" emphasis=\"minimal\" size=\"small\" (clicked)=\"goTo(-1)\">{{ text.reorderRoot }}</hm-button>\n <ng-template #rootCurrent><span aria-current=\"location\">{{ text.reorderRoot }}</span></ng-template>\n </li>\n <li *ngFor=\"let node of path; let i = index; let last = last\">\n <hm-icon shape=\"angle\" direction=\"right\" class=\"reorder__sep\" />\n <hm-button *ngIf=\"!last\" emphasis=\"minimal\" size=\"small\" (clicked)=\"goTo(i)\">{{ nodeName(node) }}</hm-button>\n <span *ngIf=\"last\" aria-current=\"location\">{{ nodeName(node) }}</span>\n </li>\n </ol>\n </nav>\n\n <hm-reorder-list [label]=\"listLabel\" [labelHidden]=\"true\" [items]=\"items\" (itemsChange)=\"onItemsChange($event)\" [disabled]=\"saving\">\n <ng-template let-item let-index=\"index\">\n <div class=\"reorder__row\">\n <hm-icon *ngIf=\"item.node.route_icon\" [shape]=\"item.node.route_icon\" class=\"reorder__icon\" />\n <span class=\"reorder__name\">\n <span class=\"reorder__label\">{{ item.label }}</span>\n <span *ngIf=\"item.node.children.length\" class=\"reorder__count\">{{ childCount(item.node) }}</span>\n </span>\n <hm-tag [name]=\"typeLabel(item.node.route_type)\" color=\"neutral\" />\n <span class=\"reorder__actions\">\n <hm-icon-button\n emphasis=\"subtle\" size=\"small\" icon=\"arrow\" iconDirection=\"up\" [label]=\"label('moveUp', item)\"\n [attr.data-move]=\"'up'\" [attr.data-item]=\"item.id\"\n [disabled]=\"saving || index === 0\" (clicked)=\"move(index, -1)\" />\n <hm-icon-button\n emphasis=\"subtle\" size=\"small\" icon=\"arrow\" iconDirection=\"down\" [label]=\"label('moveDown', item)\"\n [attr.data-move]=\"'down'\" [attr.data-item]=\"item.id\"\n [disabled]=\"saving || index === items.length - 1\" (clicked)=\"move(index, 1)\" />\n <hm-icon-button\n *ngIf=\"item.node.children.length\"\n emphasis=\"subtle\" size=\"small\" icon=\"angle\" iconDirection=\"right\" [label]=\"label('openLevel', item)\"\n (clicked)=\"enter(item)\" />\n </span>\n </div>\n </ng-template>\n </hm-reorder-list>\n </ng-container>\n\n <div *ngIf=\"!loading && !error && !tree.length\" class=\"reorder__empty\">\n <hm-icon shape=\"sort-vertical\" size=\"xl\" />\n <p>{{ text.reorderEmpty }}</p>\n </div>\n\n <div class=\"hm-visually-hidden\" role=\"status\" aria-live=\"polite\" aria-atomic=\"true\">{{ announcement }}</div>\n </div>\n\n <ng-container footer>\n <hm-button emphasis=\"medium\" (clicked)=\"close()\">{{ text.cancel }}</hm-button>\n <hm-button [disabled]=\"!hasChanges\" [loading]=\"saving\" (clicked)=\"save()\">{{ text.reorderSave }}</hm-button>\n </ng-container>\n</hm-modal>\n", styles: [".as-stack{display:flex;flex-direction:column;gap:var(--hm-space-md);min-inline-size:0}.as-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-xs)}.as-field-row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--hm-space-sm)}.as-field-row>:first-child{flex:1 1 20rem;min-inline-size:0}.as-summary{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-sm)}.as-summary__info{display:flex;flex:1 1 16rem;flex-direction:column;gap:var(--hm-space-micro);min-inline-size:0}.as-summary__title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs);margin:0;color:var(--hm-text);font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-semibold)}.as-summary__detail{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.muted{color:var(--hm-muted)}.reorder{display:flex;flex-direction:column;gap:var(--hm-space-sm)}.reorder__hint{margin:0}.reorder__loading{display:flex;justify-content:center;padding:var(--hm-space-xl);color:var(--hm-muted)}.reorder__path ol{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-micro);margin:0;padding:0;list-style:none;font-size:var(--hm-font-size-sm)}.reorder__path li{display:inline-flex;align-items:center;gap:var(--hm-space-micro)}.reorder__path [aria-current]{font-weight:var(--hm-font-weight-semibold);color:var(--hm-text)}.reorder__sep{color:var(--hm-muted)}.reorder__row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs);min-inline-size:0}.reorder__icon{flex:0 0 auto;color:var(--hm-action)}.reorder__name{display:flex;flex:1 1 10rem;flex-direction:column;min-inline-size:0}.reorder__label{overflow-wrap:anywhere}.reorder__count{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.reorder__actions{display:inline-flex;gap:var(--hm-space-micro);margin-inline-start:auto}.reorder__empty{display:flex;flex-direction:column;align-items:center;gap:var(--hm-space-xs);padding:var(--hm-space-xl);color:var(--hm-muted);text-align:center}\n"] }]
321
+ }], propDecorators: { clientId: [{
322
+ type: Input
323
+ }], open: [{
324
+ type: Input
325
+ }], openChange: [{
326
+ type: Output
327
+ }], saved: [{
328
+ type: Output
329
+ }] } });
330
+
331
+ class AutoServiceRoutersComponent {
332
+ constructor(service) {
333
+ this.service = service;
334
+ this.text = inject(SPINE_MESSAGES).autoService;
335
+ /** Telas com estado em campo comum, preenchido depois de `await`: marca a view ao terminar (app sem zone.js). */
336
+ this.cdr = inject(ChangeDetectorRef);
337
+ this.deletion = inject(AutoServiceDeleteFeedback);
338
+ this.clientId = '';
339
+ this.routes = [];
340
+ this.routers = [];
341
+ this.loading = false;
342
+ this.showRouterModal = false;
343
+ /** Modal "Reordenar menu" (ordem dos itens no menu do app, por nível). */
344
+ this.showReorder = false;
345
+ this.editingRouterId = '';
346
+ this.routerForm = { page_id: '', header_id: '', navigation_id: '', menu_id: '', item_id: '', submenu_id: '', is_private: true, description: '' };
347
+ this.rowId = (r) => r.id;
348
+ this.columns = computed(() => [
349
+ { key: 'page', title: this.text.colPage, value: (r) => this.routeName(r.page_id), sortable: true },
350
+ { key: 'built_path', title: this.text.colBuiltPath, value: (r) => r.built_path },
351
+ { key: 'description', title: this.text.colDescription, value: (r) => r.description ?? '' },
352
+ { key: 'is_private', title: this.text.colVisibility, value: (r) => (r.is_private ? this.text.private : this.text.public), sortable: true },
353
+ ], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
354
+ // Router de template não se edita nem se exclui aqui (403 no admin-server): a linha fica sem o kebab.
355
+ this.rowActions = computed(() => [
356
+ { id: 'edit', icon: 'pencil', label: this.text.edit, available: (r) => !r.templated },
357
+ {
358
+ id: 'delete', icon: 'trash', label: this.text.delete, danger: true, separatorAbove: true, available: (r) => !r.templated,
359
+ confirm: (r) => ({ heading: this.text.confirmDeleteRouterHeading, message: formatMessage(this.text.confirmDeleteRouter, { path: r.built_path }) }),
360
+ },
361
+ ], ...(ngDevMode ? [{ debugName: "rowActions" }] : /* istanbul ignore next */ []));
362
+ this.pathLevels = ['header_id', 'navigation_id', 'menu_id', 'item_id', 'submenu_id', 'page_id'];
363
+ }
364
+ get routeOptions() {
365
+ return this.routes.map((r) => ({ value: r.id, label: r.display_name || r.name }));
366
+ }
367
+ ngOnChanges(changes) { if (changes['clientId'])
368
+ this.load(); }
369
+ async load() {
370
+ if (!this.clientId) {
371
+ this.routes = [];
372
+ this.routers = [];
373
+ return;
374
+ }
375
+ this.loading = true;
376
+ try {
377
+ const [routesRes, routersRes] = await Promise.all([this.service.getWebRoutes(this.clientId), this.service.getRouters(this.clientId)]);
378
+ this.routes = routesRes.routes;
379
+ this.routers = routersRes.routers.map((r) => ({ ...r, built_path: this.builtPath(r) }));
380
+ }
381
+ finally {
382
+ this.loading = false;
383
+ this.cdr.markForCheck();
384
+ }
385
+ }
386
+ routeName(id) { if (!id)
387
+ return '—'; return this.routes.find(r => r.id === id)?.name ?? id; }
388
+ builtPath(r) {
389
+ const segments = this.pathLevels
390
+ .map(level => this.routes.find(x => x.id === r[level])?.path)
391
+ .filter((p) => !!p)
392
+ .map(p => p.replace(/^\/+|\/+$/g, ''));
393
+ return segments.length ? '/' + segments.join('/') : '—';
394
+ }
395
+ openCreateRouter() {
396
+ this.editingRouterId = '';
397
+ this.routerForm = { page_id: '', header_id: '', navigation_id: '', menu_id: '', item_id: '', submenu_id: '', is_private: true, description: '' };
398
+ this.showRouterModal = true;
399
+ }
400
+ openEditRouter(r) {
401
+ this.editingRouterId = r.id;
402
+ this.routerForm = {
403
+ page_id: r.page_id, header_id: r.header_id ?? '', navigation_id: r.navigation_id ?? '',
404
+ menu_id: r.menu_id ?? '', item_id: r.item_id ?? '', submenu_id: r.submenu_id ?? '', is_private: r.is_private ?? true,
405
+ description: r.description ?? '',
406
+ };
407
+ this.showRouterModal = true;
408
+ }
409
+ async saveRouter() {
410
+ const body = {
411
+ page_id: this.routerForm.page_id,
412
+ header_id: this.routerForm.header_id || null,
413
+ navigation_id: this.routerForm.navigation_id || null,
414
+ menu_id: this.routerForm.menu_id || null,
415
+ item_id: this.routerForm.item_id || null,
416
+ submenu_id: this.routerForm.submenu_id || null,
417
+ is_private: this.routerForm.is_private,
418
+ description: this.routerForm.description.trim() || null,
419
+ };
420
+ if (this.editingRouterId) {
421
+ await this.service.updateRouter(this.clientId, this.editingRouterId, body);
422
+ }
423
+ else {
424
+ await this.service.createRouter(this.clientId, body);
425
+ }
426
+ this.showRouterModal = false;
427
+ await this.load();
428
+ }
429
+ /** Já confirmado pelo `confirm` da ação na grade. */
430
+ async deleteRouter(routerId) {
431
+ if (await this.deletion.run(() => this.service.deleteRouter(this.clientId, routerId)))
432
+ await this.load();
433
+ }
434
+ onAction(e) {
435
+ if (e.row.templated)
436
+ return;
437
+ if (e.action === 'edit')
438
+ this.openEditRouter(e.row);
439
+ if (e.action === 'delete')
440
+ void this.deleteRouter(e.row.id);
441
+ }
442
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceRoutersComponent, deps: [{ token: AutoServiceService }], target: i0.ɵɵFactoryTarget.Component }); }
443
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.24", type: AutoServiceRoutersComponent, isStandalone: true, selector: "spine-auto-service-routers", inputs: { clientId: "clientId" }, usesOnChanges: true, ngImport: i0, template: "<hm-data-grid\n [columns]=\"columns()\"\n [rows]=\"routers\"\n [rowId]=\"rowId\"\n [caption]=\"text.routersCaption\"\n [loading]=\"loading\"\n [rowActions]=\"rowActions()\"\n (actionTriggered)=\"onAction($event)\"\n [emptyMessage]=\"text.noRouters\"\n>\n <hm-button dataGridActions emphasis=\"medium\" icon=\"sort-vertical\" [disabled]=\"!routers.length\" (clicked)=\"showReorder = true\">{{ text.reorder }}</hm-button>\n <hm-button dataGridActions icon=\"plus\" [disabled]=\"!routes.length\" (clicked)=\"openCreateRouter()\">{{ text.newRouter }}</hm-button>\n <ng-template hmCell=\"page\" let-r>\n <span class=\"name-cell\">\n {{ routeName(r.page_id) }}\n <hm-tag *ngIf=\"r.templated\" [name]=\"text.templateTag\" color=\"info\" [title]=\"text.templateLocked\" />\n </span>\n </ng-template>\n <ng-template hmCell=\"is_private\" let-r>\n <hm-tag [name]=\"r.is_private ? text.private : text.public\" [color]=\"r.is_private ? 'warning' : 'success'\" />\n </ng-template>\n <ng-template hmCell=\"built_path\" let-r>\n <code>{{ r.built_path }}</code>\n </ng-template>\n</hm-data-grid>\n\n<!-- Modal: ordem dos itens no menu do app (vale tamb\u00E9m para roteadores de template) -->\n<spine-auto-service-routers-reorder [clientId]=\"clientId\" [open]=\"showReorder\" (openChange)=\"showReorder = $event\" (saved)=\"load()\" />\n\n<!-- Modal: criar/editar roteador -->\n<hm-modal [open]=\"showRouterModal\" (openChange)=\"showRouterModal = $event\" [heading]=\"editingRouterId ? text.editRouter : text.newRouter\" size=\"large\" (closed)=\"showRouterModal = false\">\n <div class=\"form-stack\">\n <hm-combobox [label]=\"text.page\" [options]=\"routeOptions\" [value]=\"routerForm.page_id\" (valueChange)=\"routerForm.page_id = $event\"></hm-combobox>\n <hm-textarea [label]=\"text.routerDescription\" [rows]=\"2\" [hint]=\"text.routerDescriptionHint\" [(ngModel)]=\"routerForm.description\" />\n\n <hm-divider [label]=\"text.hierarchy\" />\n\n <hm-combobox [label]=\"text.levelHeader\" [options]=\"routeOptions\" [clearable]=\"true\" [value]=\"routerForm.header_id\" (valueChange)=\"routerForm.header_id = $event\"></hm-combobox>\n <hm-combobox [label]=\"text.levelNavigation\" [options]=\"routeOptions\" [clearable]=\"true\" [value]=\"routerForm.navigation_id\" (valueChange)=\"routerForm.navigation_id = $event\"></hm-combobox>\n <hm-combobox [label]=\"text.levelMenu\" [options]=\"routeOptions\" [clearable]=\"true\" [value]=\"routerForm.menu_id\" (valueChange)=\"routerForm.menu_id = $event\"></hm-combobox>\n <hm-combobox [label]=\"text.levelItem\" [options]=\"routeOptions\" [clearable]=\"true\" [value]=\"routerForm.item_id\" (valueChange)=\"routerForm.item_id = $event\"></hm-combobox>\n <hm-combobox [label]=\"text.levelSubmenu\" [options]=\"routeOptions\" [clearable]=\"true\" [value]=\"routerForm.submenu_id\" (valueChange)=\"routerForm.submenu_id = $event\"></hm-combobox>\n\n <hm-divider />\n <hm-checkbox [(ngModel)]=\"routerForm.is_private\" [label]=\"text.privateHint\" />\n </div>\n\n <ng-container footer>\n <hm-button emphasis=\"medium\" (clicked)=\"showRouterModal = false\">{{ text.cancel }}</hm-button>\n <hm-button [disabled]=\"!routerForm.page_id\" (clicked)=\"saveRouter()\">\n {{ editingRouterId ? text.save : text.create }}\n </hm-button>\n </ng-container>\n</hm-modal>\n", styles: [".as-stack{display:flex;flex-direction:column;gap:var(--hm-space-md);min-inline-size:0}.as-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-xs)}.as-field-row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--hm-space-sm)}.as-field-row>:first-child{flex:1 1 20rem;min-inline-size:0}.as-summary{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-sm)}.as-summary__info{display:flex;flex:1 1 16rem;flex-direction:column;gap:var(--hm-space-micro);min-inline-size:0}.as-summary__title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs);margin:0;color:var(--hm-text);font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-semibold)}.as-summary__detail{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.muted{color:var(--hm-muted)}:host{display:block}.name-cell{display:inline-flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.form-stack{display:flex;flex-direction:column;gap:var(--hm-space-sm)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "busy", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: CheckboxComponent, selector: "hm-checkbox", inputs: ["label", "labelHidden", "checked", "indeterminate"], outputs: ["checkedChange"] }, { kind: "component", type: ComboboxComponent, selector: "hm-combobox", inputs: ["label", "options", "value", "placeholder", "icon", "hint", "help", "size", "labelHidden", "clearable", "requiredManual", "loading", "allowCustomValue"], outputs: ["valueChange"] }, { kind: "component", type: DividerComponent, selector: "hm-divider", inputs: ["orientation", "label", "labelAlign", "emphasis", "thickness", "spacing", "decorative"] }, { kind: "component", type: ModalComponent, selector: "hm-modal", inputs: ["open", "heading", "size", "closeLabel", "closable", "beforeClose"], outputs: ["openChange", "closed"] }, { kind: "component", type: DataGridComponent, selector: "hm-data-grid", inputs: ["columns", "rows", "rowId", "caption", "rowActions", "loading", "error", "emptyMessage", "noResultsMessage", "filterable", "paginated", "pageState", "pagination", "totalItems", "sort", "selectable", "selectionMode", "selection", "rowSelectable", "rowSelectableReason", "rowDetailDisabled", "rowDetailHidden", "virtual", "rowHeight", "pinnedColumns", "columnState", "pageSizes", "fillHeight", "maxHeight", "bordered", "rowState", "rowClickable", "remoteSearch", "remoteSort", "itemsLabel", "pageSizeLabel", "exportable", "exportFileName", "groupBy", "aggregates", "aggregateFormatter", "persistKey", "persistStorage", "advancedFilter", "filterQuery", "columnManager", "columnOrder", "searchTerm"], outputs: ["pageStateChange", "sortChange", "selectionChange", "pinnedColumnsChange", "columnStateChange", "actionTriggered", "detailToggled", "rowClicked", "clearFiltersRequested", "filteredChange", "searchTermChanged", "groupByChange", "filterQueryChange", "columnOrderChange", "searchTermChange", "detailPaneToggled"] }, { kind: "directive", type: DataGridCellDirective, selector: "ng-template[hmCell]", inputs: ["hmCell"] }, { kind: "component", type: TagComponent, selector: "hm-tag", inputs: ["name", "color", "compact"] }, { kind: "component", type: TextareaComponent, selector: "hm-textarea", inputs: ["value", "label", "hint", "help", "placeholder", "maxLength", "rows", "autoGrow", "labelHidden", "loading", "maxRows", "submitKey"], outputs: ["valueChange", "submitted"] }, { kind: "component", type: AutoServiceRoutersReorderComponent, selector: "spine-auto-service-routers-reorder", inputs: ["clientId", "open"], outputs: ["openChange", "saved"] }], changeDetection: i0.ChangeDetectionStrategy.Eager }); }
444
+ }
445
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceRoutersComponent, decorators: [{
446
+ type: Component,
447
+ args: [{ selector: 'spine-auto-service-routers', standalone: true, imports: [
448
+ CommonModule, FormsModule, ButtonComponent, CheckboxComponent, ComboboxComponent, DividerComponent, ModalComponent,
449
+ DataGridComponent, DataGridCellDirective, TagComponent, TextareaComponent, AutoServiceRoutersReorderComponent,
450
+ ], changeDetection: ChangeDetectionStrategy.Eager, template: "<hm-data-grid\n [columns]=\"columns()\"\n [rows]=\"routers\"\n [rowId]=\"rowId\"\n [caption]=\"text.routersCaption\"\n [loading]=\"loading\"\n [rowActions]=\"rowActions()\"\n (actionTriggered)=\"onAction($event)\"\n [emptyMessage]=\"text.noRouters\"\n>\n <hm-button dataGridActions emphasis=\"medium\" icon=\"sort-vertical\" [disabled]=\"!routers.length\" (clicked)=\"showReorder = true\">{{ text.reorder }}</hm-button>\n <hm-button dataGridActions icon=\"plus\" [disabled]=\"!routes.length\" (clicked)=\"openCreateRouter()\">{{ text.newRouter }}</hm-button>\n <ng-template hmCell=\"page\" let-r>\n <span class=\"name-cell\">\n {{ routeName(r.page_id) }}\n <hm-tag *ngIf=\"r.templated\" [name]=\"text.templateTag\" color=\"info\" [title]=\"text.templateLocked\" />\n </span>\n </ng-template>\n <ng-template hmCell=\"is_private\" let-r>\n <hm-tag [name]=\"r.is_private ? text.private : text.public\" [color]=\"r.is_private ? 'warning' : 'success'\" />\n </ng-template>\n <ng-template hmCell=\"built_path\" let-r>\n <code>{{ r.built_path }}</code>\n </ng-template>\n</hm-data-grid>\n\n<!-- Modal: ordem dos itens no menu do app (vale tamb\u00E9m para roteadores de template) -->\n<spine-auto-service-routers-reorder [clientId]=\"clientId\" [open]=\"showReorder\" (openChange)=\"showReorder = $event\" (saved)=\"load()\" />\n\n<!-- Modal: criar/editar roteador -->\n<hm-modal [open]=\"showRouterModal\" (openChange)=\"showRouterModal = $event\" [heading]=\"editingRouterId ? text.editRouter : text.newRouter\" size=\"large\" (closed)=\"showRouterModal = false\">\n <div class=\"form-stack\">\n <hm-combobox [label]=\"text.page\" [options]=\"routeOptions\" [value]=\"routerForm.page_id\" (valueChange)=\"routerForm.page_id = $event\"></hm-combobox>\n <hm-textarea [label]=\"text.routerDescription\" [rows]=\"2\" [hint]=\"text.routerDescriptionHint\" [(ngModel)]=\"routerForm.description\" />\n\n <hm-divider [label]=\"text.hierarchy\" />\n\n <hm-combobox [label]=\"text.levelHeader\" [options]=\"routeOptions\" [clearable]=\"true\" [value]=\"routerForm.header_id\" (valueChange)=\"routerForm.header_id = $event\"></hm-combobox>\n <hm-combobox [label]=\"text.levelNavigation\" [options]=\"routeOptions\" [clearable]=\"true\" [value]=\"routerForm.navigation_id\" (valueChange)=\"routerForm.navigation_id = $event\"></hm-combobox>\n <hm-combobox [label]=\"text.levelMenu\" [options]=\"routeOptions\" [clearable]=\"true\" [value]=\"routerForm.menu_id\" (valueChange)=\"routerForm.menu_id = $event\"></hm-combobox>\n <hm-combobox [label]=\"text.levelItem\" [options]=\"routeOptions\" [clearable]=\"true\" [value]=\"routerForm.item_id\" (valueChange)=\"routerForm.item_id = $event\"></hm-combobox>\n <hm-combobox [label]=\"text.levelSubmenu\" [options]=\"routeOptions\" [clearable]=\"true\" [value]=\"routerForm.submenu_id\" (valueChange)=\"routerForm.submenu_id = $event\"></hm-combobox>\n\n <hm-divider />\n <hm-checkbox [(ngModel)]=\"routerForm.is_private\" [label]=\"text.privateHint\" />\n </div>\n\n <ng-container footer>\n <hm-button emphasis=\"medium\" (clicked)=\"showRouterModal = false\">{{ text.cancel }}</hm-button>\n <hm-button [disabled]=\"!routerForm.page_id\" (clicked)=\"saveRouter()\">\n {{ editingRouterId ? text.save : text.create }}\n </hm-button>\n </ng-container>\n</hm-modal>\n", styles: [".as-stack{display:flex;flex-direction:column;gap:var(--hm-space-md);min-inline-size:0}.as-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-xs)}.as-field-row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--hm-space-sm)}.as-field-row>:first-child{flex:1 1 20rem;min-inline-size:0}.as-summary{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-sm)}.as-summary__info{display:flex;flex:1 1 16rem;flex-direction:column;gap:var(--hm-space-micro);min-inline-size:0}.as-summary__title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs);margin:0;color:var(--hm-text);font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-semibold)}.as-summary__detail{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.muted{color:var(--hm-muted)}:host{display:block}.name-cell{display:inline-flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.form-stack{display:flex;flex-direction:column;gap:var(--hm-space-sm)}\n"] }]
451
+ }], ctorParameters: () => [{ type: AutoServiceService }], propDecorators: { clientId: [{
452
+ type: Input
453
+ }] } });
454
+
455
+ class AutoServiceAccessCreateComponent {
456
+ constructor(service) {
457
+ this.service = service;
458
+ this.text = inject(SPINE_MESSAGES).autoService;
459
+ /** Telas com estado em campo comum, preenchido depois de `await`: marca a view ao terminar (app sem zone.js). */
460
+ this.cdr = inject(ChangeDetectorRef);
461
+ this.clientId = '';
462
+ this.availableGroups = [];
463
+ this.open = false;
464
+ this.openChange = new EventEmitter();
465
+ this.created = new EventEmitter();
466
+ this.groupId = '';
467
+ this.saving = false;
468
+ }
469
+ get groupOptions() {
470
+ return this.availableGroups.map((g) => ({ value: g.id, label: g.type ? `${g.name} (${g.type.name})` : g.name }));
471
+ }
472
+ ngOnChanges(changes) { if (changes['open'] && this.open) {
473
+ this.groupId = '';
474
+ } }
475
+ onModalOpenChange(value) { this.open = value; this.openChange.emit(value); }
476
+ close() { this.onModalOpenChange(false); }
477
+ async save() {
478
+ if (!this.groupId)
479
+ return;
480
+ this.saving = true;
481
+ try {
482
+ await this.service.createAccess(this.clientId, this.groupId);
483
+ this.created.emit();
484
+ this.close();
485
+ }
486
+ finally {
487
+ this.saving = false;
488
+ this.cdr.markForCheck();
489
+ }
490
+ }
491
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceAccessCreateComponent, deps: [{ token: AutoServiceService }], target: i0.ɵɵFactoryTarget.Component }); }
492
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.24", type: AutoServiceAccessCreateComponent, isStandalone: true, selector: "spine-auto-service-access-create", inputs: { clientId: "clientId", availableGroups: "availableGroups", open: "open" }, outputs: { openChange: "openChange", created: "created" }, usesOnChanges: true, ngImport: i0, template: "<hm-modal [open]=\"open\" (openChange)=\"onModalOpenChange($event)\" [heading]=\"text.newAccessHeading\" (closed)=\"close()\">\n <hm-combobox [label]=\"text.group\" [options]=\"groupOptions\" [value]=\"groupId\" (valueChange)=\"groupId = $event\"></hm-combobox>\n\n <ng-container footer>\n <hm-button emphasis=\"medium\" (clicked)=\"close()\">{{ text.cancel }}</hm-button>\n <hm-button [disabled]=\"!groupId\" [loading]=\"saving\" (clicked)=\"save()\">{{ text.create }}</hm-button>\n </ng-container>\n</hm-modal>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "busy", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: ComboboxComponent, selector: "hm-combobox", inputs: ["label", "options", "value", "placeholder", "icon", "hint", "help", "size", "labelHidden", "clearable", "requiredManual", "loading", "allowCustomValue"], outputs: ["valueChange"] }, { kind: "component", type: ModalComponent, selector: "hm-modal", inputs: ["open", "heading", "size", "closeLabel", "closable", "beforeClose"], outputs: ["openChange", "closed"] }], changeDetection: i0.ChangeDetectionStrategy.Eager }); }
493
+ }
494
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceAccessCreateComponent, decorators: [{
495
+ type: Component,
496
+ args: [{ selector: 'spine-auto-service-access-create', standalone: true, imports: [CommonModule, ButtonComponent, ComboboxComponent, ModalComponent], changeDetection: ChangeDetectionStrategy.Eager, template: "<hm-modal [open]=\"open\" (openChange)=\"onModalOpenChange($event)\" [heading]=\"text.newAccessHeading\" (closed)=\"close()\">\n <hm-combobox [label]=\"text.group\" [options]=\"groupOptions\" [value]=\"groupId\" (valueChange)=\"groupId = $event\"></hm-combobox>\n\n <ng-container footer>\n <hm-button emphasis=\"medium\" (clicked)=\"close()\">{{ text.cancel }}</hm-button>\n <hm-button [disabled]=\"!groupId\" [loading]=\"saving\" (clicked)=\"save()\">{{ text.create }}</hm-button>\n </ng-container>\n</hm-modal>\n" }]
497
+ }], ctorParameters: () => [{ type: AutoServiceService }], propDecorators: { clientId: [{
498
+ type: Input
499
+ }], availableGroups: [{
500
+ type: Input
501
+ }], open: [{
502
+ type: Input
503
+ }], openChange: [{
504
+ type: Output
505
+ }], created: [{
506
+ type: Output
507
+ }] } });
508
+
509
+ /**
510
+ * Um nível da árvore de rotas — se referencia (`imports: [RouteTreeNodeComponent]`) pra
511
+ * desenhar os filhos, já que não existe nenhum componente de árvore no DS novo (o antigo
512
+ * usava `hermes-tree`/`*hermesRecursiveFor`, sem equivalente). Folha = checkbox (marca/
513
+ * desmarca um guard); pasta = só rótulo, sem estado próprio.
514
+ */
515
+ class RouteTreeNodeComponent {
516
+ constructor() {
517
+ this.selectedChange = new EventEmitter();
518
+ }
519
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: RouteTreeNodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
520
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.24", type: RouteTreeNodeComponent, isStandalone: true, selector: "spine-route-tree-node", inputs: { node: "node" }, outputs: { selectedChange: "selectedChange" }, ngImport: i0, template: `
521
+ <li class="tree-node" [class.tree-node--folder]="node.isFolder">
522
+ <label *ngIf="!node.isFolder" class="tree-node__leaf">
523
+ <input type="checkbox" class="hm-check" [(ngModel)]="node.selected" (ngModelChange)="selectedChange.emit()" />
524
+ <hm-icon shape="router" class="tree-node__icon"></hm-icon>
525
+ <span>{{ node.name }}</span>
526
+ </label>
527
+
528
+ <div *ngIf="node.isFolder" class="tree-node__folder">
529
+ <hm-icon shape="filter-grid" class="tree-node__icon"></hm-icon>
530
+ <span>{{ node.name }}</span>
531
+ </div>
532
+
533
+ <ul class="tree-node__children" *ngIf="node.children.length">
534
+ <spine-route-tree-node *ngFor="let child of node.children" [node]="child" (selectedChange)="selectedChange.emit()"></spine-route-tree-node>
535
+ </ul>
536
+ </li>
537
+ `, isInline: true, styles: [".tree-node{list-style:none}.tree-node__children{list-style:none;margin:0;padding-inline-start:var(--hm-space-lg)}.tree-node__leaf,.tree-node__folder{display:flex;align-items:center;gap:var(--hm-space-xs);padding:var(--hm-space-micro) 0;font-size:var(--hm-font-size-sm)}.tree-node__leaf{cursor:pointer}.tree-node__folder{font-weight:var(--hm-font-weight-semibold);color:var(--hm-muted)}.tree-node__icon{flex:0 0 auto;color:var(--hm-action)}\n"], dependencies: [{ kind: "component", type: RouteTreeNodeComponent, selector: "spine-route-tree-node", inputs: ["node"], outputs: ["selectedChange"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: IconComponent, selector: "hm-icon", inputs: ["shape", "direction", "size", "solid", "badge", "status", "flip", "inverse", "label"] }], changeDetection: i0.ChangeDetectionStrategy.Eager }); }
538
+ }
539
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: RouteTreeNodeComponent, decorators: [{
540
+ type: Component,
541
+ args: [{ selector: 'spine-route-tree-node', standalone: true, imports: [CommonModule, FormsModule, IconComponent, RouteTreeNodeComponent], changeDetection: ChangeDetectionStrategy.Eager, template: `
542
+ <li class="tree-node" [class.tree-node--folder]="node.isFolder">
543
+ <label *ngIf="!node.isFolder" class="tree-node__leaf">
544
+ <input type="checkbox" class="hm-check" [(ngModel)]="node.selected" (ngModelChange)="selectedChange.emit()" />
545
+ <hm-icon shape="router" class="tree-node__icon"></hm-icon>
546
+ <span>{{ node.name }}</span>
547
+ </label>
548
+
549
+ <div *ngIf="node.isFolder" class="tree-node__folder">
550
+ <hm-icon shape="filter-grid" class="tree-node__icon"></hm-icon>
551
+ <span>{{ node.name }}</span>
552
+ </div>
553
+
554
+ <ul class="tree-node__children" *ngIf="node.children.length">
555
+ <spine-route-tree-node *ngFor="let child of node.children" [node]="child" (selectedChange)="selectedChange.emit()"></spine-route-tree-node>
556
+ </ul>
557
+ </li>
558
+ `, styles: [".tree-node{list-style:none}.tree-node__children{list-style:none;margin:0;padding-inline-start:var(--hm-space-lg)}.tree-node__leaf,.tree-node__folder{display:flex;align-items:center;gap:var(--hm-space-xs);padding:var(--hm-space-micro) 0;font-size:var(--hm-font-size-sm)}.tree-node__leaf{cursor:pointer}.tree-node__folder{font-weight:var(--hm-font-weight-semibold);color:var(--hm-muted)}.tree-node__icon{flex:0 0 auto;color:var(--hm-action)}\n"] }]
559
+ }], propDecorators: { node: [{
560
+ type: Input,
561
+ args: [{ required: true }]
562
+ }], selectedChange: [{
563
+ type: Output
564
+ }] } });
565
+
566
+ class AutoServiceAccessGuardsComponent {
567
+ constructor(service) {
568
+ this.service = service;
569
+ this.text = inject(SPINE_MESSAGES).autoService;
570
+ /** Telas com estado em campo comum, preenchido depois de `await`: marca a view ao terminar (app sem zone.js). */
571
+ this.cdr = inject(ChangeDetectorRef);
572
+ this.fmt = formatMessage;
573
+ /** Para `[innerHTML]`: escapa os parâmetros (o tipo de grupo vem do servidor). */
574
+ this.fmtHtml = formatMessageHtml;
575
+ this.clientId = '';
576
+ this.access = null;
577
+ this.open = false;
578
+ this.openChange = new EventEmitter();
579
+ this.availableRouters = [];
580
+ this.availableRoutes = [];
581
+ this.clientPrivateRouters = [];
582
+ this.guards = [];
583
+ this.tree = [];
584
+ this.loading = false;
585
+ this.saving = false;
586
+ this.levels = ['header_id', 'navigation_id', 'menu_id', 'item_id', 'submenu_id'];
587
+ }
588
+ ngOnChanges(changes) { if (changes['open'] && this.open) {
589
+ this.reset();
590
+ } }
591
+ close() { this.open = false; this.openChange.emit(false); }
592
+ async reset() {
593
+ this.loading = true;
594
+ try {
595
+ const [routersRes, routesRes] = await Promise.all([this.service.getRouters(this.clientId), this.service.getWebRoutes(this.clientId)]);
596
+ this.availableRouters = routersRes.routers;
597
+ this.availableRoutes = routesRes.routes;
598
+ this.clientPrivateRouters = this.availableRouters.filter((r) => r.is_private && (!r.allowed_type_ids?.length || r.allowed_type_ids.includes(this.access.group_type_id)));
599
+ await this.loadGuards();
600
+ this.buildTree();
601
+ }
602
+ finally {
603
+ this.loading = false;
604
+ this.cdr.markForCheck();
605
+ }
606
+ }
607
+ async loadGuards() { const res = await this.service.getGuards(this.clientId, this.access.access_id); this.guards = res.guards; }
608
+ routeName(id) {
609
+ if (!id)
610
+ return '—';
611
+ return this.availableRoutes.find((r) => r.id === id)?.name ?? id;
612
+ }
613
+ buildTree() {
614
+ const root = [];
615
+ for (const router of this.clientPrivateRouters) {
616
+ let currentLevel = root;
617
+ for (const level of this.levels) {
618
+ const id = router[level];
619
+ if (!id)
620
+ continue;
621
+ const key = `${level}:${id}`;
622
+ let node = currentLevel.find(n => n.key === key);
623
+ if (!node) {
624
+ node = { key, name: this.routeName(id), isFolder: true, children: [] };
625
+ currentLevel.push(node);
626
+ }
627
+ currentLevel = node.children;
628
+ }
629
+ const guard = this.guards.find((g) => g.router_id === router.id);
630
+ currentLevel.push({
631
+ key: `page:${router.id}`, name: router.page_name || this.routeName(router.page_id), isFolder: false,
632
+ routerId: router.id, guardId: guard?.guard_id, selected: !!guard, children: [],
633
+ });
634
+ }
635
+ this.tree = root;
636
+ }
637
+ collectLeaves(node) {
638
+ if (!node.isFolder)
639
+ return [node];
640
+ return node.children.flatMap(child => this.collectLeaves(child));
641
+ }
642
+ get allLeaves() { return this.tree.flatMap(node => this.collectLeaves(node)); }
643
+ get hasChanges() { return this.allLeaves.some(leaf => !!leaf.selected !== !!leaf.guardId); }
644
+ async confirm() {
645
+ const leaves = this.allLeaves;
646
+ const toAdd = leaves.filter(l => l.selected && !l.guardId);
647
+ const toRemove = leaves.filter(l => !l.selected && l.guardId);
648
+ if (toAdd.length === 0 && toRemove.length === 0) {
649
+ this.close();
650
+ return;
651
+ }
652
+ this.saving = true;
653
+ try {
654
+ for (const l of toAdd) {
655
+ await this.service.addGuard(this.clientId, this.access.access_id, l.routerId);
656
+ }
657
+ for (const l of toRemove) {
658
+ await this.service.removeGuard(this.clientId, this.access.access_id, l.guardId);
659
+ }
660
+ this.close();
661
+ }
662
+ finally {
663
+ this.saving = false;
664
+ this.cdr.markForCheck();
665
+ }
666
+ }
667
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceAccessGuardsComponent, deps: [{ token: AutoServiceService }], target: i0.ɵɵFactoryTarget.Component }); }
668
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.24", type: AutoServiceAccessGuardsComponent, isStandalone: true, selector: "spine-auto-service-access-guards", inputs: { clientId: "clientId", access: "access", open: "open" }, outputs: { openChange: "openChange" }, usesOnChanges: true, ngImport: i0, template: "<hm-drawer [open]=\"open\" (openChange)=\"open = $event\" [heading]=\"fmt(text.routesOf, { group: access?.group_name ?? '' })\" (closed)=\"close()\">\n <p class=\"muted\" *ngIf=\"!loading\" [innerHTML]=\"fmtHtml(text.guardsHint, { badge: access?.group_type_name ?? '' })\"></p>\n\n <div *ngIf=\"loading\" class=\"loading-row\">\n <span>{{ text.loading }}</span>\n </div>\n\n <ul class=\"route-tree\" *ngIf=\"!loading\">\n <spine-route-tree-node *ngFor=\"let node of tree\" [node]=\"node\"></spine-route-tree-node>\n </ul>\n\n <div class=\"empty-state\" *ngIf=\"!loading && tree.length === 0\">\n <hm-icon shape=\"filter-grid\" size=\"xl\"></hm-icon>\n <p>{{ text.noPrivateRoutes }}</p>\n </div>\n\n <ng-container footer>\n <hm-button emphasis=\"medium\" (clicked)=\"close()\">{{ text.cancel }}</hm-button>\n <hm-button [disabled]=\"!hasChanges\" [loading]=\"saving\" (clicked)=\"confirm()\">{{ text.confirmChanges }}</hm-button>\n </ng-container>\n</hm-drawer>\n", styles: [".as-stack{display:flex;flex-direction:column;gap:var(--hm-space-md);min-inline-size:0}.as-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-xs)}.as-field-row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--hm-space-sm)}.as-field-row>:first-child{flex:1 1 20rem;min-inline-size:0}.as-summary{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-sm)}.as-summary__info{display:flex;flex:1 1 16rem;flex-direction:column;gap:var(--hm-space-micro);min-inline-size:0}.as-summary__title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs);margin:0;color:var(--hm-text);font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-semibold)}.as-summary__detail{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.muted{color:var(--hm-muted)}.loading-row{display:flex;justify-content:center;padding:var(--hm-space-xl);color:var(--hm-muted)}.route-tree{list-style:none;margin:0;padding:0}.empty-state{display:flex;flex-direction:column;align-items:center;gap:var(--hm-space-xs);padding:var(--hm-space-xl);color:var(--hm-muted);text-align:center}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "busy", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: IconComponent, selector: "hm-icon", inputs: ["shape", "direction", "size", "solid", "badge", "status", "flip", "inverse", "label"] }, { kind: "component", type: DrawerComponent, selector: "hm-drawer", inputs: ["open", "heading", "size", "position", "pinned", "closeLabel", "closable", "beforeClose"], outputs: ["openChange", "closed"] }, { kind: "component", type: RouteTreeNodeComponent, selector: "spine-route-tree-node", inputs: ["node"], outputs: ["selectedChange"] }], changeDetection: i0.ChangeDetectionStrategy.Eager }); }
669
+ }
670
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceAccessGuardsComponent, decorators: [{
671
+ type: Component,
672
+ args: [{ selector: 'spine-auto-service-access-guards', standalone: true, imports: [CommonModule, ButtonComponent, IconComponent, DrawerComponent, RouteTreeNodeComponent], changeDetection: ChangeDetectionStrategy.Eager, template: "<hm-drawer [open]=\"open\" (openChange)=\"open = $event\" [heading]=\"fmt(text.routesOf, { group: access?.group_name ?? '' })\" (closed)=\"close()\">\n <p class=\"muted\" *ngIf=\"!loading\" [innerHTML]=\"fmtHtml(text.guardsHint, { badge: access?.group_type_name ?? '' })\"></p>\n\n <div *ngIf=\"loading\" class=\"loading-row\">\n <span>{{ text.loading }}</span>\n </div>\n\n <ul class=\"route-tree\" *ngIf=\"!loading\">\n <spine-route-tree-node *ngFor=\"let node of tree\" [node]=\"node\"></spine-route-tree-node>\n </ul>\n\n <div class=\"empty-state\" *ngIf=\"!loading && tree.length === 0\">\n <hm-icon shape=\"filter-grid\" size=\"xl\"></hm-icon>\n <p>{{ text.noPrivateRoutes }}</p>\n </div>\n\n <ng-container footer>\n <hm-button emphasis=\"medium\" (clicked)=\"close()\">{{ text.cancel }}</hm-button>\n <hm-button [disabled]=\"!hasChanges\" [loading]=\"saving\" (clicked)=\"confirm()\">{{ text.confirmChanges }}</hm-button>\n </ng-container>\n</hm-drawer>\n", styles: [".as-stack{display:flex;flex-direction:column;gap:var(--hm-space-md);min-inline-size:0}.as-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-xs)}.as-field-row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--hm-space-sm)}.as-field-row>:first-child{flex:1 1 20rem;min-inline-size:0}.as-summary{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-sm)}.as-summary__info{display:flex;flex:1 1 16rem;flex-direction:column;gap:var(--hm-space-micro);min-inline-size:0}.as-summary__title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs);margin:0;color:var(--hm-text);font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-semibold)}.as-summary__detail{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.muted{color:var(--hm-muted)}.loading-row{display:flex;justify-content:center;padding:var(--hm-space-xl);color:var(--hm-muted)}.route-tree{list-style:none;margin:0;padding:0}.empty-state{display:flex;flex-direction:column;align-items:center;gap:var(--hm-space-xs);padding:var(--hm-space-xl);color:var(--hm-muted);text-align:center}\n"] }]
673
+ }], ctorParameters: () => [{ type: AutoServiceService }], propDecorators: { clientId: [{
674
+ type: Input
675
+ }], access: [{
676
+ type: Input
677
+ }], open: [{
678
+ type: Input
679
+ }], openChange: [{
680
+ type: Output
681
+ }] } });
682
+
683
+ class AutoServiceAccessComponent {
684
+ constructor(service) {
685
+ this.service = service;
686
+ this.text = inject(SPINE_MESSAGES).autoService;
687
+ /** Telas com estado em campo comum, preenchido depois de `await`: marca a view ao terminar (app sem zone.js). */
688
+ this.cdr = inject(ChangeDetectorRef);
689
+ this.deletion = inject(AutoServiceDeleteFeedback);
690
+ this.clientId = '';
691
+ this.entries = [];
692
+ this.availableGroups = [];
693
+ this.loading = false;
694
+ this.showCreate = false;
695
+ this.showGuards = false;
696
+ this.activeAccess = null;
697
+ this.rowId = (a) => a.access_id;
698
+ this.columns = computed(() => [
699
+ { key: 'group_name', title: this.text.colGroup, value: (a) => a.group_name, sortable: true },
700
+ { key: 'group_type_name', title: this.text.colBadge, value: (a) => a.group_type_name, sortable: true },
701
+ ], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
702
+ this.rowActions = computed(() => [
703
+ { id: 'guards', icon: 'shield', label: this.text.manageRoutes },
704
+ {
705
+ id: 'delete', icon: 'trash', label: this.text.remove, danger: true, separatorAbove: true,
706
+ confirm: (a) => ({ heading: this.text.confirmRemoveAccessHeading, message: formatMessage(this.text.confirmRemoveAccess, { group: a.group_name }) }),
707
+ },
708
+ ], ...(ngDevMode ? [{ debugName: "rowActions" }] : /* istanbul ignore next */ []));
709
+ }
710
+ ngOnChanges(changes) {
711
+ if (changes['clientId']) {
712
+ this.load();
713
+ this.loadGroups();
714
+ }
715
+ }
716
+ async load() {
717
+ if (!this.clientId) {
718
+ this.entries = [];
719
+ return;
720
+ }
721
+ this.loading = true;
722
+ try {
723
+ const res = await this.service.getAccess(this.clientId);
724
+ this.entries = res.access;
725
+ }
726
+ finally {
727
+ this.loading = false;
728
+ this.cdr.markForCheck();
729
+ }
730
+ }
731
+ async loadGroups() {
732
+ if (!this.clientId) {
733
+ this.availableGroups = [];
734
+ return;
735
+ }
736
+ const res = await this.service.getGroups(this.clientId);
737
+ this.availableGroups = res.groups;
738
+ this.cdr.markForCheck();
739
+ }
740
+ openCreate() { this.showCreate = true; }
741
+ async onCreated() { await this.load(); }
742
+ /** Já confirmado pelo `confirm` da ação na grade. */
743
+ async deleteAccess(accessId) {
744
+ if (await this.deletion.run(() => this.service.deleteAccess(this.clientId, accessId)))
745
+ await this.load();
746
+ }
747
+ openGuards(access) { this.activeAccess = access; this.showGuards = true; }
748
+ onAction(e) {
749
+ if (e.action === 'guards')
750
+ this.openGuards(e.row);
751
+ if (e.action === 'delete')
752
+ void this.deleteAccess(e.row.access_id);
753
+ }
754
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceAccessComponent, deps: [{ token: AutoServiceService }], target: i0.ɵɵFactoryTarget.Component }); }
755
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.24", type: AutoServiceAccessComponent, isStandalone: true, selector: "spine-auto-service-access", inputs: { clientId: "clientId" }, usesOnChanges: true, ngImport: i0, template: "<hm-data-grid\n [columns]=\"columns()\"\n [rows]=\"entries\"\n [rowId]=\"rowId\"\n [caption]=\"text.accessCaption\"\n [loading]=\"loading\"\n [rowActions]=\"rowActions()\"\n (actionTriggered)=\"onAction($event)\"\n [emptyMessage]=\"text.noAccess\"\n>\n <hm-button dataGridActions icon=\"plus\" (clicked)=\"openCreate()\">{{ text.newAccess }}</hm-button>\n</hm-data-grid>\n\n<spine-auto-service-access-create\n [clientId]=\"clientId\"\n [availableGroups]=\"availableGroups\"\n [open]=\"showCreate\"\n (openChange)=\"showCreate = $event\"\n (created)=\"onCreated()\">\n</spine-auto-service-access-create>\n\n<spine-auto-service-access-guards\n *ngIf=\"activeAccess\"\n [clientId]=\"clientId\"\n [access]=\"activeAccess\"\n [open]=\"showGuards\"\n (openChange)=\"showGuards = $event\">\n</spine-auto-service-access-guards>\n", styles: [".as-stack{display:flex;flex-direction:column;gap:var(--hm-space-md);min-inline-size:0}.as-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-xs)}.as-field-row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--hm-space-sm)}.as-field-row>:first-child{flex:1 1 20rem;min-inline-size:0}.as-summary{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-sm)}.as-summary__info{display:flex;flex:1 1 16rem;flex-direction:column;gap:var(--hm-space-micro);min-inline-size:0}.as-summary__title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs);margin:0;color:var(--hm-text);font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-semibold)}.as-summary__detail{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.muted{color:var(--hm-muted)}:host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "busy", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: DataGridComponent, selector: "hm-data-grid", inputs: ["columns", "rows", "rowId", "caption", "rowActions", "loading", "error", "emptyMessage", "noResultsMessage", "filterable", "paginated", "pageState", "pagination", "totalItems", "sort", "selectable", "selectionMode", "selection", "rowSelectable", "rowSelectableReason", "rowDetailDisabled", "rowDetailHidden", "virtual", "rowHeight", "pinnedColumns", "columnState", "pageSizes", "fillHeight", "maxHeight", "bordered", "rowState", "rowClickable", "remoteSearch", "remoteSort", "itemsLabel", "pageSizeLabel", "exportable", "exportFileName", "groupBy", "aggregates", "aggregateFormatter", "persistKey", "persistStorage", "advancedFilter", "filterQuery", "columnManager", "columnOrder", "searchTerm"], outputs: ["pageStateChange", "sortChange", "selectionChange", "pinnedColumnsChange", "columnStateChange", "actionTriggered", "detailToggled", "rowClicked", "clearFiltersRequested", "filteredChange", "searchTermChanged", "groupByChange", "filterQueryChange", "columnOrderChange", "searchTermChange", "detailPaneToggled"] }, { kind: "component", type: AutoServiceAccessCreateComponent, selector: "spine-auto-service-access-create", inputs: ["clientId", "availableGroups", "open"], outputs: ["openChange", "created"] }, { kind: "component", type: AutoServiceAccessGuardsComponent, selector: "spine-auto-service-access-guards", inputs: ["clientId", "access", "open"], outputs: ["openChange"] }], changeDetection: i0.ChangeDetectionStrategy.Eager }); }
756
+ }
757
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceAccessComponent, decorators: [{
758
+ type: Component,
759
+ args: [{ selector: 'spine-auto-service-access', standalone: true, imports: [CommonModule, ButtonComponent, IconComponent, DataGridComponent, AutoServiceAccessCreateComponent, AutoServiceAccessGuardsComponent], changeDetection: ChangeDetectionStrategy.Eager, template: "<hm-data-grid\n [columns]=\"columns()\"\n [rows]=\"entries\"\n [rowId]=\"rowId\"\n [caption]=\"text.accessCaption\"\n [loading]=\"loading\"\n [rowActions]=\"rowActions()\"\n (actionTriggered)=\"onAction($event)\"\n [emptyMessage]=\"text.noAccess\"\n>\n <hm-button dataGridActions icon=\"plus\" (clicked)=\"openCreate()\">{{ text.newAccess }}</hm-button>\n</hm-data-grid>\n\n<spine-auto-service-access-create\n [clientId]=\"clientId\"\n [availableGroups]=\"availableGroups\"\n [open]=\"showCreate\"\n (openChange)=\"showCreate = $event\"\n (created)=\"onCreated()\">\n</spine-auto-service-access-create>\n\n<spine-auto-service-access-guards\n *ngIf=\"activeAccess\"\n [clientId]=\"clientId\"\n [access]=\"activeAccess\"\n [open]=\"showGuards\"\n (openChange)=\"showGuards = $event\">\n</spine-auto-service-access-guards>\n", styles: [".as-stack{display:flex;flex-direction:column;gap:var(--hm-space-md);min-inline-size:0}.as-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-xs)}.as-field-row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--hm-space-sm)}.as-field-row>:first-child{flex:1 1 20rem;min-inline-size:0}.as-summary{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-sm)}.as-summary__info{display:flex;flex:1 1 16rem;flex-direction:column;gap:var(--hm-space-micro);min-inline-size:0}.as-summary__title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs);margin:0;color:var(--hm-text);font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-semibold)}.as-summary__detail{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.muted{color:var(--hm-muted)}:host{display:block}\n"] }]
760
+ }], ctorParameters: () => [{ type: AutoServiceService }], propDecorators: { clientId: [{
761
+ type: Input
762
+ }] } });
763
+
764
+ class AutoServiceClientComponent {
765
+ constructor() {
766
+ this.text = inject(SPINE_MESSAGES).autoService;
767
+ this.client = null;
768
+ this.activeTab = 'routes';
769
+ }
770
+ onClientChange(c) { this.client = c; }
771
+ setTab(tab) { this.activeTab = tab; }
772
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceClientComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
773
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.24", type: AutoServiceClientComponent, isStandalone: true, selector: "spine-auto-service-client", ngImport: i0, template: "<!-- T\u00EDtulo e descri\u00E7\u00E3o da tela v\u00EAm da barra da p\u00E1gina (nome + description do router). -->\n<div class=\"as-stack\">\n <spine-auto-service-client-picker (clientChange)=\"onClientChange($event)\"></spine-auto-service-client-picker>\n\n <!-- Conte\u00FAdo de cada aba \u00E9 pregui\u00E7oso (*hmTabContent): s\u00F3 busca na API quando a aba \u00E9 aberta. -->\n <hm-tabs *ngIf=\"client as c\" [label]=\"text.clientSections\" [active]=\"activeTab\" (activeChange)=\"setTab($any($event))\">\n <hm-tab id=\"routes\" [label]=\"text.tabRoutes\">\n <spine-auto-service-routes *hmTabContent [clientId]=\"c.id\"></spine-auto-service-routes>\n </hm-tab>\n <hm-tab id=\"routers\" [label]=\"text.tabRouters\">\n <spine-auto-service-routers *hmTabContent [clientId]=\"c.id\"></spine-auto-service-routers>\n </hm-tab>\n <hm-tab id=\"access\" [label]=\"text.tabAccess\">\n <spine-auto-service-access *hmTabContent [clientId]=\"c.id\"></spine-auto-service-access>\n </hm-tab>\n </hm-tabs>\n</div>\n", styles: [".as-stack{display:flex;flex-direction:column;gap:var(--hm-space-md);min-inline-size:0}.as-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-xs)}.as-field-row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--hm-space-sm)}.as-field-row>:first-child{flex:1 1 20rem;min-inline-size:0}.as-summary{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-sm)}.as-summary__info{display:flex;flex:1 1 16rem;flex-direction:column;gap:var(--hm-space-micro);min-inline-size:0}.as-summary__title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs);margin:0;color:var(--hm-text);font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-semibold)}.as-summary__detail{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.muted{color:var(--hm-muted)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: TabsComponent, selector: "hm-tabs", inputs: ["label", "active", "orientation", "mode", "overflow", "fillHeight", "noPanelGap"], outputs: ["activeChange"] }, { kind: "component", type: TabComponent, selector: "hm-tab", inputs: ["id", "label", "disabled", "count", "route", "exact", "destroyOnHide"] }, { kind: "directive", type: TabContentDirective, selector: "ng-template[hmTabContent]" }, { kind: "component", type: AutoServiceClientPickerComponent, selector: "spine-auto-service-client-picker", outputs: ["clientChange"] }, { kind: "component", type: AutoServiceRoutesComponent, selector: "spine-auto-service-routes", inputs: ["clientId"] }, { kind: "component", type: AutoServiceRoutersComponent, selector: "spine-auto-service-routers", inputs: ["clientId"] }, { kind: "component", type: AutoServiceAccessComponent, selector: "spine-auto-service-access", inputs: ["clientId"] }], changeDetection: i0.ChangeDetectionStrategy.Eager }); }
774
+ }
775
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceClientComponent, decorators: [{
776
+ type: Component,
777
+ args: [{ selector: 'spine-auto-service-client', standalone: true, imports: [
778
+ CommonModule, TabsComponent, TabComponent, TabContentDirective,
779
+ AutoServiceClientPickerComponent, AutoServiceRoutesComponent, AutoServiceRoutersComponent, AutoServiceAccessComponent,
780
+ ], changeDetection: ChangeDetectionStrategy.Eager, template: "<!-- T\u00EDtulo e descri\u00E7\u00E3o da tela v\u00EAm da barra da p\u00E1gina (nome + description do router). -->\n<div class=\"as-stack\">\n <spine-auto-service-client-picker (clientChange)=\"onClientChange($event)\"></spine-auto-service-client-picker>\n\n <!-- Conte\u00FAdo de cada aba \u00E9 pregui\u00E7oso (*hmTabContent): s\u00F3 busca na API quando a aba \u00E9 aberta. -->\n <hm-tabs *ngIf=\"client as c\" [label]=\"text.clientSections\" [active]=\"activeTab\" (activeChange)=\"setTab($any($event))\">\n <hm-tab id=\"routes\" [label]=\"text.tabRoutes\">\n <spine-auto-service-routes *hmTabContent [clientId]=\"c.id\"></spine-auto-service-routes>\n </hm-tab>\n <hm-tab id=\"routers\" [label]=\"text.tabRouters\">\n <spine-auto-service-routers *hmTabContent [clientId]=\"c.id\"></spine-auto-service-routers>\n </hm-tab>\n <hm-tab id=\"access\" [label]=\"text.tabAccess\">\n <spine-auto-service-access *hmTabContent [clientId]=\"c.id\"></spine-auto-service-access>\n </hm-tab>\n </hm-tabs>\n</div>\n", styles: [".as-stack{display:flex;flex-direction:column;gap:var(--hm-space-md);min-inline-size:0}.as-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-xs)}.as-field-row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--hm-space-sm)}.as-field-row>:first-child{flex:1 1 20rem;min-inline-size:0}.as-summary{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-sm)}.as-summary__info{display:flex;flex:1 1 16rem;flex-direction:column;gap:var(--hm-space-micro);min-inline-size:0}.as-summary__title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs);margin:0;color:var(--hm-text);font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-semibold)}.as-summary__detail{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.muted{color:var(--hm-muted)}\n"] }]
781
+ }] });
782
+
783
+ export { AutoServiceClientComponent };
784
+ //# sourceMappingURL=trismegisto-spine-auto-service-auto-service-client.component-C7SPavrX.mjs.map