@trismegisto/spine 3.3.0 → 3.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +19 -11
  2. package/fesm2022/trismegisto-spine-audit.mjs +16 -12
  3. package/fesm2022/trismegisto-spine-audit.mjs.map +1 -1
  4. package/fesm2022/{trismegisto-spine-auto-service-auto-service-audit.component-tfGcVR3i.mjs → trismegisto-spine-auto-service-auto-service-audit.component-D8HUpZtA.mjs} +4 -4
  5. package/fesm2022/{trismegisto-spine-auto-service-auto-service-audit.component-tfGcVR3i.mjs.map → trismegisto-spine-auto-service-auto-service-audit.component-D8HUpZtA.mjs.map} +1 -1
  6. package/fesm2022/trismegisto-spine-auto-service-auto-service-client-picker.component-NawbfdLW.mjs +65 -0
  7. package/fesm2022/trismegisto-spine-auto-service-auto-service-client-picker.component-NawbfdLW.mjs.map +1 -0
  8. package/fesm2022/{trismegisto-spine-auto-service-auto-service-client.component-IATeedbr.mjs → trismegisto-spine-auto-service-auto-service-client.component-BAfeLyTB.mjs} +27 -19
  9. package/fesm2022/trismegisto-spine-auto-service-auto-service-client.component-BAfeLyTB.mjs.map +1 -0
  10. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-BYjIQ9ID.mjs +635 -0
  11. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-BYjIQ9ID.mjs.map +1 -0
  12. package/fesm2022/{trismegisto-spine-auto-service-auto-service-login-attempts.component-CRvxfSWD.mjs → trismegisto-spine-auto-service-auto-service-login-attempts.component-DoyhkSYc.mjs} +4 -4
  13. package/fesm2022/{trismegisto-spine-auto-service-auto-service-login-attempts.component-CRvxfSWD.mjs.map → trismegisto-spine-auto-service-auto-service-login-attempts.component-DoyhkSYc.mjs.map} +1 -1
  14. package/fesm2022/trismegisto-spine-auto-service-auto-service-news-page.component-CBemH4hr.mjs +432 -0
  15. package/fesm2022/trismegisto-spine-auto-service-auto-service-news-page.component-CBemH4hr.mjs.map +1 -0
  16. package/fesm2022/trismegisto-spine-auto-service-auto-service-ratings-page.component-C_THGTkw.mjs +769 -0
  17. package/fesm2022/trismegisto-spine-auto-service-auto-service-ratings-page.component-C_THGTkw.mjs.map +1 -0
  18. package/fesm2022/trismegisto-spine-auto-service-auto-service-suggestions-page.component-DIeunpQ9.mjs +2123 -0
  19. package/fesm2022/trismegisto-spine-auto-service-auto-service-suggestions-page.component-DIeunpQ9.mjs.map +1 -0
  20. package/fesm2022/{trismegisto-spine-auto-service-auto-service.component-DGrs_Jvw.mjs → trismegisto-spine-auto-service-auto-service.component-QsIQ61w2.mjs} +31 -23
  21. package/fesm2022/trismegisto-spine-auto-service-auto-service.component-QsIQ61w2.mjs.map +1 -0
  22. package/fesm2022/{trismegisto-spine-auto-service-client.usage-C_2zBCRN.mjs → trismegisto-spine-auto-service-client.usage-CJxn9Ysz.mjs} +5 -3
  23. package/fesm2022/trismegisto-spine-auto-service-client.usage-CJxn9Ysz.mjs.map +1 -0
  24. package/fesm2022/{trismegisto-spine-auto-service-groups.technical-9aPPulhy.mjs → trismegisto-spine-auto-service-groups.technical-_jQrg1Qh.mjs} +7 -5
  25. package/fesm2022/trismegisto-spine-auto-service-groups.technical-_jQrg1Qh.mjs.map +1 -0
  26. package/fesm2022/{trismegisto-spine-auto-service-groups.usage-CYh61LFL.mjs → trismegisto-spine-auto-service-groups.usage-OvWjNeeT.mjs} +7 -6
  27. package/fesm2022/{trismegisto-spine-auto-service-groups.usage-CYh61LFL.mjs.map → trismegisto-spine-auto-service-groups.usage-OvWjNeeT.mjs.map} +1 -1
  28. package/fesm2022/trismegisto-spine-auto-service-home.component-rQ3dIGIE.mjs +315 -0
  29. package/fesm2022/trismegisto-spine-auto-service-home.component-rQ3dIGIE.mjs.map +1 -0
  30. package/fesm2022/trismegisto-spine-auto-service-home.technical-Ce7oYRdg.mjs +46 -0
  31. package/fesm2022/trismegisto-spine-auto-service-home.technical-Ce7oYRdg.mjs.map +1 -0
  32. package/fesm2022/trismegisto-spine-auto-service-home.usage-D9R_ZMuU.mjs +48 -0
  33. package/fesm2022/trismegisto-spine-auto-service-home.usage-D9R_ZMuU.mjs.map +1 -0
  34. package/fesm2022/trismegisto-spine-auto-service-news.technical-DV9UpobG.mjs +38 -0
  35. package/fesm2022/trismegisto-spine-auto-service-news.technical-DV9UpobG.mjs.map +1 -0
  36. package/fesm2022/trismegisto-spine-auto-service-news.usage-CeuZnG0V.mjs +48 -0
  37. package/fesm2022/trismegisto-spine-auto-service-news.usage-CeuZnG0V.mjs.map +1 -0
  38. package/fesm2022/trismegisto-spine-auto-service-ratings.technical-QIVj6HBU.mjs +52 -0
  39. package/fesm2022/trismegisto-spine-auto-service-ratings.technical-QIVj6HBU.mjs.map +1 -0
  40. package/fesm2022/trismegisto-spine-auto-service-ratings.usage-BkteY8lz.mjs +45 -0
  41. package/fesm2022/trismegisto-spine-auto-service-ratings.usage-BkteY8lz.mjs.map +1 -0
  42. package/fesm2022/trismegisto-spine-auto-service-suggestions.technical-BtUK6sKD.mjs +69 -0
  43. package/fesm2022/trismegisto-spine-auto-service-suggestions.technical-BtUK6sKD.mjs.map +1 -0
  44. package/fesm2022/trismegisto-spine-auto-service-suggestions.usage-BwO9Hjsx.mjs +72 -0
  45. package/fesm2022/trismegisto-spine-auto-service-suggestions.usage-BwO9Hjsx.mjs.map +1 -0
  46. package/fesm2022/trismegisto-spine-auto-service.mjs +49 -19
  47. package/fesm2022/trismegisto-spine-auto-service.mjs.map +1 -1
  48. package/fesm2022/trismegisto-spine-graphql.mjs +11 -2
  49. package/fesm2022/trismegisto-spine-graphql.mjs.map +1 -1
  50. package/fesm2022/trismegisto-spine-layout.mjs +3113 -1221
  51. package/fesm2022/trismegisto-spine-layout.mjs.map +1 -1
  52. package/fesm2022/trismegisto-spine-modal.mjs +7 -5
  53. package/fesm2022/trismegisto-spine-modal.mjs.map +1 -1
  54. package/fesm2022/trismegisto-spine-routing.mjs +12 -20
  55. package/fesm2022/trismegisto-spine-routing.mjs.map +1 -1
  56. package/fesm2022/trismegisto-spine-shell.mjs +15 -39
  57. package/fesm2022/trismegisto-spine-shell.mjs.map +1 -1
  58. package/fesm2022/trismegisto-spine-side-panel.mjs +7 -5
  59. package/fesm2022/trismegisto-spine-side-panel.mjs.map +1 -1
  60. package/fesm2022/trismegisto-spine.mjs +1969 -279
  61. package/fesm2022/trismegisto-spine.mjs.map +1 -1
  62. package/package.json +6 -6
  63. package/types/trismegisto-spine-audit.d.ts +2 -12
  64. package/types/trismegisto-spine-audit.d.ts.map +1 -1
  65. package/types/trismegisto-spine-auto-service.d.ts +22 -5
  66. package/types/trismegisto-spine-auto-service.d.ts.map +1 -1
  67. package/types/trismegisto-spine-layout.d.ts +1083 -283
  68. package/types/trismegisto-spine-layout.d.ts.map +1 -1
  69. package/types/trismegisto-spine-modal.d.ts +1 -0
  70. package/types/trismegisto-spine-modal.d.ts.map +1 -1
  71. package/types/trismegisto-spine-routing.d.ts +3 -12
  72. package/types/trismegisto-spine-routing.d.ts.map +1 -1
  73. package/types/trismegisto-spine-shell.d.ts +2 -15
  74. package/types/trismegisto-spine-shell.d.ts.map +1 -1
  75. package/types/trismegisto-spine-side-panel.d.ts +1 -0
  76. package/types/trismegisto-spine-side-panel.d.ts.map +1 -1
  77. package/types/trismegisto-spine.d.ts +739 -165
  78. package/types/trismegisto-spine.d.ts.map +1 -1
  79. package/fesm2022/trismegisto-spine-auto-service-auto-service-client-picker.component-Cl7ahisu.mjs +0 -65
  80. package/fesm2022/trismegisto-spine-auto-service-auto-service-client-picker.component-Cl7ahisu.mjs.map +0 -1
  81. package/fesm2022/trismegisto-spine-auto-service-auto-service-client.component-IATeedbr.mjs.map +0 -1
  82. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-D6L0gTmd.mjs +0 -605
  83. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-D6L0gTmd.mjs.map +0 -1
  84. package/fesm2022/trismegisto-spine-auto-service-auto-service.component-DGrs_Jvw.mjs.map +0 -1
  85. package/fesm2022/trismegisto-spine-auto-service-client.usage-C_2zBCRN.mjs.map +0 -1
  86. package/fesm2022/trismegisto-spine-auto-service-groups.technical-9aPPulhy.mjs.map +0 -1
  87. package/fesm2022/trismegisto-spine-auto-service-home.component-5_aji1hf.mjs +0 -239
  88. package/fesm2022/trismegisto-spine-auto-service-home.component-5_aji1hf.mjs.map +0 -1
  89. package/fesm2022/trismegisto-spine-auto-service-home.technical-BrVhcgDO.mjs +0 -35
  90. package/fesm2022/trismegisto-spine-auto-service-home.technical-BrVhcgDO.mjs.map +0 -1
  91. package/fesm2022/trismegisto-spine-auto-service-home.usage-DWYGtngX.mjs +0 -43
  92. package/fesm2022/trismegisto-spine-auto-service-home.usage-DWYGtngX.mjs.map +0 -1
@@ -0,0 +1,432 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, Injectable, input, signal, computed, effect, untracked, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { A as AutoServiceClientPickerComponent } from './trismegisto-spine-auto-service-auto-service-client-picker.component-NawbfdLW.mjs';
4
+ import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
5
+ import { HermesDialogService, AccountAvatarComponent, AlertComponent, ButtonComponent, ComboboxMultiComponent, DataGridComponent, DataGridCellDirective, DatePickerComponent, IconComponent, InputComponent, ListEditorComponent, ModalComponent, RichTextComponent, SegmentedControlComponent, TagComponent, TextareaComponent, TimePickerComponent } from '@trismegisto/angular';
6
+ import { urlPath, formatMessage } from '@trismegisto/primitives';
7
+ import { SPINE_MESSAGES } from '@trismegisto/spine';
8
+ import { A as AutoServiceDeleteFeedback } from './trismegisto-spine-auto-service-auto-service-delete-CV_t9vxm.mjs';
9
+ import { firstValueFrom } from 'rxjs';
10
+ import { AuthorizationService } from '@orfeu/angular/core';
11
+ import { AUTO_SERVICE_CONFIG } from './trismegisto-spine-auto-service.mjs';
12
+
13
+ /**
14
+ * Novidades do app no auto-serviço (`{adminApi}/auto-service/:clientId/news*`): o servidor recorta pelo client da URL
15
+ * e exige o grupo de administração com o crachá Administrador. Próprio da aba (o `AutoServiceService` é o das outras
16
+ * telas).
17
+ */
18
+ class AutoServiceNewsService {
19
+ constructor() {
20
+ this.http = inject(HttpClient);
21
+ this.auth = inject(AuthorizationService);
22
+ this.config = inject(AUTO_SERVICE_CONFIG);
23
+ }
24
+ url(clientId, rest = '') {
25
+ return `${this.config.adminApi}/auto-service${urlPath `/${clientId}`}/news${rest}`;
26
+ }
27
+ get headers() {
28
+ return new HttpHeaders({ 'x-access-token': this.auth.getToken() ?? '' });
29
+ }
30
+ list(clientId, page, limit) {
31
+ return firstValueFrom(this.http.get(this.url(clientId), { headers: this.headers, params: { page, limit } }));
32
+ }
33
+ get(clientId, newsId) {
34
+ return firstValueFrom(this.http.get(this.url(clientId, urlPath `/${newsId}`), { headers: this.headers }));
35
+ }
36
+ audience(clientId) {
37
+ return firstValueFrom(this.http.get(this.url(clientId, '/audience'), { headers: this.headers }));
38
+ }
39
+ create(clientId, body) {
40
+ return firstValueFrom(this.http.post(this.url(clientId), body, { headers: this.headers }));
41
+ }
42
+ update(clientId, newsId, body) {
43
+ return firstValueFrom(this.http.put(this.url(clientId, urlPath `/${newsId}`), body, { headers: this.headers }));
44
+ }
45
+ remove(clientId, newsId) {
46
+ return firstValueFrom(this.http.delete(this.url(clientId, urlPath `/${newsId}`), { headers: this.headers }));
47
+ }
48
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceNewsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
49
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceNewsService, providedIn: 'root' }); }
50
+ }
51
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceNewsService, decorators: [{
52
+ type: Injectable,
53
+ args: [{ providedIn: 'root' }]
54
+ }] });
55
+
56
+ const PAGE_SIZE = 20;
57
+ const TITLE_MAX = 140;
58
+ const SUMMARY_MAX = 300;
59
+ let sequence = 0;
60
+ /** Teto da lista de mudanças (implementação) e de cada item -- os mesmos do servidor. */
61
+ const ITEMS_MAX = 30;
62
+ const ITEM_MAX = 300;
63
+ /** Ícone de cada tipo (seletor do topo e prévia). */
64
+ const KIND_ICONS = {
65
+ informativo: 'file-text',
66
+ implementacao: 'rocket-diagonal',
67
+ alteracao: 'two-way-arrows',
68
+ };
69
+ /** HTML de rich text com texto de verdade (não só tags vazias). */
70
+ function newsHtmlHasText(html) {
71
+ return !!html && html.replace(/<[^>]*>/g, '').replace(/&nbsp;|&#160;/g, ' ').trim() !== '';
72
+ }
73
+ const pad = (n) => String(n).padStart(2, '0');
74
+ /** Data e hora LOCAIS (do navegador) de um instante ISO, no formato dos campos (`AAAA-MM-DD`, `HH:mm`). */
75
+ function newsLocalParts(iso) {
76
+ const d = new Date(iso);
77
+ if (Number.isNaN(d.getTime()))
78
+ return { date: null, time: null };
79
+ return { date: `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`, time: `${pad(d.getHours())}:${pad(d.getMinutes())}` };
80
+ }
81
+ /** Instante ISO (UTC) da data e hora locais dos campos; sem data = null (o servidor publica ao salvar); sem hora = 00:00. */
82
+ function newsPublishedAt(date, time) {
83
+ if (!date)
84
+ return null;
85
+ const d = new Date(`${date}T${time || '00:00'}:00`);
86
+ return Number.isNaN(d.getTime()) ? null : d.toISOString();
87
+ }
88
+ /** Código do argos-admin (`{ error }`) → texto no idioma ativo; desconhecido, o `detail` do servidor ou `fallback`. */
89
+ function newsErrorText(err, text, fallback) {
90
+ if (!(err instanceof HttpErrorResponse))
91
+ return fallback;
92
+ const body = err.error;
93
+ const known = {
94
+ invalid_title: text.errors.invalidTitle,
95
+ invalid_summary: text.errors.invalidSummary,
96
+ body_too_large: text.errors.bodyTooLarge,
97
+ invalid_published_at: text.errors.invalidPublishedAt,
98
+ invalid_until: text.errors.invalidUntil,
99
+ invalid_group_ids: text.errors.invalidGroupIds,
100
+ invalid_kind: text.errors.invalidKind,
101
+ invalid_items: text.errors.invalidItems,
102
+ invalid_compare: text.errors.invalidCompare,
103
+ compare_too_large: text.errors.compareTooLarge,
104
+ };
105
+ const code = typeof body?.error === 'string' ? body.error : '';
106
+ if (Object.prototype.hasOwnProperty.call(known, code))
107
+ return known[code];
108
+ return typeof body?.detail === 'string' && body.detail ? body.detail : fallback;
109
+ }
110
+ const emptyForm = () => ({
111
+ kind: 'informativo', title: '', summary: '', body: '', items: [], before: '', after: '', publishDate: null, publishTime: null,
112
+ until: null, groupIds: [],
113
+ });
114
+ /**
115
+ * Aba Novidades do Cliente: o que o spine mostra em "Novidades" (menu do perfil, aviso da entrada, card do Início)
116
+ * para quem entra no app. Grade paginada no servidor (agendadas e vencidas também), criar/editar num modal e excluir
117
+ * com confirmação (no menu da linha ou no rodapé do modal, ao editar). O modal é um editor em seções numeradas
118
+ * (título e resumo, texto em rich text, publicação, validade e público) com a prévia ao vivo do card do Início ao
119
+ * lado. Sem rascunho: a novidade não tem esse estado (sem data, sai ao salvar). O texto é saneado pelo argos-admin ao
120
+ * gravar; o público vazio é todo mundo que entra no app.
121
+ */
122
+ class AutoServiceNewsComponent {
123
+ /** Validade antes do dia da publicação: a novidade nunca apareceria. */
124
+ get untilBeforePublish() {
125
+ const publishDay = this.form.publishDate ?? newsLocalParts(new Date().toISOString()).date;
126
+ return !!this.form.until && !!publishDay && this.form.until < publishDay;
127
+ }
128
+ /** Data e hora da prévia: as dos campos; sem data, "ao salvar". */
129
+ get previewDate() {
130
+ const at = newsPublishedAt(this.form.publishDate, this.form.publishTime);
131
+ return at ? this.formatDate(at) : this.text.previewNow;
132
+ }
133
+ /** Publicação no futuro: a prévia marca "Agendada". */
134
+ get previewScheduled() {
135
+ const at = newsPublishedAt(this.form.publishDate, this.form.publishTime);
136
+ return !!at && new Date(at).getTime() > Date.now();
137
+ }
138
+ get canSave() {
139
+ const title = this.form.title.trim();
140
+ return !!title && title.length <= TITLE_MAX && this.form.summary.trim().length <= SUMMARY_MAX && !this.untilBeforePublish
141
+ && this.kindReady;
142
+ }
143
+ /** O conteúdo do tipo está completo: a implementação com ao menos uma mudança; a alteração com o antes e o agora. */
144
+ get kindReady() {
145
+ if (this.form.kind === 'implementacao')
146
+ return this.form.items.length > 0 && this.form.items.length <= ITEMS_MAX;
147
+ if (this.form.kind === 'alteracao')
148
+ return newsHtmlHasText(this.form.before) && newsHtmlHasText(this.form.after);
149
+ return true;
150
+ }
151
+ get kindIcon() {
152
+ return KIND_ICONS[this.form.kind];
153
+ }
154
+ get kindLabel() {
155
+ return this.kindOptions.find((o) => o.value === this.form.kind)?.label ?? '';
156
+ }
157
+ setKind(kind) {
158
+ if (kind === 'informativo' || kind === 'implementacao' || kind === 'alteracao')
159
+ this.form = { ...this.form, kind };
160
+ this.saveError.set(null);
161
+ }
162
+ constructor() {
163
+ this.service = inject(AutoServiceNewsService);
164
+ this.deletion = inject(AutoServiceDeleteFeedback);
165
+ this.dialogs = inject(HermesDialogService);
166
+ this.text = inject(SPINE_MESSAGES).autoServiceNews;
167
+ this.common = inject(SPINE_MESSAGES).autoService;
168
+ /** Rótulos do card Novidades do Início, repetidos na prévia. */
169
+ this.home = inject(SPINE_MESSAGES).home;
170
+ this.uid = `spine-as-news-${++sequence}`;
171
+ this.dateFormat = new Intl.DateTimeFormat(inject(SPINE_MESSAGES).meta.locale, { dateStyle: 'short', timeStyle: 'short' });
172
+ this.dayFormat = new Intl.DateTimeFormat(inject(SPINE_MESSAGES).meta.locale, { dateStyle: 'short', timeZone: 'UTC' });
173
+ this.TITLE_MAX = TITLE_MAX;
174
+ this.SUMMARY_MAX = SUMMARY_MAX;
175
+ this.ITEMS_MAX = ITEMS_MAX;
176
+ /** Tipos do seletor do topo, com ícone. */
177
+ this.kindOptions = [
178
+ { value: 'informativo', label: this.text.kindInformativo, icon: KIND_ICONS.informativo },
179
+ { value: 'implementacao', label: this.text.kindImplementacao, icon: KIND_ICONS.implementacao },
180
+ { value: 'alteracao', label: this.text.kindAlteracao, icon: KIND_ICONS.alteracao },
181
+ ];
182
+ /** Cada mudança até 300 caracteres (o vazio e o repetido o próprio hm-list-editor recusa). */
183
+ this.itemRule = (item) => item.length > ITEM_MAX ? this.text.errors.invalidItems : null;
184
+ this.clientId = input('', ...(ngDevMode ? [{ debugName: "clientId" }] : /* istanbul ignore next */ []));
185
+ this.rows = signal([], ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
186
+ this.total = signal(0, ...(ngDevMode ? [{ debugName: "total" }] : /* istanbul ignore next */ []));
187
+ this.pageState = signal({ page: 1, size: PAGE_SIZE }, ...(ngDevMode ? [{ debugName: "pageState" }] : /* istanbul ignore next */ []));
188
+ this.loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
189
+ this.loadError = signal('', ...(ngDevMode ? [{ debugName: "loadError" }] : /* istanbul ignore next */ []));
190
+ this.audienceOptions = signal([], ...(ngDevMode ? [{ debugName: "audienceOptions" }] : /* istanbul ignore next */ []));
191
+ this.modalOpen = signal(false, ...(ngDevMode ? [{ debugName: "modalOpen" }] : /* istanbul ignore next */ []));
192
+ this.editingId = signal('', ...(ngDevMode ? [{ debugName: "editingId" }] : /* istanbul ignore next */ []));
193
+ this.form = emptyForm();
194
+ this.saving = signal(false, ...(ngDevMode ? [{ debugName: "saving" }] : /* istanbul ignore next */ []));
195
+ this.saveError = signal(null, ...(ngDevMode ? [{ debugName: "saveError" }] : /* istanbul ignore next */ []));
196
+ /** O texto da novidade em edição ainda está vindo (o editor espera). */
197
+ this.loadingBody = signal(false, ...(ngDevMode ? [{ debugName: "loadingBody" }] : /* istanbul ignore next */ []));
198
+ /** Excluindo pelo rodapé do modal. */
199
+ this.removing = signal(false, ...(ngDevMode ? [{ debugName: "removing" }] : /* istanbul ignore next */ []));
200
+ this.rowId = (n) => n.id;
201
+ this.columns = computed(() => [
202
+ { key: 'title', title: this.text.colTitle, value: (n) => n.title },
203
+ { key: 'status', title: this.text.colStatus, value: (n) => this.statusLabel(n.status) },
204
+ { key: 'published_at', title: this.text.colPublishedAt, value: (n) => this.formatDate(n.published_at) },
205
+ { key: 'until', title: this.text.colUntil, value: (n) => this.formatDay(n.until) },
206
+ { key: 'groups', title: this.text.colAudience, value: (n) => n.groups.map((g) => g.name).join(', ') || this.text.audienceAll },
207
+ { key: 'author', title: this.text.colAuthor, value: (n) => n.author?.name || n.author?.username || '' },
208
+ ], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
209
+ this.rowActions = computed(() => [
210
+ { id: 'edit', icon: 'pencil', label: this.common.edit },
211
+ {
212
+ id: 'delete', icon: 'trash', label: this.common.delete, danger: true, separatorAbove: true,
213
+ confirm: (n) => ({ heading: this.text.confirmDeleteHeading, message: formatMessage(this.text.confirmDelete, { title: n.title }) }),
214
+ },
215
+ ], ...(ngDevMode ? [{ debugName: "rowActions" }] : /* istanbul ignore next */ []));
216
+ effect(() => {
217
+ const id = this.clientId();
218
+ untracked(() => {
219
+ this.pageState.set({ page: 1, size: this.pageState().size });
220
+ void this.load();
221
+ void this.loadAudience(id);
222
+ });
223
+ });
224
+ }
225
+ statusLabel(status) {
226
+ return status === 'scheduled' ? this.text.statusScheduled : status === 'expired' ? this.text.statusExpired : this.text.statusPublished;
227
+ }
228
+ statusColor(status) {
229
+ return status === 'scheduled' ? 'info' : status === 'expired' ? 'neutral' : 'success';
230
+ }
231
+ formatDate(iso) {
232
+ const d = new Date(iso);
233
+ return Number.isNaN(d.getTime()) ? '' : this.dateFormat.format(d);
234
+ }
235
+ /** `until` é um dia (AAAA-MM-DD), sem fuso: formatado em UTC para não mudar de dia. */
236
+ formatDay(day) {
237
+ if (!day)
238
+ return this.text.noUntil;
239
+ const d = new Date(`${day}T00:00:00Z`);
240
+ return Number.isNaN(d.getTime()) ? day : this.dayFormat.format(d);
241
+ }
242
+ async load(page = this.pageState().page, size = this.pageState().size) {
243
+ const clientId = this.clientId();
244
+ if (!clientId) {
245
+ this.rows.set([]);
246
+ this.total.set(0);
247
+ return;
248
+ }
249
+ this.loading.set(true);
250
+ this.loadError.set('');
251
+ try {
252
+ const res = await this.service.list(clientId, page, size);
253
+ this.rows.set(res.news ?? []);
254
+ this.total.set(res.total ?? 0);
255
+ this.pageState.set({ page, size });
256
+ }
257
+ catch (err) {
258
+ this.rows.set([]);
259
+ this.total.set(0);
260
+ this.loadError.set(newsErrorText(err, this.text, this.text.loadError));
261
+ }
262
+ finally {
263
+ this.loading.set(false);
264
+ }
265
+ }
266
+ /** Sem a lista de público o campo fica sem opção (a novidade sai para todos). */
267
+ async loadAudience(clientId) {
268
+ if (!clientId) {
269
+ this.audienceOptions.set([]);
270
+ return;
271
+ }
272
+ try {
273
+ const res = await this.service.audience(clientId);
274
+ this.audienceOptions.set((res.groups ?? []).map((g) => ({ value: g.id, label: g.name })));
275
+ }
276
+ catch {
277
+ this.audienceOptions.set([]);
278
+ }
279
+ }
280
+ onPageStateChange(state) {
281
+ void this.load(state.page, state.size);
282
+ }
283
+ openCreate() {
284
+ this.editingId.set('');
285
+ this.form = emptyForm();
286
+ this.saveError.set(null);
287
+ this.loadingBody.set(false);
288
+ this.removing.set(false);
289
+ this.modalOpen.set(true);
290
+ }
291
+ /** O corpo não vem na lista: busca o detalhe antes de liberar o editor. */
292
+ async openEdit(row) {
293
+ const parts = newsLocalParts(row.published_at);
294
+ this.editingId.set(row.id);
295
+ this.form = {
296
+ kind: row.kind ?? 'informativo', title: row.title, summary: row.summary, body: '', items: [], before: '', after: '',
297
+ publishDate: parts.date, publishTime: parts.time, until: row.until, groupIds: row.groups.map((g) => g.id),
298
+ };
299
+ this.saveError.set(null);
300
+ this.loadingBody.set(true);
301
+ this.removing.set(false);
302
+ this.modalOpen.set(true);
303
+ try {
304
+ const full = await this.service.get(this.clientId(), row.id);
305
+ if (this.editingId() === row.id) {
306
+ this.form = {
307
+ ...this.form, kind: full.kind ?? this.form.kind, body: full.body_html ?? '', items: [...(full.items ?? [])],
308
+ before: full.before_html ?? '', after: full.after_html ?? '',
309
+ };
310
+ }
311
+ }
312
+ catch (err) {
313
+ this.saveError.set(newsErrorText(err, this.text, this.text.loadError));
314
+ }
315
+ finally {
316
+ this.loadingBody.set(false);
317
+ }
318
+ }
319
+ async save() {
320
+ if (!this.canSave || this.saving() || this.loadingBody())
321
+ return;
322
+ const body = {
323
+ kind: this.form.kind,
324
+ title: this.form.title.trim(),
325
+ summary: this.form.summary.trim(),
326
+ body_html: this.form.body,
327
+ // Só o conteúdo do tipo escolhido; o resto vai vazio (o servidor zera também).
328
+ items: this.form.kind === 'implementacao' ? [...this.form.items] : [],
329
+ before_html: this.form.kind === 'alteracao' ? this.form.before : '',
330
+ after_html: this.form.kind === 'alteracao' ? this.form.after : '',
331
+ published_at: newsPublishedAt(this.form.publishDate, this.form.publishTime),
332
+ until: this.form.until || null,
333
+ group_ids: [...this.form.groupIds],
334
+ };
335
+ this.saving.set(true);
336
+ this.saveError.set(null);
337
+ try {
338
+ if (this.editingId())
339
+ await this.service.update(this.clientId(), this.editingId(), body);
340
+ else
341
+ await this.service.create(this.clientId(), body);
342
+ }
343
+ catch (err) {
344
+ // O modal fica aberto com o motivo no idioma ativo.
345
+ this.saveError.set(newsErrorText(err, this.text, this.text.saveError));
346
+ return;
347
+ }
348
+ finally {
349
+ this.saving.set(false);
350
+ }
351
+ this.modalOpen.set(false);
352
+ await this.load(this.editingId() ? this.pageState().page : 1);
353
+ }
354
+ /** "Excluir" no rodapé do modal (só ao editar): confirma, exclui, fecha e recarrega; falha deixa o modal aberto. */
355
+ async removeFromModal() {
356
+ const id = this.editingId();
357
+ if (!id || this.saving() || this.removing())
358
+ return;
359
+ const title = this.form.title.trim();
360
+ const ok = await this.dialogs.confirm({
361
+ heading: this.text.confirmDeleteHeading, message: formatMessage(this.text.confirmDelete, { title }),
362
+ confirmLabel: this.common.delete, danger: true,
363
+ });
364
+ if (!ok || this.editingId() !== id || !this.modalOpen())
365
+ return;
366
+ this.removing.set(true);
367
+ try {
368
+ if (!(await this.deletion.run(() => this.service.remove(this.clientId(), id))))
369
+ return;
370
+ }
371
+ finally {
372
+ this.removing.set(false);
373
+ }
374
+ this.modalOpen.set(false);
375
+ await this.load();
376
+ }
377
+ /** Já confirmado pelo `confirm` da ação na grade. */
378
+ async remove(row) {
379
+ if (await this.deletion.run(() => this.service.remove(this.clientId(), row.id)))
380
+ await this.load();
381
+ }
382
+ onAction(e) {
383
+ if (e.action === 'edit')
384
+ void this.openEdit(e.row);
385
+ if (e.action === 'delete')
386
+ void this.remove(e.row);
387
+ }
388
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceNewsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
389
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: AutoServiceNewsComponent, isStandalone: true, selector: "spine-auto-service-news", inputs: { clientId: { classPropertyName: "clientId", publicName: "clientId", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<!-- Pagina\u00E7\u00E3o no servidor (agendadas e vencidas tamb\u00E9m); a busca/filtro local da grade fica desligada. -->\n<hm-data-grid\n [columns]=\"columns()\"\n [rows]=\"rows()\"\n [rowId]=\"rowId\"\n [caption]=\"text.caption\"\n [loading]=\"loading()\"\n [error]=\"loadError()\"\n [emptyMessage]=\"text.empty\"\n [rowActions]=\"rowActions()\"\n [filterable]=\"false\"\n [paginated]=\"false\"\n [totalItems]=\"total()\"\n [pageState]=\"pageState()\"\n (pageStateChange)=\"onPageStateChange($event)\"\n [pageSizes]=\"[10, 20, 50, 100]\"\n (actionTriggered)=\"onAction($event)\"\n>\n <hm-button dataGridActions icon=\"plus\" (clicked)=\"openCreate()\">{{ text.newNews }}</hm-button>\n <ng-template hmCell=\"title\" let-n>\n <span class=\"news-title\">\n <span>{{ n.title }}</span>\n @if (n.summary) { <span class=\"news-title__summary\">{{ n.summary }}</span> }\n </span>\n </ng-template>\n <ng-template hmCell=\"status\" let-n>\n <hm-tag [name]=\"statusLabel(n.status)\" [color]=\"statusColor(n.status)\" />\n </ng-template>\n <ng-template hmCell=\"groups\" let-n>\n @if (n.groups.length) {\n <span class=\"news-tags\">\n @for (g of n.groups; track g.id) { <hm-tag [name]=\"g.name\" color=\"info\" /> }\n </span>\n } @else {\n <span class=\"muted\">{{ text.audienceAll }}</span>\n }\n </ng-template>\n <ng-template hmCell=\"author\" let-n>\n @if (n.author; as a) {\n <span class=\"news-author\">\n <hm-account-avatar size=\"small\" [name]=\"a.name || a.username\" [imageUrl]=\"a.picture\" />\n <span>{{ a.name || a.username }}</span>\n </span>\n }\n </ng-template>\n</hm-data-grid>\n\n<!-- Modal: criar/editar novidade. O cabe\u00E7alho do hm-modal some da vista (o t\u00EDtulo segue como nome do di\u00E1logo, ver o\n .scss) e o editor desenha o seu: \u00EDcone, t\u00EDtulo e subt\u00EDtulo. Pensado para caber sem rolagem num notebook: em cima,\n \"1. Informa\u00E7\u00F5es principais\" (t\u00EDtulo) com o resumo ao lado; embaixo, o conte\u00FAdo do tipo escolhido no topo (texto;\n introdu\u00E7\u00E3o + lista; ou introdu\u00E7\u00E3o + antes e agora), que estica e rola por dentro, ao lado do painel com publica\u00E7\u00E3o,\n validade, p\u00FAblico, dica e a pr\u00E9via do In\u00EDcio no fim. O modal n\u00E3o rola; em tela estreita, tudo empilha. -->\n<hm-modal [open]=\"modalOpen()\" (openChange)=\"modalOpen.set($event)\" [heading]=\"editingId() ? text.editNews : text.newNews\" size=\"xlarge\"\n (closed)=\"modalOpen.set(false)\">\n <div class=\"ne\">\n <header class=\"ne-head\">\n <span class=\"ne-head__icon\" aria-hidden=\"true\"><hm-icon shape=\"announcement\" size=\"md\" /></span>\n <span class=\"ne-head__text\">\n <!-- O h2 do hm-modal j\u00E1 d\u00E1 o nome ao di\u00E1logo: este \u00E9 s\u00F3 o visual. -->\n <span class=\"ne-head__title\" aria-hidden=\"true\">{{ editingId() ? text.editNews : text.newNews }}</span>\n <span class=\"ne-head__intro\">{{ editingId() ? text.editNewsIntro : text.newNewsIntro }}</span>\n </span>\n <!-- Tipo de publica\u00E7\u00E3o: muda os campos do conte\u00FAdo e a pr\u00E9via. -->\n <hm-segmented-control class=\"ne-kind\" [label]=\"text.kind\" [options]=\"kindOptions\" [value]=\"form.kind\" (valueChange)=\"setKind($event)\" />\n </header>\n\n <div class=\"ne-scroll\">\n @if (saveError()) { <hm-alert variant=\"error\">{{ saveError() }}</hm-alert> }\n\n <div class=\"ne-grid\">\n <!-- Linha de cima: a se\u00E7\u00E3o 1 ocupa as duas colunas (subgrid): t\u00EDtulo \u00E0 esquerda, resumo \u00E0 direita. -->\n <section class=\"ne-section ne-intro\" [attr.aria-labelledby]=\"uid + '-main'\">\n <div class=\"ne-intro__lead\">\n <div class=\"ne-section__head\">\n <h3 class=\"ne-section__title\" [id]=\"uid + '-main'\"><span class=\"ne-step\" aria-hidden=\"true\">1</span>{{ text.sectionMain }}</h3>\n <p class=\"ne-section__hint\">{{ text.sectionMainHint }}</p>\n </div>\n <hm-input [label]=\"text.title\" required [maxLength]=\"TITLE_MAX\" [(value)]=\"form.title\" (valueChange)=\"saveError.set(null)\" />\n </div>\n <hm-textarea class=\"ne-intro__summary\" [label]=\"text.summary\" [hint]=\"text.summaryHint\" [rows]=\"3\" [maxLength]=\"SUMMARY_MAX\"\n [(value)]=\"form.summary\" />\n </section>\n\n <section class=\"ne-section ne-content\" [attr.aria-labelledby]=\"uid + '-body'\">\n <div class=\"ne-section__head\">\n <h3 class=\"ne-section__title\" [id]=\"uid + '-body'\"><span class=\"ne-step\" aria-hidden=\"true\">2</span>{{ text.sectionBody }}</h3>\n <p class=\"ne-section__hint\">\n {{ form.kind === 'implementacao' ? text.sectionBodyHintImpl : form.kind === 'alteracao' ? text.sectionBodyHintAlt : text.sectionBodyHint }}\n </p>\n </div>\n <!-- Estica at\u00E9 o fim da linha; quem rola \u00E9 o texto (ou a lista), n\u00E3o o modal. -->\n @switch (form.kind) {\n @case ('implementacao') {\n <hm-rich-text class=\"ne-content__intro\" [label]=\"text.intro\" [rows]=\"2\" [maxRows]=\"4\" [loading]=\"loadingBody()\"\n [(value)]=\"form.body\" />\n <div class=\"ne-content__items\">\n <hm-list-editor [label]=\"text.items\" [hint]=\"text.itemsHint\" [placeholder]=\"text.itemPlaceholder\" [emptyText]=\"text.itemsEmpty\"\n [max]=\"ITEMS_MAX\" [validate]=\"itemRule\" editable [items]=\"form.items\" (itemsChange)=\"form.items = [...$event]\" />\n </div>\n }\n @case ('alteracao') {\n <hm-rich-text class=\"ne-content__intro\" [label]=\"text.intro\" [rows]=\"2\" [maxRows]=\"4\" [loading]=\"loadingBody()\"\n [(value)]=\"form.body\" />\n <div class=\"ne-content__compare\">\n <hm-rich-text class=\"ne-content__editor ne-content__editor--labelled\" [label]=\"text.before\" required [rows]=\"4\" [maxRows]=\"16\" [loading]=\"loadingBody()\"\n [(value)]=\"form.before\" />\n <hm-rich-text class=\"ne-content__editor ne-content__editor--labelled\" [label]=\"text.after\" required [rows]=\"4\" [maxRows]=\"16\" [loading]=\"loadingBody()\"\n [(value)]=\"form.after\" />\n </div>\n }\n @default {\n <hm-rich-text class=\"ne-content__editor\" [label]=\"text.sectionBody\" labelHidden [hint]=\"text.bodyHint\" [rows]=\"6\" [maxRows]=\"24\"\n showCounter [loading]=\"loadingBody()\" [(value)]=\"form.body\" />\n }\n }\n </section>\n\n <aside class=\"ne-side\">\n <section class=\"ne-section ne-side__publish\" [attr.aria-labelledby]=\"uid + '-publish'\">\n <h3 class=\"ne-section__title\" [id]=\"uid + '-publish'\"><span class=\"ne-step\" aria-hidden=\"true\">3</span>{{ text.sectionPublish }}</h3>\n <div class=\"ne-row\">\n <hm-date-picker [label]=\"text.publishDate\" [(value)]=\"form.publishDate\" />\n <hm-time-picker [label]=\"text.publishTime\" [(value)]=\"form.publishTime\" />\n </div>\n <p class=\"ne-note\">{{ text.publishHint }}</p>\n </section>\n\n <section class=\"ne-section ne-side__until\" [attr.aria-labelledby]=\"uid + '-until'\">\n <h3 class=\"ne-section__title\" [id]=\"uid + '-until'\"><span class=\"ne-step\" aria-hidden=\"true\">4</span>{{ text.sectionUntil }}</h3>\n <hm-date-picker [label]=\"text.untilDate\" [hint]=\"text.untilHint\" [min]=\"form.publishDate\" [(value)]=\"form.until\" />\n @if (untilBeforePublish) { <hm-alert variant=\"warning\">{{ text.errors.untilBeforePublish }}</hm-alert> }\n </section>\n\n <section class=\"ne-section ne-side__audience\" [attr.aria-labelledby]=\"uid + '-audience'\">\n <h3 class=\"ne-section__title\" [id]=\"uid + '-audience'\"><span class=\"ne-step\" aria-hidden=\"true\">5</span>{{ text.sectionAudience }}</h3>\n <!-- Vazio = todos que entram no app. -->\n <hm-combobox-multi [label]=\"text.audienceGroups\" [hint]=\"text.audienceHint\" [placeholder]=\"text.audienceAll\" [options]=\"audienceOptions()\"\n [visibleChipsLimit]=\"null\" [selected]=\"form.groupIds\" (selectedChange)=\"form.groupIds = [...$event]\" />\n </section>\n\n <p class=\"ne-tip\"><hm-icon class=\"ne-tip__icon\" shape=\"lightbulb\" size=\"sm\" /><span><strong>{{ text.tipTitle }}</strong> {{ text.tipText }}</span></p>\n\n <section class=\"ne-section ne-side__preview\" [attr.aria-labelledby]=\"uid + '-preview'\">\n <h3 class=\"ne-section__title\" [id]=\"uid + '-preview'\"><hm-icon class=\"ne-section__icon\" shape=\"eye\" size=\"sm\" />{{ text.preview }}</h3>\n <!-- Mesmo desenho do card Novidades do In\u00EDcio; repete o que est\u00E1 nos campos, ent\u00E3o fica fora da leitura. -->\n <div class=\"ne-preview\" aria-hidden=\"true\">\n <div class=\"ne-preview__head\">\n <span class=\"ne-preview__heading\">{{ home.news }}</span>\n <span class=\"ne-preview__badge\">{{ home.newsBadge }}</span>\n </div>\n <div class=\"ne-preview__item\">\n <span class=\"ne-preview__icon\"><hm-icon [shape]=\"kindIcon\" size=\"sm\" /></span>\n <span class=\"ne-preview__text\">\n <span class=\"ne-preview__title\" [class.ne-preview__placeholder]=\"!form.title.trim()\">{{ form.title.trim() || text.previewTitle }}</span>\n <span class=\"ne-preview__summary\" [class.ne-preview__placeholder]=\"!form.summary.trim()\">{{ form.summary.trim() || text.previewSummary }}</span>\n <span class=\"ne-preview__date\">\n <hm-icon shape=\"calendar\" size=\"xs\" />{{ previewDate }}\n @if (previewScheduled) { <span class=\"ne-preview__status\">{{ text.statusScheduled }}</span> }\n <span class=\"ne-preview__kind\">{{ kindLabel }}</span>\n </span>\n </span>\n <hm-icon class=\"ne-preview__chevron\" shape=\"angle-right\" size=\"sm\" />\n </div>\n </div>\n </section>\n </aside>\n </div>\n </div>\n </div>\n\n <ng-container footer>\n <!-- Sem rascunho: a novidade n\u00E3o tem esse estado (sem data = publica ao salvar). -->\n @if (editingId()) {\n <hm-button class=\"ne-delete\" emphasis=\"medium\" intent=\"danger\" icon=\"trash\" [disabled]=\"saving() || removing()\" [loading]=\"removing()\"\n (clicked)=\"removeFromModal()\">{{ common.delete }}</hm-button>\n }\n <hm-button emphasis=\"medium\" (clicked)=\"modalOpen.set(false)\">{{ common.cancel }}</hm-button>\n <hm-button [disabled]=\"!canSave || loadingBody() || removing()\" [loading]=\"saving()\" (clicked)=\"save()\">\n {{ editingId() ? common.save : common.create }}\n </hm-button>\n </ng-container>\n</hm-modal>\n", styles: [".as-stack{display:flex;flex-direction:column;gap:var(--hm-space-md);min-inline-size:0}.as-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-xs)}.as-field-row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--hm-space-sm)}.as-field-row>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}.news-title{display:flex;flex-direction:column;gap:2px;min-inline-size:0}.news-title__summary{overflow:hidden;color:var(--hm-muted);font-size:var(--hm-font-size-xs);text-overflow:ellipsis;white-space:nowrap}.news-tags,.news-author{display:inline-flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}:host ::ng-deep .hm-modal__window:has(>.hm-modal__body>.ne){block-size:min(100dvh - 2 * var(--hm-space-md),60rem);max-block-size:none;grid-template-rows:minmax(0,1fr) auto}:host ::ng-deep .hm-modal__window:has(>.hm-modal__body>.ne)>.hm-modal__header{position:absolute;z-index:1;inset-block-start:0;inset-inline-end:0;border:0;padding:var(--hm-space-sm)}:host ::ng-deep .hm-modal__window:has(>.hm-modal__body>.ne) .hm-modal__heading{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}:host ::ng-deep .hm-modal__window:has(>.hm-modal__body>.ne)>.hm-modal__body{display:flex;flex-direction:column;min-block-size:0;overflow:hidden;padding:0}.ne{container:ne/inline-size;display:flex;flex:1 1 auto;flex-direction:column;min-block-size:0}.ne-head{display:flex;flex:none;flex-wrap:wrap;align-items:center;gap:var(--hm-space-sm);padding:var(--hm-space-sm) var(--hm-space-md);padding-inline-end:calc(var(--hm-space-md) + var(--hm-control-height) + var(--hm-space-sm));border-block-end:var(--hm-border-hairline) solid var(--hm-divider)}.ne-head__icon{display:grid;flex:none;place-items:center;inline-size:2.5rem;block-size:2.5rem;border-radius:var(--hm-radius);background:var(--hm-accent-subtle);color:var(--hm-accent)}.ne-head__text{display:flex;flex-direction:column;gap:var(--hm-space-nano);min-inline-size:0}.ne-head__title{color:var(--hm-text);font-size:var(--hm-font-size-xl);font-weight:var(--hm-font-weight-bold);line-height:var(--hm-line-height-tight)}.ne-head__intro{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.ne-kind{margin-inline-start:auto}.ne-content__intro{flex:none}.ne-content__compare{display:flex;flex-direction:column;gap:var(--hm-space-sm)}.ne-preview__kind{padding:var(--hm-space-nano) var(--hm-space-xs);border:var(--hm-border-hairline) solid var(--hm-divider);border-radius:var(--hm-radius-pill);color:var(--hm-text);font-size:var(--hm-font-size-3xs)}.ne-scroll{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--hm-space-sm);min-block-size:0;overflow-y:auto;padding:var(--hm-space-md)}.ne-grid{display:grid;flex:1 0 auto;grid-template-columns:minmax(0,1fr);gap:var(--hm-space-md)}.ne-section{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-inline-size:0}.ne-section__head{display:flex;flex-direction:column;gap:var(--hm-space-nano)}.ne-section__title{display:flex;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)}.ne-section__icon{flex:none;color:var(--hm-accent)}.ne-section__hint{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.ne-step{display:inline-grid;flex:none;place-items:center;inline-size:1.5rem;block-size:1.5rem;border-radius:var(--hm-radius-pill);background:var(--hm-accent-subtle);color:var(--hm-accent);font-size:var(--hm-font-size-xs);font-weight:var(--hm-font-weight-bold)}.ne-intro{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--hm-space-sm) var(--hm-space-md)}.ne-intro__lead{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-inline-size:0}.ne-side{container:ne-side/inline-size;display:grid;grid-template-areas:\"publish\" \"until\" \"audience\" \"tip\" \"preview\";grid-template-columns:minmax(0,1fr);grid-template-rows:auto auto auto auto 1fr;gap:var(--hm-space-md);min-inline-size:0;padding:var(--hm-space-sm) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-divider);border-radius:var(--hm-radius);background:var(--hm-surface-muted)}.ne-side__publish{grid-area:publish}.ne-side__until{grid-area:until}.ne-side__audience{grid-area:audience}.ne-tip{grid-area:tip}.ne-side__preview{grid-area:preview;align-self:end}@container ne (min-width: 52rem){.ne-scroll{overflow:hidden}.ne-grid{flex:1 1 auto;grid-template-columns:repeat(2,minmax(0,1fr));grid-template-rows:auto minmax(0,1fr);min-block-size:0}.ne-intro{grid-column:1/-1;grid-template-columns:subgrid}.ne-content{min-block-size:0;overflow:hidden}.ne-side{min-block-size:0;overflow-y:auto}.ne-content__items{flex:1 1 0;min-block-size:0;overflow-y:auto}.ne-content__compare{display:grid;flex:1 1 0;grid-template-columns:repeat(2,minmax(0,1fr));min-block-size:0}.ne-content__editor{flex:1 1 0;grid-template-rows:minmax(0,1fr);grid-auto-rows:auto;min-block-size:0}.ne-content__editor ::ng-deep .hm-rich-text__frame,.ne-content__editor ::ng-deep .hm-rich-text__loading{min-block-size:0}.ne-content__editor ::ng-deep .hm-rich-text__body{display:flex;flex:1 1 auto;flex-direction:column;min-block-size:0}.ne-content__editor ::ng-deep .hm-rich-text__area{flex:1 1 0;min-block-size:2lh;max-block-size:none;overflow-y:auto}.ne-content__editor--labelled{grid-template-rows:auto minmax(0,1fr)}}@media(max-height:55rem){@container ne-side (min-width: 34rem){.ne-side{grid-template-areas:\"publish audience\" \"until preview\" \"tip preview\";grid-template-columns:repeat(2,minmax(0,1fr));grid-template-rows:auto auto 1fr}}}.ne-row{display:flex;flex-wrap:wrap;gap:var(--hm-space-sm)}.ne-row>*{flex:1 1 8rem;min-inline-size:0}.ne-note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.ne-preview{display:flex;flex-direction:column;gap:var(--hm-space-micro);padding:var(--hm-space-sm) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-divider);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.ne-preview__head{display:flex;align-items:center;gap:var(--hm-space-xs)}.ne-preview__heading{color:var(--hm-text);font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-semibold)}.ne-preview__badge,.ne-preview__status{flex:none;padding:var(--hm-space-nano) var(--hm-space-xs);border-radius:var(--hm-radius-micro);background:var(--hm-accent-subtle);color:var(--hm-accent);font-size:var(--hm-font-size-3xs);font-weight:var(--hm-font-weight-semibold);letter-spacing:.04em;text-transform:uppercase}.ne-preview__item{display:flex;align-items:center;gap:var(--hm-space-sm);padding:var(--hm-space-xs) 0;border-block-start:var(--hm-border-hairline) solid var(--hm-divider)}.ne-preview__icon{display:grid;flex:none;place-items:center;inline-size:2rem;block-size:2rem;border-radius:var(--hm-radius-control);background:var(--hm-accent-subtle);color:var(--hm-accent)}.ne-preview__text{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--hm-space-nano);min-inline-size:0}.ne-preview__title{overflow:hidden;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold);text-overflow:ellipsis;white-space:nowrap}.ne-preview__summary{display:-webkit-box;overflow:hidden;color:var(--hm-muted);font-size:var(--hm-font-size-xs);overflow-wrap:anywhere;-webkit-box-orient:vertical;-webkit-line-clamp:2}.ne-preview__placeholder{color:var(--hm-muted);font-style:italic;font-weight:var(--hm-font-weight-regular)}.ne-preview__date{display:inline-flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-micro);color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.ne-preview__chevron{flex:none;color:var(--hm-muted)}.ne-tip{display:flex;align-items:flex-start;gap:var(--hm-space-xs);margin:0;padding:var(--hm-space-xs) var(--hm-space-sm);border-inline-start:var(--hm-border-thin) solid var(--hm-info);border-radius:var(--hm-radius-control);background:var(--hm-info-bg);color:var(--hm-text);font-size:var(--hm-font-size-xs)}@media(forced-colors:active){.ne-tip{border:var(--hm-border-hairline) solid CanvasText}}.ne-tip__icon{flex:none;color:var(--hm-info)}.ne-delete{margin-inline-end:auto}\n"], dependencies: [{ 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: 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: ComboboxMultiComponent, selector: "hm-combobox-multi", inputs: ["label", "options", "selected", "extraOptions", "unknownLabel", "placeholder", "hint", "max", "visibleChipsLimit"], outputs: ["selectedChange"] }, { 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: DatePickerComponent, selector: "hm-date-picker", inputs: ["value", "label", "labelHidden", "hint", "placeholder", "min", "max", "valueFormat"], outputs: ["valueChange"] }, { kind: "component", type: IconComponent, selector: "hm-icon", inputs: ["shape", "direction", "size", "solid", "badge", "status", "flip", "inverse", "label"] }, { kind: "component", type: InputComponent, selector: "hm-input", inputs: ["value", "label", "type", "placeholder", "icon", "labelHidden", "hint", "prefix", "suffix", "prefixIcon", "mask", "maskValue", "min", "max", "step", "stepButtons", "maxLength", "autocomplete", "inputMode"], outputs: ["valueChange"] }, { kind: "component", type: ListEditorComponent, selector: "hm-list-editor", inputs: ["items", "label", "labelHidden", "hint", "placeholder", "descriptionPlaceholder", "pair", "itemLabel", "titleLabel", "descriptionLabel", "addLabel", "emptyText", "descriptionRequired", "allowDuplicates", "ignoreCase", "max", "validate", "editable", "readonly"], outputs: ["itemsChange", "added", "removed", "moved", "edited"] }, { kind: "component", type: ModalComponent, selector: "hm-modal", inputs: ["open", "heading", "size", "closeLabel", "closable", "beforeClose"], outputs: ["openChange", "closed"] }, { kind: "component", type: RichTextComponent, selector: "hm-rich-text", inputs: ["value", "label", "labelHidden", "hint", "placeholder", "readonly", "loading", "tools", "headingLevels", "hideToolbar", "rows", "maxRows", "maxLength", "showCounter", "pasteMode", "linkProtocols", "allowRelativeLinks", "spellcheck"], outputs: ["valueChange", "focusChange"] }, { kind: "component", type: SegmentedControlComponent, selector: "hm-segmented-control", inputs: ["options", "value", "label", "size", "emphasis"], outputs: ["valueChange"] }, { kind: "component", type: TagComponent, selector: "hm-tag", inputs: ["name", "color", "compact"] }, { kind: "component", type: TextareaComponent, selector: "hm-textarea", inputs: ["value", "label", "hint", "placeholder", "maxLength", "rows", "autoGrow", "labelHidden", "loading", "maxRows", "submitKey"], outputs: ["valueChange", "submitted"] }, { kind: "component", type: TimePickerComponent, selector: "hm-time-picker", inputs: ["value", "label", "labelHidden", "hint", "placeholder", "min", "max", "interval", "hourCycle"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
390
+ }
391
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceNewsComponent, decorators: [{
392
+ type: Component,
393
+ args: [{ selector: 'spine-auto-service-news', imports: [
394
+ AccountAvatarComponent, AlertComponent, ButtonComponent, ComboboxMultiComponent, DataGridComponent, DataGridCellDirective,
395
+ DatePickerComponent, IconComponent, InputComponent, ListEditorComponent, ModalComponent, RichTextComponent, SegmentedControlComponent,
396
+ TagComponent, TextareaComponent, TimePickerComponent,
397
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Pagina\u00E7\u00E3o no servidor (agendadas e vencidas tamb\u00E9m); a busca/filtro local da grade fica desligada. -->\n<hm-data-grid\n [columns]=\"columns()\"\n [rows]=\"rows()\"\n [rowId]=\"rowId\"\n [caption]=\"text.caption\"\n [loading]=\"loading()\"\n [error]=\"loadError()\"\n [emptyMessage]=\"text.empty\"\n [rowActions]=\"rowActions()\"\n [filterable]=\"false\"\n [paginated]=\"false\"\n [totalItems]=\"total()\"\n [pageState]=\"pageState()\"\n (pageStateChange)=\"onPageStateChange($event)\"\n [pageSizes]=\"[10, 20, 50, 100]\"\n (actionTriggered)=\"onAction($event)\"\n>\n <hm-button dataGridActions icon=\"plus\" (clicked)=\"openCreate()\">{{ text.newNews }}</hm-button>\n <ng-template hmCell=\"title\" let-n>\n <span class=\"news-title\">\n <span>{{ n.title }}</span>\n @if (n.summary) { <span class=\"news-title__summary\">{{ n.summary }}</span> }\n </span>\n </ng-template>\n <ng-template hmCell=\"status\" let-n>\n <hm-tag [name]=\"statusLabel(n.status)\" [color]=\"statusColor(n.status)\" />\n </ng-template>\n <ng-template hmCell=\"groups\" let-n>\n @if (n.groups.length) {\n <span class=\"news-tags\">\n @for (g of n.groups; track g.id) { <hm-tag [name]=\"g.name\" color=\"info\" /> }\n </span>\n } @else {\n <span class=\"muted\">{{ text.audienceAll }}</span>\n }\n </ng-template>\n <ng-template hmCell=\"author\" let-n>\n @if (n.author; as a) {\n <span class=\"news-author\">\n <hm-account-avatar size=\"small\" [name]=\"a.name || a.username\" [imageUrl]=\"a.picture\" />\n <span>{{ a.name || a.username }}</span>\n </span>\n }\n </ng-template>\n</hm-data-grid>\n\n<!-- Modal: criar/editar novidade. O cabe\u00E7alho do hm-modal some da vista (o t\u00EDtulo segue como nome do di\u00E1logo, ver o\n .scss) e o editor desenha o seu: \u00EDcone, t\u00EDtulo e subt\u00EDtulo. Pensado para caber sem rolagem num notebook: em cima,\n \"1. Informa\u00E7\u00F5es principais\" (t\u00EDtulo) com o resumo ao lado; embaixo, o conte\u00FAdo do tipo escolhido no topo (texto;\n introdu\u00E7\u00E3o + lista; ou introdu\u00E7\u00E3o + antes e agora), que estica e rola por dentro, ao lado do painel com publica\u00E7\u00E3o,\n validade, p\u00FAblico, dica e a pr\u00E9via do In\u00EDcio no fim. O modal n\u00E3o rola; em tela estreita, tudo empilha. -->\n<hm-modal [open]=\"modalOpen()\" (openChange)=\"modalOpen.set($event)\" [heading]=\"editingId() ? text.editNews : text.newNews\" size=\"xlarge\"\n (closed)=\"modalOpen.set(false)\">\n <div class=\"ne\">\n <header class=\"ne-head\">\n <span class=\"ne-head__icon\" aria-hidden=\"true\"><hm-icon shape=\"announcement\" size=\"md\" /></span>\n <span class=\"ne-head__text\">\n <!-- O h2 do hm-modal j\u00E1 d\u00E1 o nome ao di\u00E1logo: este \u00E9 s\u00F3 o visual. -->\n <span class=\"ne-head__title\" aria-hidden=\"true\">{{ editingId() ? text.editNews : text.newNews }}</span>\n <span class=\"ne-head__intro\">{{ editingId() ? text.editNewsIntro : text.newNewsIntro }}</span>\n </span>\n <!-- Tipo de publica\u00E7\u00E3o: muda os campos do conte\u00FAdo e a pr\u00E9via. -->\n <hm-segmented-control class=\"ne-kind\" [label]=\"text.kind\" [options]=\"kindOptions\" [value]=\"form.kind\" (valueChange)=\"setKind($event)\" />\n </header>\n\n <div class=\"ne-scroll\">\n @if (saveError()) { <hm-alert variant=\"error\">{{ saveError() }}</hm-alert> }\n\n <div class=\"ne-grid\">\n <!-- Linha de cima: a se\u00E7\u00E3o 1 ocupa as duas colunas (subgrid): t\u00EDtulo \u00E0 esquerda, resumo \u00E0 direita. -->\n <section class=\"ne-section ne-intro\" [attr.aria-labelledby]=\"uid + '-main'\">\n <div class=\"ne-intro__lead\">\n <div class=\"ne-section__head\">\n <h3 class=\"ne-section__title\" [id]=\"uid + '-main'\"><span class=\"ne-step\" aria-hidden=\"true\">1</span>{{ text.sectionMain }}</h3>\n <p class=\"ne-section__hint\">{{ text.sectionMainHint }}</p>\n </div>\n <hm-input [label]=\"text.title\" required [maxLength]=\"TITLE_MAX\" [(value)]=\"form.title\" (valueChange)=\"saveError.set(null)\" />\n </div>\n <hm-textarea class=\"ne-intro__summary\" [label]=\"text.summary\" [hint]=\"text.summaryHint\" [rows]=\"3\" [maxLength]=\"SUMMARY_MAX\"\n [(value)]=\"form.summary\" />\n </section>\n\n <section class=\"ne-section ne-content\" [attr.aria-labelledby]=\"uid + '-body'\">\n <div class=\"ne-section__head\">\n <h3 class=\"ne-section__title\" [id]=\"uid + '-body'\"><span class=\"ne-step\" aria-hidden=\"true\">2</span>{{ text.sectionBody }}</h3>\n <p class=\"ne-section__hint\">\n {{ form.kind === 'implementacao' ? text.sectionBodyHintImpl : form.kind === 'alteracao' ? text.sectionBodyHintAlt : text.sectionBodyHint }}\n </p>\n </div>\n <!-- Estica at\u00E9 o fim da linha; quem rola \u00E9 o texto (ou a lista), n\u00E3o o modal. -->\n @switch (form.kind) {\n @case ('implementacao') {\n <hm-rich-text class=\"ne-content__intro\" [label]=\"text.intro\" [rows]=\"2\" [maxRows]=\"4\" [loading]=\"loadingBody()\"\n [(value)]=\"form.body\" />\n <div class=\"ne-content__items\">\n <hm-list-editor [label]=\"text.items\" [hint]=\"text.itemsHint\" [placeholder]=\"text.itemPlaceholder\" [emptyText]=\"text.itemsEmpty\"\n [max]=\"ITEMS_MAX\" [validate]=\"itemRule\" editable [items]=\"form.items\" (itemsChange)=\"form.items = [...$event]\" />\n </div>\n }\n @case ('alteracao') {\n <hm-rich-text class=\"ne-content__intro\" [label]=\"text.intro\" [rows]=\"2\" [maxRows]=\"4\" [loading]=\"loadingBody()\"\n [(value)]=\"form.body\" />\n <div class=\"ne-content__compare\">\n <hm-rich-text class=\"ne-content__editor ne-content__editor--labelled\" [label]=\"text.before\" required [rows]=\"4\" [maxRows]=\"16\" [loading]=\"loadingBody()\"\n [(value)]=\"form.before\" />\n <hm-rich-text class=\"ne-content__editor ne-content__editor--labelled\" [label]=\"text.after\" required [rows]=\"4\" [maxRows]=\"16\" [loading]=\"loadingBody()\"\n [(value)]=\"form.after\" />\n </div>\n }\n @default {\n <hm-rich-text class=\"ne-content__editor\" [label]=\"text.sectionBody\" labelHidden [hint]=\"text.bodyHint\" [rows]=\"6\" [maxRows]=\"24\"\n showCounter [loading]=\"loadingBody()\" [(value)]=\"form.body\" />\n }\n }\n </section>\n\n <aside class=\"ne-side\">\n <section class=\"ne-section ne-side__publish\" [attr.aria-labelledby]=\"uid + '-publish'\">\n <h3 class=\"ne-section__title\" [id]=\"uid + '-publish'\"><span class=\"ne-step\" aria-hidden=\"true\">3</span>{{ text.sectionPublish }}</h3>\n <div class=\"ne-row\">\n <hm-date-picker [label]=\"text.publishDate\" [(value)]=\"form.publishDate\" />\n <hm-time-picker [label]=\"text.publishTime\" [(value)]=\"form.publishTime\" />\n </div>\n <p class=\"ne-note\">{{ text.publishHint }}</p>\n </section>\n\n <section class=\"ne-section ne-side__until\" [attr.aria-labelledby]=\"uid + '-until'\">\n <h3 class=\"ne-section__title\" [id]=\"uid + '-until'\"><span class=\"ne-step\" aria-hidden=\"true\">4</span>{{ text.sectionUntil }}</h3>\n <hm-date-picker [label]=\"text.untilDate\" [hint]=\"text.untilHint\" [min]=\"form.publishDate\" [(value)]=\"form.until\" />\n @if (untilBeforePublish) { <hm-alert variant=\"warning\">{{ text.errors.untilBeforePublish }}</hm-alert> }\n </section>\n\n <section class=\"ne-section ne-side__audience\" [attr.aria-labelledby]=\"uid + '-audience'\">\n <h3 class=\"ne-section__title\" [id]=\"uid + '-audience'\"><span class=\"ne-step\" aria-hidden=\"true\">5</span>{{ text.sectionAudience }}</h3>\n <!-- Vazio = todos que entram no app. -->\n <hm-combobox-multi [label]=\"text.audienceGroups\" [hint]=\"text.audienceHint\" [placeholder]=\"text.audienceAll\" [options]=\"audienceOptions()\"\n [visibleChipsLimit]=\"null\" [selected]=\"form.groupIds\" (selectedChange)=\"form.groupIds = [...$event]\" />\n </section>\n\n <p class=\"ne-tip\"><hm-icon class=\"ne-tip__icon\" shape=\"lightbulb\" size=\"sm\" /><span><strong>{{ text.tipTitle }}</strong> {{ text.tipText }}</span></p>\n\n <section class=\"ne-section ne-side__preview\" [attr.aria-labelledby]=\"uid + '-preview'\">\n <h3 class=\"ne-section__title\" [id]=\"uid + '-preview'\"><hm-icon class=\"ne-section__icon\" shape=\"eye\" size=\"sm\" />{{ text.preview }}</h3>\n <!-- Mesmo desenho do card Novidades do In\u00EDcio; repete o que est\u00E1 nos campos, ent\u00E3o fica fora da leitura. -->\n <div class=\"ne-preview\" aria-hidden=\"true\">\n <div class=\"ne-preview__head\">\n <span class=\"ne-preview__heading\">{{ home.news }}</span>\n <span class=\"ne-preview__badge\">{{ home.newsBadge }}</span>\n </div>\n <div class=\"ne-preview__item\">\n <span class=\"ne-preview__icon\"><hm-icon [shape]=\"kindIcon\" size=\"sm\" /></span>\n <span class=\"ne-preview__text\">\n <span class=\"ne-preview__title\" [class.ne-preview__placeholder]=\"!form.title.trim()\">{{ form.title.trim() || text.previewTitle }}</span>\n <span class=\"ne-preview__summary\" [class.ne-preview__placeholder]=\"!form.summary.trim()\">{{ form.summary.trim() || text.previewSummary }}</span>\n <span class=\"ne-preview__date\">\n <hm-icon shape=\"calendar\" size=\"xs\" />{{ previewDate }}\n @if (previewScheduled) { <span class=\"ne-preview__status\">{{ text.statusScheduled }}</span> }\n <span class=\"ne-preview__kind\">{{ kindLabel }}</span>\n </span>\n </span>\n <hm-icon class=\"ne-preview__chevron\" shape=\"angle-right\" size=\"sm\" />\n </div>\n </div>\n </section>\n </aside>\n </div>\n </div>\n </div>\n\n <ng-container footer>\n <!-- Sem rascunho: a novidade n\u00E3o tem esse estado (sem data = publica ao salvar). -->\n @if (editingId()) {\n <hm-button class=\"ne-delete\" emphasis=\"medium\" intent=\"danger\" icon=\"trash\" [disabled]=\"saving() || removing()\" [loading]=\"removing()\"\n (clicked)=\"removeFromModal()\">{{ common.delete }}</hm-button>\n }\n <hm-button emphasis=\"medium\" (clicked)=\"modalOpen.set(false)\">{{ common.cancel }}</hm-button>\n <hm-button [disabled]=\"!canSave || loadingBody() || removing()\" [loading]=\"saving()\" (clicked)=\"save()\">\n {{ editingId() ? common.save : common.create }}\n </hm-button>\n </ng-container>\n</hm-modal>\n", styles: [".as-stack{display:flex;flex-direction:column;gap:var(--hm-space-md);min-inline-size:0}.as-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-xs)}.as-field-row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--hm-space-sm)}.as-field-row>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}.news-title{display:flex;flex-direction:column;gap:2px;min-inline-size:0}.news-title__summary{overflow:hidden;color:var(--hm-muted);font-size:var(--hm-font-size-xs);text-overflow:ellipsis;white-space:nowrap}.news-tags,.news-author{display:inline-flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}:host ::ng-deep .hm-modal__window:has(>.hm-modal__body>.ne){block-size:min(100dvh - 2 * var(--hm-space-md),60rem);max-block-size:none;grid-template-rows:minmax(0,1fr) auto}:host ::ng-deep .hm-modal__window:has(>.hm-modal__body>.ne)>.hm-modal__header{position:absolute;z-index:1;inset-block-start:0;inset-inline-end:0;border:0;padding:var(--hm-space-sm)}:host ::ng-deep .hm-modal__window:has(>.hm-modal__body>.ne) .hm-modal__heading{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}:host ::ng-deep .hm-modal__window:has(>.hm-modal__body>.ne)>.hm-modal__body{display:flex;flex-direction:column;min-block-size:0;overflow:hidden;padding:0}.ne{container:ne/inline-size;display:flex;flex:1 1 auto;flex-direction:column;min-block-size:0}.ne-head{display:flex;flex:none;flex-wrap:wrap;align-items:center;gap:var(--hm-space-sm);padding:var(--hm-space-sm) var(--hm-space-md);padding-inline-end:calc(var(--hm-space-md) + var(--hm-control-height) + var(--hm-space-sm));border-block-end:var(--hm-border-hairline) solid var(--hm-divider)}.ne-head__icon{display:grid;flex:none;place-items:center;inline-size:2.5rem;block-size:2.5rem;border-radius:var(--hm-radius);background:var(--hm-accent-subtle);color:var(--hm-accent)}.ne-head__text{display:flex;flex-direction:column;gap:var(--hm-space-nano);min-inline-size:0}.ne-head__title{color:var(--hm-text);font-size:var(--hm-font-size-xl);font-weight:var(--hm-font-weight-bold);line-height:var(--hm-line-height-tight)}.ne-head__intro{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.ne-kind{margin-inline-start:auto}.ne-content__intro{flex:none}.ne-content__compare{display:flex;flex-direction:column;gap:var(--hm-space-sm)}.ne-preview__kind{padding:var(--hm-space-nano) var(--hm-space-xs);border:var(--hm-border-hairline) solid var(--hm-divider);border-radius:var(--hm-radius-pill);color:var(--hm-text);font-size:var(--hm-font-size-3xs)}.ne-scroll{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--hm-space-sm);min-block-size:0;overflow-y:auto;padding:var(--hm-space-md)}.ne-grid{display:grid;flex:1 0 auto;grid-template-columns:minmax(0,1fr);gap:var(--hm-space-md)}.ne-section{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-inline-size:0}.ne-section__head{display:flex;flex-direction:column;gap:var(--hm-space-nano)}.ne-section__title{display:flex;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)}.ne-section__icon{flex:none;color:var(--hm-accent)}.ne-section__hint{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.ne-step{display:inline-grid;flex:none;place-items:center;inline-size:1.5rem;block-size:1.5rem;border-radius:var(--hm-radius-pill);background:var(--hm-accent-subtle);color:var(--hm-accent);font-size:var(--hm-font-size-xs);font-weight:var(--hm-font-weight-bold)}.ne-intro{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--hm-space-sm) var(--hm-space-md)}.ne-intro__lead{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-inline-size:0}.ne-side{container:ne-side/inline-size;display:grid;grid-template-areas:\"publish\" \"until\" \"audience\" \"tip\" \"preview\";grid-template-columns:minmax(0,1fr);grid-template-rows:auto auto auto auto 1fr;gap:var(--hm-space-md);min-inline-size:0;padding:var(--hm-space-sm) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-divider);border-radius:var(--hm-radius);background:var(--hm-surface-muted)}.ne-side__publish{grid-area:publish}.ne-side__until{grid-area:until}.ne-side__audience{grid-area:audience}.ne-tip{grid-area:tip}.ne-side__preview{grid-area:preview;align-self:end}@container ne (min-width: 52rem){.ne-scroll{overflow:hidden}.ne-grid{flex:1 1 auto;grid-template-columns:repeat(2,minmax(0,1fr));grid-template-rows:auto minmax(0,1fr);min-block-size:0}.ne-intro{grid-column:1/-1;grid-template-columns:subgrid}.ne-content{min-block-size:0;overflow:hidden}.ne-side{min-block-size:0;overflow-y:auto}.ne-content__items{flex:1 1 0;min-block-size:0;overflow-y:auto}.ne-content__compare{display:grid;flex:1 1 0;grid-template-columns:repeat(2,minmax(0,1fr));min-block-size:0}.ne-content__editor{flex:1 1 0;grid-template-rows:minmax(0,1fr);grid-auto-rows:auto;min-block-size:0}.ne-content__editor ::ng-deep .hm-rich-text__frame,.ne-content__editor ::ng-deep .hm-rich-text__loading{min-block-size:0}.ne-content__editor ::ng-deep .hm-rich-text__body{display:flex;flex:1 1 auto;flex-direction:column;min-block-size:0}.ne-content__editor ::ng-deep .hm-rich-text__area{flex:1 1 0;min-block-size:2lh;max-block-size:none;overflow-y:auto}.ne-content__editor--labelled{grid-template-rows:auto minmax(0,1fr)}}@media(max-height:55rem){@container ne-side (min-width: 34rem){.ne-side{grid-template-areas:\"publish audience\" \"until preview\" \"tip preview\";grid-template-columns:repeat(2,minmax(0,1fr));grid-template-rows:auto auto 1fr}}}.ne-row{display:flex;flex-wrap:wrap;gap:var(--hm-space-sm)}.ne-row>*{flex:1 1 8rem;min-inline-size:0}.ne-note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.ne-preview{display:flex;flex-direction:column;gap:var(--hm-space-micro);padding:var(--hm-space-sm) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-divider);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.ne-preview__head{display:flex;align-items:center;gap:var(--hm-space-xs)}.ne-preview__heading{color:var(--hm-text);font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-semibold)}.ne-preview__badge,.ne-preview__status{flex:none;padding:var(--hm-space-nano) var(--hm-space-xs);border-radius:var(--hm-radius-micro);background:var(--hm-accent-subtle);color:var(--hm-accent);font-size:var(--hm-font-size-3xs);font-weight:var(--hm-font-weight-semibold);letter-spacing:.04em;text-transform:uppercase}.ne-preview__item{display:flex;align-items:center;gap:var(--hm-space-sm);padding:var(--hm-space-xs) 0;border-block-start:var(--hm-border-hairline) solid var(--hm-divider)}.ne-preview__icon{display:grid;flex:none;place-items:center;inline-size:2rem;block-size:2rem;border-radius:var(--hm-radius-control);background:var(--hm-accent-subtle);color:var(--hm-accent)}.ne-preview__text{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--hm-space-nano);min-inline-size:0}.ne-preview__title{overflow:hidden;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold);text-overflow:ellipsis;white-space:nowrap}.ne-preview__summary{display:-webkit-box;overflow:hidden;color:var(--hm-muted);font-size:var(--hm-font-size-xs);overflow-wrap:anywhere;-webkit-box-orient:vertical;-webkit-line-clamp:2}.ne-preview__placeholder{color:var(--hm-muted);font-style:italic;font-weight:var(--hm-font-weight-regular)}.ne-preview__date{display:inline-flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-micro);color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.ne-preview__chevron{flex:none;color:var(--hm-muted)}.ne-tip{display:flex;align-items:flex-start;gap:var(--hm-space-xs);margin:0;padding:var(--hm-space-xs) var(--hm-space-sm);border-inline-start:var(--hm-border-thin) solid var(--hm-info);border-radius:var(--hm-radius-control);background:var(--hm-info-bg);color:var(--hm-text);font-size:var(--hm-font-size-xs)}@media(forced-colors:active){.ne-tip{border:var(--hm-border-hairline) solid CanvasText}}.ne-tip__icon{flex:none;color:var(--hm-info)}.ne-delete{margin-inline-end:auto}\n"] }]
398
+ }], ctorParameters: () => [], propDecorators: { clientId: [{ type: i0.Input, args: [{ isSignal: true, alias: "clientId", required: false }] }] } });
399
+
400
+ /**
401
+ * Tela "Novidades" do auto-serviço (router `AUTO_SERVICE_NEWS_TEMPLATE_ROUTER_ID`, na raiz do menu; migration 0036 do
402
+ * argos): o seletor de client + o painel de novidades que era a aba Cliente › Novidades. Carregada só por
403
+ * `AUTO_SERVICE_SCREENS.news` -- nunca exportada pelo ponto de entrada (ver `public-api.ts`).
404
+ */
405
+ class AutoServiceNewsPageComponent {
406
+ constructor() {
407
+ this.client = signal(null, ...(ngDevMode ? [{ debugName: "client" }] : /* istanbul ignore next */ []));
408
+ }
409
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceNewsPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
410
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: AutoServiceNewsPageComponent, isStandalone: true, selector: "spine-auto-service-news-page", ngImport: i0, template: `
411
+ <div class="as-stack">
412
+ <spine-auto-service-client-picker (clientChange)="client.set($event)" />
413
+ @if (client(); as c) {
414
+ <spine-auto-service-news [clientId]="c.id" />
415
+ }
416
+ </div>
417
+ `, isInline: true, 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;min-inline-size:0}\n"], dependencies: [{ kind: "component", type: AutoServiceClientPickerComponent, selector: "spine-auto-service-client-picker", outputs: ["clientChange"] }, { kind: "component", type: AutoServiceNewsComponent, selector: "spine-auto-service-news", inputs: ["clientId"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
418
+ }
419
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AutoServiceNewsPageComponent, decorators: [{
420
+ type: Component,
421
+ args: [{ selector: 'spine-auto-service-news-page', imports: [AutoServiceClientPickerComponent, AutoServiceNewsComponent], template: `
422
+ <div class="as-stack">
423
+ <spine-auto-service-client-picker (clientChange)="client.set($event)" />
424
+ @if (client(); as c) {
425
+ <spine-auto-service-news [clientId]="c.id" />
426
+ }
427
+ </div>
428
+ `, changeDetection: ChangeDetectionStrategy.OnPush, 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;min-inline-size:0}\n"] }]
429
+ }] });
430
+
431
+ export { AutoServiceNewsPageComponent };
432
+ //# sourceMappingURL=trismegisto-spine-auto-service-auto-service-news-page.component-CBemH4hr.mjs.map