forlogic-core 1.8.1 → 1.8.3
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 +243 -20
- package/dist/README.md +243 -20
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.esm.js +2 -2
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/dist/assets/index-2WBeKMgq.css +0 -1
- package/dist/assets/index-Dg0JuUel.js +0 -696
- package/dist/index.html +0 -19
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
# 🧱
|
|
1
|
+
# 🧱 forlogic-core - Guia de Desenvolvimento
|
|
2
2
|
|
|
3
|
-
**
|
|
4
|
-
|
|
5
|
-
> **Nota sobre schemas:** Temos o schema padrão, mas módulos podem usar schemas diferentes quando necessário para organização ou isolamento de dados. Por exemplo, o módulo de treinamentos usa o schema `trainings`. Você pode especificar o schema no service usando `schemaName: 'nome_do_schema'`.
|
|
3
|
+
> **IMPORTANTE**: Este README é genérico para todos os projetos. Para configurações específicas do projeto (como schema padrão), consulte o arquivo **KNOWLEDGE.md**.
|
|
6
4
|
|
|
7
5
|
---
|
|
8
6
|
|
|
9
7
|
## 🤖 REGRAS CRÍTICAS
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
> **📋 Schema Padrão**: Veja no arquivo **KNOWLEDGE.md** qual é o schema padrão deste projeto específico.
|
|
10
|
+
|
|
11
|
+
### ⚠️ TOP 4 ERROS MAIS COMUNS
|
|
12
12
|
|
|
13
13
|
1. **ESQUECER SCHEMA**
|
|
14
14
|
|
|
@@ -16,31 +16,42 @@
|
|
|
16
16
|
// ❌ ERRADO
|
|
17
17
|
.from('table')
|
|
18
18
|
|
|
19
|
-
// ✅ CORRETO
|
|
20
|
-
.schema('
|
|
19
|
+
// ✅ CORRETO (verifique o schema padrão no KNOWLEDGE.md)
|
|
20
|
+
.schema('SEU_SCHEMA').from('table')
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
2. **RLS COM SINTAXE INCORRETA**
|
|
24
24
|
|
|
25
25
|
```sql
|
|
26
|
-
-- ❌ ERRADO
|
|
27
|
-
CREATE POLICY "Users view own" ON
|
|
28
|
-
FOR SELECT
|
|
26
|
+
-- ❌ ERRADO: Usar WITH CHECK em SELECT
|
|
27
|
+
CREATE POLICY "Users view own" ON seu_schema.table
|
|
28
|
+
FOR SELECT WITH CHECK (auth.uid() = id_user);
|
|
29
29
|
|
|
30
|
-
-- ✅ CORRETO
|
|
31
|
-
CREATE POLICY "Users view own" ON
|
|
30
|
+
-- ✅ CORRETO: USING para SELECT
|
|
31
|
+
CREATE POLICY "Users view own" ON seu_schema.table
|
|
32
32
|
FOR SELECT USING (
|
|
33
33
|
((SELECT auth.jwt()) ->> 'alias'::text) = alias
|
|
34
34
|
);
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
3. **
|
|
37
|
+
3. **CRIAR POLÍTICAS DELETE**
|
|
38
|
+
|
|
39
|
+
```sql
|
|
40
|
+
-- ❌ PROIBIDO: Política DELETE
|
|
41
|
+
CREATE POLICY "table_delete" ON seu_schema.table
|
|
42
|
+
FOR DELETE USING (...);
|
|
43
|
+
|
|
44
|
+
-- ✅ CORRETO: Use soft delete com UPDATE
|
|
45
|
+
ALTER TABLE seu_schema.table ADD COLUMN deleted_at TIMESTAMP WITH TIME ZONE;
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
4. **CRIAR ÍNDICES AUTOMATICAMENTE**
|
|
38
49
|
|
|
39
50
|
```sql
|
|
40
51
|
-- ❌ PROIBIDO criar índices sem aprovação
|
|
41
|
-
CREATE INDEX idx_table_user ON
|
|
52
|
+
CREATE INDEX idx_table_user ON seu_schema.table(id_user);
|
|
42
53
|
|
|
43
|
-
-- ✅ Apenas quando solicitado explicitamente
|
|
54
|
+
-- ✅ Apenas quando solicitado explicitamente e aprovado
|
|
44
55
|
```
|
|
45
56
|
|
|
46
57
|
---
|
|
@@ -126,9 +137,13 @@ CREATE INDEX idx_deliverable_status ON deliverables(id_subprocess, is_completed)
|
|
|
126
137
|
|
|
127
138
|
```markdown
|
|
128
139
|
- [ ] A migration NÃO contém NENHUM `CREATE INDEX`?
|
|
129
|
-
- [ ]
|
|
130
|
-
- [ ] Se contém,
|
|
131
|
-
- [ ] Se contém,
|
|
140
|
+
- [ ] A migration NÃO contém políticas DELETE?
|
|
141
|
+
- [ ] Se contém índice, o usuário solicitou EXPLICITAMENTE?
|
|
142
|
+
- [ ] Se contém índice, foi feita análise de performance (EXPLAIN ANALYZE)?
|
|
143
|
+
- [ ] Se contém índice, o usuário APROVOU adicionar à migration?
|
|
144
|
+
- [ ] Estou usando soft delete ao invés de DELETE físico?
|
|
145
|
+
- [ ] Especifiquei o schema correto em todas as tabelas?
|
|
146
|
+
- [ ] Usei sintaxe correta nas políticas RLS (USING vs WITH CHECK)?
|
|
132
147
|
```
|
|
133
148
|
|
|
134
149
|
### 🔧 Processo Correto Para Criar Índices
|
|
@@ -208,7 +223,7 @@ import { Button } from 'forlogic-core'
|
|
|
208
223
|
// Formulários
|
|
209
224
|
Button, Input, Textarea, Label, Select, SelectContent,
|
|
210
225
|
SelectItem, SelectTrigger, SelectValue, Checkbox, RadioGroup,
|
|
211
|
-
RadioGroupItem, Switch, CreatableCombobox
|
|
226
|
+
RadioGroupItem, Switch, CreatableCombobox, EntitySelect, MultiSelect
|
|
212
227
|
|
|
213
228
|
// Layout
|
|
214
229
|
Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle
|
|
@@ -462,6 +477,183 @@ export function DepartmentSelect(props: DepartmentSelectProps) {
|
|
|
462
477
|
|
|
463
478
|
---
|
|
464
479
|
|
|
480
|
+
#### MultiSelect - Seleção Múltipla Genérica
|
|
481
|
+
|
|
482
|
+
**Quando usar?**
|
|
483
|
+
|
|
484
|
+
Use o `MultiSelect` para permitir seleção múltipla de itens com busca inteligente e exibição em badges:
|
|
485
|
+
|
|
486
|
+
- ✅ Seleção de múltiplos processos, colaboradores, treinamentos, etc
|
|
487
|
+
- ✅ Busca com normalização (remove acentos, case-insensitive)
|
|
488
|
+
- ✅ Badges interativos para remover seleções
|
|
489
|
+
- ✅ Estados de loading e erro inclusos
|
|
490
|
+
- ✅ Limite de badges exibidos com contador (+N)
|
|
491
|
+
|
|
492
|
+
**Uso Básico:**
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
import { MultiSelect } from 'forlogic-core';
|
|
496
|
+
import { useTrainings } from './trainingService';
|
|
497
|
+
|
|
498
|
+
function MyForm() {
|
|
499
|
+
const { data: trainings = [], isLoading, error } = useTrainings();
|
|
500
|
+
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
|
501
|
+
|
|
502
|
+
return (
|
|
503
|
+
<MultiSelect
|
|
504
|
+
value={selectedIds}
|
|
505
|
+
onChange={setSelectedIds}
|
|
506
|
+
options={trainings}
|
|
507
|
+
isLoading={isLoading}
|
|
508
|
+
error={error}
|
|
509
|
+
getOptionValue={(training) => training.id}
|
|
510
|
+
getOptionLabel={(training) => training.title}
|
|
511
|
+
placeholder="Selecione treinamentos..."
|
|
512
|
+
label="Treinamentos"
|
|
513
|
+
icon={GraduationCap}
|
|
514
|
+
/>
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
**Props do MultiSelect:**
|
|
520
|
+
|
|
521
|
+
| Prop | Tipo | Obrigatório | Descrição |
|
|
522
|
+
| ---- | ---- | ----------- | --------- |
|
|
523
|
+
| `options` | `T[]` | ✅ | Array de itens disponíveis |
|
|
524
|
+
| `value` | `string[]` | ❌ | Array de IDs selecionados |
|
|
525
|
+
| `onChange` | `(value: string[]) => void` | ❌ | Callback quando seleção muda |
|
|
526
|
+
| `getOptionValue` | `(option: T) => string` | ❌ | Extrai valor único (default: `opt.value`) |
|
|
527
|
+
| `getOptionLabel` | `(option: T) => string` | ❌ | Extrai label exibido (default: `opt.label`) |
|
|
528
|
+
| `placeholder` | `string` | ❌ | Placeholder quando nada selecionado |
|
|
529
|
+
| `label` | `string` | ❌ | Label do campo |
|
|
530
|
+
| `icon` | `LucideIcon` | ❌ | Ícone exibido no trigger |
|
|
531
|
+
| `emptyMessage` | `string` | ❌ | Mensagem quando não há itens |
|
|
532
|
+
| `searchPlaceholder` | `string` | ❌ | Placeholder da busca |
|
|
533
|
+
| `disabled` | `boolean` | ❌ | Desabilita o componente |
|
|
534
|
+
| `required` | `boolean` | ❌ | Adiciona asterisco vermelho ao label |
|
|
535
|
+
| `isLoading` | `boolean` | ❌ | Exibe skeleton durante carregamento |
|
|
536
|
+
| `error` | `string \| boolean` | ❌ | Mensagem de erro |
|
|
537
|
+
| `className` | `string` | ❌ | Classes CSS adicionais |
|
|
538
|
+
| `sortOptions` | `boolean` | ❌ | Ordena alfabeticamente (default: true) |
|
|
539
|
+
| `maxDisplayedBadges` | `number` | ❌ | Limite de badges visíveis |
|
|
540
|
+
| `onOpen` | `() => void` | ❌ | Callback ao abrir popover |
|
|
541
|
+
| `onClose` | `() => void` | ❌ | Callback ao fechar popover |
|
|
542
|
+
|
|
543
|
+
**Exemplo com Objetos Customizados:**
|
|
544
|
+
|
|
545
|
+
```typescript
|
|
546
|
+
interface User {
|
|
547
|
+
id: string;
|
|
548
|
+
name: string;
|
|
549
|
+
email: string;
|
|
550
|
+
department: string;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
<MultiSelect<User>
|
|
554
|
+
options={users}
|
|
555
|
+
value={selectedUserIds}
|
|
556
|
+
onChange={setSelectedUserIds}
|
|
557
|
+
getOptionValue={(user) => user.id}
|
|
558
|
+
getOptionLabel={(user) => `${user.name} - ${user.department}`}
|
|
559
|
+
placeholder="Selecione colaboradores..."
|
|
560
|
+
label="Colaboradores"
|
|
561
|
+
icon={Users}
|
|
562
|
+
required
|
|
563
|
+
/>
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
**Limite de Badges Exibidos:**
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
<MultiSelect
|
|
570
|
+
options={items}
|
|
571
|
+
value={selected}
|
|
572
|
+
onChange={setSelected}
|
|
573
|
+
maxDisplayedBadges={3} // Exibe: [Badge 1] [Badge 2] [Badge 3] [+5]
|
|
574
|
+
placeholder="Selecione itens..."
|
|
575
|
+
/>
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
**Integração com React Hook Form:**
|
|
579
|
+
|
|
580
|
+
```typescript
|
|
581
|
+
import { useForm } from 'react-hook-form';
|
|
582
|
+
import { MultiSelect } from 'forlogic-core';
|
|
583
|
+
|
|
584
|
+
function MyForm() {
|
|
585
|
+
const { control } = useForm();
|
|
586
|
+
const { data: categories = [] } = useCategories();
|
|
587
|
+
|
|
588
|
+
return (
|
|
589
|
+
<FormField
|
|
590
|
+
control={control}
|
|
591
|
+
name="category_ids"
|
|
592
|
+
render={({ field }) => (
|
|
593
|
+
<FormItem>
|
|
594
|
+
<FormControl>
|
|
595
|
+
<MultiSelect
|
|
596
|
+
value={field.value}
|
|
597
|
+
onChange={field.onChange}
|
|
598
|
+
options={categories}
|
|
599
|
+
getOptionValue={(c) => c.id}
|
|
600
|
+
getOptionLabel={(c) => c.name}
|
|
601
|
+
placeholder="Selecione categorias..."
|
|
602
|
+
label="Categorias"
|
|
603
|
+
/>
|
|
604
|
+
</FormControl>
|
|
605
|
+
<FormMessage />
|
|
606
|
+
</FormItem>
|
|
607
|
+
)}
|
|
608
|
+
/>
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
**Usando em Formulários CRUD:**
|
|
614
|
+
|
|
615
|
+
Para usar o `MultiSelect` em formulários CRUD gerados automaticamente, crie um wrapper e use `type: 'custom'`:
|
|
616
|
+
|
|
617
|
+
```typescript
|
|
618
|
+
// 1. Criar wrapper do MultiSelect
|
|
619
|
+
// src/components/TrainingMultiSelect.tsx
|
|
620
|
+
import { MultiSelect } from 'forlogic-core';
|
|
621
|
+
import { useTrainings } from '@/trainings/trainingService';
|
|
622
|
+
import { GraduationCap } from 'lucide-react';
|
|
623
|
+
|
|
624
|
+
export function TrainingMultiSelect({ value, onChange, disabled, error }: any) {
|
|
625
|
+
const { data: trainings = [], isLoading } = useTrainings();
|
|
626
|
+
|
|
627
|
+
return (
|
|
628
|
+
<MultiSelect
|
|
629
|
+
value={value || []}
|
|
630
|
+
onChange={onChange}
|
|
631
|
+
options={trainings}
|
|
632
|
+
isLoading={isLoading}
|
|
633
|
+
error={error}
|
|
634
|
+
getOptionValue={(t) => t.id}
|
|
635
|
+
getOptionLabel={(t) => t.title}
|
|
636
|
+
disabled={disabled}
|
|
637
|
+
placeholder="Selecione treinamentos..."
|
|
638
|
+
icon={GraduationCap}
|
|
639
|
+
/>
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// 2. Usar na configuração do formulário com type: 'custom'
|
|
644
|
+
import { TrainingMultiSelect } from './components/TrainingMultiSelect';
|
|
645
|
+
|
|
646
|
+
{
|
|
647
|
+
name: 'training_ids',
|
|
648
|
+
label: 'Treinamentos',
|
|
649
|
+
type: 'custom' as const,
|
|
650
|
+
component: TrainingMultiSelect,
|
|
651
|
+
required: true
|
|
652
|
+
}
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
---
|
|
656
|
+
|
|
465
657
|
### 🎨 Campos Customizados no BaseForm
|
|
466
658
|
|
|
467
659
|
O `BaseForm` suporta campos totalmente customizados através do tipo `'custom'`, permitindo usar **qualquer componente React** como campo de formulário.
|
|
@@ -3810,11 +4002,42 @@ const config = useMemo((): CrudPageConfig<Process> => ({
|
|
|
3810
4002
|
|
|
3811
4003
|
Quando `enableBulkActions: true`, as seguintes ações ficam disponíveis automaticamente:
|
|
3812
4004
|
|
|
3813
|
-
- ✅ **Deletar em lote**: Deleta múltiplos itens selecionados (usa a mesma função do delete individual)
|
|
3814
4005
|
- ✅ **Checkbox "Selecionar Todos"**: No header da tabela para selecionar/deselecionar todos os itens da página
|
|
3815
4006
|
- ✅ **Indicador Visual**: Linhas/cards selecionados têm background destacado
|
|
3816
4007
|
- ✅ **Barra de Ações**: Aparece no topo quando há itens selecionados
|
|
3817
4008
|
|
|
4009
|
+
**⚠️ IMPORTANTE - Botão "Remover" Padrão:**
|
|
4010
|
+
|
|
4011
|
+
- ❌ **Se você definir `bulkActions` customizadas**, o botão "Remover" padrão **NÃO** será adicionado automaticamente
|
|
4012
|
+
- ✅ **Se você NÃO definir `bulkActions`**, um botão "Remover" padrão será adicionado automaticamente
|
|
4013
|
+
|
|
4014
|
+
```typescript
|
|
4015
|
+
// ❌ Duplicação: Botão padrão + custom "Remover"
|
|
4016
|
+
bulkActions: [
|
|
4017
|
+
{
|
|
4018
|
+
label: 'Remover', // ← Vai aparecer 2x (padrão + este)
|
|
4019
|
+
icon: Trash2,
|
|
4020
|
+
variant: 'destructive',
|
|
4021
|
+
action: async (items) => { /* ... */ }
|
|
4022
|
+
}
|
|
4023
|
+
]
|
|
4024
|
+
|
|
4025
|
+
// ✅ Correto: Apenas o padrão
|
|
4026
|
+
enableBulkActions: true,
|
|
4027
|
+
// Não definir bulkActions → Usa botão "Remover" padrão
|
|
4028
|
+
|
|
4029
|
+
// ✅ Correto: Apenas ações customizadas (sem padrão)
|
|
4030
|
+
bulkActions: [
|
|
4031
|
+
{
|
|
4032
|
+
label: 'Remover',
|
|
4033
|
+
icon: Trash2,
|
|
4034
|
+
variant: 'destructive',
|
|
4035
|
+
action: async (items) => { /* ... */ }
|
|
4036
|
+
}
|
|
4037
|
+
]
|
|
4038
|
+
```
|
|
4039
|
+
|
|
4040
|
+
|
|
3818
4041
|
### **Ações Customizadas**
|
|
3819
4042
|
|
|
3820
4043
|
Você pode adicionar ações customizadas além do delete padrão:
|
package/dist/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
# 🧱
|
|
1
|
+
# 🧱 forlogic-core - Guia de Desenvolvimento
|
|
2
2
|
|
|
3
|
-
**
|
|
4
|
-
|
|
5
|
-
> **Nota sobre schemas:** Temos o schema padrão, mas módulos podem usar schemas diferentes quando necessário para organização ou isolamento de dados. Por exemplo, o módulo de treinamentos usa o schema `trainings`. Você pode especificar o schema no service usando `schemaName: 'nome_do_schema'`.
|
|
3
|
+
> **IMPORTANTE**: Este README é genérico para todos os projetos. Para configurações específicas do projeto (como schema padrão), consulte o arquivo **KNOWLEDGE.md**.
|
|
6
4
|
|
|
7
5
|
---
|
|
8
6
|
|
|
9
7
|
## 🤖 REGRAS CRÍTICAS
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
> **📋 Schema Padrão**: Veja no arquivo **KNOWLEDGE.md** qual é o schema padrão deste projeto específico.
|
|
10
|
+
|
|
11
|
+
### ⚠️ TOP 4 ERROS MAIS COMUNS
|
|
12
12
|
|
|
13
13
|
1. **ESQUECER SCHEMA**
|
|
14
14
|
|
|
@@ -16,31 +16,42 @@
|
|
|
16
16
|
// ❌ ERRADO
|
|
17
17
|
.from('table')
|
|
18
18
|
|
|
19
|
-
// ✅ CORRETO
|
|
20
|
-
.schema('
|
|
19
|
+
// ✅ CORRETO (verifique o schema padrão no KNOWLEDGE.md)
|
|
20
|
+
.schema('SEU_SCHEMA').from('table')
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
2. **RLS COM SINTAXE INCORRETA**
|
|
24
24
|
|
|
25
25
|
```sql
|
|
26
|
-
-- ❌ ERRADO
|
|
27
|
-
CREATE POLICY "Users view own" ON
|
|
28
|
-
FOR SELECT
|
|
26
|
+
-- ❌ ERRADO: Usar WITH CHECK em SELECT
|
|
27
|
+
CREATE POLICY "Users view own" ON seu_schema.table
|
|
28
|
+
FOR SELECT WITH CHECK (auth.uid() = id_user);
|
|
29
29
|
|
|
30
|
-
-- ✅ CORRETO
|
|
31
|
-
CREATE POLICY "Users view own" ON
|
|
30
|
+
-- ✅ CORRETO: USING para SELECT
|
|
31
|
+
CREATE POLICY "Users view own" ON seu_schema.table
|
|
32
32
|
FOR SELECT USING (
|
|
33
33
|
((SELECT auth.jwt()) ->> 'alias'::text) = alias
|
|
34
34
|
);
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
3. **
|
|
37
|
+
3. **CRIAR POLÍTICAS DELETE**
|
|
38
|
+
|
|
39
|
+
```sql
|
|
40
|
+
-- ❌ PROIBIDO: Política DELETE
|
|
41
|
+
CREATE POLICY "table_delete" ON seu_schema.table
|
|
42
|
+
FOR DELETE USING (...);
|
|
43
|
+
|
|
44
|
+
-- ✅ CORRETO: Use soft delete com UPDATE
|
|
45
|
+
ALTER TABLE seu_schema.table ADD COLUMN deleted_at TIMESTAMP WITH TIME ZONE;
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
4. **CRIAR ÍNDICES AUTOMATICAMENTE**
|
|
38
49
|
|
|
39
50
|
```sql
|
|
40
51
|
-- ❌ PROIBIDO criar índices sem aprovação
|
|
41
|
-
CREATE INDEX idx_table_user ON
|
|
52
|
+
CREATE INDEX idx_table_user ON seu_schema.table(id_user);
|
|
42
53
|
|
|
43
|
-
-- ✅ Apenas quando solicitado explicitamente
|
|
54
|
+
-- ✅ Apenas quando solicitado explicitamente e aprovado
|
|
44
55
|
```
|
|
45
56
|
|
|
46
57
|
---
|
|
@@ -126,9 +137,13 @@ CREATE INDEX idx_deliverable_status ON deliverables(id_subprocess, is_completed)
|
|
|
126
137
|
|
|
127
138
|
```markdown
|
|
128
139
|
- [ ] A migration NÃO contém NENHUM `CREATE INDEX`?
|
|
129
|
-
- [ ]
|
|
130
|
-
- [ ] Se contém,
|
|
131
|
-
- [ ] Se contém,
|
|
140
|
+
- [ ] A migration NÃO contém políticas DELETE?
|
|
141
|
+
- [ ] Se contém índice, o usuário solicitou EXPLICITAMENTE?
|
|
142
|
+
- [ ] Se contém índice, foi feita análise de performance (EXPLAIN ANALYZE)?
|
|
143
|
+
- [ ] Se contém índice, o usuário APROVOU adicionar à migration?
|
|
144
|
+
- [ ] Estou usando soft delete ao invés de DELETE físico?
|
|
145
|
+
- [ ] Especifiquei o schema correto em todas as tabelas?
|
|
146
|
+
- [ ] Usei sintaxe correta nas políticas RLS (USING vs WITH CHECK)?
|
|
132
147
|
```
|
|
133
148
|
|
|
134
149
|
### 🔧 Processo Correto Para Criar Índices
|
|
@@ -208,7 +223,7 @@ import { Button } from 'forlogic-core'
|
|
|
208
223
|
// Formulários
|
|
209
224
|
Button, Input, Textarea, Label, Select, SelectContent,
|
|
210
225
|
SelectItem, SelectTrigger, SelectValue, Checkbox, RadioGroup,
|
|
211
|
-
RadioGroupItem, Switch, CreatableCombobox
|
|
226
|
+
RadioGroupItem, Switch, CreatableCombobox, EntitySelect, MultiSelect
|
|
212
227
|
|
|
213
228
|
// Layout
|
|
214
229
|
Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle
|
|
@@ -462,6 +477,183 @@ export function DepartmentSelect(props: DepartmentSelectProps) {
|
|
|
462
477
|
|
|
463
478
|
---
|
|
464
479
|
|
|
480
|
+
#### MultiSelect - Seleção Múltipla Genérica
|
|
481
|
+
|
|
482
|
+
**Quando usar?**
|
|
483
|
+
|
|
484
|
+
Use o `MultiSelect` para permitir seleção múltipla de itens com busca inteligente e exibição em badges:
|
|
485
|
+
|
|
486
|
+
- ✅ Seleção de múltiplos processos, colaboradores, treinamentos, etc
|
|
487
|
+
- ✅ Busca com normalização (remove acentos, case-insensitive)
|
|
488
|
+
- ✅ Badges interativos para remover seleções
|
|
489
|
+
- ✅ Estados de loading e erro inclusos
|
|
490
|
+
- ✅ Limite de badges exibidos com contador (+N)
|
|
491
|
+
|
|
492
|
+
**Uso Básico:**
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
import { MultiSelect } from 'forlogic-core';
|
|
496
|
+
import { useTrainings } from './trainingService';
|
|
497
|
+
|
|
498
|
+
function MyForm() {
|
|
499
|
+
const { data: trainings = [], isLoading, error } = useTrainings();
|
|
500
|
+
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
|
501
|
+
|
|
502
|
+
return (
|
|
503
|
+
<MultiSelect
|
|
504
|
+
value={selectedIds}
|
|
505
|
+
onChange={setSelectedIds}
|
|
506
|
+
options={trainings}
|
|
507
|
+
isLoading={isLoading}
|
|
508
|
+
error={error}
|
|
509
|
+
getOptionValue={(training) => training.id}
|
|
510
|
+
getOptionLabel={(training) => training.title}
|
|
511
|
+
placeholder="Selecione treinamentos..."
|
|
512
|
+
label="Treinamentos"
|
|
513
|
+
icon={GraduationCap}
|
|
514
|
+
/>
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
**Props do MultiSelect:**
|
|
520
|
+
|
|
521
|
+
| Prop | Tipo | Obrigatório | Descrição |
|
|
522
|
+
| ---- | ---- | ----------- | --------- |
|
|
523
|
+
| `options` | `T[]` | ✅ | Array de itens disponíveis |
|
|
524
|
+
| `value` | `string[]` | ❌ | Array de IDs selecionados |
|
|
525
|
+
| `onChange` | `(value: string[]) => void` | ❌ | Callback quando seleção muda |
|
|
526
|
+
| `getOptionValue` | `(option: T) => string` | ❌ | Extrai valor único (default: `opt.value`) |
|
|
527
|
+
| `getOptionLabel` | `(option: T) => string` | ❌ | Extrai label exibido (default: `opt.label`) |
|
|
528
|
+
| `placeholder` | `string` | ❌ | Placeholder quando nada selecionado |
|
|
529
|
+
| `label` | `string` | ❌ | Label do campo |
|
|
530
|
+
| `icon` | `LucideIcon` | ❌ | Ícone exibido no trigger |
|
|
531
|
+
| `emptyMessage` | `string` | ❌ | Mensagem quando não há itens |
|
|
532
|
+
| `searchPlaceholder` | `string` | ❌ | Placeholder da busca |
|
|
533
|
+
| `disabled` | `boolean` | ❌ | Desabilita o componente |
|
|
534
|
+
| `required` | `boolean` | ❌ | Adiciona asterisco vermelho ao label |
|
|
535
|
+
| `isLoading` | `boolean` | ❌ | Exibe skeleton durante carregamento |
|
|
536
|
+
| `error` | `string \| boolean` | ❌ | Mensagem de erro |
|
|
537
|
+
| `className` | `string` | ❌ | Classes CSS adicionais |
|
|
538
|
+
| `sortOptions` | `boolean` | ❌ | Ordena alfabeticamente (default: true) |
|
|
539
|
+
| `maxDisplayedBadges` | `number` | ❌ | Limite de badges visíveis |
|
|
540
|
+
| `onOpen` | `() => void` | ❌ | Callback ao abrir popover |
|
|
541
|
+
| `onClose` | `() => void` | ❌ | Callback ao fechar popover |
|
|
542
|
+
|
|
543
|
+
**Exemplo com Objetos Customizados:**
|
|
544
|
+
|
|
545
|
+
```typescript
|
|
546
|
+
interface User {
|
|
547
|
+
id: string;
|
|
548
|
+
name: string;
|
|
549
|
+
email: string;
|
|
550
|
+
department: string;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
<MultiSelect<User>
|
|
554
|
+
options={users}
|
|
555
|
+
value={selectedUserIds}
|
|
556
|
+
onChange={setSelectedUserIds}
|
|
557
|
+
getOptionValue={(user) => user.id}
|
|
558
|
+
getOptionLabel={(user) => `${user.name} - ${user.department}`}
|
|
559
|
+
placeholder="Selecione colaboradores..."
|
|
560
|
+
label="Colaboradores"
|
|
561
|
+
icon={Users}
|
|
562
|
+
required
|
|
563
|
+
/>
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
**Limite de Badges Exibidos:**
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
<MultiSelect
|
|
570
|
+
options={items}
|
|
571
|
+
value={selected}
|
|
572
|
+
onChange={setSelected}
|
|
573
|
+
maxDisplayedBadges={3} // Exibe: [Badge 1] [Badge 2] [Badge 3] [+5]
|
|
574
|
+
placeholder="Selecione itens..."
|
|
575
|
+
/>
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
**Integração com React Hook Form:**
|
|
579
|
+
|
|
580
|
+
```typescript
|
|
581
|
+
import { useForm } from 'react-hook-form';
|
|
582
|
+
import { MultiSelect } from 'forlogic-core';
|
|
583
|
+
|
|
584
|
+
function MyForm() {
|
|
585
|
+
const { control } = useForm();
|
|
586
|
+
const { data: categories = [] } = useCategories();
|
|
587
|
+
|
|
588
|
+
return (
|
|
589
|
+
<FormField
|
|
590
|
+
control={control}
|
|
591
|
+
name="category_ids"
|
|
592
|
+
render={({ field }) => (
|
|
593
|
+
<FormItem>
|
|
594
|
+
<FormControl>
|
|
595
|
+
<MultiSelect
|
|
596
|
+
value={field.value}
|
|
597
|
+
onChange={field.onChange}
|
|
598
|
+
options={categories}
|
|
599
|
+
getOptionValue={(c) => c.id}
|
|
600
|
+
getOptionLabel={(c) => c.name}
|
|
601
|
+
placeholder="Selecione categorias..."
|
|
602
|
+
label="Categorias"
|
|
603
|
+
/>
|
|
604
|
+
</FormControl>
|
|
605
|
+
<FormMessage />
|
|
606
|
+
</FormItem>
|
|
607
|
+
)}
|
|
608
|
+
/>
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
**Usando em Formulários CRUD:**
|
|
614
|
+
|
|
615
|
+
Para usar o `MultiSelect` em formulários CRUD gerados automaticamente, crie um wrapper e use `type: 'custom'`:
|
|
616
|
+
|
|
617
|
+
```typescript
|
|
618
|
+
// 1. Criar wrapper do MultiSelect
|
|
619
|
+
// src/components/TrainingMultiSelect.tsx
|
|
620
|
+
import { MultiSelect } from 'forlogic-core';
|
|
621
|
+
import { useTrainings } from '@/trainings/trainingService';
|
|
622
|
+
import { GraduationCap } from 'lucide-react';
|
|
623
|
+
|
|
624
|
+
export function TrainingMultiSelect({ value, onChange, disabled, error }: any) {
|
|
625
|
+
const { data: trainings = [], isLoading } = useTrainings();
|
|
626
|
+
|
|
627
|
+
return (
|
|
628
|
+
<MultiSelect
|
|
629
|
+
value={value || []}
|
|
630
|
+
onChange={onChange}
|
|
631
|
+
options={trainings}
|
|
632
|
+
isLoading={isLoading}
|
|
633
|
+
error={error}
|
|
634
|
+
getOptionValue={(t) => t.id}
|
|
635
|
+
getOptionLabel={(t) => t.title}
|
|
636
|
+
disabled={disabled}
|
|
637
|
+
placeholder="Selecione treinamentos..."
|
|
638
|
+
icon={GraduationCap}
|
|
639
|
+
/>
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// 2. Usar na configuração do formulário com type: 'custom'
|
|
644
|
+
import { TrainingMultiSelect } from './components/TrainingMultiSelect';
|
|
645
|
+
|
|
646
|
+
{
|
|
647
|
+
name: 'training_ids',
|
|
648
|
+
label: 'Treinamentos',
|
|
649
|
+
type: 'custom' as const,
|
|
650
|
+
component: TrainingMultiSelect,
|
|
651
|
+
required: true
|
|
652
|
+
}
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
---
|
|
656
|
+
|
|
465
657
|
### 🎨 Campos Customizados no BaseForm
|
|
466
658
|
|
|
467
659
|
O `BaseForm` suporta campos totalmente customizados através do tipo `'custom'`, permitindo usar **qualquer componente React** como campo de formulário.
|
|
@@ -3810,11 +4002,42 @@ const config = useMemo((): CrudPageConfig<Process> => ({
|
|
|
3810
4002
|
|
|
3811
4003
|
Quando `enableBulkActions: true`, as seguintes ações ficam disponíveis automaticamente:
|
|
3812
4004
|
|
|
3813
|
-
- ✅ **Deletar em lote**: Deleta múltiplos itens selecionados (usa a mesma função do delete individual)
|
|
3814
4005
|
- ✅ **Checkbox "Selecionar Todos"**: No header da tabela para selecionar/deselecionar todos os itens da página
|
|
3815
4006
|
- ✅ **Indicador Visual**: Linhas/cards selecionados têm background destacado
|
|
3816
4007
|
- ✅ **Barra de Ações**: Aparece no topo quando há itens selecionados
|
|
3817
4008
|
|
|
4009
|
+
**⚠️ IMPORTANTE - Botão "Remover" Padrão:**
|
|
4010
|
+
|
|
4011
|
+
- ❌ **Se você definir `bulkActions` customizadas**, o botão "Remover" padrão **NÃO** será adicionado automaticamente
|
|
4012
|
+
- ✅ **Se você NÃO definir `bulkActions`**, um botão "Remover" padrão será adicionado automaticamente
|
|
4013
|
+
|
|
4014
|
+
```typescript
|
|
4015
|
+
// ❌ Duplicação: Botão padrão + custom "Remover"
|
|
4016
|
+
bulkActions: [
|
|
4017
|
+
{
|
|
4018
|
+
label: 'Remover', // ← Vai aparecer 2x (padrão + este)
|
|
4019
|
+
icon: Trash2,
|
|
4020
|
+
variant: 'destructive',
|
|
4021
|
+
action: async (items) => { /* ... */ }
|
|
4022
|
+
}
|
|
4023
|
+
]
|
|
4024
|
+
|
|
4025
|
+
// ✅ Correto: Apenas o padrão
|
|
4026
|
+
enableBulkActions: true,
|
|
4027
|
+
// Não definir bulkActions → Usa botão "Remover" padrão
|
|
4028
|
+
|
|
4029
|
+
// ✅ Correto: Apenas ações customizadas (sem padrão)
|
|
4030
|
+
bulkActions: [
|
|
4031
|
+
{
|
|
4032
|
+
label: 'Remover',
|
|
4033
|
+
icon: Trash2,
|
|
4034
|
+
variant: 'destructive',
|
|
4035
|
+
action: async (items) => { /* ... */ }
|
|
4036
|
+
}
|
|
4037
|
+
]
|
|
4038
|
+
```
|
|
4039
|
+
|
|
4040
|
+
|
|
3818
4041
|
### **Ações Customizadas**
|
|
3819
4042
|
|
|
3820
4043
|
Você pode adicionar ações customizadas além do delete padrão:
|