@trismegisto/spine 3.3.3 → 3.3.5
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.
- package/README.md +15 -2
- package/fesm2022/trismegisto-spine-audit.mjs +6 -6
- package/fesm2022/trismegisto-spine-audit.mjs.map +1 -1
- package/fesm2022/{trismegisto-spine-auto-service-auto-service-client.component-BAfeLyTB.mjs → trismegisto-spine-auto-service-auto-service-client.component-kAXaCM5W.mjs} +8 -8
- package/fesm2022/{trismegisto-spine-auto-service-auto-service-client.component-BAfeLyTB.mjs.map → trismegisto-spine-auto-service-auto-service-client.component-kAXaCM5W.mjs.map} +1 -1
- package/fesm2022/{trismegisto-spine-auto-service-auto-service-groups.component-BYjIQ9ID.mjs → trismegisto-spine-auto-service-auto-service-groups.component-DE2QIGRH.mjs} +251 -85
- package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-DE2QIGRH.mjs.map +1 -0
- package/fesm2022/{trismegisto-spine-auto-service-auto-service-news-page.component-CBemH4hr.mjs → trismegisto-spine-auto-service-auto-service-news-page.component-BW7BJ0oD.mjs} +2 -2
- package/fesm2022/{trismegisto-spine-auto-service-auto-service-news-page.component-CBemH4hr.mjs.map → trismegisto-spine-auto-service-auto-service-news-page.component-BW7BJ0oD.mjs.map} +1 -1
- package/fesm2022/{trismegisto-spine-auto-service-auto-service-ratings-page.component-C_THGTkw.mjs → trismegisto-spine-auto-service-auto-service-ratings-page.component-CHPU-QLJ.mjs} +2 -2
- package/fesm2022/{trismegisto-spine-auto-service-auto-service-ratings-page.component-C_THGTkw.mjs.map → trismegisto-spine-auto-service-auto-service-ratings-page.component-CHPU-QLJ.mjs.map} +1 -1
- package/fesm2022/trismegisto-spine-auto-service-auto-service-suggestions-page.component-CkiCTmXD.mjs +3733 -0
- package/fesm2022/trismegisto-spine-auto-service-auto-service-suggestions-page.component-CkiCTmXD.mjs.map +1 -0
- package/fesm2022/{trismegisto-spine-auto-service-auto-service.component-QsIQ61w2.mjs → trismegisto-spine-auto-service-auto-service.component-D58Ht9PH.mjs} +7 -7
- package/fesm2022/{trismegisto-spine-auto-service-auto-service.component-QsIQ61w2.mjs.map → trismegisto-spine-auto-service-auto-service.component-D58Ht9PH.mjs.map} +1 -1
- package/fesm2022/{trismegisto-spine-auto-service-groups.technical-_jQrg1Qh.mjs → trismegisto-spine-auto-service-groups.technical-CB1D3ANC.mjs} +12 -3
- package/fesm2022/trismegisto-spine-auto-service-groups.technical-CB1D3ANC.mjs.map +1 -0
- package/fesm2022/{trismegisto-spine-auto-service-groups.usage-OvWjNeeT.mjs → trismegisto-spine-auto-service-groups.usage-De2MhlE7.mjs} +7 -2
- package/fesm2022/{trismegisto-spine-auto-service-groups.usage-OvWjNeeT.mjs.map → trismegisto-spine-auto-service-groups.usage-De2MhlE7.mjs.map} +1 -1
- package/fesm2022/{trismegisto-spine-auto-service-suggestions.technical-BtUK6sKD.mjs → trismegisto-spine-auto-service-suggestions.technical-CGBBzAR8.mjs} +14 -1
- package/fesm2022/{trismegisto-spine-auto-service-suggestions.technical-BtUK6sKD.mjs.map → trismegisto-spine-auto-service-suggestions.technical-CGBBzAR8.mjs.map} +1 -1
- package/fesm2022/{trismegisto-spine-auto-service-suggestions.usage-BwO9Hjsx.mjs → trismegisto-spine-auto-service-suggestions.usage-DnAJS-Z0.mjs} +26 -3
- package/fesm2022/trismegisto-spine-auto-service-suggestions.usage-DnAJS-Z0.mjs.map +1 -0
- package/fesm2022/trismegisto-spine-auto-service.mjs +24 -11
- package/fesm2022/trismegisto-spine-auto-service.mjs.map +1 -1
- package/fesm2022/{trismegisto-spine-layout-suggestions-page.component-xI2gDMok.mjs → trismegisto-spine-layout-suggestions-page.component-CEKMqera.mjs} +3 -3
- package/fesm2022/{trismegisto-spine-layout-suggestions-page.component-xI2gDMok.mjs.map → trismegisto-spine-layout-suggestions-page.component-CEKMqera.mjs.map} +1 -1
- package/fesm2022/{trismegisto-spine-layout-trismegisto-spine-layout-Clphrsgc.mjs → trismegisto-spine-layout-trismegisto-spine-layout-DiAfxIGP.mjs} +428 -93
- package/fesm2022/trismegisto-spine-layout-trismegisto-spine-layout-DiAfxIGP.mjs.map +1 -0
- package/fesm2022/trismegisto-spine-layout.mjs +1 -1
- package/fesm2022/trismegisto-spine-side-panel.mjs +1 -1
- package/fesm2022/trismegisto-spine-side-panel.mjs.map +1 -1
- package/fesm2022/trismegisto-spine.mjs +528 -10
- package/fesm2022/trismegisto-spine.mjs.map +1 -1
- package/package.json +6 -6
- package/types/trismegisto-spine-auto-service.d.ts +20 -4
- package/types/trismegisto-spine-auto-service.d.ts.map +1 -1
- package/types/trismegisto-spine-layout.d.ts +327 -63
- package/types/trismegisto-spine-layout.d.ts.map +1 -1
- package/types/trismegisto-spine.d.ts +221 -3
- package/types/trismegisto-spine.d.ts.map +1 -1
- package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-BYjIQ9ID.mjs.map +0 -1
- package/fesm2022/trismegisto-spine-auto-service-auto-service-suggestions-page.component-mMjE3JOL.mjs +0 -2179
- package/fesm2022/trismegisto-spine-auto-service-auto-service-suggestions-page.component-mMjE3JOL.mjs.map +0 -1
- package/fesm2022/trismegisto-spine-auto-service-groups.technical-_jQrg1Qh.mjs.map +0 -1
- package/fesm2022/trismegisto-spine-auto-service-suggestions.usage-BwO9Hjsx.mjs.map +0 -1
- package/fesm2022/trismegisto-spine-layout-trismegisto-spine-layout-Clphrsgc.mjs.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, ChangeDetectorRef, EventEmitter, Output, Input, ChangeDetectionStrategy, Component, computed } from '@angular/core';
|
|
2
|
+
import { inject, ChangeDetectorRef, EventEmitter, Output, Input, ChangeDetectionStrategy, Component, signal, computed, DestroyRef } from '@angular/core';
|
|
3
3
|
import * as i2 from '@angular/common';
|
|
4
4
|
import { CommonModule } from '@angular/common';
|
|
5
|
-
import { AlertComponent, ButtonComponent, CheckboxComponent, ComboboxComponent, InputComponent, ModalComponent, TextareaComponent, TagComponent, AccountAvatarComponent, DataGridComponent, DataGridCellDirective, HERMES_MESSAGES, EMPTY_SELECTION, BadgeComponent, IconButtonComponent, IconComponent, HermesDialogService, CardComponent } from '@trismegisto/angular';
|
|
5
|
+
import { AlertComponent, ButtonComponent, CheckboxComponent, ComboboxComponent, InputComponent, ModalComponent, TextareaComponent, HermesI18nService, TagComponent, AccountAvatarComponent, DataGridComponent, DataGridCellDirective, HERMES_MESSAGES, EMPTY_SELECTION, BadgeComponent, IconButtonComponent, IconComponent, HermesDialogService, CardComponent } from '@trismegisto/angular';
|
|
6
6
|
import { formatMessage } from '@trismegisto/primitives';
|
|
7
7
|
import { SPINE_MESSAGES } from '@trismegisto/spine';
|
|
8
8
|
import { a as autoServiceErrorText, A as AutoServiceDeleteFeedback } from './trismegisto-spine-auto-service-auto-service-delete-CV_t9vxm.mjs';
|
|
@@ -11,6 +11,7 @@ import { A as AutoServiceClientPickerComponent } from './trismegisto-spine-auto-
|
|
|
11
11
|
import * as i2$1 from '@angular/forms';
|
|
12
12
|
import { FormControl, Validators, ReactiveFormsModule, FormsModule } from '@angular/forms';
|
|
13
13
|
import { o as optionalIdValidator, a as optionalIdBody } from './trismegisto-spine-auto-service-optional-id.validator-0JPeqhjm.mjs';
|
|
14
|
+
import { Subscription, tap, map } from 'rxjs';
|
|
14
15
|
|
|
15
16
|
/** Limite do nome no servidor (1–100). */
|
|
16
17
|
const GROUP_NAME_MAX = 100;
|
|
@@ -102,7 +103,7 @@ class AutoServiceGroupModalComponent {
|
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceGroupModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
105
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: AutoServiceGroupModalComponent, isStandalone: true, selector: "spine-auto-service-group-modal", inputs: { group: "group", clientId: "clientId", groupTypes: "groupTypes", open: "open" }, outputs: { openChange: "openChange", saved: "saved" }, usesOnChanges: true, ngImport: i0, template: "<hm-modal [open]=\"open\" (openChange)=\"onModalOpenChange($event)\" [heading]=\"isEdit ? text.editGroup : text.newGroup\" (closed)=\"close()\">\n <div class=\"form-stack\">\n <hm-alert *ngIf=\"saveError\" variant=\"error\">{{ saveError }}</hm-alert>\n @if (!isEdit) {\n <hm-input [formControl]=\"newId\" [label]=\"text.optionalId\" [placeholder]=\"text.optionalIdPlaceholder\" [hint]=\"text.optionalIdHint\" [messages]=\"{ optionalId: text.optionalIdInvalid }\" />\n }\n <hm-input [formControl]=\"name\" [label]=\"text.groupName\" [maxLength]=\"nameMax\" />\n <hm-combobox [formControl]=\"typeId\" [options]=\"typeOptions\" [label]=\"text.groupType\" [placeholder]=\"text.groupTypePlaceholder\" />\n <hm-textarea [formControl]=\"description\" [label]=\"text.groupDescription\" />\n <hm-checkbox *ngIf=\"isEdit\" [formControl]=\"enabled\" [label]=\"text.active\" />\n </div>\n\n <ng-container footer>\n <hm-button emphasis=\"medium\" (clicked)=\"close()\">{{ text.cancel }}</hm-button>\n <hm-button [disabled]=\"!canSave\" [loading]=\"saving\" (clicked)=\"save()\">{{ isEdit ? text.save : text.create }}</hm-button>\n </ng-container>\n</hm-modal>\n", styles: [".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: ReactiveFormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { 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", "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", "size", "labelHidden", "clearable", "loading", "allowCustomValue"], outputs: ["valueChange"] }, { 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: ModalComponent, selector: "hm-modal", inputs: ["open", "heading", "size", "closeLabel", "closable", "beforeClose"], outputs: ["openChange", "closed"] }, { kind: "component", type: TextareaComponent, selector: "hm-textarea", inputs: ["value", "label", "hint", "placeholder", "maxLength", "rows", "autoGrow", "labelHidden", "loading", "maxRows", "submitKey"], outputs: ["valueChange", "submitted"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
106
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: AutoServiceGroupModalComponent, isStandalone: true, selector: "spine-auto-service-group-modal", inputs: { group: "group", clientId: "clientId", groupTypes: "groupTypes", open: "open" }, outputs: { openChange: "openChange", saved: "saved" }, usesOnChanges: true, ngImport: i0, template: "<hm-modal [open]=\"open\" (openChange)=\"onModalOpenChange($event)\" [heading]=\"isEdit ? text.editGroup : text.newGroup\" (closed)=\"close()\">\n <div class=\"form-stack\">\n <hm-alert *ngIf=\"saveError\" variant=\"error\">{{ saveError }}</hm-alert>\n @if (!isEdit) {\n <hm-input [formControl]=\"newId\" [label]=\"text.optionalId\" [placeholder]=\"text.optionalIdPlaceholder\" [hint]=\"text.optionalIdHint\" [messages]=\"{ optionalId: text.optionalIdInvalid }\" />\n }\n <hm-input [formControl]=\"name\" [label]=\"text.groupName\" [maxLength]=\"nameMax\" />\n <hm-combobox [formControl]=\"typeId\" [options]=\"typeOptions\" [label]=\"text.groupType\" [placeholder]=\"text.groupTypePlaceholder\" />\n <hm-textarea [formControl]=\"description\" [label]=\"text.groupDescription\" />\n <hm-checkbox *ngIf=\"isEdit\" [formControl]=\"enabled\" [label]=\"text.active\" />\n </div>\n\n <ng-container footer>\n <hm-button emphasis=\"medium\" (clicked)=\"close()\">{{ text.cancel }}</hm-button>\n <hm-button [disabled]=\"!canSave\" [loading]=\"saving\" (clicked)=\"save()\">{{ isEdit ? text.save : text.create }}</hm-button>\n </ng-container>\n</hm-modal>\n", styles: [".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: ReactiveFormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { 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", "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", "size", "labelHidden", "clearable", "loading", "allowCustomValue"], outputs: ["valueChange"] }, { 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: ModalComponent, selector: "hm-modal", inputs: ["open", "heading", "size", "closeLabel", "closable", "beforeClose"], outputs: ["openChange", "closed"] }, { kind: "component", type: TextareaComponent, selector: "hm-textarea", inputs: ["value", "label", "hint", "placeholder", "maxLength", "rows", "autoGrow", "labelHidden", "loading", "maxRows", "submitKey", "mentionCandidates", "mentionTrigger", "texts"], outputs: ["valueChange", "submitted", "mentioned"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
106
107
|
}
|
|
107
108
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceGroupModalComponent, decorators: [{
|
|
108
109
|
type: Component,
|
|
@@ -124,9 +125,118 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
|
|
|
124
125
|
type: Output
|
|
125
126
|
}] } });
|
|
126
127
|
|
|
128
|
+
// Mesma peça de argos admin/ui src/shared/members-loader.ts (o painel central usa a dele). Interna deste ponto de
|
|
129
|
+
// entrada: não vai no public-api.
|
|
130
|
+
/** Membros por página pedidos ao servidor: o teto das rotas `GET …/members` do argos-admin. */
|
|
131
|
+
const MEMBERS_PAGE_LIMIT = 200;
|
|
132
|
+
/**
|
|
133
|
+
* Lista inteira de membros carregada aos poucos: o servidor pagina (nunca monta o grupo inteiro em memória) e a tela
|
|
134
|
+
* pede as páginas uma depois da outra, acrescentando conforme chegam, até ter o `total` (ou vir uma página vazia). A
|
|
135
|
+
* lista já aparece e funciona com a primeira página; `loading` diz que ainda há páginas a caminho.
|
|
136
|
+
*
|
|
137
|
+
* `cancel()` desfaz a inscrição da requisição pendente (o HttpClient a cancela); chame ao fechar a tela ou trocar de
|
|
138
|
+
* grupo. Falha numa página para em `error`, com o que já chegou intacto; `retry()` continua da página que falhou.
|
|
139
|
+
*/
|
|
140
|
+
class MembersLoader {
|
|
141
|
+
constructor(fetchPage, idOf) {
|
|
142
|
+
this.fetchPage = fetchPage;
|
|
143
|
+
this.idOf = idOf;
|
|
144
|
+
this.items = signal([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
145
|
+
/** Total do servidor (o grupo inteiro). */
|
|
146
|
+
this.total = signal(0, ...(ngDevMode ? [{ debugName: "total" }] : /* istanbul ignore next */ []));
|
|
147
|
+
/** Ainda pedindo páginas. */
|
|
148
|
+
this.loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
149
|
+
/** A primeira página já chegou (antes dela a tela mostra o estado de carregando). */
|
|
150
|
+
this.ready = signal(false, ...(ngDevMode ? [{ debugName: "ready" }] : /* istanbul ignore next */ []));
|
|
151
|
+
this.error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
152
|
+
this.loaded = computed(() => this.items().length, ...(ngDevMode ? [{ debugName: "loaded" }] : /* istanbul ignore next */ []));
|
|
153
|
+
this.current = null;
|
|
154
|
+
this.nextPage = 1;
|
|
155
|
+
this.ids = new Set();
|
|
156
|
+
}
|
|
157
|
+
/** Recomeça do zero (cancela o que estava a caminho). */
|
|
158
|
+
start() {
|
|
159
|
+
this.cancel();
|
|
160
|
+
this.items.set([]);
|
|
161
|
+
this.ids.clear();
|
|
162
|
+
this.total.set(0);
|
|
163
|
+
this.ready.set(false);
|
|
164
|
+
this.error.set(null);
|
|
165
|
+
this.nextPage = 1;
|
|
166
|
+
this.fetchNext();
|
|
167
|
+
}
|
|
168
|
+
/** Depois de uma falha: continua da página que falhou, sem perder o que já chegou. */
|
|
169
|
+
retry() {
|
|
170
|
+
if (this.loading())
|
|
171
|
+
return;
|
|
172
|
+
this.error.set(null);
|
|
173
|
+
this.fetchNext();
|
|
174
|
+
}
|
|
175
|
+
cancel() {
|
|
176
|
+
this.current?.unsubscribe();
|
|
177
|
+
this.current = null;
|
|
178
|
+
this.loading.set(false);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Mudança feita pela própria tela depois de gravar (incluir, remover, trocar função), sem reler: `totalDelta` mantém o
|
|
182
|
+
* total coerente com a lista.
|
|
183
|
+
*/
|
|
184
|
+
patch(change, totalDelta = 0) {
|
|
185
|
+
const next = change(this.items());
|
|
186
|
+
this.ids.clear();
|
|
187
|
+
for (const item of next)
|
|
188
|
+
this.ids.add(this.idOf(item));
|
|
189
|
+
this.items.set(next);
|
|
190
|
+
this.total.update((n) => Math.max(next.length, n + totalDelta));
|
|
191
|
+
}
|
|
192
|
+
fetchNext() {
|
|
193
|
+
this.loading.set(true);
|
|
194
|
+
// A continuação (próxima página) roda no `complete`; com uma resposta síncrona ela começa antes do `subscribe`
|
|
195
|
+
// devolver, por isso a inscrição vai num contêiner criado antes.
|
|
196
|
+
const holder = new Subscription();
|
|
197
|
+
this.current = holder;
|
|
198
|
+
let more = false;
|
|
199
|
+
holder.add(this.fetchPage(this.nextPage).subscribe({
|
|
200
|
+
next: (res) => {
|
|
201
|
+
const page = res.members ?? [];
|
|
202
|
+
const fresh = page.filter((item) => !this.ids.has(this.idOf(item)));
|
|
203
|
+
for (const item of fresh)
|
|
204
|
+
this.ids.add(this.idOf(item));
|
|
205
|
+
if (fresh.length)
|
|
206
|
+
this.items.update((list) => [...list, ...fresh]);
|
|
207
|
+
this.total.set(Math.max(res.total ?? 0, this.items().length));
|
|
208
|
+
this.ready.set(true);
|
|
209
|
+
this.nextPage += 1;
|
|
210
|
+
// Página vazia, ou só com quem já chegou (o servidor mudou no meio), encerra: sem isso a carga não teria fim.
|
|
211
|
+
more = fresh.length > 0 && this.items().length < this.total();
|
|
212
|
+
},
|
|
213
|
+
error: (err) => {
|
|
214
|
+
if (this.current !== holder)
|
|
215
|
+
return;
|
|
216
|
+
this.current = null;
|
|
217
|
+
this.error.set(err);
|
|
218
|
+
this.ready.set(true);
|
|
219
|
+
this.loading.set(false);
|
|
220
|
+
},
|
|
221
|
+
complete: () => {
|
|
222
|
+
if (this.current !== holder)
|
|
223
|
+
return;
|
|
224
|
+
if (more) {
|
|
225
|
+
this.fetchNext();
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
this.current = null;
|
|
229
|
+
this.loading.set(false);
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
}));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
127
236
|
// Mirror de admin/ui/.../groups/members/group-members.component.ts (argos-admin),
|
|
128
237
|
// adaptado pra falar com /auto-service/:clientId/groups/:groupId/members em vez de
|
|
129
|
-
// /admin/groups/:groupId/members.
|
|
238
|
+
// /admin/groups/:groupId/members. Todos os membros na grade: o servidor pagina e a tela pede as páginas até o fim
|
|
239
|
+
// (`MembersLoader`); busca local e instantânea, rolagem virtual, sem paginador.
|
|
130
240
|
class AutoServiceGroupMembersComponent {
|
|
131
241
|
/** Data e hora curtas no idioma ativo. */
|
|
132
242
|
formatDate(value) {
|
|
@@ -135,55 +245,58 @@ class AutoServiceGroupMembersComponent {
|
|
|
135
245
|
constructor(service) {
|
|
136
246
|
this.service = service;
|
|
137
247
|
this.catalog = inject(SPINE_MESSAGES);
|
|
248
|
+
this.i18n = inject(HermesI18nService);
|
|
138
249
|
this.text = inject(SPINE_MESSAGES).autoService;
|
|
139
|
-
/** Telas com estado em campo comum, preenchido depois de `await`: marca a view ao terminar (app sem zone.js). */
|
|
140
|
-
this.cdr = inject(ChangeDetectorRef);
|
|
141
250
|
this.clientId = '';
|
|
142
251
|
this.groupId = '';
|
|
143
252
|
this.refreshTrigger = 0;
|
|
253
|
+
/** Quantos membros o grupo tem (o total do servidor). */
|
|
144
254
|
this.countChange = new EventEmitter();
|
|
145
255
|
this.manageMembers = new EventEmitter();
|
|
146
|
-
this.
|
|
147
|
-
this.loading = false;
|
|
256
|
+
this.loader = new MembersLoader((page) => this.service.getGroupMembers(this.clientId, this.groupId, { page, limit: MEMBERS_PAGE_LIMIT }).pipe(tap((res) => this.countChange.emit(res.total ?? 0))), (m) => m.user_id);
|
|
148
257
|
this.rowId = (m) => m.user_id;
|
|
149
258
|
this.columns = computed(() => [
|
|
150
|
-
|
|
259
|
+
// A busca da grade procura nome, login e e-mail; a ordem é pelo nome.
|
|
260
|
+
{ key: 'username', title: this.text.colUser, value: (m) => m.name || m.username, raw: (m) => [m.name, m.username, m.email].filter(Boolean).join(' '), sortable: true },
|
|
151
261
|
{
|
|
152
262
|
key: 'joined_at',
|
|
153
263
|
title: this.text.colMemberSince,
|
|
154
264
|
value: (m) => (m.joined_at ? this.formatDate(m.joined_at) : '—'),
|
|
155
265
|
sortable: true,
|
|
266
|
+
searchable: false,
|
|
156
267
|
},
|
|
157
268
|
], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
|
|
269
|
+
/** "Carregando membros… 400 de 1.250". */
|
|
270
|
+
this.progressLabel = computed(() => {
|
|
271
|
+
const number = new Intl.NumberFormat(this.i18n.formatLocale());
|
|
272
|
+
return formatMessage(this.text.membersLoadingProgress, { loaded: number.format(this.loader.loaded()), total: number.format(this.loader.total()) });
|
|
273
|
+
}, ...(ngDevMode ? [{ debugName: "progressLabel" }] : /* istanbul ignore next */ []));
|
|
274
|
+
this.incompleteLabel = computed(() => {
|
|
275
|
+
const number = new Intl.NumberFormat(this.i18n.formatLocale());
|
|
276
|
+
return formatMessage(this.text.membersLoadIncomplete, { loaded: number.format(this.loader.loaded()), total: number.format(this.loader.total()) });
|
|
277
|
+
}, ...(ngDevMode ? [{ debugName: "incompleteLabel" }] : /* istanbul ignore next */ []));
|
|
278
|
+
inject(DestroyRef).onDestroy(() => this.loader.cancel());
|
|
158
279
|
}
|
|
159
280
|
ngOnChanges(changes) {
|
|
160
|
-
if (changes['groupId'] || changes['refreshTrigger'])
|
|
281
|
+
if (changes['groupId'] || changes['refreshTrigger'])
|
|
161
282
|
this.load();
|
|
162
|
-
}
|
|
163
283
|
}
|
|
164
|
-
|
|
284
|
+
/** Recomeça do zero (outro grupo, ou depois de salvar no modal). */
|
|
285
|
+
load() {
|
|
165
286
|
if (!this.clientId || !this.groupId) {
|
|
166
|
-
this.
|
|
287
|
+
this.loader.cancel();
|
|
288
|
+
this.loader.patch(() => [], -this.loader.total());
|
|
167
289
|
this.countChange.emit(0);
|
|
168
290
|
return;
|
|
169
291
|
}
|
|
170
|
-
this.
|
|
171
|
-
try {
|
|
172
|
-
const res = await this.service.getGroupMembers(this.clientId, this.groupId);
|
|
173
|
-
this.members = res.members;
|
|
174
|
-
this.countChange.emit(this.members.length);
|
|
175
|
-
}
|
|
176
|
-
finally {
|
|
177
|
-
this.loading = false;
|
|
178
|
-
this.cdr.markForCheck();
|
|
179
|
-
}
|
|
292
|
+
this.loader.start();
|
|
180
293
|
}
|
|
181
294
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceGroupMembersComponent, deps: [{ token: AutoServiceService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
182
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
295
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: AutoServiceGroupMembersComponent, isStandalone: true, selector: "spine-auto-service-group-members", inputs: { clientId: "clientId", groupId: "groupId", refreshTrigger: "refreshTrigger" }, outputs: { countChange: "countChange", manageMembers: "manageMembers" }, usesOnChanges: true, ngImport: i0, template: "@if (loader.error()) {\n <!-- Falha numa p\u00E1gina: o que j\u00E1 chegou continua na grade; \"Tentar de novo\" segue de onde parou. -->\n <div class=\"members-status\">\n <span class=\"members-status__error\">{{ incompleteLabel() }}</span>\n <hm-button emphasis=\"medium\" size=\"small\" icon=\"refresh\" (clicked)=\"loader.retry()\">{{ text.retryLoad }}</hm-button>\n </div>\n} @else if (loader.loading() && loader.ready()) {\n <p class=\"members-status members-status--progress\" role=\"status\">{{ progressLabel() }} \u00B7 {{ text.membersPartialResults }}</p>\n}\n\n<!-- Todos os membros na grade (carregados aos poucos): busca local, rolagem virtual, sem paginador. -->\n<hm-data-grid\n [columns]=\"columns()\"\n [rows]=\"loader.items()\"\n [rowId]=\"rowId\"\n [caption]=\"text.membersCaption\"\n [loading]=\"!loader.ready()\"\n [emptyMessage]=\"text.noMembers\"\n [noResultsMessage]=\"text.noMembersMatch\"\n [paginated]=\"false\"\n [pagination]=\"false\"\n [virtual]=\"true\"\n maxHeight=\"32rem\"\n>\n <hm-button dataGridActions icon=\"employee-group\" (clicked)=\"manageMembers.emit()\">{{ text.manageMembers }}</hm-button>\n <ng-template hmCell=\"username\" let-m>\n <div class=\"user-cell\">\n <hm-account-avatar class=\"user-cell__avatar\" [name]=\"m.name || m.username\" [imageUrl]=\"m.picture\"></hm-account-avatar>\n <div class=\"user-cell__body\">\n <div class=\"user-cell__name\">\n <b>{{ m.name || m.username }}</b>\n <hm-tag [name]=\"m.enabled ? text.active : text.inactive\" [color]=\"m.enabled ? 'success' : 'danger'\" />\n </div>\n <div class=\"user-cell__email\">\n <ng-container *ngIf=\"m.name\">{{ m.username }} \u00B7 </ng-container>{{ m.email }}\n </div>\n </div>\n </div>\n </ng-template>\n</hm-data-grid>\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>hm-combobox{flex:1 1 16rem;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)}.as-empty{margin:var(--hm-space-xs) 0 0;font-size:var(--hm-font-size-sm)}.as-secret{display:grid;gap:var(--hm-space-micro)}.as-secret__label{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.as-secret__row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.as-secret__value{flex:1 1 240px;min-width:0;border:var(--hm-border-hairline) solid var(--hm-divider);border-radius:var(--hm-radius-control);padding:var(--hm-space-xs) var(--hm-space-sm);background:var(--hm-surface-muted);color:var(--hm-text);font-family:var(--hm-font-mono);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere;-webkit-user-select:all;user-select:all}:host{display:block}.members-status{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs);margin:0 0 var(--hm-space-sm);font-size:var(--hm-font-size-sm)}.members-status--progress{color:var(--hm-muted)}.members-status__error{color:var(--hm-danger)}.user-cell{display:flex;align-items:center;gap:var(--hm-space-xs)}.user-cell__avatar{flex-shrink:0}.user-cell__body{min-width:0}.user-cell__name{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.user-cell__email{margin-top:var(--hm-space-micro);color:var(--hm-muted);font-size:var(--hm-font-size-xs)}\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", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: TagComponent, selector: "hm-tag", inputs: ["name", "color", "compact"] }, { kind: "component", type: AccountAvatarComponent, selector: "hm-account-avatar", inputs: ["name", "imageUrl", "size", "loading", "presence", "texts"] }, { 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", "striped", "columnGroups", "editMode", "editBatch", "texts", "treeChildren", "treeHasChildren", "treeExpanded", "searchTerm"], outputs: ["pageStateChange", "sortChange", "selectionChange", "pinnedColumnsChange", "columnStateChange", "actionTriggered", "detailToggled", "rowClicked", "clearFiltersRequested", "filteredChange", "searchTermChanged", "groupByChange", "filterQueryChange", "columnOrderChange", "editSaved", "editStarted", "editCanceled", "treeExpandedChange", "treeToggled", "searchTermChange", "detailPaneToggled"] }, { kind: "directive", type: DataGridCellDirective, selector: "ng-template[hmCell]", inputs: ["hmCell"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
183
296
|
}
|
|
184
297
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceGroupMembersComponent, decorators: [{
|
|
185
298
|
type: Component,
|
|
186
|
-
args: [{ selector: 'spine-auto-service-group-members', standalone: true, imports: [CommonModule, ButtonComponent, TagComponent, AccountAvatarComponent, DataGridComponent, DataGridCellDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<hm-data-grid\n [columns]=\"columns()\"\n [rows]=\"
|
|
299
|
+
args: [{ selector: 'spine-auto-service-group-members', standalone: true, imports: [CommonModule, ButtonComponent, TagComponent, AccountAvatarComponent, DataGridComponent, DataGridCellDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (loader.error()) {\n <!-- Falha numa p\u00E1gina: o que j\u00E1 chegou continua na grade; \"Tentar de novo\" segue de onde parou. -->\n <div class=\"members-status\">\n <span class=\"members-status__error\">{{ incompleteLabel() }}</span>\n <hm-button emphasis=\"medium\" size=\"small\" icon=\"refresh\" (clicked)=\"loader.retry()\">{{ text.retryLoad }}</hm-button>\n </div>\n} @else if (loader.loading() && loader.ready()) {\n <p class=\"members-status members-status--progress\" role=\"status\">{{ progressLabel() }} \u00B7 {{ text.membersPartialResults }}</p>\n}\n\n<!-- Todos os membros na grade (carregados aos poucos): busca local, rolagem virtual, sem paginador. -->\n<hm-data-grid\n [columns]=\"columns()\"\n [rows]=\"loader.items()\"\n [rowId]=\"rowId\"\n [caption]=\"text.membersCaption\"\n [loading]=\"!loader.ready()\"\n [emptyMessage]=\"text.noMembers\"\n [noResultsMessage]=\"text.noMembersMatch\"\n [paginated]=\"false\"\n [pagination]=\"false\"\n [virtual]=\"true\"\n maxHeight=\"32rem\"\n>\n <hm-button dataGridActions icon=\"employee-group\" (clicked)=\"manageMembers.emit()\">{{ text.manageMembers }}</hm-button>\n <ng-template hmCell=\"username\" let-m>\n <div class=\"user-cell\">\n <hm-account-avatar class=\"user-cell__avatar\" [name]=\"m.name || m.username\" [imageUrl]=\"m.picture\"></hm-account-avatar>\n <div class=\"user-cell__body\">\n <div class=\"user-cell__name\">\n <b>{{ m.name || m.username }}</b>\n <hm-tag [name]=\"m.enabled ? text.active : text.inactive\" [color]=\"m.enabled ? 'success' : 'danger'\" />\n </div>\n <div class=\"user-cell__email\">\n <ng-container *ngIf=\"m.name\">{{ m.username }} \u00B7 </ng-container>{{ m.email }}\n </div>\n </div>\n </div>\n </ng-template>\n</hm-data-grid>\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>hm-combobox{flex:1 1 16rem;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)}.as-empty{margin:var(--hm-space-xs) 0 0;font-size:var(--hm-font-size-sm)}.as-secret{display:grid;gap:var(--hm-space-micro)}.as-secret__label{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.as-secret__row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.as-secret__value{flex:1 1 240px;min-width:0;border:var(--hm-border-hairline) solid var(--hm-divider);border-radius:var(--hm-radius-control);padding:var(--hm-space-xs) var(--hm-space-sm);background:var(--hm-surface-muted);color:var(--hm-text);font-family:var(--hm-font-mono);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere;-webkit-user-select:all;user-select:all}:host{display:block}.members-status{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs);margin:0 0 var(--hm-space-sm);font-size:var(--hm-font-size-sm)}.members-status--progress{color:var(--hm-muted)}.members-status__error{color:var(--hm-danger)}.user-cell{display:flex;align-items:center;gap:var(--hm-space-xs)}.user-cell__avatar{flex-shrink:0}.user-cell__body{min-width:0}.user-cell__name{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.user-cell__email{margin-top:var(--hm-space-micro);color:var(--hm-muted);font-size:var(--hm-font-size-xs)}\n"] }]
|
|
187
300
|
}], ctorParameters: () => [{ type: AutoServiceService }], propDecorators: { clientId: [{
|
|
188
301
|
type: Input
|
|
189
302
|
}], groupId: [{
|
|
@@ -201,6 +314,17 @@ const ROLE_OPTIONS = ['Administrador', 'Editor'];
|
|
|
201
314
|
/** Sugestões mostradas: quem já está em outros grupos deste sistema (o servidor ordena pelos mais conectados). */
|
|
202
315
|
const SUGGESTIONS = 8;
|
|
203
316
|
let searchSequence = 0;
|
|
317
|
+
function toStaged(m) {
|
|
318
|
+
return {
|
|
319
|
+
user_id: m.user_id, username: m.username, email: m.email, name: m.name ?? null,
|
|
320
|
+
picture: m.picture ?? null, enabled: m.enabled, role: m.role ?? null,
|
|
321
|
+
joined_at: m.joined_at, added_by_username: m.added_by_username ?? null,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Membros do grupo, com as alterações guardadas até o Salvar. A lista inteira vem do servidor em páginas, uma depois
|
|
326
|
+
* da outra (`MembersLoader`), e já funciona com a primeira; a busca é local e instantânea sobre o que já chegou.
|
|
327
|
+
*/
|
|
204
328
|
class AutoServiceGroupMembersModalComponent {
|
|
205
329
|
/** Data e hora curtas no idioma ativo. */
|
|
206
330
|
formatDate(value) {
|
|
@@ -209,6 +333,7 @@ class AutoServiceGroupMembersModalComponent {
|
|
|
209
333
|
constructor(service) {
|
|
210
334
|
this.service = service;
|
|
211
335
|
this.catalog = inject(SPINE_MESSAGES);
|
|
336
|
+
this.i18n = inject(HermesI18nService);
|
|
212
337
|
this.text = inject(SPINE_MESSAGES).autoService;
|
|
213
338
|
/** Telas com estado em campo comum, preenchido depois de `await`: marca a view ao terminar (app sem zone.js). */
|
|
214
339
|
this.cdr = inject(ChangeDetectorRef);
|
|
@@ -229,16 +354,45 @@ class AutoServiceGroupMembersModalComponent {
|
|
|
229
354
|
this.searchId = `spine-members-search-${++searchSequence}`;
|
|
230
355
|
this.titleId = `${this.searchId}-title`;
|
|
231
356
|
this.clearLabel = inject(HERMES_MESSAGES).searchField.clear;
|
|
232
|
-
/**
|
|
233
|
-
this.memberSearch = '';
|
|
234
|
-
this.loading = false;
|
|
357
|
+
/** Busca na lista de membros (nome, username ou e-mail): local e instantânea. */
|
|
358
|
+
this.memberSearch = signal('', ...(ngDevMode ? [{ debugName: "memberSearch" }] : /* istanbul ignore next */ []));
|
|
235
359
|
this.saving = false;
|
|
236
360
|
/** Falha ao gravar (no idioma ativo); o que já tinha sido gravado fica — a lista é recarregada. */
|
|
237
361
|
this.saveError = null;
|
|
238
|
-
|
|
362
|
+
/** Os membros gravados, carregados aos poucos. Página que traz quem estava como "novo" o tira das inclusões. */
|
|
363
|
+
this.loader = new MembersLoader((page) => this.service.getGroupMembers(this.clientId, this.groupId, { page, limit: MEMBERS_PAGE_LIMIT }).pipe(map((res) => ({ total: res.total, members: (res.members ?? []).map(toStaged) })), tap((res) => this.dropStagedExisting(res.members))), (m) => m.user_id);
|
|
364
|
+
/** Inclusões ainda não salvas, a mais recente primeiro. */
|
|
365
|
+
this.added = signal([], ...(ngDevMode ? [{ debugName: "added" }] : /* istanbul ignore next */ []));
|
|
366
|
+
/** Trocas de função dos que já são membros (id -> função nova; null = sem função). */
|
|
367
|
+
this.roleChanges = signal(new Map(), ...(ngDevMode ? [{ debugName: "roleChanges" }] : /* istanbul ignore next */ []));
|
|
368
|
+
/** A lista inteira: as inclusões no topo, depois os gravados com as trocas de função aplicadas. */
|
|
369
|
+
this.members = computed(() => {
|
|
370
|
+
const changes = this.roleChanges();
|
|
371
|
+
return [
|
|
372
|
+
...this.added(),
|
|
373
|
+
...this.loader.items().map((m) => (changes.has(m.user_id) ? { ...m, role: changes.get(m.user_id) ?? null } : m)),
|
|
374
|
+
];
|
|
375
|
+
}, ...(ngDevMode ? [{ debugName: "members" }] : /* istanbul ignore next */ []));
|
|
376
|
+
this.filteredMembers = computed(() => {
|
|
377
|
+
const term = this.memberSearch().trim().toLowerCase();
|
|
378
|
+
const all = this.members();
|
|
379
|
+
if (!term)
|
|
380
|
+
return all;
|
|
381
|
+
return all.filter((m) => [m.name, m.username, m.email].some((v) => (v ?? '').toLowerCase().includes(term)));
|
|
382
|
+
}, ...(ngDevMode ? [{ debugName: "filteredMembers" }] : /* istanbul ignore next */ []));
|
|
383
|
+
/** Número no título: o grupo inteiro (total do servidor) mais as inclusões ainda não salvas. */
|
|
384
|
+
this.memberCount = computed(() => this.loader.total() + this.added().length, ...(ngDevMode ? [{ debugName: "memberCount" }] : /* istanbul ignore next */ []));
|
|
385
|
+
/** "Carregando membros… 400 de 1.250". */
|
|
386
|
+
this.progressLabel = computed(() => {
|
|
387
|
+
const number = new Intl.NumberFormat(this.i18n.formatLocale());
|
|
388
|
+
return formatMessage(this.text.membersLoadingProgress, { loaded: number.format(this.loader.loaded()), total: number.format(this.loader.total()) });
|
|
389
|
+
}, ...(ngDevMode ? [{ debugName: "progressLabel" }] : /* istanbul ignore next */ []));
|
|
390
|
+
this.incompleteLabel = computed(() => {
|
|
391
|
+
const number = new Intl.NumberFormat(this.i18n.formatLocale());
|
|
392
|
+
return formatMessage(this.text.membersLoadIncomplete, { loaded: number.format(this.loader.loaded()), total: number.format(this.loader.total()) });
|
|
393
|
+
}, ...(ngDevMode ? [{ debugName: "incompleteLabel" }] : /* istanbul ignore next */ []));
|
|
239
394
|
/** Ids marcados pra remoção — o grid seleciona por id (não por objeto, ver `GridSelection`). */
|
|
240
395
|
this.checkedForRemovalIds = EMPTY_SELECTION;
|
|
241
|
-
this.originalRoles = new Map();
|
|
242
396
|
this.addSearch = '';
|
|
243
397
|
this.addSearchResults = [];
|
|
244
398
|
this.selectedToAdd = null;
|
|
@@ -247,12 +401,7 @@ class AutoServiceGroupMembersModalComponent {
|
|
|
247
401
|
this.suggestions = [];
|
|
248
402
|
this.rowId = (m) => m.user_id;
|
|
249
403
|
this.columns = computed(() => [
|
|
250
|
-
{
|
|
251
|
-
key: 'username',
|
|
252
|
-
title: this.text.colUser,
|
|
253
|
-
value: (m) => [m.name, m.username, m.email].filter(Boolean).join(' '),
|
|
254
|
-
sortable: true,
|
|
255
|
-
},
|
|
404
|
+
{ key: 'username', title: this.text.colUser, value: (m) => [m.name, m.username, m.email].filter(Boolean).join(' '), sortable: true },
|
|
256
405
|
{ key: 'role', title: this.text.colRole, value: (m) => m.role ?? '—' },
|
|
257
406
|
{
|
|
258
407
|
key: 'joined_at',
|
|
@@ -261,22 +410,23 @@ class AutoServiceGroupMembersModalComponent {
|
|
|
261
410
|
sortable: true,
|
|
262
411
|
},
|
|
263
412
|
], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
|
|
413
|
+
inject(DestroyRef).onDestroy(() => this.loader.cancel());
|
|
264
414
|
}
|
|
265
415
|
ngOnChanges(changes) { if (changes['open'] && this.open) {
|
|
266
416
|
this.resetAndLoad();
|
|
267
417
|
} }
|
|
268
|
-
onModalOpenChange(value) {
|
|
418
|
+
onModalOpenChange(value) {
|
|
419
|
+
this.open = value;
|
|
420
|
+
// Fechado, nada mais vem do servidor (a página pendente é cancelada).
|
|
421
|
+
if (!value)
|
|
422
|
+
this.loader.cancel();
|
|
423
|
+
this.openChange.emit(value);
|
|
424
|
+
}
|
|
269
425
|
close() { this.onModalOpenChange(false); }
|
|
270
426
|
get checkedForRemoval() {
|
|
271
|
-
return this.
|
|
427
|
+
return this.loader.items().filter((m) => this.checkedForRemovalIds.has(m.user_id));
|
|
272
428
|
}
|
|
273
429
|
get removalCount() { return this.checkedForRemovalIds.size; }
|
|
274
|
-
get filteredMembers() {
|
|
275
|
-
const term = this.memberSearch.trim().toLowerCase();
|
|
276
|
-
if (!term)
|
|
277
|
-
return this.members;
|
|
278
|
-
return this.members.filter((m) => [m.name, m.username, m.email].some((v) => (v ?? '').toLowerCase().includes(term)));
|
|
279
|
-
}
|
|
280
430
|
/** Segunda linha da pessoa: "username · e-mail" (só o e-mail quando o nome já é o username). */
|
|
281
431
|
metaOf(u) {
|
|
282
432
|
return [u.name ? u.username : null, u.email].filter(Boolean).join(' · ');
|
|
@@ -286,44 +436,45 @@ class AutoServiceGroupMembersModalComponent {
|
|
|
286
436
|
return [this.metaOf(u), u.groups?.length ? formatMessage(this.text.suggestionGroups, { groups: u.groups.join(', ') }) : '']
|
|
287
437
|
.filter(Boolean).join('\n');
|
|
288
438
|
}
|
|
289
|
-
|
|
439
|
+
resetAndLoad() {
|
|
290
440
|
this.addSearch = '';
|
|
291
441
|
this.addSearchResults = [];
|
|
292
442
|
this.selectedToAdd = null;
|
|
293
443
|
this.selectedRole = null;
|
|
294
|
-
this.memberSearch
|
|
444
|
+
this.memberSearch.set('');
|
|
295
445
|
this.checkedForRemovalIds = EMPTY_SELECTION;
|
|
296
446
|
this.saveError = null;
|
|
297
|
-
this.
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
this.
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
/** Sugestões do servidor; falha (Argos sem a rota, rede) só deixa sem sugestões. */
|
|
447
|
+
this.added.set([]);
|
|
448
|
+
this.roleChanges.set(new Map());
|
|
449
|
+
this.loader.start();
|
|
450
|
+
void this.loadSuggestions();
|
|
451
|
+
}
|
|
452
|
+
/** Quem foi incluído como "novo" mas a página que chegou mostra que já é membro: sai das inclusões. */
|
|
453
|
+
dropStagedExisting(page) {
|
|
454
|
+
const ids = new Set(page.map((m) => m.user_id));
|
|
455
|
+
if (this.added().some((m) => ids.has(m.user_id)))
|
|
456
|
+
this.added.update((list) => list.filter((m) => !ids.has(m.user_id)));
|
|
457
|
+
}
|
|
458
|
+
/** Ids que não podem entrar de novo: os membros já carregados e as inclusões. */
|
|
459
|
+
takenIds() {
|
|
460
|
+
return new Set([...this.loader.items().map((m) => m.user_id), ...this.added().map((m) => m.user_id)]);
|
|
461
|
+
}
|
|
462
|
+
/** Sugestões do servidor (ele já tira quem é membro); falha (Argos sem a rota, rede) só deixa sem sugestões. */
|
|
315
463
|
async loadSuggestions() {
|
|
316
464
|
this.suggestions = [];
|
|
317
465
|
if (!this.clientId || !this.groupId)
|
|
318
466
|
return;
|
|
319
467
|
try {
|
|
320
468
|
const res = await this.service.getMemberSuggestions(this.clientId, this.groupId, SUGGESTIONS);
|
|
321
|
-
const
|
|
322
|
-
this.suggestions = (res.users ?? []).filter((u) => !
|
|
469
|
+
const taken = this.takenIds();
|
|
470
|
+
this.suggestions = (res.users ?? []).filter((u) => !taken.has(u.id)).slice(0, SUGGESTIONS);
|
|
323
471
|
}
|
|
324
472
|
catch {
|
|
325
473
|
this.suggestions = [];
|
|
326
474
|
}
|
|
475
|
+
finally {
|
|
476
|
+
this.cdr.markForCheck();
|
|
477
|
+
}
|
|
327
478
|
}
|
|
328
479
|
/** Chip de sugestão: inclui direto (com a função escolhida ao lado), sem passar pela busca. */
|
|
329
480
|
addSuggestion(user) { this.stageMember(user); }
|
|
@@ -337,8 +488,8 @@ class AutoServiceGroupMembersModalComponent {
|
|
|
337
488
|
}
|
|
338
489
|
this.addSearchTimer = setTimeout(async () => {
|
|
339
490
|
const res = await this.service.searchUsers(this.clientId, { search: value.trim(), limit: 10 });
|
|
340
|
-
const
|
|
341
|
-
this.addSearchResults = (res.users ?? []).filter((u) => !
|
|
491
|
+
const taken = this.takenIds();
|
|
492
|
+
this.addSearchResults = (res.users ?? []).filter((u) => !taken.has(u.id));
|
|
342
493
|
this.cdr.markForCheck();
|
|
343
494
|
}, 300);
|
|
344
495
|
}
|
|
@@ -349,22 +500,36 @@ class AutoServiceGroupMembersModalComponent {
|
|
|
349
500
|
this.stageMember(this.selectedToAdd);
|
|
350
501
|
}
|
|
351
502
|
stageMember(user) {
|
|
352
|
-
if (this.
|
|
503
|
+
if (this.takenIds().has(user.id))
|
|
353
504
|
return;
|
|
354
|
-
this.
|
|
505
|
+
this.added.update((list) => [{
|
|
355
506
|
user_id: user.id, username: user.username, email: user.email, name: user.name ?? null, picture: user.picture ?? null, enabled: user.enabled,
|
|
356
507
|
role: this.selectedRole, joined_at: null, added_by_username: null, new: true,
|
|
357
|
-
}, ...
|
|
508
|
+
}, ...list]);
|
|
358
509
|
this.suggestions = this.suggestions.filter((u) => u.id !== user.id);
|
|
359
510
|
this.clearSelectedToAdd();
|
|
360
511
|
this.selectedRole = null;
|
|
361
512
|
}
|
|
362
|
-
removeStagedNew(member) { this.
|
|
513
|
+
removeStagedNew(member) { this.added.update((list) => list.filter((m) => m.user_id !== member.user_id)); }
|
|
514
|
+
/** Função escolhida na linha: inclusão guarda nela; membro guarda como troca (ou deixa de ser, se voltou à gravada). */
|
|
515
|
+
setRole(member, value) {
|
|
516
|
+
const role = value || null;
|
|
517
|
+
if (member.new) {
|
|
518
|
+
this.added.update((list) => list.map((m) => (m.user_id === member.user_id ? { ...m, role } : m)));
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const stored = this.loader.items().find((m) => m.user_id === member.user_id);
|
|
522
|
+
const next = new Map(this.roleChanges());
|
|
523
|
+
if (stored && role === (stored.role ?? null))
|
|
524
|
+
next.delete(member.user_id);
|
|
525
|
+
else
|
|
526
|
+
next.set(member.user_id, role);
|
|
527
|
+
this.roleChanges.set(next);
|
|
528
|
+
}
|
|
363
529
|
/** `[(selection)]` do grid: separa o que ainda é staged (nunca existiu no servidor — remove
|
|
364
530
|
* na hora, não "marca pra remover") do que já existe (fica marcado, removido só no Salvar). */
|
|
365
531
|
onSelectionChange(ids) {
|
|
366
|
-
const
|
|
367
|
-
const stillNew = cleaned.filter((m) => m.new);
|
|
532
|
+
const stillNew = this.added().filter((m) => ids.has(m.user_id));
|
|
368
533
|
for (const m of stillNew)
|
|
369
534
|
this.removeStagedNew(m);
|
|
370
535
|
this.checkedForRemovalIds = new Set([...ids].filter((id) => !stillNew.some((m) => m.user_id === id)));
|
|
@@ -379,17 +544,18 @@ class AutoServiceGroupMembersModalComponent {
|
|
|
379
544
|
this.saving = true;
|
|
380
545
|
this.saveError = null;
|
|
381
546
|
try {
|
|
382
|
-
const
|
|
383
|
-
for (const m of toAdd) {
|
|
547
|
+
for (const m of this.added()) {
|
|
384
548
|
await this.service.addGroupMember(this.clientId, this.groupId, m.user_id, m.role ?? null);
|
|
385
549
|
}
|
|
386
|
-
|
|
550
|
+
const removed = this.checkedForRemoval;
|
|
551
|
+
for (const m of removed) {
|
|
387
552
|
await this.service.removeGroupMember(this.clientId, this.groupId, m.user_id);
|
|
388
553
|
}
|
|
389
|
-
const removedIds =
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
554
|
+
const removedIds = new Set(removed.map((m) => m.user_id));
|
|
555
|
+
for (const [userId, role] of this.roleChanges()) {
|
|
556
|
+
if (removedIds.has(userId))
|
|
557
|
+
continue;
|
|
558
|
+
await this.service.updateGroupMemberRole(this.clientId, this.groupId, userId, role);
|
|
393
559
|
}
|
|
394
560
|
this.saved.emit();
|
|
395
561
|
this.close();
|
|
@@ -399,7 +565,7 @@ class AutoServiceGroupMembersModalComponent {
|
|
|
399
565
|
this.saveError = autoServiceErrorText(err, this.text, this.text.membersSaveError);
|
|
400
566
|
this.saved.emit();
|
|
401
567
|
const error = this.saveError;
|
|
402
|
-
|
|
568
|
+
this.resetAndLoad();
|
|
403
569
|
this.saveError = error;
|
|
404
570
|
}
|
|
405
571
|
finally {
|
|
@@ -408,14 +574,14 @@ class AutoServiceGroupMembersModalComponent {
|
|
|
408
574
|
}
|
|
409
575
|
}
|
|
410
576
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceGroupMembersModalComponent, deps: [{ token: AutoServiceService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
411
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: AutoServiceGroupMembersModalComponent, isStandalone: true, selector: "spine-auto-service-group-members-modal", inputs: { clientId: "clientId", groupId: "groupId", groupName: "groupName", open: "open" }, outputs: { openChange: "openChange", saved: "saved" }, usesOnChanges: true, ngImport: i0, template: "<hm-modal [open]=\"open\" (openChange)=\"onModalOpenChange($event)\" [heading]=\"fmt(text.membersHeading, { group: groupName })\" size=\"large\" (closed)=\"close()\">\n <div class=\"gm\">\n @if (saveError) {\n <hm-alert variant=\"error\">{{ saveError }}</hm-alert>\n }\n\n <!-- Adicionar: busca (candidatos da API a cada tecla) + fun\u00E7\u00E3o + Adicionar; sugest\u00F5es em chips. -->\n <section class=\"gm-add\" [attr.aria-label]=\"text.addUser\">\n <div class=\"gm-add__row\">\n <div class=\"gm-field gm-field--grow\">\n <label class=\"gm-label\" [attr.for]=\"searchId\">{{ text.addUser }}</label>\n <div class=\"gm-search\">\n <hm-icon shape=\"search\" size=\"sm\" />\n <input type=\"text\" [id]=\"searchId\" [placeholder]=\"text.userSearch\" autocomplete=\"off\"\n [ngModel]=\"addSearch\" (ngModelChange)=\"onAddSearchChange($event)\" />\n @if (addSearch) {\n <button type=\"button\" class=\"gm-search__clear\" [attr.aria-label]=\"clearLabel\" (click)=\"clearSelectedToAdd()\">\n <hm-icon shape=\"times\" size=\"sm\" />\n </button>\n }\n @if (addSearchResults.length) {\n <ul class=\"gm-search__results\">\n @for (u of addSearchResults; track u.id) {\n <li>\n <button type=\"button\" class=\"gm-search__result\" (click)=\"selectCandidate(u)\">\n <span class=\"gm-user\">\n <hm-account-avatar size=\"small\" [name]=\"u.name || u.username\" [imageUrl]=\"u.picture\" />\n <span class=\"gm-user__body\">\n <span class=\"gm-user__name\"><span>{{ u.name || u.username }}</span></span>\n <span class=\"gm-user__meta\">{{ metaOf(u) }}</span>\n </span>\n </span>\n </button>\n </li>\n }\n </ul>\n }\n </div>\n </div>\n\n <hm-combobox class=\"gm-field--role\" [label]=\"text.role\" [options]=\"roleCombo()\" [value]=\"selectedRole ?? ''\" (valueChange)=\"selectedRole = $event || null\" />\n\n <hm-button icon=\"plus\" [disabled]=\"!selectedToAdd\" (clicked)=\"addSelectedMember()\">{{ text.add }}</hm-button>\n </div>\n\n @if (suggestions.length) {\n <div class=\"gm-suggestions\">\n <span class=\"gm-suggestions__title\" [title]=\"text.suggestionsHint\">{{ text.suggestions }}</span>\n <ul class=\"gm-suggestions__list\" [attr.aria-label]=\"text.suggestionsHint\">\n @for (u of suggestions; track u.id) {\n <li>\n <button type=\"button\" class=\"gm-chip\" [title]=\"suggestionTitle(u)\"\n [attr.aria-label]=\"fmt(text.addSuggestion, { name: u.name || u.username })\" (click)=\"addSuggestion(u)\">\n <hm-account-avatar [name]=\"u.name || u.username\" [imageUrl]=\"u.picture\" />\n <span class=\"gm-chip__name\">{{ u.name || u.username }}</span>\n <hm-icon class=\"gm-chip__plus\" shape=\"plus\" size=\"xs\" />\n </button>\n </li>\n }\n </ul>\n </div>\n }\n </section>\n\n <div class=\"gm-split\">\n <section class=\"gm-members\" [attr.aria-labelledby]=\"titleId\">\n <div class=\"gm-members__head\">\n <h3 class=\"gm-members__title\" [id]=\"titleId\">{{ text.membersTitle }} <hm-badge [value]=\"members.length\" intent=\"primary\" [max]=\"999\" /></h3>\n <div class=\"gm-search gm-members__filter\">\n <hm-icon shape=\"search\" size=\"sm\" />\n <input type=\"text\" [placeholder]=\"text.memberSearch\" [attr.aria-label]=\"text.memberSearch\" [ngModel]=\"memberSearch\" (ngModelChange)=\"memberSearch = $event\" />\n </div>\n </div>\n\n <hm-data-grid\n [columns]=\"columns()\"\n [rows]=\"filteredMembers\"\n [rowId]=\"rowId\"\n [caption]=\"fmt(text.membersOf, { group: groupName })\"\n [loading]=\"loading\"\n [selectable]=\"true\"\n [selection]=\"checkedForRemovalIds\"\n [pageSizes]=\"[10, 25, 50]\"\n (selectionChange)=\"onSelectionChange($event)\"\n [emptyMessage]=\"text.noMembersShort\"\n >\n <ng-template hmCell=\"username\" let-m>\n <span class=\"gm-user\">\n <hm-account-avatar [name]=\"m.name || m.username\" [imageUrl]=\"m.picture\" />\n <span class=\"gm-user__body\">\n <span class=\"gm-user__name\">\n <span>{{ m.name || m.username }}</span>\n @if (m.new) { <hm-tag [name]=\"text.newMember\" color=\"info\" /> }\n </span>\n <span class=\"gm-user__meta\">{{ metaOf(m) }}</span>\n </span>\n </span>\n </ng-template>\n\n <ng-template hmCell=\"role\" let-m>\n <hm-combobox class=\"gm-role\" [label]=\"text.colRole\" labelHidden [options]=\"roleCombo()\" [disabled]=\"isMarkedForRemoval(m)\"\n [value]=\"m.role ?? ''\" (valueChange)=\"m.role = $event || null\" />\n </ng-template>\n\n <ng-template hmCell=\"joined_at\" let-m>\n @if (m.joined_at) {\n {{ formatDate(m.joined_at) }}\n @if (m.added_by_username) {\n <div class=\"gm-joined-by\">{{ fmt(text.addedBy, { user: m.added_by_username }) }}</div>\n }\n } @else {\n \u2014\n }\n </ng-template>\n </hm-data-grid>\n </section>\n\n <!-- Remo\u00E7\u00E3o: sempre ao lado; vazia, s\u00F3 a dica. A remo\u00E7\u00E3o s\u00F3 vale ao salvar. -->\n <aside class=\"gm-removal\" [attr.aria-label]=\"text.removal\">\n <div class=\"gm-removal__head\">\n <hm-icon shape=\"trash\" size=\"sm\" /> {{ text.removal }}\n @if (removalCount) { <hm-badge [value]=\"removalCount\" intent=\"danger\" /> }\n </div>\n @if (removalCount === 0) {\n <div class=\"gm-removal__empty\">\n <hm-icon shape=\"employee-group\" size=\"md\" />\n <p>{{ text.removalEmpty }}</p>\n </div>\n } @else {\n <ul class=\"gm-removal__list\">\n @for (m of checkedForRemoval; track m.user_id) {\n <li class=\"gm-removal__item\">\n <span class=\"gm-user\">\n <hm-account-avatar size=\"small\" [name]=\"m.name || m.username\" [imageUrl]=\"m.picture\" />\n <span class=\"gm-user__body\">\n <span class=\"gm-user__name\"><span>{{ m.name || m.username }}</span></span>\n <span class=\"gm-user__meta\">{{ metaOf(m) }}</span>\n </span>\n </span>\n <hm-icon-button emphasis=\"subtle\" icon=\"undo\" [label]=\"text.undoRemoval\" (clicked)=\"undoRemoval(m)\" />\n </li>\n }\n </ul>\n }\n </aside>\n </div>\n </div>\n\n <ng-container footer>\n <hm-button emphasis=\"medium\" (clicked)=\"close()\">{{ text.cancel }}</hm-button>\n <hm-button [loading]=\"saving\" [disabled]=\"saving\" (clicked)=\"save()\" icon=\"floppy\">{{ text.saveChanges }}</hm-button>\n </ng-container>\n</hm-modal>\n", styles: ["@charset \"UTF-8\";:host{display:contents}.gm{display:flex;flex-direction:column;gap:var(--hm-space-md);min-width:0}.gm-add{display:flex;flex-direction:column;gap:var(--hm-space-sm);padding:var(--hm-space-md);border:1px solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface-raised);min-width:0}.gm-add__row{display:flex;align-items:flex-end;gap:var(--hm-space-sm);flex-wrap:wrap;min-width:0}.gm-field{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0}.gm-field--grow{flex:1 1 18rem}.gm-field--role{flex:0 1 14rem}.gm-label{font-size:var(--hm-font-size-sm);font-weight:600}.gm-search{position:relative;display:flex;align-items:center;gap:var(--hm-space-xs);height:var(--hm-control-height);padding:0 var(--hm-space-sm);border:1px solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised);color:var(--hm-muted)}.gm-search:focus-within{outline:2px solid var(--hm-focus-color);outline-offset:1px}.gm-search input{flex:1;min-width:0;height:100%;border:0;outline:none;background:transparent;font:inherit;font-size:var(--hm-font-size-sm);color:var(--hm-text)}.gm-search input::placeholder{color:var(--hm-muted)}.gm-search__clear{display:inline-grid;place-items:center;flex:none;padding:0;border:0;background:none;color:inherit;cursor:pointer;line-height:0}.gm-search__results{position:absolute;inset-block-start:calc(100% + 4px);inset-inline:0;z-index:20;max-height:16rem;margin:0;padding:var(--hm-space-xs) 0;overflow-y:auto;list-style:none;background:var(--hm-surface-overlay);border:1px solid var(--hm-border);border-radius:var(--hm-radius-control);box-shadow:var(--hm-shadow-overlay)}.gm-search__result{display:flex;align-items:center;gap:var(--hm-space-sm);width:100%;padding:var(--hm-space-xs) var(--hm-space-sm);border:0;background:none;color:var(--hm-text);font:inherit;text-align:start;cursor:pointer}.gm-search__result:hover,.gm-search__result:focus-visible{background:var(--hm-hover)}.gm-search__result:focus-visible{outline:2px solid var(--hm-focus-color);outline-offset:-2px}.gm-suggestions{display:flex;align-items:flex-start;gap:var(--hm-space-sm);min-width:0}.gm-suggestions__title{flex:none;display:inline-flex;align-items:center;min-height:2rem;font-size:var(--hm-font-size-xs);font-weight:600;color:var(--hm-muted)}.gm-suggestions__list{display:flex;flex-wrap:wrap;gap:var(--hm-space-xs);min-width:0;margin:0;padding:0;list-style:none}.gm-chip{display:inline-flex;align-items:center;gap:var(--hm-space-xs);max-width:16rem;min-height:2rem;padding:2px var(--hm-space-sm) 2px 2px;border:1px solid var(--hm-border);border-radius:var(--hm-radius-pill);background:var(--hm-surface-raised);color:var(--hm-text);font:inherit;font-size:var(--hm-font-size-sm);cursor:pointer}.gm-chip hm-account-avatar{--hm-avatar-size: 1.625rem;flex:none}.gm-chip:hover{background:var(--hm-hover);border-color:var(--hm-accent)}.gm-chip:focus-visible{outline:2px solid var(--hm-focus-color);outline-offset:1px}.gm-chip__name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.gm-chip__plus{flex:none;color:var(--hm-accent)}.gm-split{display:grid;grid-template-columns:minmax(0,1fr) minmax(15rem,18rem);gap:var(--hm-space-md);align-items:start;min-width:0}@media(max-width:960px){.gm-split{grid-template-columns:minmax(0,1fr)}}.gm-members{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0}.gm-members__head{display:flex;align-items:center;justify-content:space-between;gap:var(--hm-space-sm);flex-wrap:wrap}.gm-members__title{display:inline-flex;align-items:center;gap:var(--hm-space-xs);margin:0;font-size:var(--hm-font-size-md);font-weight:600}.gm-members__filter{flex:0 1 16rem;height:2rem}.gm-user{display:flex;align-items:center;gap:var(--hm-space-sm);min-width:0}.gm-user hm-account-avatar{flex:none}.gm-user__body{display:flex;flex-direction:column;min-width:0}.gm-user__name{display:flex;align-items:center;gap:var(--hm-space-xs);min-width:0;font-weight:600}.gm-user__name>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.gm-user__meta{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.gm-role{display:block;min-width:11rem}.gm-joined-by{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.gm-removal{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0;padding:var(--hm-space-md);border:1px solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.gm-removal__head{display:flex;align-items:center;gap:var(--hm-space-xs);font-weight:600;color:var(--hm-danger)}.gm-removal__empty{display:flex;align-items:flex-start;gap:var(--hm-space-sm);padding:var(--hm-space-sm);border-radius:var(--hm-radius-control);background:var(--hm-surface-muted);color:var(--hm-muted);font-size:var(--hm-font-size-sm);line-height:1.45}.gm-removal__empty hm-icon{flex:none;margin-top:2px}.gm-removal__empty p{margin:0}.gm-removal__list{display:flex;flex-direction:column;gap:var(--hm-space-xs);margin:0;padding:0;list-style:none}.gm-removal__item{display:flex;align-items:center;justify-content:space-between;gap:var(--hm-space-xs);min-width:0}.gm-removal__item .gm-user{flex:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: AccountAvatarComponent, selector: "hm-account-avatar", inputs: ["name", "imageUrl", "size", "loading"] }, { kind: "component", type: AlertComponent, selector: "hm-alert", inputs: ["variant", "appearance", "heading", "closable", "size", "showIcon", "link", "linkLabel", "linkTarget", "dismissKey", "duration"], outputs: ["closed"] }, { kind: "component", type: BadgeComponent, selector: "hm-badge", inputs: ["value", "max", "dot", "intent", "label"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "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", "size", "labelHidden", "clearable", "loading", "allowCustomValue"], outputs: ["valueChange"] }, { 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: 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: TagComponent, selector: "hm-tag", inputs: ["name", "color", "compact"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
577
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: AutoServiceGroupMembersModalComponent, isStandalone: true, selector: "spine-auto-service-group-members-modal", inputs: { clientId: "clientId", groupId: "groupId", groupName: "groupName", open: "open" }, outputs: { openChange: "openChange", saved: "saved" }, usesOnChanges: true, ngImport: i0, template: "<hm-modal [open]=\"open\" (openChange)=\"onModalOpenChange($event)\" [heading]=\"fmt(text.membersHeading, { group: groupName })\" size=\"large\" (closed)=\"close()\">\n <div class=\"gm\">\n @if (saveError) {\n <hm-alert variant=\"error\">{{ saveError }}</hm-alert>\n }\n\n <!-- Adicionar: busca (candidatos da API a cada tecla) + fun\u00E7\u00E3o + Adicionar; sugest\u00F5es em chips. -->\n <section class=\"gm-add\" [attr.aria-label]=\"text.addUser\">\n <div class=\"gm-add__row\">\n <div class=\"gm-field gm-field--grow\">\n <label class=\"gm-label\" [attr.for]=\"searchId\">{{ text.addUser }}</label>\n <div class=\"gm-search\">\n <hm-icon shape=\"search\" size=\"sm\" />\n <input type=\"text\" [id]=\"searchId\" [placeholder]=\"text.userSearch\" autocomplete=\"off\"\n [ngModel]=\"addSearch\" (ngModelChange)=\"onAddSearchChange($event)\" />\n @if (addSearch) {\n <button type=\"button\" class=\"gm-search__clear\" [attr.aria-label]=\"clearLabel\" (click)=\"clearSelectedToAdd()\">\n <hm-icon shape=\"times\" size=\"sm\" />\n </button>\n }\n @if (addSearchResults.length) {\n <ul class=\"gm-search__results\">\n @for (u of addSearchResults; track u.id) {\n <li>\n <button type=\"button\" class=\"gm-search__result\" (click)=\"selectCandidate(u)\">\n <span class=\"gm-user\">\n <hm-account-avatar size=\"small\" [name]=\"u.name || u.username\" [imageUrl]=\"u.picture\" />\n <span class=\"gm-user__body\">\n <span class=\"gm-user__name\"><span>{{ u.name || u.username }}</span></span>\n <span class=\"gm-user__meta\">{{ metaOf(u) }}</span>\n </span>\n </span>\n </button>\n </li>\n }\n </ul>\n }\n </div>\n </div>\n\n <hm-combobox class=\"gm-field--role\" [label]=\"text.role\" [options]=\"roleCombo()\" [value]=\"selectedRole ?? ''\" (valueChange)=\"selectedRole = $event || null\" />\n\n <hm-button icon=\"plus\" [disabled]=\"!selectedToAdd\" (clicked)=\"addSelectedMember()\">{{ text.add }}</hm-button>\n </div>\n\n @if (suggestions.length) {\n <div class=\"gm-suggestions\">\n <span class=\"gm-suggestions__title\" [title]=\"text.suggestionsHint\">{{ text.suggestions }}</span>\n <ul class=\"gm-suggestions__list\" [attr.aria-label]=\"text.suggestionsHint\">\n @for (u of suggestions; track u.id) {\n <li>\n <button type=\"button\" class=\"gm-chip\" [title]=\"suggestionTitle(u)\"\n [attr.aria-label]=\"fmt(text.addSuggestion, { name: u.name || u.username })\" (click)=\"addSuggestion(u)\">\n <hm-account-avatar [name]=\"u.name || u.username\" [imageUrl]=\"u.picture\" />\n <span class=\"gm-chip__name\">{{ u.name || u.username }}</span>\n <hm-icon class=\"gm-chip__plus\" shape=\"plus\" size=\"xs\" />\n </button>\n </li>\n }\n </ul>\n </div>\n }\n </section>\n\n <div class=\"gm-split\">\n <section class=\"gm-members\" [attr.aria-labelledby]=\"titleId\">\n <div class=\"gm-members__head\">\n <h3 class=\"gm-members__title\" [id]=\"titleId\">{{ text.membersTitle }} <hm-badge [value]=\"memberCount()\" intent=\"primary\" [max]=\"9999\" /></h3>\n <div class=\"gm-search gm-members__filter\">\n <hm-icon shape=\"search\" size=\"sm\" />\n <input type=\"text\" [placeholder]=\"text.memberSearch\" [attr.aria-label]=\"text.memberSearch\" [ngModel]=\"memberSearch()\" (ngModelChange)=\"memberSearch.set($event)\" />\n </div>\n </div>\n\n @if (loader.error()) {\n <div class=\"gm-status\">\n <span class=\"gm-status__error\">{{ incompleteLabel() }}</span>\n <hm-button emphasis=\"medium\" size=\"small\" icon=\"refresh\" (clicked)=\"loader.retry()\">{{ text.retryLoad }}</hm-button>\n </div>\n } @else if (loader.loading() && loader.ready()) {\n <p class=\"gm-status gm-status--progress\" role=\"status\">\n {{ progressLabel() }}@if (memberSearch().trim()) { \u00B7 {{ text.membersPartialResults }} }\n </p>\n }\n\n <!-- Todos os membros (carregados aos poucos), os novos no topo; busca local, rolagem virtual, sem paginador. -->\n <hm-data-grid\n [columns]=\"columns()\"\n [rows]=\"filteredMembers()\"\n [rowId]=\"rowId\"\n [caption]=\"fmt(text.membersOf, { group: groupName })\"\n [loading]=\"!loader.ready()\"\n [filterable]=\"false\"\n [paginated]=\"false\"\n [pagination]=\"false\"\n [virtual]=\"true\"\n maxHeight=\"24rem\"\n [selectable]=\"true\"\n [selection]=\"checkedForRemovalIds\"\n (selectionChange)=\"onSelectionChange($event)\"\n [emptyMessage]=\"memberSearch().trim() ? text.noMembersMatch : text.noMembersShort\"\n >\n <ng-template hmCell=\"username\" let-m>\n <span class=\"gm-user\">\n <hm-account-avatar [name]=\"m.name || m.username\" [imageUrl]=\"m.picture\" />\n <span class=\"gm-user__body\">\n <span class=\"gm-user__name\">\n <span>{{ m.name || m.username }}</span>\n @if (m.new) { <hm-tag [name]=\"text.newMember\" color=\"info\" /> }\n </span>\n <span class=\"gm-user__meta\">{{ metaOf(m) }}</span>\n </span>\n </span>\n </ng-template>\n\n <ng-template hmCell=\"role\" let-m>\n <hm-combobox class=\"gm-role\" [label]=\"text.colRole\" labelHidden [options]=\"roleCombo()\" [disabled]=\"isMarkedForRemoval(m)\"\n [value]=\"m.role ?? ''\" (valueChange)=\"setRole(m, $event)\" />\n </ng-template>\n\n <ng-template hmCell=\"joined_at\" let-m>\n @if (m.joined_at) {\n {{ formatDate(m.joined_at) }}\n @if (m.added_by_username) {\n <div class=\"gm-joined-by\">{{ fmt(text.addedBy, { user: m.added_by_username }) }}</div>\n }\n } @else {\n \u2014\n }\n </ng-template>\n </hm-data-grid>\n </section>\n\n <!-- Remo\u00E7\u00E3o: sempre ao lado; vazia, s\u00F3 a dica. A remo\u00E7\u00E3o s\u00F3 vale ao salvar. -->\n <aside class=\"gm-removal\" [attr.aria-label]=\"text.removal\">\n <div class=\"gm-removal__head\">\n <hm-icon shape=\"trash\" size=\"sm\" /> {{ text.removal }}\n @if (removalCount) { <hm-badge [value]=\"removalCount\" intent=\"danger\" /> }\n </div>\n @if (removalCount === 0) {\n <div class=\"gm-removal__empty\">\n <hm-icon shape=\"employee-group\" size=\"md\" />\n <p>{{ text.removalEmpty }}</p>\n </div>\n } @else {\n <ul class=\"gm-removal__list\">\n @for (m of checkedForRemoval; track m.user_id) {\n <li class=\"gm-removal__item\">\n <span class=\"gm-user\">\n <hm-account-avatar size=\"small\" [name]=\"m.name || m.username\" [imageUrl]=\"m.picture\" />\n <span class=\"gm-user__body\">\n <span class=\"gm-user__name\"><span>{{ m.name || m.username }}</span></span>\n <span class=\"gm-user__meta\">{{ metaOf(m) }}</span>\n </span>\n </span>\n <hm-icon-button emphasis=\"subtle\" icon=\"undo\" [label]=\"text.undoRemoval\" (clicked)=\"undoRemoval(m)\" />\n </li>\n }\n </ul>\n }\n </aside>\n </div>\n </div>\n\n <ng-container footer>\n <hm-button emphasis=\"medium\" (clicked)=\"close()\">{{ text.cancel }}</hm-button>\n <hm-button [loading]=\"saving\" [disabled]=\"saving\" (clicked)=\"save()\" icon=\"floppy\">{{ text.saveChanges }}</hm-button>\n </ng-container>\n</hm-modal>\n", styles: ["@charset \"UTF-8\";:host{display:contents}.gm{display:flex;flex-direction:column;gap:var(--hm-space-md);min-width:0}.gm-add{display:flex;flex-direction:column;gap:var(--hm-space-sm);padding:var(--hm-space-md);border:1px solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface-raised);min-width:0}.gm-add__row{display:flex;align-items:flex-end;gap:var(--hm-space-sm);flex-wrap:wrap;min-width:0}.gm-field{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0}.gm-field--grow{flex:1 1 18rem}.gm-field--role{flex:0 1 14rem}.gm-label{font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.gm-search{position:relative;display:flex;align-items:center;gap:var(--hm-space-xs);height:var(--hm-control-height);padding:0 var(--hm-space-sm);border:1px solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised);color:var(--hm-muted)}.gm-search:focus-within{outline:2px solid var(--hm-focus-color);outline-offset:1px}.gm-search input{flex:1;min-width:0;height:100%;border:0;outline:none;background:transparent;font:inherit;font-size:var(--hm-font-size-sm);color:var(--hm-text)}.gm-search input::placeholder{color:var(--hm-muted)}.gm-search__clear{display:inline-grid;place-items:center;flex:none;padding:0;border:0;background:none;color:inherit;cursor:pointer;line-height:0}.gm-search__results{position:absolute;inset-block-start:calc(100% + 4px);inset-inline:0;z-index:20;max-height:16rem;margin:0;padding:var(--hm-space-xs) 0;overflow-y:auto;list-style:none;background:var(--hm-surface-overlay);border:1px solid var(--hm-border);border-radius:var(--hm-radius-control);box-shadow:var(--hm-shadow-overlay)}.gm-search__result{display:flex;align-items:center;gap:var(--hm-space-sm);width:100%;padding:var(--hm-space-xs) var(--hm-space-sm);border:0;background:none;color:var(--hm-text);font:inherit;font-size:var(--hm-font-size-sm);text-align:start;cursor:pointer}.gm-search__result:hover,.gm-search__result:focus-visible{background:var(--hm-hover)}.gm-search__result:focus-visible{outline:2px solid var(--hm-focus-color);outline-offset:-2px}.gm-suggestions{display:flex;align-items:flex-start;gap:var(--hm-space-sm);min-width:0}.gm-suggestions__title{flex:none;display:inline-flex;align-items:center;min-height:2rem;font-size:var(--hm-font-size-xs);font-weight:var(--hm-font-weight-semibold);color:var(--hm-muted)}.gm-suggestions__list{display:flex;flex-wrap:wrap;gap:var(--hm-space-xs);min-width:0;margin:0;padding:0;list-style:none}.gm-chip{display:inline-flex;align-items:center;gap:var(--hm-space-xs);max-width:16rem;min-height:2rem;padding:2px var(--hm-space-sm) 2px 2px;border:1px solid var(--hm-border);border-radius:var(--hm-radius-pill);background:var(--hm-surface-raised);color:var(--hm-text);font:inherit;font-size:var(--hm-font-size-sm);cursor:pointer}.gm-chip hm-account-avatar{--hm-avatar-size: 1.625rem;flex:none}.gm-chip:hover{background:var(--hm-hover);border-color:var(--hm-accent)}.gm-chip:focus-visible{outline:2px solid var(--hm-focus-color);outline-offset:1px}.gm-chip__name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.gm-chip__plus{flex:none;color:var(--hm-accent)}.gm-split{display:grid;grid-template-columns:minmax(0,1fr) minmax(15rem,18rem);gap:var(--hm-space-md);align-items:start;min-width:0}@media(max-width:960px){.gm-split{grid-template-columns:minmax(0,1fr)}}.gm-members{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0}.gm-members__head{display:flex;align-items:center;justify-content:space-between;gap:var(--hm-space-sm);flex-wrap:wrap}.gm-members__title{display:inline-flex;align-items:center;gap:var(--hm-space-xs);margin:0;font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-semibold)}.gm-members__filter{flex:0 1 16rem;height:2rem}.gm-user{display:flex;align-items:center;gap:var(--hm-space-sm);min-width:0}.gm-user hm-account-avatar{flex:none}.gm-user__body{display:flex;flex-direction:column;min-width:0}.gm-user__name{display:flex;align-items:center;gap:var(--hm-space-xs);min-width:0;font-weight:var(--hm-font-weight-semibold)}.gm-user__name>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.gm-user__meta{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.gm-role{display:block;min-width:11rem}.gm-joined-by{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.gm-status{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs);margin:0;font-size:var(--hm-font-size-sm)}.gm-status--progress{color:var(--hm-muted)}.gm-status__error{color:var(--hm-danger)}.gm-removal{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0;padding:var(--hm-space-md);border:1px solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.gm-removal__head{display:flex;align-items:center;gap:var(--hm-space-xs);font-weight:var(--hm-font-weight-semibold);color:var(--hm-danger)}.gm-removal__empty{display:flex;align-items:flex-start;gap:var(--hm-space-sm);padding:var(--hm-space-sm);border-radius:var(--hm-radius-control);background:var(--hm-surface-muted);color:var(--hm-muted);font-size:var(--hm-font-size-sm);line-height:1.45}.gm-removal__empty hm-icon{flex:none;margin-top:2px}.gm-removal__empty p{margin:0}.gm-removal__list{display:flex;flex-direction:column;gap:var(--hm-space-xs);margin:0;padding:0;list-style:none}.gm-removal__item{display:flex;align-items:center;justify-content:space-between;gap:var(--hm-space-xs);min-width:0}.gm-removal__item .gm-user{flex:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: AccountAvatarComponent, selector: "hm-account-avatar", inputs: ["name", "imageUrl", "size", "loading", "presence", "texts"] }, { kind: "component", type: AlertComponent, selector: "hm-alert", inputs: ["variant", "appearance", "heading", "closable", "size", "showIcon", "link", "linkLabel", "linkTarget", "dismissKey", "duration"], outputs: ["closed"] }, { kind: "component", type: BadgeComponent, selector: "hm-badge", inputs: ["value", "max", "dot", "intent", "label"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "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", "size", "labelHidden", "clearable", "loading", "allowCustomValue"], outputs: ["valueChange"] }, { 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", "striped", "columnGroups", "editMode", "editBatch", "texts", "treeChildren", "treeHasChildren", "treeExpanded", "searchTerm"], outputs: ["pageStateChange", "sortChange", "selectionChange", "pinnedColumnsChange", "columnStateChange", "actionTriggered", "detailToggled", "rowClicked", "clearFiltersRequested", "filteredChange", "searchTermChanged", "groupByChange", "filterQueryChange", "columnOrderChange", "editSaved", "editStarted", "editCanceled", "treeExpandedChange", "treeToggled", "searchTermChange", "detailPaneToggled"] }, { kind: "directive", type: DataGridCellDirective, selector: "ng-template[hmCell]", inputs: ["hmCell"] }, { 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: TagComponent, selector: "hm-tag", inputs: ["name", "color", "compact"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
412
578
|
}
|
|
413
579
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceGroupMembersModalComponent, decorators: [{
|
|
414
580
|
type: Component,
|
|
415
581
|
args: [{ selector: 'spine-auto-service-group-members-modal', standalone: true, imports: [
|
|
416
582
|
CommonModule, FormsModule, AccountAvatarComponent, AlertComponent, BadgeComponent, ButtonComponent, ComboboxComponent, DataGridComponent,
|
|
417
583
|
DataGridCellDirective, IconButtonComponent, IconComponent, ModalComponent, TagComponent,
|
|
418
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<hm-modal [open]=\"open\" (openChange)=\"onModalOpenChange($event)\" [heading]=\"fmt(text.membersHeading, { group: groupName })\" size=\"large\" (closed)=\"close()\">\n <div class=\"gm\">\n @if (saveError) {\n <hm-alert variant=\"error\">{{ saveError }}</hm-alert>\n }\n\n <!-- Adicionar: busca (candidatos da API a cada tecla) + fun\u00E7\u00E3o + Adicionar; sugest\u00F5es em chips. -->\n <section class=\"gm-add\" [attr.aria-label]=\"text.addUser\">\n <div class=\"gm-add__row\">\n <div class=\"gm-field gm-field--grow\">\n <label class=\"gm-label\" [attr.for]=\"searchId\">{{ text.addUser }}</label>\n <div class=\"gm-search\">\n <hm-icon shape=\"search\" size=\"sm\" />\n <input type=\"text\" [id]=\"searchId\" [placeholder]=\"text.userSearch\" autocomplete=\"off\"\n [ngModel]=\"addSearch\" (ngModelChange)=\"onAddSearchChange($event)\" />\n @if (addSearch) {\n <button type=\"button\" class=\"gm-search__clear\" [attr.aria-label]=\"clearLabel\" (click)=\"clearSelectedToAdd()\">\n <hm-icon shape=\"times\" size=\"sm\" />\n </button>\n }\n @if (addSearchResults.length) {\n <ul class=\"gm-search__results\">\n @for (u of addSearchResults; track u.id) {\n <li>\n <button type=\"button\" class=\"gm-search__result\" (click)=\"selectCandidate(u)\">\n <span class=\"gm-user\">\n <hm-account-avatar size=\"small\" [name]=\"u.name || u.username\" [imageUrl]=\"u.picture\" />\n <span class=\"gm-user__body\">\n <span class=\"gm-user__name\"><span>{{ u.name || u.username }}</span></span>\n <span class=\"gm-user__meta\">{{ metaOf(u) }}</span>\n </span>\n </span>\n </button>\n </li>\n }\n </ul>\n }\n </div>\n </div>\n\n <hm-combobox class=\"gm-field--role\" [label]=\"text.role\" [options]=\"roleCombo()\" [value]=\"selectedRole ?? ''\" (valueChange)=\"selectedRole = $event || null\" />\n\n <hm-button icon=\"plus\" [disabled]=\"!selectedToAdd\" (clicked)=\"addSelectedMember()\">{{ text.add }}</hm-button>\n </div>\n\n @if (suggestions.length) {\n <div class=\"gm-suggestions\">\n <span class=\"gm-suggestions__title\" [title]=\"text.suggestionsHint\">{{ text.suggestions }}</span>\n <ul class=\"gm-suggestions__list\" [attr.aria-label]=\"text.suggestionsHint\">\n @for (u of suggestions; track u.id) {\n <li>\n <button type=\"button\" class=\"gm-chip\" [title]=\"suggestionTitle(u)\"\n [attr.aria-label]=\"fmt(text.addSuggestion, { name: u.name || u.username })\" (click)=\"addSuggestion(u)\">\n <hm-account-avatar [name]=\"u.name || u.username\" [imageUrl]=\"u.picture\" />\n <span class=\"gm-chip__name\">{{ u.name || u.username }}</span>\n <hm-icon class=\"gm-chip__plus\" shape=\"plus\" size=\"xs\" />\n </button>\n </li>\n }\n </ul>\n </div>\n }\n </section>\n\n <div class=\"gm-split\">\n <section class=\"gm-members\" [attr.aria-labelledby]=\"titleId\">\n <div class=\"gm-members__head\">\n <h3 class=\"gm-members__title\" [id]=\"titleId\">{{ text.membersTitle }} <hm-badge [value]=\"members.length\" intent=\"primary\" [max]=\"999\" /></h3>\n <div class=\"gm-search gm-members__filter\">\n <hm-icon shape=\"search\" size=\"sm\" />\n <input type=\"text\" [placeholder]=\"text.memberSearch\" [attr.aria-label]=\"text.memberSearch\" [ngModel]=\"memberSearch\" (ngModelChange)=\"memberSearch = $event\" />\n </div>\n </div>\n\n <hm-data-grid\n [columns]=\"columns()\"\n [rows]=\"filteredMembers\"\n [rowId]=\"rowId\"\n [caption]=\"fmt(text.membersOf, { group: groupName })\"\n [loading]=\"loading\"\n [selectable]=\"true\"\n [selection]=\"checkedForRemovalIds\"\n [pageSizes]=\"[10, 25, 50]\"\n (selectionChange)=\"onSelectionChange($event)\"\n [emptyMessage]=\"text.noMembersShort\"\n >\n <ng-template hmCell=\"username\" let-m>\n <span class=\"gm-user\">\n <hm-account-avatar [name]=\"m.name || m.username\" [imageUrl]=\"m.picture\" />\n <span class=\"gm-user__body\">\n <span class=\"gm-user__name\">\n <span>{{ m.name || m.username }}</span>\n @if (m.new) { <hm-tag [name]=\"text.newMember\" color=\"info\" /> }\n </span>\n <span class=\"gm-user__meta\">{{ metaOf(m) }}</span>\n </span>\n </span>\n </ng-template>\n\n <ng-template hmCell=\"role\" let-m>\n <hm-combobox class=\"gm-role\" [label]=\"text.colRole\" labelHidden [options]=\"roleCombo()\" [disabled]=\"isMarkedForRemoval(m)\"\n [value]=\"m.role ?? ''\" (valueChange)=\"m.role = $event || null\" />\n </ng-template>\n\n <ng-template hmCell=\"joined_at\" let-m>\n @if (m.joined_at) {\n {{ formatDate(m.joined_at) }}\n @if (m.added_by_username) {\n <div class=\"gm-joined-by\">{{ fmt(text.addedBy, { user: m.added_by_username }) }}</div>\n }\n } @else {\n \u2014\n }\n </ng-template>\n </hm-data-grid>\n </section>\n\n <!-- Remo\u00E7\u00E3o: sempre ao lado; vazia, s\u00F3 a dica. A remo\u00E7\u00E3o s\u00F3 vale ao salvar. -->\n <aside class=\"gm-removal\" [attr.aria-label]=\"text.removal\">\n <div class=\"gm-removal__head\">\n <hm-icon shape=\"trash\" size=\"sm\" /> {{ text.removal }}\n @if (removalCount) { <hm-badge [value]=\"removalCount\" intent=\"danger\" /> }\n </div>\n @if (removalCount === 0) {\n <div class=\"gm-removal__empty\">\n <hm-icon shape=\"employee-group\" size=\"md\" />\n <p>{{ text.removalEmpty }}</p>\n </div>\n } @else {\n <ul class=\"gm-removal__list\">\n @for (m of checkedForRemoval; track m.user_id) {\n <li class=\"gm-removal__item\">\n <span class=\"gm-user\">\n <hm-account-avatar size=\"small\" [name]=\"m.name || m.username\" [imageUrl]=\"m.picture\" />\n <span class=\"gm-user__body\">\n <span class=\"gm-user__name\"><span>{{ m.name || m.username }}</span></span>\n <span class=\"gm-user__meta\">{{ metaOf(m) }}</span>\n </span>\n </span>\n <hm-icon-button emphasis=\"subtle\" icon=\"undo\" [label]=\"text.undoRemoval\" (clicked)=\"undoRemoval(m)\" />\n </li>\n }\n </ul>\n }\n </aside>\n </div>\n </div>\n\n <ng-container footer>\n <hm-button emphasis=\"medium\" (clicked)=\"close()\">{{ text.cancel }}</hm-button>\n <hm-button [loading]=\"saving\" [disabled]=\"saving\" (clicked)=\"save()\" icon=\"floppy\">{{ text.saveChanges }}</hm-button>\n </ng-container>\n</hm-modal>\n", styles: ["@charset \"UTF-8\";:host{display:contents}.gm{display:flex;flex-direction:column;gap:var(--hm-space-md);min-width:0}.gm-add{display:flex;flex-direction:column;gap:var(--hm-space-sm);padding:var(--hm-space-md);border:1px solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface-raised);min-width:0}.gm-add__row{display:flex;align-items:flex-end;gap:var(--hm-space-sm);flex-wrap:wrap;min-width:0}.gm-field{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0}.gm-field--grow{flex:1 1 18rem}.gm-field--role{flex:0 1 14rem}.gm-label{font-size:var(--hm-font-size-sm);font-weight:600}.gm-search{position:relative;display:flex;align-items:center;gap:var(--hm-space-xs);height:var(--hm-control-height);padding:0 var(--hm-space-sm);border:1px solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised);color:var(--hm-muted)}.gm-search:focus-within{outline:2px solid var(--hm-focus-color);outline-offset:1px}.gm-search input{flex:1;min-width:0;height:100%;border:0;outline:none;background:transparent;font:inherit;font-size:var(--hm-font-size-sm);color:var(--hm-text)}.gm-search input::placeholder{color:var(--hm-muted)}.gm-search__clear{display:inline-grid;place-items:center;flex:none;padding:0;border:0;background:none;color:inherit;cursor:pointer;line-height:0}.gm-search__results{position:absolute;inset-block-start:calc(100% + 4px);inset-inline:0;z-index:20;max-height:16rem;margin:0;padding:var(--hm-space-xs) 0;overflow-y:auto;list-style:none;background:var(--hm-surface-overlay);border:1px solid var(--hm-border);border-radius:var(--hm-radius-control);box-shadow:var(--hm-shadow-overlay)}.gm-search__result{display:flex;align-items:center;gap:var(--hm-space-sm);width:100%;padding:var(--hm-space-xs) var(--hm-space-sm);border:0;background:none;color:var(--hm-text);font:inherit;text-align:start;cursor:pointer}.gm-search__result:hover,.gm-search__result:focus-visible{background:var(--hm-hover)}.gm-search__result:focus-visible{outline:2px solid var(--hm-focus-color);outline-offset:-2px}.gm-suggestions{display:flex;align-items:flex-start;gap:var(--hm-space-sm);min-width:0}.gm-suggestions__title{flex:none;display:inline-flex;align-items:center;min-height:2rem;font-size:var(--hm-font-size-xs);font-weight:600;color:var(--hm-muted)}.gm-suggestions__list{display:flex;flex-wrap:wrap;gap:var(--hm-space-xs);min-width:0;margin:0;padding:0;list-style:none}.gm-chip{display:inline-flex;align-items:center;gap:var(--hm-space-xs);max-width:16rem;min-height:2rem;padding:2px var(--hm-space-sm) 2px 2px;border:1px solid var(--hm-border);border-radius:var(--hm-radius-pill);background:var(--hm-surface-raised);color:var(--hm-text);font:inherit;font-size:var(--hm-font-size-sm);cursor:pointer}.gm-chip hm-account-avatar{--hm-avatar-size: 1.625rem;flex:none}.gm-chip:hover{background:var(--hm-hover);border-color:var(--hm-accent)}.gm-chip:focus-visible{outline:2px solid var(--hm-focus-color);outline-offset:1px}.gm-chip__name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.gm-chip__plus{flex:none;color:var(--hm-accent)}.gm-split{display:grid;grid-template-columns:minmax(0,1fr) minmax(15rem,18rem);gap:var(--hm-space-md);align-items:start;min-width:0}@media(max-width:960px){.gm-split{grid-template-columns:minmax(0,1fr)}}.gm-members{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0}.gm-members__head{display:flex;align-items:center;justify-content:space-between;gap:var(--hm-space-sm);flex-wrap:wrap}.gm-members__title{display:inline-flex;align-items:center;gap:var(--hm-space-xs);margin:0;font-size:var(--hm-font-size-md);font-weight:600}.gm-members__filter{flex:0 1 16rem;height:2rem}.gm-user{display:flex;align-items:center;gap:var(--hm-space-sm);min-width:0}.gm-user hm-account-avatar{flex:none}.gm-user__body{display:flex;flex-direction:column;min-width:0}.gm-user__name{display:flex;align-items:center;gap:var(--hm-space-xs);min-width:0;font-weight:600}.gm-user__name>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.gm-user__meta{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.gm-role{display:block;min-width:11rem}.gm-joined-by{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.gm-removal{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0;padding:var(--hm-space-md);border:1px solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.gm-removal__head{display:flex;align-items:center;gap:var(--hm-space-xs);font-weight:600;color:var(--hm-danger)}.gm-removal__empty{display:flex;align-items:flex-start;gap:var(--hm-space-sm);padding:var(--hm-space-sm);border-radius:var(--hm-radius-control);background:var(--hm-surface-muted);color:var(--hm-muted);font-size:var(--hm-font-size-sm);line-height:1.45}.gm-removal__empty hm-icon{flex:none;margin-top:2px}.gm-removal__empty p{margin:0}.gm-removal__list{display:flex;flex-direction:column;gap:var(--hm-space-xs);margin:0;padding:0;list-style:none}.gm-removal__item{display:flex;align-items:center;justify-content:space-between;gap:var(--hm-space-xs);min-width:0}.gm-removal__item .gm-user{flex:1}\n"] }]
|
|
584
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<hm-modal [open]=\"open\" (openChange)=\"onModalOpenChange($event)\" [heading]=\"fmt(text.membersHeading, { group: groupName })\" size=\"large\" (closed)=\"close()\">\n <div class=\"gm\">\n @if (saveError) {\n <hm-alert variant=\"error\">{{ saveError }}</hm-alert>\n }\n\n <!-- Adicionar: busca (candidatos da API a cada tecla) + fun\u00E7\u00E3o + Adicionar; sugest\u00F5es em chips. -->\n <section class=\"gm-add\" [attr.aria-label]=\"text.addUser\">\n <div class=\"gm-add__row\">\n <div class=\"gm-field gm-field--grow\">\n <label class=\"gm-label\" [attr.for]=\"searchId\">{{ text.addUser }}</label>\n <div class=\"gm-search\">\n <hm-icon shape=\"search\" size=\"sm\" />\n <input type=\"text\" [id]=\"searchId\" [placeholder]=\"text.userSearch\" autocomplete=\"off\"\n [ngModel]=\"addSearch\" (ngModelChange)=\"onAddSearchChange($event)\" />\n @if (addSearch) {\n <button type=\"button\" class=\"gm-search__clear\" [attr.aria-label]=\"clearLabel\" (click)=\"clearSelectedToAdd()\">\n <hm-icon shape=\"times\" size=\"sm\" />\n </button>\n }\n @if (addSearchResults.length) {\n <ul class=\"gm-search__results\">\n @for (u of addSearchResults; track u.id) {\n <li>\n <button type=\"button\" class=\"gm-search__result\" (click)=\"selectCandidate(u)\">\n <span class=\"gm-user\">\n <hm-account-avatar size=\"small\" [name]=\"u.name || u.username\" [imageUrl]=\"u.picture\" />\n <span class=\"gm-user__body\">\n <span class=\"gm-user__name\"><span>{{ u.name || u.username }}</span></span>\n <span class=\"gm-user__meta\">{{ metaOf(u) }}</span>\n </span>\n </span>\n </button>\n </li>\n }\n </ul>\n }\n </div>\n </div>\n\n <hm-combobox class=\"gm-field--role\" [label]=\"text.role\" [options]=\"roleCombo()\" [value]=\"selectedRole ?? ''\" (valueChange)=\"selectedRole = $event || null\" />\n\n <hm-button icon=\"plus\" [disabled]=\"!selectedToAdd\" (clicked)=\"addSelectedMember()\">{{ text.add }}</hm-button>\n </div>\n\n @if (suggestions.length) {\n <div class=\"gm-suggestions\">\n <span class=\"gm-suggestions__title\" [title]=\"text.suggestionsHint\">{{ text.suggestions }}</span>\n <ul class=\"gm-suggestions__list\" [attr.aria-label]=\"text.suggestionsHint\">\n @for (u of suggestions; track u.id) {\n <li>\n <button type=\"button\" class=\"gm-chip\" [title]=\"suggestionTitle(u)\"\n [attr.aria-label]=\"fmt(text.addSuggestion, { name: u.name || u.username })\" (click)=\"addSuggestion(u)\">\n <hm-account-avatar [name]=\"u.name || u.username\" [imageUrl]=\"u.picture\" />\n <span class=\"gm-chip__name\">{{ u.name || u.username }}</span>\n <hm-icon class=\"gm-chip__plus\" shape=\"plus\" size=\"xs\" />\n </button>\n </li>\n }\n </ul>\n </div>\n }\n </section>\n\n <div class=\"gm-split\">\n <section class=\"gm-members\" [attr.aria-labelledby]=\"titleId\">\n <div class=\"gm-members__head\">\n <h3 class=\"gm-members__title\" [id]=\"titleId\">{{ text.membersTitle }} <hm-badge [value]=\"memberCount()\" intent=\"primary\" [max]=\"9999\" /></h3>\n <div class=\"gm-search gm-members__filter\">\n <hm-icon shape=\"search\" size=\"sm\" />\n <input type=\"text\" [placeholder]=\"text.memberSearch\" [attr.aria-label]=\"text.memberSearch\" [ngModel]=\"memberSearch()\" (ngModelChange)=\"memberSearch.set($event)\" />\n </div>\n </div>\n\n @if (loader.error()) {\n <div class=\"gm-status\">\n <span class=\"gm-status__error\">{{ incompleteLabel() }}</span>\n <hm-button emphasis=\"medium\" size=\"small\" icon=\"refresh\" (clicked)=\"loader.retry()\">{{ text.retryLoad }}</hm-button>\n </div>\n } @else if (loader.loading() && loader.ready()) {\n <p class=\"gm-status gm-status--progress\" role=\"status\">\n {{ progressLabel() }}@if (memberSearch().trim()) { \u00B7 {{ text.membersPartialResults }} }\n </p>\n }\n\n <!-- Todos os membros (carregados aos poucos), os novos no topo; busca local, rolagem virtual, sem paginador. -->\n <hm-data-grid\n [columns]=\"columns()\"\n [rows]=\"filteredMembers()\"\n [rowId]=\"rowId\"\n [caption]=\"fmt(text.membersOf, { group: groupName })\"\n [loading]=\"!loader.ready()\"\n [filterable]=\"false\"\n [paginated]=\"false\"\n [pagination]=\"false\"\n [virtual]=\"true\"\n maxHeight=\"24rem\"\n [selectable]=\"true\"\n [selection]=\"checkedForRemovalIds\"\n (selectionChange)=\"onSelectionChange($event)\"\n [emptyMessage]=\"memberSearch().trim() ? text.noMembersMatch : text.noMembersShort\"\n >\n <ng-template hmCell=\"username\" let-m>\n <span class=\"gm-user\">\n <hm-account-avatar [name]=\"m.name || m.username\" [imageUrl]=\"m.picture\" />\n <span class=\"gm-user__body\">\n <span class=\"gm-user__name\">\n <span>{{ m.name || m.username }}</span>\n @if (m.new) { <hm-tag [name]=\"text.newMember\" color=\"info\" /> }\n </span>\n <span class=\"gm-user__meta\">{{ metaOf(m) }}</span>\n </span>\n </span>\n </ng-template>\n\n <ng-template hmCell=\"role\" let-m>\n <hm-combobox class=\"gm-role\" [label]=\"text.colRole\" labelHidden [options]=\"roleCombo()\" [disabled]=\"isMarkedForRemoval(m)\"\n [value]=\"m.role ?? ''\" (valueChange)=\"setRole(m, $event)\" />\n </ng-template>\n\n <ng-template hmCell=\"joined_at\" let-m>\n @if (m.joined_at) {\n {{ formatDate(m.joined_at) }}\n @if (m.added_by_username) {\n <div class=\"gm-joined-by\">{{ fmt(text.addedBy, { user: m.added_by_username }) }}</div>\n }\n } @else {\n \u2014\n }\n </ng-template>\n </hm-data-grid>\n </section>\n\n <!-- Remo\u00E7\u00E3o: sempre ao lado; vazia, s\u00F3 a dica. A remo\u00E7\u00E3o s\u00F3 vale ao salvar. -->\n <aside class=\"gm-removal\" [attr.aria-label]=\"text.removal\">\n <div class=\"gm-removal__head\">\n <hm-icon shape=\"trash\" size=\"sm\" /> {{ text.removal }}\n @if (removalCount) { <hm-badge [value]=\"removalCount\" intent=\"danger\" /> }\n </div>\n @if (removalCount === 0) {\n <div class=\"gm-removal__empty\">\n <hm-icon shape=\"employee-group\" size=\"md\" />\n <p>{{ text.removalEmpty }}</p>\n </div>\n } @else {\n <ul class=\"gm-removal__list\">\n @for (m of checkedForRemoval; track m.user_id) {\n <li class=\"gm-removal__item\">\n <span class=\"gm-user\">\n <hm-account-avatar size=\"small\" [name]=\"m.name || m.username\" [imageUrl]=\"m.picture\" />\n <span class=\"gm-user__body\">\n <span class=\"gm-user__name\"><span>{{ m.name || m.username }}</span></span>\n <span class=\"gm-user__meta\">{{ metaOf(m) }}</span>\n </span>\n </span>\n <hm-icon-button emphasis=\"subtle\" icon=\"undo\" [label]=\"text.undoRemoval\" (clicked)=\"undoRemoval(m)\" />\n </li>\n }\n </ul>\n }\n </aside>\n </div>\n </div>\n\n <ng-container footer>\n <hm-button emphasis=\"medium\" (clicked)=\"close()\">{{ text.cancel }}</hm-button>\n <hm-button [loading]=\"saving\" [disabled]=\"saving\" (clicked)=\"save()\" icon=\"floppy\">{{ text.saveChanges }}</hm-button>\n </ng-container>\n</hm-modal>\n", styles: ["@charset \"UTF-8\";:host{display:contents}.gm{display:flex;flex-direction:column;gap:var(--hm-space-md);min-width:0}.gm-add{display:flex;flex-direction:column;gap:var(--hm-space-sm);padding:var(--hm-space-md);border:1px solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface-raised);min-width:0}.gm-add__row{display:flex;align-items:flex-end;gap:var(--hm-space-sm);flex-wrap:wrap;min-width:0}.gm-field{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0}.gm-field--grow{flex:1 1 18rem}.gm-field--role{flex:0 1 14rem}.gm-label{font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.gm-search{position:relative;display:flex;align-items:center;gap:var(--hm-space-xs);height:var(--hm-control-height);padding:0 var(--hm-space-sm);border:1px solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised);color:var(--hm-muted)}.gm-search:focus-within{outline:2px solid var(--hm-focus-color);outline-offset:1px}.gm-search input{flex:1;min-width:0;height:100%;border:0;outline:none;background:transparent;font:inherit;font-size:var(--hm-font-size-sm);color:var(--hm-text)}.gm-search input::placeholder{color:var(--hm-muted)}.gm-search__clear{display:inline-grid;place-items:center;flex:none;padding:0;border:0;background:none;color:inherit;cursor:pointer;line-height:0}.gm-search__results{position:absolute;inset-block-start:calc(100% + 4px);inset-inline:0;z-index:20;max-height:16rem;margin:0;padding:var(--hm-space-xs) 0;overflow-y:auto;list-style:none;background:var(--hm-surface-overlay);border:1px solid var(--hm-border);border-radius:var(--hm-radius-control);box-shadow:var(--hm-shadow-overlay)}.gm-search__result{display:flex;align-items:center;gap:var(--hm-space-sm);width:100%;padding:var(--hm-space-xs) var(--hm-space-sm);border:0;background:none;color:var(--hm-text);font:inherit;font-size:var(--hm-font-size-sm);text-align:start;cursor:pointer}.gm-search__result:hover,.gm-search__result:focus-visible{background:var(--hm-hover)}.gm-search__result:focus-visible{outline:2px solid var(--hm-focus-color);outline-offset:-2px}.gm-suggestions{display:flex;align-items:flex-start;gap:var(--hm-space-sm);min-width:0}.gm-suggestions__title{flex:none;display:inline-flex;align-items:center;min-height:2rem;font-size:var(--hm-font-size-xs);font-weight:var(--hm-font-weight-semibold);color:var(--hm-muted)}.gm-suggestions__list{display:flex;flex-wrap:wrap;gap:var(--hm-space-xs);min-width:0;margin:0;padding:0;list-style:none}.gm-chip{display:inline-flex;align-items:center;gap:var(--hm-space-xs);max-width:16rem;min-height:2rem;padding:2px var(--hm-space-sm) 2px 2px;border:1px solid var(--hm-border);border-radius:var(--hm-radius-pill);background:var(--hm-surface-raised);color:var(--hm-text);font:inherit;font-size:var(--hm-font-size-sm);cursor:pointer}.gm-chip hm-account-avatar{--hm-avatar-size: 1.625rem;flex:none}.gm-chip:hover{background:var(--hm-hover);border-color:var(--hm-accent)}.gm-chip:focus-visible{outline:2px solid var(--hm-focus-color);outline-offset:1px}.gm-chip__name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.gm-chip__plus{flex:none;color:var(--hm-accent)}.gm-split{display:grid;grid-template-columns:minmax(0,1fr) minmax(15rem,18rem);gap:var(--hm-space-md);align-items:start;min-width:0}@media(max-width:960px){.gm-split{grid-template-columns:minmax(0,1fr)}}.gm-members{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0}.gm-members__head{display:flex;align-items:center;justify-content:space-between;gap:var(--hm-space-sm);flex-wrap:wrap}.gm-members__title{display:inline-flex;align-items:center;gap:var(--hm-space-xs);margin:0;font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-semibold)}.gm-members__filter{flex:0 1 16rem;height:2rem}.gm-user{display:flex;align-items:center;gap:var(--hm-space-sm);min-width:0}.gm-user hm-account-avatar{flex:none}.gm-user__body{display:flex;flex-direction:column;min-width:0}.gm-user__name{display:flex;align-items:center;gap:var(--hm-space-xs);min-width:0;font-weight:var(--hm-font-weight-semibold)}.gm-user__name>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.gm-user__meta{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.gm-role{display:block;min-width:11rem}.gm-joined-by{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.gm-status{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs);margin:0;font-size:var(--hm-font-size-sm)}.gm-status--progress{color:var(--hm-muted)}.gm-status__error{color:var(--hm-danger)}.gm-removal{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0;padding:var(--hm-space-md);border:1px solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.gm-removal__head{display:flex;align-items:center;gap:var(--hm-space-xs);font-weight:var(--hm-font-weight-semibold);color:var(--hm-danger)}.gm-removal__empty{display:flex;align-items:flex-start;gap:var(--hm-space-sm);padding:var(--hm-space-sm);border-radius:var(--hm-radius-control);background:var(--hm-surface-muted);color:var(--hm-muted);font-size:var(--hm-font-size-sm);line-height:1.45}.gm-removal__empty hm-icon{flex:none;margin-top:2px}.gm-removal__empty p{margin:0}.gm-removal__list{display:flex;flex-direction:column;gap:var(--hm-space-xs);margin:0;padding:0;list-style:none}.gm-removal__item{display:flex;align-items:center;justify-content:space-between;gap:var(--hm-space-xs);min-width:0}.gm-removal__item .gm-user{flex:1}\n"] }]
|
|
419
585
|
}], ctorParameters: () => [{ type: AutoServiceService }], propDecorators: { clientId: [{
|
|
420
586
|
type: Input
|
|
421
587
|
}], groupId: [{
|
|
@@ -632,4 +798,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
|
|
|
632
798
|
}] });
|
|
633
799
|
|
|
634
800
|
export { AutoServiceGroupsComponent, NO_LEVEL };
|
|
635
|
-
//# sourceMappingURL=trismegisto-spine-auto-service-auto-service-groups.component-
|
|
801
|
+
//# sourceMappingURL=trismegisto-spine-auto-service-auto-service-groups.component-DE2QIGRH.mjs.map
|