forlogic-core 1.8.2 → 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
@@ -223,7 +223,7 @@ import { Button } from 'forlogic-core'
223
223
  // Formulários
224
224
  Button, Input, Textarea, Label, Select, SelectContent,
225
225
  SelectItem, SelectTrigger, SelectValue, Checkbox, RadioGroup,
226
- RadioGroupItem, Switch, CreatableCombobox
226
+ RadioGroupItem, Switch, CreatableCombobox, EntitySelect, MultiSelect
227
227
 
228
228
  // Layout
229
229
  Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle
@@ -477,6 +477,183 @@ export function DepartmentSelect(props: DepartmentSelectProps) {
477
477
 
478
478
  ---
479
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
+
480
657
  ### 🎨 Campos Customizados no BaseForm
481
658
 
482
659
  O `BaseForm` suporta campos totalmente customizados através do tipo `'custom'`, permitindo usar **qualquer componente React** como campo de formulário.
@@ -3825,11 +4002,42 @@ const config = useMemo((): CrudPageConfig<Process> => ({
3825
4002
 
3826
4003
  Quando `enableBulkActions: true`, as seguintes ações ficam disponíveis automaticamente:
3827
4004
 
3828
- - ✅ **Deletar em lote**: Deleta múltiplos itens selecionados (usa a mesma função do delete individual)
3829
4005
  - ✅ **Checkbox "Selecionar Todos"**: No header da tabela para selecionar/deselecionar todos os itens da página
3830
4006
  - ✅ **Indicador Visual**: Linhas/cards selecionados têm background destacado
3831
4007
  - ✅ **Barra de Ações**: Aparece no topo quando há itens selecionados
3832
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
+
3833
4041
  ### **Ações Customizadas**
3834
4042
 
3835
4043
  Você pode adicionar ações customizadas além do delete padrão:
package/dist/README.md CHANGED
@@ -223,7 +223,7 @@ import { Button } from 'forlogic-core'
223
223
  // Formulários
224
224
  Button, Input, Textarea, Label, Select, SelectContent,
225
225
  SelectItem, SelectTrigger, SelectValue, Checkbox, RadioGroup,
226
- RadioGroupItem, Switch, CreatableCombobox
226
+ RadioGroupItem, Switch, CreatableCombobox, EntitySelect, MultiSelect
227
227
 
228
228
  // Layout
229
229
  Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle
@@ -477,6 +477,183 @@ export function DepartmentSelect(props: DepartmentSelectProps) {
477
477
 
478
478
  ---
479
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
+
480
657
  ### 🎨 Campos Customizados no BaseForm
481
658
 
482
659
  O `BaseForm` suporta campos totalmente customizados através do tipo `'custom'`, permitindo usar **qualquer componente React** como campo de formulário.
@@ -3825,11 +4002,42 @@ const config = useMemo((): CrudPageConfig<Process> => ({
3825
4002
 
3826
4003
  Quando `enableBulkActions: true`, as seguintes ações ficam disponíveis automaticamente:
3827
4004
 
3828
- - ✅ **Deletar em lote**: Deleta múltiplos itens selecionados (usa a mesma função do delete individual)
3829
4005
  - ✅ **Checkbox "Selecionar Todos"**: No header da tabela para selecionar/deselecionar todos os itens da página
3830
4006
  - ✅ **Indicador Visual**: Linhas/cards selecionados têm background destacado
3831
4007
  - ✅ **Barra de Ações**: Aparece no topo quando há itens selecionados
3832
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
+
3833
4041
  ### **Ações Customizadas**
3834
4042
 
3835
4043
  Você pode adicionar ações customizadas além do delete padrão: