From c9e34af619bdbba02d93ab77e983eaac18b7ab15 Mon Sep 17 00:00:00 2001 From: NANDO9322 Date: Tue, 10 Feb 2026 19:24:28 -0300 Subject: [PATCH] feat:(agenda) implementado filtro e busca de fot no cadastro de evento --- frontend/components/EventForm.tsx | 53 ++++---- frontend/components/SearchableSelect.tsx | 160 +++++++++++++++++++++++ 2 files changed, 183 insertions(+), 30 deletions(-) create mode 100644 frontend/components/SearchableSelect.tsx diff --git a/frontend/components/EventForm.tsx b/frontend/components/EventForm.tsx index 7c7b9d0..4050d90 100644 --- a/frontend/components/EventForm.tsx +++ b/frontend/components/EventForm.tsx @@ -27,6 +27,7 @@ import { UserRole } from "../types"; import { InstitutionForm } from "./InstitutionForm"; import { MapboxMap } from "./MapboxMap"; import { getEventTypes, EventTypeResponse, getCadastroFot, createAgenda, getCompanies } from "../services/apiService"; +import { SearchableSelect, SearchableSelectOption } from "./SearchableSelect"; interface EventFormProps { onCancel: () => void; @@ -855,39 +856,31 @@ export const EventForm: React.FC = ({ {/* Consolidated Turma Selection */}
- - + options={availableFots.map(f => ({ + value: f.id, + label: `${f.curso_nome} - ${f.instituicao} - ${f.ano_formatura_label} (FOT ${f.fot})${f.finalizada ? " (FINALIZADA)" : ""}`, + disabled: f.finalizada, + className: f.finalizada ? "text-red-600 bg-red-50 font-bold" : "", + style: f.finalizada ? { color: 'red', fontWeight: 'bold' } : undefined + }))} + /> + {loadingFots &&

Carregando turmas...

}
diff --git a/frontend/components/SearchableSelect.tsx b/frontend/components/SearchableSelect.tsx new file mode 100644 index 0000000..882ae17 --- /dev/null +++ b/frontend/components/SearchableSelect.tsx @@ -0,0 +1,160 @@ +import React, { useState, useRef, useEffect, useMemo } from "react"; +import { ChevronDown, Search, X } from "lucide-react"; + +export interface SearchableSelectOption { + value: string; + label: string; + disabled?: boolean; + className?: string; // For custom styling like red text + style?: React.CSSProperties; // For custom inline styles +} + +interface SearchableSelectProps { + label?: string; + placeholder?: string; + options: SearchableSelectOption[]; + value: string; + onChange: (value: string) => void; + disabled?: boolean; + className?: string; + error?: string; + required?: boolean; +} + +export const SearchableSelect: React.FC = ({ + label, + placeholder = "Selecione...", + options, + value, + onChange, + disabled = false, + className = "", + error, + required = false, +}) => { + const [isOpen, setIsOpen] = useState(false); + const [searchTerm, setSearchTerm] = useState(""); + const containerRef = useRef(null); + const searchInputRef = useRef(null); + + // Close when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (containerRef.current && !containerRef.current.contains(event.target as Node)) { + setIsOpen(false); + } + }; + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + // Focus search input when opening + useEffect(() => { + if (isOpen && searchInputRef.current) { + setTimeout(() => { + searchInputRef.current?.focus(); + }, 50); + } + }, [isOpen]); + + // Handle Selection + const handleSelect = (optionValue: string) => { + onChange(optionValue); + setIsOpen(false); + setSearchTerm(""); + }; + + const selectedOption = options.find((opt) => opt.value === value); + + // Filter options + const filteredOptions = useMemo(() => { + if (!searchTerm) return options; + const lowerTerm = searchTerm.toLowerCase(); + return options.filter((opt) => opt.label.toLowerCase().includes(lowerTerm)); + }, [options, searchTerm]); + + return ( +
+ {label && ( + + )} + +
+ {/* Trigger Button */} + + + {/* Dropdown Menu */} + {isOpen && ( +
+ {/* Search Input */} +
+
+ + setSearchTerm(e.target.value)} + /> + {searchTerm && ( + + )} +
+
+ + {/* Options List */} +
+ {filteredOptions.length > 0 ? ( + filteredOptions.map((opt) => ( + + )) + ) : ( +
+ Nenhum resultado encontrado +
+ )} +
+
+ )} +
+ + {error && {error}} +
+ ); +};