@sapphire-caitlyn/schema-kit-ng 0.0.4 → 0.0.6
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 +2 -0
- package/docs/inputs/date/input-date/README.md +37 -0
- package/docs/inputs/date/input-date-span/README.md +36 -0
- package/docs/inputs/date/input-date-time/README.md +32 -0
- package/docs/inputs/date/input-date-time-span/README.md +32 -0
- package/docs/inputs/date/input-time/README.md +28 -0
- package/docs/inputs/date/input-time-span/README.md +29 -0
- package/docs/inputs/input-base/README.md +42 -0
- package/docs/inputs/input-bool/README.md +30 -0
- package/docs/inputs/input-color/README.md +24 -0
- package/docs/inputs/input-color/color-picker/README.md +25 -0
- package/docs/inputs/input-cpf-cnpj/README.md +34 -0
- package/docs/inputs/input-file/README.md +33 -0
- package/docs/inputs/input-icon/README.md +28 -0
- package/docs/inputs/input-json/README.md +36 -0
- package/docs/inputs/input-mask/README.md +39 -0
- package/docs/inputs/input-number/README.md +44 -0
- package/docs/inputs/input-phone/README.md +25 -0
- package/docs/inputs/input-select/README.md +42 -0
- package/docs/inputs/input-string/README.md +32 -0
- package/docs/inputs/input-text-area/README.md +27 -0
- package/docs/schema-table/README.md +444 -0
- package/docs/schema-view/README.md +439 -0
- package/docs/ui/blurry-text-reveal/README.md +49 -0
- package/docs/ui/breadcrumbs/README.md +62 -0
- package/docs/ui/file-preview/README.md +60 -0
- package/docs/ui/infinte-scroll/README.md +10 -0
- package/docs/ui/loading/README.md +62 -0
- package/docs/ui/save-button/README.md +68 -0
- package/docs/ui/schema-dialog/README.md +154 -0
- package/docs/ui/suspended-slider/README.md +95 -0
- package/docs/ui/tree-drag-drop/README.md +202 -0
- package/fesm2022/sapphire-caitlyn-schema-kit-ng-helm.mjs.map +1 -1
- package/fesm2022/sapphire-caitlyn-schema-kit-ng.mjs +106 -103
- package/fesm2022/sapphire-caitlyn-schema-kit-ng.mjs.map +1 -1
- package/package.json +1 -1
- package/types/sapphire-caitlyn-schema-kit-ng.d.ts +93 -17
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# input-text-area
|
|
2
|
+
|
|
3
|
+
Campo de texto com várias linhas (`<textarea>`).
|
|
4
|
+
|
|
5
|
+
## Uso
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { InputTextAreaComponent } from '@schema-kit';
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<input-text-area label="Observações" placeholder="Digite um texto" [formField]="form.observacoes" />
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Inputs
|
|
16
|
+
|
|
17
|
+
Só os comuns (`label`, `placeholder`, `loading`, `tooltipInfo`): [input-base](../input-base/README.md).
|
|
18
|
+
|
|
19
|
+
**Valor:** `string | null`
|
|
20
|
+
|
|
21
|
+
## Slots
|
|
22
|
+
|
|
23
|
+
- `[slot=input]`: dentro do wrapper `relative` do campo.
|
|
24
|
+
|
|
25
|
+
## Atenção
|
|
26
|
+
|
|
27
|
+
- O input `type` existe, mas não tem efeito no `<textarea>`.
|
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
# schema-table
|
|
2
|
+
|
|
3
|
+
Tela de listagem pronta a partir de um `SchemaService`: busca, um filtro por campo, ordenação, paginação e as ações de
|
|
4
|
+
criar, editar e excluir. As colunas, os filtros e a formatação de cada célula saem do schema do service.
|
|
5
|
+
|
|
6
|
+
Feita para trabalhar junto com o [schema-view](../schema-view/README.md) (a tela de criar/editar).
|
|
7
|
+
|
|
8
|
+
## Uso
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { SchemaTableComponent, SchemaTableActionsDirective, SchemaTableReplacementForDirective, SchemaTableToolbarDirective } from '@schema-kit';
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### 1. O service
|
|
15
|
+
|
|
16
|
+
A tabela não recebe dados: recebe um `SchemaService`, que sabe o endpoint, o título e o schema dos campos. Os exemplos
|
|
17
|
+
deste README usam este service:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { inject, Injectable } from '@angular/core';
|
|
21
|
+
import { required } from '@angular/forms/signals';
|
|
22
|
+
import { FieldType, IView, SchemaService } from '@schema-kit';
|
|
23
|
+
|
|
24
|
+
export interface IProduto extends IView {
|
|
25
|
+
nome: string;
|
|
26
|
+
preco: number;
|
|
27
|
+
estoque: number;
|
|
28
|
+
ativo: boolean;
|
|
29
|
+
unidade: 'UN' | 'KG' | 'CX';
|
|
30
|
+
uuidCategoria: string | null;
|
|
31
|
+
descricao: string | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@Injectable({ providedIn: 'root' })
|
|
35
|
+
export class ProdutoService extends SchemaService<IProduto> {
|
|
36
|
+
constructor() {
|
|
37
|
+
// `this` não existe antes do super(): injete as dependências em variáveis locais
|
|
38
|
+
const categoriaService = inject(CategoriaService);
|
|
39
|
+
|
|
40
|
+
super({
|
|
41
|
+
endpoint: '/produtos',
|
|
42
|
+
title: 'Produtos',
|
|
43
|
+
bindLabel: 'nome', // texto do registro (confirmação de exclusão, selects de outros services)
|
|
44
|
+
bindValue: 'uuid', // valor do registro (selects de outros services)
|
|
45
|
+
schema: {
|
|
46
|
+
uuid: { type: FieldType.UUID, label: 'UUID', hidden: true },
|
|
47
|
+
nome: { type: FieldType.String, label: 'Nome', validators: required },
|
|
48
|
+
preco: { type: FieldType.Currency, label: 'Preço' },
|
|
49
|
+
estoque: { type: FieldType.Number, label: 'Estoque' },
|
|
50
|
+
ativo: { type: FieldType.Bool, label: 'Ativo', defaultValue: true },
|
|
51
|
+
uuidCategoria: {
|
|
52
|
+
type: FieldType.Select,
|
|
53
|
+
label: 'Categoria',
|
|
54
|
+
options: { service: () => categoriaService },
|
|
55
|
+
},
|
|
56
|
+
unidade: {
|
|
57
|
+
type: FieldType.Select,
|
|
58
|
+
label: 'Unidade',
|
|
59
|
+
options: {
|
|
60
|
+
lstOptions: [
|
|
61
|
+
{ id: 'UN', nome: 'Unidade' },
|
|
62
|
+
{ id: 'KG', nome: 'Quilo' },
|
|
63
|
+
{ id: 'CX', nome: 'Caixa' },
|
|
64
|
+
],
|
|
65
|
+
bindValue: 'id',
|
|
66
|
+
bindLabel: 'nome',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
descricao: { type: FieldType.TextArea, label: 'Descrição', hidden: { hiddenList: true } },
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Opções do service usadas pela tabela:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
interface SchemaServiceOptions<T> {
|
|
80
|
+
endpoint: string; // base das requisições: `${apiUrl}${endpoint}`
|
|
81
|
+
title: string; // breadcrumb da listagem
|
|
82
|
+
schema: SchemaFieldOptionsRecord<T>; // campos (colunas, filtros e o form do schema-view)
|
|
83
|
+
listSchema?: SchemaFieldOptionsRecord<T>; // se informado, define as colunas no lugar de `schema`
|
|
84
|
+
breadcrumbs?: IBreadcrumbItem[]; // substitui o breadcrumb padrão ([{ label: title }])
|
|
85
|
+
bindValue: keyof T | ((value: T) => T[keyof T]);
|
|
86
|
+
bindLabel: keyof T | ((value: T) => string | T[keyof T]);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 2. As rotas
|
|
91
|
+
|
|
92
|
+
A tabela navega relativo à rota em que está: **Novo** vai para `./view` e a edição para `./view/:uuid`. Monte as rotas
|
|
93
|
+
assim (é a mesma estrutura que o schema-view espera):
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
const routes: Routes = [
|
|
97
|
+
{ path: '', component: ProdutoListComponent },
|
|
98
|
+
{ path: 'view', component: ProdutoViewComponent }, // criar
|
|
99
|
+
{ path: 'view/:uuid', component: ProdutoViewComponent }, // editar
|
|
100
|
+
];
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 3. O componente
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
@Component({
|
|
107
|
+
selector: 'produto-list',
|
|
108
|
+
imports: [SchemaTableComponent],
|
|
109
|
+
template: `<schema-table [schemaService]="produtoService" />`,
|
|
110
|
+
})
|
|
111
|
+
export class ProdutoListComponent {
|
|
112
|
+
protected produtoService = inject(ProdutoService);
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Só com isso a tela tem: breadcrumb, campo de busca, um botão de filtro por campo, botões **Novo**, **Resetar filtros** e
|
|
117
|
+
**Atualizar**, cabeçalhos ordenáveis, coluna **Ações** (editar e excluir) e paginação.
|
|
118
|
+
|
|
119
|
+
## Inputs
|
|
120
|
+
|
|
121
|
+
| Input | Tipo | Padrão | Descrição |
|
|
122
|
+
| ----------------- | ---------------------- | ----------- | ---------------------------------------------------------------------------- |
|
|
123
|
+
| `schemaService` | `SchemaService<T, IP>` | obrigatório | Fonte dos dados e do schema |
|
|
124
|
+
| `lstPresetFilters`| `IIndexFilter<T, any>[]` | `[]` | Filtros fixos, enviados sempre junto com os do usuário. Não aparecem na barra |
|
|
125
|
+
| `noQuery` | `boolean` | `false` | Esconde o campo de busca |
|
|
126
|
+
| `noNew` | `boolean` | `false` | Esconde o botão Novo |
|
|
127
|
+
| `editPermissao` | `string \| null` | `null` | Permissão para editar. `null`: sempre permitido. Ver [Permissões](#permissões) |
|
|
128
|
+
| `deletePermissao` | `string \| null` | `null` | Permissão para excluir. `null`: sempre permitido |
|
|
129
|
+
|
|
130
|
+
Two-way binding (`model`), para controlar ou ler o estado de fora:
|
|
131
|
+
|
|
132
|
+
| Model | Tipo | Padrão | Descrição |
|
|
133
|
+
| ------- | ----------------------- | --------------------------------- | ---------------------------- |
|
|
134
|
+
| `page` | `number` | `1` | Página atual |
|
|
135
|
+
| `limit` | `number` | `paginationOptions.rows` (20) | Itens por página |
|
|
136
|
+
| `sort` | `IIndexParamsSort<T>[]` | `[]` | Ordenação (várias colunas) |
|
|
137
|
+
|
|
138
|
+
```html
|
|
139
|
+
<schema-table [schemaService]="produtoService" [(page)]="pagina" [(sort)]="ordenacao" />
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Outputs
|
|
143
|
+
|
|
144
|
+
| Output | Tipo | Descrição |
|
|
145
|
+
| ------ | ------- | ----------------------------------------------------------------------------------- |
|
|
146
|
+
| `novo` | `Event` | Clique em Novo, antes de navegar. `event.preventDefault()` cancela a navegação |
|
|
147
|
+
|
|
148
|
+
Abrir um dialog no lugar da tela de criação:
|
|
149
|
+
|
|
150
|
+
```html
|
|
151
|
+
<schema-table [schemaService]="produtoService" (novo)="$event.preventDefault(); abrirDialogNovo()" />
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Métodos
|
|
155
|
+
|
|
156
|
+
| Método | Descrição |
|
|
157
|
+
| ----------- | ---------------------------------------------------------------------------- |
|
|
158
|
+
| `refresh()` | Recarrega a página atual com os mesmos filtros |
|
|
159
|
+
| `reset()` | Volta à página 1, limite padrão, sem ordenação, sem filtros e sem busca |
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
export class ProdutoListComponent {
|
|
163
|
+
private table = viewChild.required(SchemaTableComponent);
|
|
164
|
+
|
|
165
|
+
async importarPlanilha(arquivo: File) {
|
|
166
|
+
await this.importacaoService.enviar(arquivo);
|
|
167
|
+
this.table().refresh();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Templates
|
|
173
|
+
|
|
174
|
+
Passados como `<ng-template>` dentro do `<schema-table>`. Importe a diretiva correspondente no componente que usa a
|
|
175
|
+
tabela.
|
|
176
|
+
|
|
177
|
+
Cliques no conteúdo de um template (e no texto das células padrão) não chegam à linha: botões, links e inputs funcionam
|
|
178
|
+
sem `stopPropagation`. O resto da linha continua abrindo a edição.
|
|
179
|
+
|
|
180
|
+
### Ações da linha: `schemaTableActions`
|
|
181
|
+
|
|
182
|
+
Botões extras em cada linha. Contexto: `$implicit` = o item da linha.
|
|
183
|
+
|
|
184
|
+
| Input | Tipo | Padrão | Descrição |
|
|
185
|
+
| ---------- | ------------------ | ------- | ---------------------------------------------------------------------- |
|
|
186
|
+
| `position` | `'start' \| 'end'` | `'end'` | `start`: coluna própria antes das colunas. `end`: na coluna **Ações**, entre editar e excluir |
|
|
187
|
+
|
|
188
|
+
```html
|
|
189
|
+
<schema-table [schemaService]="produtoService">
|
|
190
|
+
<ng-template schemaTableActions let-produto>
|
|
191
|
+
<button hlmBtn size="icon-sm" variant="ghost" (click)="duplicar(produto)" hlmTooltip="Duplicar">
|
|
192
|
+
<ng-icon name="lucideCopy" />
|
|
193
|
+
</button>
|
|
194
|
+
</ng-template>
|
|
195
|
+
|
|
196
|
+
<ng-template schemaTableActions position="start" let-produto>
|
|
197
|
+
<input type="checkbox" (change)="alternarSelecao(produto)" />
|
|
198
|
+
</ng-template>
|
|
199
|
+
</schema-table>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Barra de ferramentas: `schemaTableToolbar`
|
|
203
|
+
|
|
204
|
+
Conteúdo extra na barra do topo. Não tem contexto (`let-*` fica `undefined`).
|
|
205
|
+
|
|
206
|
+
| Input | Tipo | Padrão | Descrição |
|
|
207
|
+
| ---------- | ------------------ | ------- | ----------------------------------------------------------------- |
|
|
208
|
+
| `position` | `'start' \| 'end'` | `'end'` | `start`: depois dos filtros, antes de Novo. `end`: depois de Atualizar |
|
|
209
|
+
|
|
210
|
+
```html
|
|
211
|
+
<schema-table [schemaService]="produtoService">
|
|
212
|
+
<ng-template schemaTableToolbar position="start">
|
|
213
|
+
<button hlmBtn size="sm" variant="outline" (click)="exportarCsv()">Exportar</button>
|
|
214
|
+
</ng-template>
|
|
215
|
+
</schema-table>
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Célula personalizada: `schemaTableReplacementFor`
|
|
219
|
+
|
|
220
|
+
Substitui a célula de uma coluna. O valor da diretiva é o `index` do campo.
|
|
221
|
+
|
|
222
|
+
Contexto (`ITemplateContext`):
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
interface ITemplateContext<T, V> {
|
|
226
|
+
$implicit: T; // o item da linha
|
|
227
|
+
value: V; // item[index]
|
|
228
|
+
index: keyof T; // nome do campo
|
|
229
|
+
label: string; // label do campo
|
|
230
|
+
loading: boolean; // true enquanto a célula salva (switch de Bool)
|
|
231
|
+
readonly: boolean; // `readonly` do campo no schema
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
```html
|
|
236
|
+
<schema-table [schemaService]="produtoService">
|
|
237
|
+
<ng-template schemaTableReplacementFor="estoque" let-value="value" let-produto>
|
|
238
|
+
<span [class.text-destructive]="value === 0">
|
|
239
|
+
{{ value === 0 ? 'Esgotado' : value + ' ' + produto.unidade }}
|
|
240
|
+
</span>
|
|
241
|
+
</ng-template>
|
|
242
|
+
</schema-table>
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Colunas
|
|
246
|
+
|
|
247
|
+
As colunas são os campos de `listSchema` (se existir) ou de `schema`, na ordem em que foram declarados. Campos com
|
|
248
|
+
`hidden: true` ou `hidden: { hiddenList: true }` não viram coluna nem filtro.
|
|
249
|
+
|
|
250
|
+
O `label` vira o cabeçalho e o `tooltipInfo` mostra um ícone (?) ao lado dele.
|
|
251
|
+
|
|
252
|
+
### Formatação padrão por tipo
|
|
253
|
+
|
|
254
|
+
| Tipo | Célula |
|
|
255
|
+
| ---------------------------------------- | -------------------------------------------------------------------------- |
|
|
256
|
+
| `String`, `UUID`, `TextArea` | O texto. Vazio: `-` |
|
|
257
|
+
| `Number` | `1.234` |
|
|
258
|
+
| `Decimal` | `1.234,50` |
|
|
259
|
+
| `Currency` | `R$ 1.234,50` |
|
|
260
|
+
| `Date` / `DateTime` / `Time` | `dd/MM/yyyy` / `dd/MM/yyyy HH:mm:ss` / `HH:mm:ss` |
|
|
261
|
+
| `DateSpan` / `DateTimeSpan` / `TimeSpan` | `início - fim`, no formato acima. Ponta aberta: `…` |
|
|
262
|
+
| `Cpf`, `Cnpj`, `CpfCnpj`, `Phone` | Com máscara |
|
|
263
|
+
| `Mask` | Com a `maskitoOptions` do campo |
|
|
264
|
+
| `Bool` | Switch que **salva na hora** (ver [Bool editável](#bool-editável)) |
|
|
265
|
+
| `Icon` | O ícone |
|
|
266
|
+
| `JSON` | Botão que abre um dialog com o JSON formatado e botão de copiar |
|
|
267
|
+
| `File` | Preview e nome de cada arquivo (URL ou lista de URLs) |
|
|
268
|
+
| `Select` com `service` | O `bindLabel` do registro relacionado (ver [Select com service](#select-com-service)) |
|
|
269
|
+
| `Select` com `lstOptions` | **O valor cru** (ex.: `KG`). Use um template para mostrar o label |
|
|
270
|
+
|
|
271
|
+
### Template no schema: `listOptions.template`
|
|
272
|
+
|
|
273
|
+
Para formatar a coluna em todas as tabelas que usam o service, defina no próprio schema. Aceita:
|
|
274
|
+
|
|
275
|
+
- uma função `(item) => string`;
|
|
276
|
+
- um `TemplateRef` (recebe o mesmo contexto do `schemaTableReplacementFor`);
|
|
277
|
+
- uma classe de componente (renderizada sem inputs: não recebe o item).
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
unidade: {
|
|
281
|
+
type: FieldType.Select,
|
|
282
|
+
label: 'Unidade',
|
|
283
|
+
options: { lstOptions: UNIDADES, bindValue: 'id', bindLabel: 'nome' },
|
|
284
|
+
listOptions: {
|
|
285
|
+
template: (produto: IProduto) => UNIDADES.find((u) => u.id === produto.unidade)?.nome ?? '-',
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Prioridade
|
|
291
|
+
|
|
292
|
+
1. `schemaTableReplacementFor` no template da tabela;
|
|
293
|
+
2. `listOptions.template` do schema;
|
|
294
|
+
3. formatação padrão do tipo.
|
|
295
|
+
|
|
296
|
+
### Select com service
|
|
297
|
+
|
|
298
|
+
Para colunas `Select` com `options.service`, a tabela busca os labels de uma vez para a página toda: um
|
|
299
|
+
`POST ${endpoint do outro service}/index` com os valores da página em `lstUuids`. Se o registro não voltar, a célula
|
|
300
|
+
mostra o valor cru.
|
|
301
|
+
|
|
302
|
+
```jsonc
|
|
303
|
+
// POST /categorias/index
|
|
304
|
+
{ "lstUuids": ["3f2a…", "9b1c…"], "page": 1, "size": 2 }
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Bool editável
|
|
308
|
+
|
|
309
|
+
A célula de um `Bool` é um switch. Ao clicar, a tabela envia `PATCH ${endpoint}/${uuid}` só com aquele campo, mostra um
|
|
310
|
+
loading na célula e um toast. Em caso de erro, recarrega a página para desfazer.
|
|
311
|
+
|
|
312
|
+
```jsonc
|
|
313
|
+
// PATCH /produtos/3f2a…
|
|
314
|
+
{ "ativo": false }
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Com `readonly: true` no schema, o switch aparece desabilitado e não salva. Para mostrar texto no lugar do switch,
|
|
318
|
+
substitua a célula:
|
|
319
|
+
|
|
320
|
+
```html
|
|
321
|
+
<ng-template schemaTableReplacementFor="ativo" let-value="value">
|
|
322
|
+
{{ value ? 'Sim' : 'Não' }}
|
|
323
|
+
</ng-template>
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Busca, filtros e ordenação
|
|
327
|
+
|
|
328
|
+
- **Busca:** o texto vai em `query` (com debounce de 400 ms). `noQuery` esconde o campo.
|
|
329
|
+
- **Filtros:** um botão por coluna (campos de `listSchema` ou `schema` sem `hiddenList`). Cada um abre um popover com o
|
|
330
|
+
input do tipo do campo (debounce de 400 ms). O botão ganha um X para limpar quando há valor. Para filtrar por um campo
|
|
331
|
+
que não é coluna, use [filtros fixos](#filtros-fixos).
|
|
332
|
+
- **Ordenação:** clicar no cabeçalho alterna `ASC` → `DESC` → sem ordenação. Dá para ordenar por várias colunas: a ordem
|
|
333
|
+
dos cliques é a ordem enviada.
|
|
334
|
+
- **Resetar filtros** chama `reset()`: limpa busca, filtros, ordenação e volta à página 1.
|
|
335
|
+
|
|
336
|
+
### Filtro por tipo
|
|
337
|
+
|
|
338
|
+
| Tipo | Input do filtro | `value` enviado |
|
|
339
|
+
| ----------------------------------- | ----------------------- | ------------------------------------------------ |
|
|
340
|
+
| `String`, `TextArea` | Texto | `string` |
|
|
341
|
+
| `UUID` | Texto | `string`. Só aplica quando é um UUID válido |
|
|
342
|
+
| `Mask` | Texto com a máscara | O retorno do `unmaskHandler` (sem ele, o texto mascarado) |
|
|
343
|
+
| `Phone`, `Cpf`, `Cnpj`, `CpfCnpj` | Input com máscara | Sem máscara (ex.: `12345678901`) |
|
|
344
|
+
| `Number`, `Decimal`, `Currency` | Mínimo e máximo | `[min, max]`. Uma ponta pode ser `null` |
|
|
345
|
+
| `Date` | Intervalo de datas | `[início, fim]` em ISO |
|
|
346
|
+
| `DateTime` | Intervalo de data/hora | `[início, fim]` em ISO |
|
|
347
|
+
| `Time` | Intervalo de horas | `[início, fim]` em `HH:mm:ss` |
|
|
348
|
+
| `Bool` | Sim / Não | `true` / `false` |
|
|
349
|
+
| `Select` | O mesmo select do campo | O `bindValue` do item escolhido |
|
|
350
|
+
| `Icon` | Seletor de ícone | Nome do ícone |
|
|
351
|
+
| `JSON`, `File`, `*Span` | Nenhum | O botão aparece, mas o popover fica vazio |
|
|
352
|
+
|
|
353
|
+
### Filtros fixos
|
|
354
|
+
|
|
355
|
+
Para restringir a listagem sem o usuário poder mudar (ex.: produtos de uma categoria vinda da rota):
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
export class ProdutosDaCategoriaComponent {
|
|
359
|
+
uuidCategoria = input.required<string>(); // parâmetro da rota, com withComponentInputBinding()
|
|
360
|
+
|
|
361
|
+
protected filtrosFixos = computed<IIndexFilter<IProduto, unknown>[]>(() => [
|
|
362
|
+
{ index: 'uuidCategoria', value: this.uuidCategoria() },
|
|
363
|
+
]);
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
```html
|
|
368
|
+
<schema-table [schemaService]="produtoService" [lstPresetFilters]="filtrosFixos()" />
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Filtros fixos com valor vazio (`null`, `''`, `[]`) são ignorados. Eles vão no mesmo `lstFilter` que os do usuário: se o
|
|
372
|
+
usuário filtrar o mesmo campo, os dois são enviados.
|
|
373
|
+
|
|
374
|
+
## Permissões
|
|
375
|
+
|
|
376
|
+
`editPermissao` e `deletePermissao` recebem o nome de uma permissão, verificada pela função configurada no
|
|
377
|
+
`SchemaKitModule.forRoot`:
|
|
378
|
+
|
|
379
|
+
```ts
|
|
380
|
+
SchemaKitModule.forRoot({
|
|
381
|
+
apiUrlGetter: (env) => env.apiUrl,
|
|
382
|
+
perfilCheckerFactory: () => {
|
|
383
|
+
const auth = inject(AuthService); // roda em contexto de injeção
|
|
384
|
+
return (permissao) => auth.temPermissao(permissao);
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
```html
|
|
390
|
+
<schema-table
|
|
391
|
+
[schemaService]="produtoService"
|
|
392
|
+
[editPermissao]="'produto.editar'"
|
|
393
|
+
[deletePermissao]="'produto.excluir'"
|
|
394
|
+
/>
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
| Permissão de edição | Efeito |
|
|
398
|
+
| ------------------- | ---------------------------------------------------------------------- |
|
|
399
|
+
| permitida | Clique na linha e botão → abrem `./view/:uuid` |
|
|
400
|
+
| negada | Sem botão de editar e a linha não é clicável |
|
|
401
|
+
|
|
402
|
+
| Permissão de exclusão | Efeito |
|
|
403
|
+
| --------------------- | ----------------------------------------------------------------------------------- |
|
|
404
|
+
| permitida | Botão de lixeira: confirma com `Deseja realmente excluir "<bindLabel>"?`, envia `DELETE ${endpoint}/${uuid}` e recarrega |
|
|
405
|
+
| negada | Sem botão de excluir |
|
|
406
|
+
|
|
407
|
+
Não há input para desligar editar/excluir: para uma tabela só de leitura, passe uma permissão que o checker nega. Sem
|
|
408
|
+
`perfilCheckerFactory`, toda permissão informada é negada.
|
|
409
|
+
|
|
410
|
+
## Contrato com a API
|
|
411
|
+
|
|
412
|
+
A listagem chama `GET ${apiUrl}${endpoint}/index`. Strings vão como estão; os demais valores vão em JSON. Parâmetros
|
|
413
|
+
`null`, `undefined` e listas vazias não são enviados.
|
|
414
|
+
|
|
415
|
+
```text
|
|
416
|
+
GET /produtos/index
|
|
417
|
+
?page=2
|
|
418
|
+
&size=20
|
|
419
|
+
&query=caneta
|
|
420
|
+
&sort=[{"field":"preco","order":"DESC"},{"field":"nome","order":"ASC"}]
|
|
421
|
+
&lstFilter=[{"index":"ativo","value":true},{"index":"preco","value":[10,null]}]
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
A resposta precisa seguir `IPaginatedResult` (a tabela usa `data` e `total`):
|
|
425
|
+
|
|
426
|
+
```ts
|
|
427
|
+
interface IPaginatedResult<T> {
|
|
428
|
+
data: T[];
|
|
429
|
+
total: number; // total de registros com os filtros, para a paginação
|
|
430
|
+
page: number;
|
|
431
|
+
size: number;
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
Cada item precisa ter `uuid` (`T extends IView`): é a chave da linha e o parâmetro da edição/exclusão.
|
|
436
|
+
|
|
437
|
+
## Celular
|
|
438
|
+
|
|
439
|
+
Em telas em retrato (`portrait`), as linhas viram cards com `label: valor` de cada coluna. As ações `start` ficam no
|
|
440
|
+
topo do card e as `end` (com excluir) no rodapé. O botão de editar não aparece: o card inteiro abre a edição.
|
|
441
|
+
|
|
442
|
+
## Atenção
|
|
443
|
+
|
|
444
|
+
- Mudar filtros, busca ou ordenação não volta para a página 1: em uma página alta, o resultado pode ficar vazio.
|