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 CHANGED
@@ -1,14 +1,14 @@
1
- # 🧱 Projeto atual
1
+ # 🧱 forlogic-core - Guia de Desenvolvimento
2
2
 
3
- **Schema padrão:** `common`
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
- ### ⚠️ TOP 3 ERROS
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('schema').from('table')
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 schema.table
28
- FOR SELECT USING (id_user = auth.uid());
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 schema.table
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. **NÃO CRIAR ÍNDICES AUTOMATICAMENTE**
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 schema.table(id_user);
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
- - [ ] Se contém, o usuário solicitou EXPLICITAMENTE?
130
- - [ ] Se contém, foi feita análise de performance (EXPLAIN ANALYZE)?
131
- - [ ] Se contém, o usuário APROVOU adicionar à migration?
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
- # 🧱 Projeto atual
1
+ # 🧱 forlogic-core - Guia de Desenvolvimento
2
2
 
3
- **Schema padrão:** `common`
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
- ### ⚠️ TOP 3 ERROS
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('schema').from('table')
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 schema.table
28
- FOR SELECT USING (id_user = auth.uid());
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 schema.table
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. **NÃO CRIAR ÍNDICES AUTOMATICAMENTE**
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 schema.table(id_user);
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
- - [ ] Se contém, o usuário solicitou EXPLICITAMENTE?
130
- - [ ] Se contém, foi feita análise de performance (EXPLAIN ANALYZE)?
131
- - [ ] Se contém, o usuário APROVOU adicionar à migration?
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: