import React, { useState, useEffect } from "react"; import { X, User, Camera, Eye, EyeOff, } from "lucide-react"; import { Button } from "./Button"; import { createProfessional, updateProfessional, getUploadURL, uploadFileToSignedUrl, } from "../services/apiService"; import { useAuth } from "../contexts/AuthContext"; import { Professional, CreateProfessionalDTO } from "../types"; interface ProfessionalModalProps { isOpen: boolean; onClose: () => void; professional: Professional | null; // If null, it's Add Mode existingProfessionals?: Professional[]; onSwitchToEdit?: (prof: Professional) => void; roles: { id: string; nome: string }[]; onSuccess: () => void; } export const ProfessionalModal: React.FC = ({ isOpen, onClose, professional, existingProfessionals = [], onSwitchToEdit, roles, onSuccess, }) => { const { token: contextToken } = useAuth(); const token = contextToken || ""; const initialFormState: CreateProfessionalDTO & { senha?: string; confirmarSenha?: string } = { nome: "", funcao_profissional_id: "", funcoes_ids: [], email: "", senha: "", confirmarSenha: "", whatsapp: "", cpf_cnpj_titular: "", endereco: "", cidade: "", uf: "", banco: "", agencia: "", conta_pix: "", tipo_cartao: "", carro_disponivel: false, tem_estudio: false, qtd_estudio: 0, observacao: "", qual_tec: 0, educacao_simpatia: 0, desempenho_evento: 0, disp_horario: 0, media: 0, tabela_free: "", extra_por_equipamento: false, equipamentos: "", avatar_url: "", }; const [formData, setFormData] = useState(initialFormState); const [avatarFile, setAvatarFile] = useState(null); const [avatarPreview, setAvatarPreview] = useState(""); const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const [isLoadingCep, setIsLoadingCep] = useState(false); const GenericAvatar = "https://ui-avatars.com/api/?background=random"; const ufs = ["AC", "AL", "AP", "AM", "BA", "CE", "DF", "ES", "GO", "MA", "MT", "MS", "MG", "PA", "PB", "PR", "PE", "PI", "RJ", "RN", "RS", "RO", "RR", "SC", "SP", "SE", "TO"]; useEffect(() => { if (isOpen) { if (professional) { // Edit Mode setFormData({ nome: professional.nome, funcao_profissional_id: professional.funcao_profissional_id, funcoes_ids: professional.functions?.map(f => f.id) || (professional.funcao_profissional_id ? [professional.funcao_profissional_id] : []), email: professional.email || "", senha: "", confirmarSenha: "", whatsapp: professional.whatsapp || "", cpf_cnpj_titular: professional.cpf_cnpj_titular || "", endereco: professional.endereco || "", cidade: professional.cidade || "", uf: professional.uf || "", cep: professional.cep || "", banco: professional.banco || "", agencia: professional.agencia || "", conta_pix: professional.conta_pix || "", tipo_cartao: professional.tipo_cartao || "", carro_disponivel: professional.carro_disponivel || false, tem_estudio: professional.tem_estudio || false, qtd_estudio: professional.qtd_estudio || 0, observacao: professional.observacao || "", qual_tec: professional.qual_tec || 0, educacao_simpatia: professional.educacao_simpatia || 0, desempenho_evento: professional.desempenho_evento || 0, disp_horario: professional.disp_horario || 0, tabela_free: professional.tabela_free || "", extra_por_equipamento: professional.extra_por_equipamento || false, equipamentos: professional.equipamentos || "", avatar_url: professional.avatar_url || "", media: professional.media || 0, }); setAvatarPreview(professional.avatar_url || (professional.avatar ?? GenericAvatar)); } else { // Add Mode setFormData(initialFormState); setAvatarPreview(""); } setAvatarFile(null); } }, [isOpen, professional]); // Helpers const maskPhone = (value: string) => { return value .replace(/\D/g, "") .replace(/^(\d{2})(\d)/g, "($1) $2") .replace(/(\d)(\d{4})$/, "$1-$2") .slice(0, 15); }; const maskCpfCnpj = (value: string) => { const clean = value.replace(/\D/g, ""); if (clean.length <= 11) { return clean .replace(/(\d{3})(\d)/, "$1.$2") .replace(/(\d{3})(\d)/, "$1.$2") .replace(/(\d{3})(\d{1,2})/, "$1-$2") .replace(/(-\d{2})\d+?$/, "$1"); } else { return clean .replace(/^(\d{2})(\d)/, "$1.$2") .replace(/^(\d{2})\.(\d{3})(\d)/, "$1.$2.$3") .replace(/\.(\d{3})(\d)/, ".$1/$2") .replace(/(\d{4})(\d)/, "$1-$2") .replace(/(-\d{2})\d+?$/, "$1") .slice(0, 18); } }; const calculateMedia = (ratings: { qual_tec: number; educacao_simpatia: number; desempenho_evento: number; disp_horario: number; }) => { const weightedScore = ratings.qual_tec * 2 + ratings.educacao_simpatia + ratings.desempenho_evento + ratings.disp_horario; return weightedScore / 5; }; // Listen for rating changes useEffect(() => { const newMedia = calculateMedia({ qual_tec: formData.qual_tec || 0, educacao_simpatia: formData.educacao_simpatia || 0, desempenho_evento: formData.desempenho_evento || 0, disp_horario: formData.disp_horario || 0, }); setFormData((prev) => { if (prev.media === newMedia) return prev; return { ...prev, media: newMedia }; }); }, [ formData.qual_tec, formData.educacao_simpatia, formData.desempenho_evento, formData.disp_horario, ]); const handleAvatarChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { setAvatarFile(file); const reader = new FileReader(); reader.onloadend = () => { setAvatarPreview(reader.result as string); }; reader.readAsDataURL(file); } }; const removeAvatar = () => { setAvatarFile(null); setAvatarPreview(""); setFormData((prev) => ({ ...prev, avatar_url: "" })); }; const handleCpfBlur = async () => { const cpf = formData.cpf_cnpj_titular?.replace(/\D/g, "") || ""; if (cpf.length < 11) return; // 1. Check Local Existence (Admin / TeamPage) if (existingProfessionals.length > 0 && !professional) { const found = existingProfessionals.find(p => { const pCpf = p.cpf_cnpj_titular?.replace(/\D/g, ""); return pCpf === cpf; }); if (found) { if (confirm(`O profissional "${found.nome}" já está cadastrado com este CPF.\nDeseja abrir o cadastro existente para edição?`)) { if (onSwitchToEdit) { onSwitchToEdit(found); return; } } else { // User said No to edit. They probably want to create NEW but with same CPF? // That's impossible due to unique constraint. // Warn them. alert("Atenção: Você não conseguirá salvar um novo profissional com o mesmo CPF."); } } } // 2. Check API (for safety or if list is incomplete) try { const response = await fetch(`${import.meta.env.VITE_API_URL || "http://localhost:8080"}/api/profissionais/check?cpf=${cpf}`); if (response.ok) { const data = await response.json(); if (data.exists) { // If we are here, matched in API but maybe NOT in local list (if pagination existed) // Or maybe user clicked "No" above. if (!professional && data.name) { // If we didn't switch to edit (e.g. user refused or logic above didn't catch it) // We show the warning. if (!existingProfessionals.some(p => p.cpf_cnpj_titular?.replace(/\D/g,"") === cpf)) { alert(`ATENÇÃO: Este CPF já está cadastrado para o profissional "${data.name}" no banco de dados.`); } } } } } catch (error) { console.error("Erro ao verificar CPF:", error); } }; const handleCepBlur = async () => { const cep = formData.cep?.replace(/\D/g, "") || ""; if (cep.length !== 8) return; setIsLoadingCep(true); try { const response = await fetch(`https://viacep.com.br/ws/${cep}/json/`); if (!response.ok) throw new Error("CEP não encontrado"); const data = await response.json(); if (data.erro) throw new Error("CEP não encontrado"); setFormData((prev) => ({ ...prev, endereco: `${data.logradouro || ""} ${data.bairro ? `- ${data.bairro}` : ""}`.trim() || prev.endereco, cidade: data.localidade || prev.cidade, uf: data.uf || prev.uf, })); } catch (error) { console.error("Erro ao buscar CEP:", error); } finally { setIsLoadingCep(false); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsSubmitting(true); try { if (!professional && (formData.senha || formData.confirmarSenha)) { if (formData.senha !== formData.confirmarSenha) { alert("As senhas não coincidem!"); setIsSubmitting(false); return; } if (formData.senha && formData.senha.length < 6) { alert("A senha deve ter pelo menos 6 caracteres."); setIsSubmitting(false); return; } } let finalAvatarUrl = formData.avatar_url; if (avatarFile) { const uploadRes = await getUploadURL(avatarFile.name, avatarFile.type); if (uploadRes.data) { await uploadFileToSignedUrl(uploadRes.data.upload_url, avatarFile); finalAvatarUrl = uploadRes.data.public_url; } } const payload: any = { ...formData, avatar_url: finalAvatarUrl }; delete payload.senha; delete payload.confirmarSenha; if (professional) { // Update await updateProfessional(professional.id, payload, token); alert("Profissional atualizado com sucesso!"); } else { // Create let targetUserId = ""; if (formData.email && formData.senha) { const { adminCreateUser } = await import("../services/apiService"); const createRes = await adminCreateUser({ email: formData.email, senha: formData.senha, nome: formData.nome, role: roles.find(r => r.id === formData.funcao_profissional_id)?.nome.toUpperCase().includes("PESQUISA") ? "RESEARCHER" : "PHOTOGRAPHER", tipo_profissional: roles.find(r => r.id === formData.funcao_profissional_id)?.nome || "", ativo: true, }, token); if (createRes.error) { throw new Error("Erro ao criar usuário de login: " + createRes.error); } if (createRes.data && createRes.data.id) { targetUserId = createRes.data.id; } } if (targetUserId) { payload.target_user_id = targetUserId; } const res = await createProfessional(payload, token); if (res.error) throw new Error(res.error); alert("Profissional criado com sucesso!"); } onSuccess(); onClose(); } catch (error: any) { console.error("Error submitting form:", error); alert(error.message || "Erro ao salvar profissional. Verifique o console."); } finally { setIsSubmitting(false); } }; if (!isOpen) return null; return (

{professional ? "Editar Profissional" : "Novo Profissional"}

{/* Avatar */}
{avatarPreview ? ( Preview ) : ( )}
{avatarPreview && ( )}
{/* Name & CPF (Top) */}
setFormData({ ...formData, nome: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-brand-gold focus:ring focus:ring-brand-gold focus:ring-opacity-50 p-2 border" />
setFormData({ ...formData, cpf_cnpj_titular: maskCpfCnpj(e.target.value) })} onBlur={handleCpfBlur} maxLength={18} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-brand-gold focus:ring focus:ring-brand-gold focus:ring-opacity-50 p-2 border" />
{/* Functions */}
{roles.map(role => ( ))}
{/* Email & Pass */}
setFormData({ ...formData, email: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-brand-gold focus:ring focus:ring-brand-gold focus:ring-opacity-50 p-2 border" />
{!professional && ( <>
setFormData({ ...formData, senha: e.target.value })} minLength={6} className="block w-full rounded-md border-gray-300 shadow-sm focus:border-brand-gold focus:ring focus:ring-brand-gold focus:ring-opacity-50 p-2 border pr-10" />
setFormData({ ...formData, confirmarSenha: e.target.value })} minLength={6} className="block w-full rounded-md border-gray-300 shadow-sm focus:border-brand-gold focus:ring focus:ring-brand-gold focus:ring-opacity-50 p-2 border pr-10" />
)}
setFormData({ ...formData, whatsapp: maskPhone(e.target.value) })} maxLength={15} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-brand-gold focus:ring focus:ring-brand-gold focus:ring-opacity-50 p-2 border" />

Endereço

{ const val = e.target.value.replace(/\D/g, "").replace(/^(\d{5})(\d)/, "$1-$2"); setFormData({ ...formData, cep: val }); }} onBlur={handleCepBlur} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-brand-gold focus:ring focus:ring-brand-gold focus:ring-opacity-50 p-2 border" placeholder="00000-000" /> {isLoadingCep && Buscando...}
setFormData({ ...formData, endereco: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-brand-gold focus:ring focus:ring-brand-gold focus:ring-opacity-50 p-2 border" />
setFormData({ ...formData, cidade: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-brand-gold focus:ring focus:ring-brand-gold focus:ring-opacity-50 p-2 border" />
{/* Banking */}

Dados Bancários

setFormData({ ...formData, banco: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-brand-gold focus:ring focus:ring-brand-gold focus:ring-opacity-50 p-2 border" />
setFormData({ ...formData, agencia: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-brand-gold focus:ring focus:ring-brand-gold focus:ring-opacity-50 p-2 border" />
setFormData({ ...formData, conta_pix: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-brand-gold focus:ring focus:ring-brand-gold focus:ring-opacity-50 p-2 border" />
setFormData({ ...formData, tipo_cartao: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-brand-gold focus:ring focus:ring-brand-gold focus:ring-opacity-50 p-2 border" placeholder="SD, XQD..." />
{/* Resources */}

Recursos

{formData.tem_estudio && (
setFormData({ ...formData, qtd_estudio: Math.max(0, parseInt(e.target.value)) })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 border" />
)}
{/* Ratings */}

Avaliações e Valores

setFormData({ ...formData, qual_tec: parseInt(e.target.value) || 0 })} className="block w-full rounded-md border-gray-300 shadow-sm p-2 border" />
setFormData({ ...formData, educacao_simpatia: parseInt(e.target.value) || 0 })} className="block w-full rounded-md border-gray-300 shadow-sm p-2 border" />
setFormData({ ...formData, desempenho_evento: parseInt(e.target.value) || 0 })} className="block w-full rounded-md border-gray-300 shadow-sm p-2 border" />
setFormData({ ...formData, disp_horario: parseInt(e.target.value) || 0 })} className="block w-full rounded-md border-gray-300 shadow-sm p-2 border" />
Média {formData.media ? (typeof formData.media === 'number' ? formData.media.toFixed(1) : parseFloat(String(formData.media)).toFixed(1)) : "0.0"}
setFormData({ ...formData, tabela_free: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-brand-gold focus:ring focus:ring-brand-gold focus:ring-opacity-50 p-2 border" placeholder="Ex: R$ 200,00" />