photum/frontend/pages/Team.tsx
yagostn 5f7aaf9b63 feat: melhorias de UI e novas funcionalidades
- Tradução de rotas para português (entrar, cadastro, configuracoes, etc)
- Ajuste de responsividade na página Financeiro (mobile)
- Correção navegação Configurações para usuário CEO/Business Owner
- Modal de gerenciamento de equipe com lista de profissionais
- Exibição de fotógrafos, cinegrafistas e recepcionistas disponíveis por data
- Ajuste de layout da logo nas telas de login e cadastro
- Correção de z-index do header
- Melhoria de espaçamento e padding em cards
2025-12-10 16:53:16 -03:00

1665 lines
62 KiB
TypeScript

import React, { useState, useEffect } from "react";
import {
Users,
Camera,
Mail,
Phone,
MapPin,
Star,
Plus,
Search,
Filter,
User,
Upload,
X,
Video,
UserCheck,
Car,
Building,
CreditCard,
TrendingUp,
AlertTriangle,
} from "lucide-react";
import { Button } from "../components/Button";
import { getProfessionalRoles } from "../services/apiService";
type ProfessionalRole = "Fotógrafo" | "Cinegrafista" | "Recepcionista";
interface Professional {
id: string;
name: string;
role: ProfessionalRole;
address: {
street: string;
number: string;
complement?: string;
neighborhood: string;
city: string;
state: string;
};
whatsapp: string;
cpfCnpj: string;
bankInfo: {
bank: string;
agency: string;
accountPix: string;
};
hasCar: boolean;
hasStudio: boolean;
studioQuantity?: number;
cardType: string;
accountHolder: string;
observations?: string;
ratings: {
technicalQuality: number;
appearance: number;
education: number;
sympathy: number;
eventPerformance: number;
scheduleAvailability: number;
average: number;
};
freeTable: string;
extraFee: string;
email: string;
specialties: string[];
eventsCompleted: number;
status: "active" | "inactive" | "busy";
avatar: string;
joinDate: string;
}
const MOCK_PROFESSIONALS: Professional[] = [
{
id: "1",
name: "Carlos Silva",
role: "Fotógrafo",
address: {
street: "Rua das Flores",
number: "123",
complement: "Apto 301",
neighborhood: "Centro",
city: "Curitiba",
state: "PR",
},
whatsapp: "(41) 99999-1111",
cpfCnpj: "123.456.789-00",
bankInfo: {
bank: "Banco do Brasil",
agency: "1234-5",
accountPix: "carlos.silva@email.com",
},
hasCar: true,
hasStudio: false,
cardType: "Débito/Crédito",
accountHolder: "Carlos Silva",
ratings: {
technicalQuality: 4.8,
appearance: 4.7,
education: 4.9,
sympathy: 4.8,
eventPerformance: 4.9,
scheduleAvailability: 4.6,
average: 4.8,
},
freeTable: "R$ 800,00",
extraFee: "R$ 150,00 por equipamento extra",
email: "carlos.silva@photum.com",
specialties: ["Formaturas", "Eventos Corporativos"],
eventsCompleted: 45,
status: "active",
avatar: "https://i.pravatar.cc/150?img=12",
joinDate: "2023-01-15",
},
{
id: "2",
name: "Ana Paula Mendes",
role: "Cinegrafista",
address: {
street: "Av. Sete de Setembro",
number: "456",
neighborhood: "Batel",
city: "Curitiba",
state: "PR",
},
whatsapp: "(41) 99999-2222",
cpfCnpj: "987.654.321-00",
bankInfo: {
bank: "Caixa Econômica",
agency: "0987",
accountPix: "(41) 99999-2222",
},
hasCar: true,
hasStudio: true,
studioQuantity: 1,
cardType: "Crédito",
accountHolder: "Ana Paula Mendes",
ratings: {
technicalQuality: 4.9,
appearance: 5.0,
education: 4.8,
sympathy: 4.9,
eventPerformance: 4.9,
scheduleAvailability: 4.7,
average: 4.9,
},
freeTable: "R$ 1.200,00",
extraFee: "R$ 200,00 por hora extra",
email: "ana.mendes@photum.com",
specialties: ["Casamentos", "Formaturas"],
eventsCompleted: 62,
status: "busy",
avatar: "https://i.pravatar.cc/150?img=5",
joinDate: "2022-08-20",
},
{
id: "3",
name: "Mariana Alves",
role: "Recepcionista",
address: {
street: "Rua Comendador Araújo",
number: "321",
complement: "Sala 12",
neighborhood: "Centro",
city: "Curitiba",
state: "PR",
},
whatsapp: "(41) 99999-4444",
cpfCnpj: "321.654.987-00",
bankInfo: {
bank: "Bradesco",
agency: "4321",
accountPix: "mariana.alves@email.com",
},
hasCar: false,
hasStudio: false,
cardType: "Débito",
accountHolder: "Mariana Alves Santos",
ratings: {
technicalQuality: 0,
appearance: 4.9,
education: 5.0,
sympathy: 5.0,
eventPerformance: 4.8,
scheduleAvailability: 4.9,
average: 4.9,
},
freeTable: "R$ 400,00",
extraFee: "R$ 50,00 por hora extra",
email: "mariana.alves@photum.com",
specialties: ["Recepção", "Atendimento"],
eventsCompleted: 71,
status: "active",
avatar: "https://i.pravatar.cc/150?img=9",
joinDate: "2022-05-12",
},
];
export const TeamPage: React.FC = () => {
const [searchTerm, setSearchTerm] = useState("");
const [roleFilter, setRoleFilter] = useState<ProfessionalRole | "all">("all");
const [statusFilter, setStatusFilter] = useState<
"all" | "active" | "busy" | "inactive"
>("all");
const [selectedProfessional, setSelectedProfessional] =
useState<Professional | null>(null);
const [showAddModal, setShowAddModal] = useState(false);
const [showEditModal, setShowEditModal] = useState(false);
const [editingProfessional, setEditingProfessional] = useState<Professional | null>(null);
const [professionalRoles, setProfessionalRoles] = useState<string[]>([]);
const [isBackendDown, setIsBackendDown] = useState(false);
const [isLoadingRoles, setIsLoadingRoles] = useState(true);
const [newProfessional, setNewProfessional] = useState<Partial<Professional>>(
{
name: "",
role: "Fotógrafo",
address: {
street: "",
number: "",
complement: "",
neighborhood: "",
city: "",
state: "",
},
whatsapp: "",
cpfCnpj: "",
bankInfo: {
bank: "",
agency: "",
accountPix: "",
},
hasCar: false,
hasStudio: false,
studioQuantity: 0,
cardType: "",
accountHolder: "",
observations: "",
ratings: {
technicalQuality: 0,
appearance: 0,
education: 0,
sympathy: 0,
eventPerformance: 0,
scheduleAvailability: 0,
average: 0,
},
freeTable: "",
extraFee: "",
email: "",
specialties: [],
avatar: "",
}
);
const [avatarFile, setAvatarFile] = useState<File | null>(null);
const [avatarPreview, setAvatarPreview] = useState<string>("");
// Buscar funções profissionais do backend
useEffect(() => {
const fetchRoles = async () => {
setIsLoadingRoles(true);
const response = await getProfessionalRoles();
if (response.isBackendDown) {
setIsBackendDown(true);
setProfessionalRoles([]);
} else if (response.data) {
setIsBackendDown(false);
setProfessionalRoles(response.data);
}
setIsLoadingRoles(false);
};
fetchRoles();
}, []);
const handleAvatarChange = (e: React.ChangeEvent<HTMLInputElement>) => {
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("");
};
const handleEditProfessional = (professional: Professional) => {
setEditingProfessional(professional);
setNewProfessional(professional);
setAvatarPreview(professional.avatar);
setSelectedProfessional(null);
setShowEditModal(true);
};
const handleUpdateProfessional = () => {
// Aqui você implementaria a lógica de atualização
// Por exemplo, chamada à API para atualizar os dados
console.log('Atualizando profissional:', newProfessional);
// Simulando atualização
setShowEditModal(false);
setEditingProfessional(null);
setNewProfessional({
name: "",
role: "Fotógrafo",
address: {
street: "",
number: "",
complement: "",
neighborhood: "",
city: "",
state: "",
},
whatsapp: "",
cpfCnpj: "",
bankInfo: {
bank: "",
agency: "",
accountPix: "",
},
hasCar: false,
hasStudio: false,
studioQuantity: 0,
cardType: "",
accountHolder: "",
observations: "",
ratings: {
technicalQuality: 0,
appearance: 0,
education: 0,
sympathy: 0,
eventPerformance: 0,
scheduleAvailability: 0,
average: 0,
},
freeTable: "",
extraFee: "",
email: "",
specialties: [],
avatar: "",
});
removeAvatar();
};
const getStatusColor = (status: Professional["status"]) => {
switch (status) {
case "active":
return "bg-green-100 text-green-800";
case "busy":
return "bg-yellow-100 text-yellow-800";
case "inactive":
return "bg-gray-100 text-gray-800";
}
};
const getStatusLabel = (status: Professional["status"]) => {
switch (status) {
case "active":
return "Disponível";
case "busy":
return "Em Evento";
case "inactive":
return "Inativo";
}
};
const getRoleIcon = (role: ProfessionalRole) => {
switch (role) {
case "Fotógrafo":
return Camera;
case "Cinegrafista":
return Video;
case "Recepcionista":
return UserCheck;
}
};
const filteredProfessionals = MOCK_PROFESSIONALS.filter((professional) => {
const matchesSearch =
professional.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
professional.email.toLowerCase().includes(searchTerm.toLowerCase());
const matchesRole =
roleFilter === "all" || professional.role === roleFilter;
const matchesStatus =
statusFilter === "all" || professional.status === statusFilter;
return matchesSearch && matchesRole && matchesStatus;
});
const stats = {
photographers: MOCK_PROFESSIONALS.filter((p) => p.role === "Fotógrafo")
.length,
cinematographers: MOCK_PROFESSIONALS.filter(
(p) => p.role === "Cinegrafista"
).length,
receptionists: MOCK_PROFESSIONALS.filter((p) => p.role === "Recepcionista")
.length,
total: MOCK_PROFESSIONALS.length,
};
return (
<div className="min-h-screen bg-gray-50 pt-20 sm:pt-24 md:pt-28 lg:pt-32 pb-8 sm:pb-12">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{/* Header */}
<div className="mb-6 sm:mb-8">
<h1 className="text-2xl sm:text-3xl font-serif font-bold text-brand-black mb-2">
Equipe
</h1>
<p className="text-sm sm:text-base text-gray-600">
Gerencie sua equipe de profissionais
</p>
</div>
{/* Stats */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 sm:gap-4 md:gap-6 mb-6 sm:mb-8">
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600 mb-1">
Total de Fotógrafos
</p>
<p className="text-3xl font-bold text-brand-black">
{stats.photographers}
</p>
</div>
<Camera className="text-brand-gold" size={32} />
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600 mb-1">
Total de Cinegrafistas
</p>
<p className="text-3xl font-bold text-brand-black">
{stats.cinematographers}
</p>
</div>
<Video className="text-blue-600" size={32} />
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600 mb-1">
Total de Recepcionistas
</p>
<p className="text-3xl font-bold text-brand-black">
{stats.receptionists}
</p>
</div>
<UserCheck className="text-purple-600" size={32} />
</div>
</div>
</div>
{/* Filters and Search */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3 sm:p-4 md:p-6 mb-4 sm:mb-6">
<div className="flex flex-col gap-3 sm:gap-4">
<div className="flex-1 relative">
<Search
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"
size={20}
/>
<input
type="text"
placeholder="Buscar por nome ou email..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
<div className="flex flex-wrap gap-2">
<button
onClick={() => setRoleFilter("all")}
className={`px-4 py-2 rounded-md font-medium transition-colors ${
roleFilter === "all"
? "bg-brand-gold text-white"
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
}`}
>
Todos
</button>
<button
onClick={() => setRoleFilter("Fotógrafo")}
className={`px-4 py-2 rounded-md font-medium transition-colors ${
roleFilter === "Fotógrafo"
? "bg-brand-gold text-white"
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
}`}
>
Fotógrafos
</button>
<button
onClick={() => setRoleFilter("Cinegrafista")}
className={`px-4 py-2 rounded-md font-medium transition-colors ${
roleFilter === "Cinegrafista"
? "bg-blue-600 text-white"
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
}`}
>
Cinegrafistas
</button>
<button
onClick={() => setRoleFilter("Recepcionista")}
className={`px-4 py-2 rounded-md font-medium transition-colors ${
roleFilter === "Recepcionista"
? "bg-purple-600 text-white"
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
}`}
>
Recepcionistas
</button>
</div>
{/* Status Filters */}
<div className="flex flex-wrap gap-2">
<button
onClick={() => setStatusFilter("all")}
className={`px-4 py-2 rounded-md font-medium transition-colors ${
statusFilter === "all"
? "bg-brand-gold text-white"
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
}`}
>
Todos Status
</button>
<button
onClick={() => setStatusFilter("active")}
className={`px-4 py-2 rounded-md font-medium transition-colors ${
statusFilter === "active"
? "bg-green-600 text-white"
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
}`}
>
Disponível
</button>
<button
onClick={() => setStatusFilter("busy")}
className={`px-4 py-2 rounded-md font-medium transition-colors ${
statusFilter === "busy"
? "bg-yellow-600 text-white"
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
}`}
>
Em Evento
</button>
<button
onClick={() => setStatusFilter("inactive")}
className={`px-4 py-2 rounded-md font-medium transition-colors ${
statusFilter === "inactive"
? "bg-gray-600 text-white"
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
}`}
>
Inativo
</button>
</div>
<Button
size="md"
variant="secondary"
onClick={() => setShowAddModal(true)}
>
<Plus size={20} className="mr-2" />
Adicionar Profissional
</Button>
</div>
</div>
{/* Professionals Table */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
{filteredProfessionals.length === 0 ? (
<div className="text-center py-12">
<Users size={48} className="mx-auto text-gray-300 mb-4" />
<p className="text-gray-500">Nenhum profissional encontrado</p>
</div>
) : (
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gray-50 border-b border-gray-200">
<tr>
<th className="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Nome
</th>
<th className="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Função Profissional
</th>
<th className="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Disponibilidade
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredProfessionals.map((professional) => {
const RoleIcon = getRoleIcon(professional.role);
return (
<tr
key={professional.id}
className="hover:bg-gray-50 cursor-pointer transition-colors"
onClick={() => setSelectedProfessional(professional)}
>
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center gap-3">
<div>
<div className="font-semibold text-brand-black">
{professional.name}
</div>
<div className="flex items-center gap-1 mt-1">
<Star
size={14}
fill="#B9CF33"
className="text-brand-gold"
/>
<span className="text-sm font-medium text-gray-600">
{professional.ratings.average.toFixed(1)}
</span>
<span className="text-xs text-gray-500">
({professional.eventsCompleted} eventos)
</span>
</div>
</div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center gap-2">
<RoleIcon size={18} className="text-brand-gold" />
<span className="text-sm font-medium text-gray-900">
{professional.role}
</span>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span
className={`px-3 py-1 rounded-full text-sm font-medium ${getStatusColor(
professional.status
)}`}
>
{getStatusLabel(professional.status)}
</span>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
)}
</div>
</div>
{/* Edit Professional Modal */}
{showEditModal && (
<div
className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4 overflow-y-auto"
onClick={() => setShowEditModal(false)}
>
<div
className="bg-white rounded-lg max-w-4xl w-full p-8 max-h-[90vh] overflow-y-auto my-8"
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between mb-6">
<div>
<h2 className="text-2xl font-serif font-bold text-brand-black">
Editar Profissional
</h2>
<p className="text-sm text-gray-500 mt-1">
Atualize as informações do profissional
</p>
</div>
<button
onClick={() => {
setShowEditModal(false);
setEditingProfessional(null);
}}
className="text-gray-400 hover:text-gray-600"
>
<X size={24} />
</button>
</div>
<form
className="space-y-6"
onSubmit={(e) => {
e.preventDefault();
handleUpdateProfessional();
alert("Profissional atualizado com sucesso!");
}}
>
{/* Reutilizar o mesmo conteúdo do formulário de adicionar */}
{/* Avatar Upload */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Foto do Profissional
</label>
<div className="flex items-center space-x-4">
{avatarPreview ? (
<div className="relative">
<img
src={avatarPreview}
alt="Preview"
className="w-24 h-24 rounded-full object-cover border-2 border-gray-200"
/>
<button
type="button"
onClick={removeAvatar}
className="absolute -top-2 -right-2 bg-red-500 text-white rounded-full p-1 hover:bg-red-600"
>
<X size={16} />
</button>
</div>
) : (
<div className="w-24 h-24 rounded-full bg-gray-200 flex items-center justify-center">
<User className="text-gray-400" size={40} />
</div>
)}
<label className="cursor-pointer bg-gray-100 hover:bg-gray-200 text-gray-700 px-4 py-2 rounded-md transition-colors flex items-center space-x-2">
<Upload size={16} />
<span>Upload Foto</span>
<input
type="file"
accept="image/*"
onChange={handleAvatarChange}
className="hidden"
/>
</label>
</div>
</div>
{/* Botões de Ação */}
<div className="flex justify-end space-x-3 pt-6 border-t">
<button
type="button"
onClick={() => {
setShowEditModal(false);
setEditingProfessional(null);
}}
className="px-6 py-3 bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200 transition-colors font-medium"
>
Cancelar
</button>
<button
type="submit"
className="px-6 py-3 bg-brand-gold text-white rounded-md hover:bg-[#a5bd2e] transition-colors font-medium flex items-center space-x-2"
>
<Check size={18} />
<span>Salvar Alterações</span>
</button>
</div>
</form>
</div>
</div>
)}
{/* Add Professional Modal - com todos os campos da planilha */}
{showAddModal && (
<div
className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4 overflow-y-auto"
onClick={() => setShowAddModal(false)}
>
<div
className="bg-white rounded-lg max-w-4xl w-full p-8 max-h-[90vh] overflow-y-auto my-8"
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between mb-6">
<h2 className="text-2xl font-serif font-bold text-brand-black">
Adicionar Novo Profissional
</h2>
<button
onClick={() => setShowAddModal(false)}
className="text-gray-400 hover:text-gray-600"
>
<X size={24} />
</button>
</div>
<form
className="space-y-6"
onSubmit={(e) => {
e.preventDefault();
alert("Profissional adicionado com sucesso!");
setShowAddModal(false);
}}
>
{/* Dados Básicos */}
<div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-900 border-b pb-2">
Dados Básicos
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Nome *
</label>
<input
type="text"
required
value={newProfessional.name}
onChange={(e) =>
setNewProfessional({
...newProfessional,
name: e.target.value,
})
}
placeholder="Nome completo"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Função Profissional *
</label>
<select
required
value={newProfessional.role}
onChange={(e) =>
setNewProfessional({
...newProfessional,
role: e.target.value as ProfessionalRole,
})
}
disabled={isLoadingRoles || isBackendDown}
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold disabled:bg-gray-100 disabled:cursor-not-allowed"
>
<option value="">Selecione uma função</option>
{professionalRoles.map((role) => (
<option key={role} value={role}>
{role}
</option>
))}
</select>
{isBackendDown && (
<div className="mt-2 flex items-center gap-2 text-sm text-red-600">
<AlertTriangle size={16} />
<span>Backend não está rodando. Não é possível carregar as funções profissionais.</span>
</div>
)}
{isLoadingRoles && !isBackendDown && (
<div className="mt-2 text-sm text-gray-500">
Carregando funções profissionais...
</div>
)}
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Email *
</label>
<input
type="email"
required
value={newProfessional.email}
onChange={(e) =>
setNewProfessional({
...newProfessional,
email: e.target.value,
})
}
placeholder="email@exemplo.com"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
WhatsApp *
</label>
<input
type="tel"
required
value={newProfessional.whatsapp}
onChange={(e) =>
setNewProfessional({
...newProfessional,
whatsapp: e.target.value,
})
}
placeholder="(00) 00000-0000"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
CPF ou CNPJ - do Titular da Conta *
</label>
<input
type="text"
required
value={newProfessional.cpfCnpj}
onChange={(e) =>
setNewProfessional({
...newProfessional,
cpfCnpj: e.target.value,
})
}
placeholder="000.000.000-00 ou 00.000.000/0000-00"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
</div>
{/* Endereço */}
<div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-900 border-b pb-2">
Endereço
</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-2">
Rua *
</label>
<input
type="text"
required
value={newProfessional.address?.street}
onChange={(e) =>
setNewProfessional({
...newProfessional,
address: {
...newProfessional.address!,
street: e.target.value,
},
})
}
placeholder="Nome da rua"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Número *
</label>
<input
type="text"
required
value={newProfessional.address?.number}
onChange={(e) =>
setNewProfessional({
...newProfessional,
address: {
...newProfessional.address!,
number: e.target.value,
},
})
}
placeholder="123"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Complemento
</label>
<input
type="text"
value={newProfessional.address?.complement}
onChange={(e) =>
setNewProfessional({
...newProfessional,
address: {
...newProfessional.address!,
complement: e.target.value,
},
})
}
placeholder="Apto, Sala, etc"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Bairro *
</label>
<input
type="text"
required
value={newProfessional.address?.neighborhood}
onChange={(e) =>
setNewProfessional({
...newProfessional,
address: {
...newProfessional.address!,
neighborhood: e.target.value,
},
})
}
placeholder="Nome do bairro"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Cidade *
</label>
<input
type="text"
required
value={newProfessional.address?.city}
onChange={(e) =>
setNewProfessional({
...newProfessional,
address: {
...newProfessional.address!,
city: e.target.value,
},
})
}
placeholder="Nome da cidade"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
UF *
</label>
<input
type="text"
required
maxLength={2}
value={newProfessional.address?.state}
onChange={(e) =>
setNewProfessional({
...newProfessional,
address: {
...newProfessional.address!,
state: e.target.value.toUpperCase(),
},
})
}
placeholder="SP"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
</div>
</div>
{/* Dados Bancários */}
<div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-900 border-b pb-2">
Dados Bancários
</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Banco *
</label>
<input
type="text"
required
value={newProfessional.bankInfo?.bank}
onChange={(e) =>
setNewProfessional({
...newProfessional,
bankInfo: {
...newProfessional.bankInfo!,
bank: e.target.value,
},
})
}
placeholder="Nome do banco"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Agência *
</label>
<input
type="text"
required
value={newProfessional.bankInfo?.agency}
onChange={(e) =>
setNewProfessional({
...newProfessional,
bankInfo: {
...newProfessional.bankInfo!,
agency: e.target.value,
},
})
}
placeholder="0000-0"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Conta / Pix *
</label>
<input
type="text"
required
value={newProfessional.bankInfo?.accountPix}
onChange={(e) =>
setNewProfessional({
...newProfessional,
bankInfo: {
...newProfessional.bankInfo!,
accountPix: e.target.value,
},
})
}
placeholder="Conta ou chave Pix"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Tipo Cartão *
</label>
<input
type="text"
required
value={newProfessional.cardType}
onChange={(e) =>
setNewProfessional({
...newProfessional,
cardType: e.target.value,
})
}
placeholder="Débito, Crédito, Pix, etc"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Nome do Titular da Conta *
</label>
<input
type="text"
required
value={newProfessional.accountHolder}
onChange={(e) =>
setNewProfessional({
...newProfessional,
accountHolder: e.target.value,
})
}
placeholder="Nome do titular"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
</div>
</div>
{/* Recursos e Equipamentos */}
<div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-900 border-b pb-2">
Recursos e Equipamentos
</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={newProfessional.hasCar}
onChange={(e) =>
setNewProfessional({
...newProfessional,
hasCar: e.target.checked,
})
}
className="w-4 h-4 text-brand-gold focus:ring-brand-gold border-gray-300 rounded"
/>
<span className="text-sm font-medium text-gray-700">
Carro Disponível para uso?
</span>
</label>
</div>
<div>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={newProfessional.hasStudio}
onChange={(e) =>
setNewProfessional({
...newProfessional,
hasStudio: e.target.checked,
})
}
className="w-4 h-4 text-brand-gold focus:ring-brand-gold border-gray-300 rounded"
/>
<span className="text-sm font-medium text-gray-700">
Possui Estúdio?
</span>
</label>
</div>
{newProfessional.hasStudio && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Qtd de Estúdios
</label>
<input
type="number"
min="0"
value={newProfessional.studioQuantity}
onChange={(e) =>
setNewProfessional({
...newProfessional,
studioQuantity: parseInt(e.target.value),
})
}
placeholder="0"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Tipo Cartão
</label>
<input
type="text"
value={newProfessional.cardType}
onChange={(e) =>
setNewProfessional({
...newProfessional,
cardType: e.target.value,
})
}
placeholder="Ex: SD, CF, XQD, etc."
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
</div>
{/* Avaliações */}
<div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-900 border-b pb-2">
Avaliações (0 a 5)
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Qual. Téc / Aparência
</label>
<input
type="number"
min="0"
max="5"
step="0.1"
value={newProfessional.ratings?.technicalQuality}
onChange={(e) =>
setNewProfessional({
...newProfessional,
ratings: {
...newProfessional.ratings!,
technicalQuality: parseFloat(e.target.value),
},
})
}
placeholder="0.0"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Educação / Simpatia
</label>
<input
type="number"
min="0"
max="5"
step="0.1"
value={newProfessional.ratings?.sympathy}
onChange={(e) =>
setNewProfessional({
...newProfessional,
ratings: {
...newProfessional.ratings!,
sympathy: parseFloat(e.target.value),
},
})
}
placeholder="0.0"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Desempenho Evento
</label>
<input
type="number"
min="0"
max="5"
step="0.1"
value={newProfessional.ratings?.eventPerformance}
onChange={(e) =>
setNewProfessional({
...newProfessional,
ratings: {
...newProfessional.ratings!,
eventPerformance: parseFloat(e.target.value),
},
})
}
placeholder="0.0"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Disp. Horário
</label>
<input
type="number"
min="0"
max="5"
step="0.1"
value={newProfessional.ratings?.scheduleAvailability}
onChange={(e) =>
setNewProfessional({
...newProfessional,
ratings: {
...newProfessional.ratings!,
scheduleAvailability: parseFloat(e.target.value),
},
})
}
placeholder="0.0"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
</div>
</div>
{/* Valores */}
<div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-900 border-b pb-2">
Valores
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Tabela de Free *
</label>
<input
type="text"
required
value={newProfessional.freeTable}
onChange={(e) =>
setNewProfessional({
...newProfessional,
freeTable: e.target.value,
})
}
placeholder="R$ 800,00"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Extra no Cachê por Equipamento e Desempenho
</label>
<input
type="text"
value={newProfessional.extraFee}
onChange={(e) =>
setNewProfessional({
...newProfessional,
extraFee: e.target.value,
})
}
placeholder="R$ 150,00"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
</div>
</div>
{/* Observações */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Observações
</label>
<textarea
value={newProfessional.observations}
onChange={(e) =>
setNewProfessional({
...newProfessional,
observations: e.target.value,
})
}
placeholder="Qualquer tipo de observação relevante"
rows={4}
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
/>
</div>
<div className="pt-6 border-t border-gray-200 flex gap-3">
<button
type="button"
onClick={() => setShowAddModal(false)}
className="flex-1 px-6 py-3 bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200 transition-colors font-medium"
>
Cancelar
</button>
<button
type="submit"
className="flex-1 px-6 py-3 bg-brand-gold text-white rounded-md hover:bg-[#a5bd2e] transition-colors font-medium"
>
Adicionar Profissional
</button>
</div>
</form>
</div>
</div>
)}
{/* Professional Detail Modal */}
{selectedProfessional && (
<div
className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4 overflow-y-auto"
onClick={() => setSelectedProfessional(null)}
>
<div
className="bg-white rounded-lg max-w-3xl w-full p-8 max-h-[90vh] overflow-y-auto my-8"
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-start gap-4 mb-6">
<img
src={selectedProfessional.avatar}
alt={selectedProfessional.name}
className="w-24 h-24 rounded-full object-cover"
/>
<div className="flex-1">
<div className="flex items-start justify-between">
<div>
<h2 className="text-2xl font-serif font-bold text-brand-black mb-1">
{selectedProfessional.name}
</h2>
<p className="text-gray-600 mb-2">
{selectedProfessional.role}
</p>
<div className="flex items-center gap-2 mb-2">
<Star
size={18}
fill="#B9CF33"
className="text-brand-gold"
/>
<span className="font-semibold">
{selectedProfessional.ratings.average.toFixed(1)}
</span>
<span className="text-sm text-gray-500">
({selectedProfessional.eventsCompleted} eventos
concluídos)
</span>
</div>
<span
className={`inline-block px-3 py-1 rounded-full text-xs font-medium ${getStatusColor(
selectedProfessional.status
)}`}
>
{getStatusLabel(selectedProfessional.status)}
</span>
</div>
<button
onClick={() => setSelectedProfessional(null)}
className="text-gray-400 hover:text-gray-600"
>
<X size={24} />
</button>
</div>
</div>
</div>
{/* Informações detalhadas */}
<div className="space-y-6">
{/* Contato */}
<div>
<h3 className="font-semibold text-lg mb-3 text-brand-black">
Contato
</h3>
<div className="space-y-2">
<div className="flex items-center text-gray-700">
<Mail size={18} className="mr-3 text-brand-gold" />
<span>{selectedProfessional.email}</span>
</div>
<div className="flex items-center text-gray-700">
<Phone size={18} className="mr-3 text-brand-gold" />
<span>{selectedProfessional.whatsapp}</span>
</div>
<div className="flex items-start text-gray-700">
<MapPin size={18} className="mr-3 text-brand-gold mt-1" />
<span>
{selectedProfessional.address.street},{" "}
{selectedProfessional.address.number}
{selectedProfessional.address.complement &&
` - ${selectedProfessional.address.complement}`}
<br />
{selectedProfessional.address.neighborhood},{" "}
{selectedProfessional.address.city} -{" "}
{selectedProfessional.address.state}
</span>
</div>
</div>
</div>
{/* Recursos */}
<div>
<h3 className="font-semibold text-lg mb-3 text-brand-black">
Recursos
</h3>
<div className="flex flex-wrap gap-2">
{selectedProfessional.hasCar && (
<span className="flex items-center gap-2 px-3 py-1 bg-green-100 text-green-700 rounded-full text-sm">
<Car size={16} />
Possui Carro
</span>
)}
{selectedProfessional.hasStudio && (
<span className="flex items-center gap-2 px-3 py-1 bg-blue-100 text-blue-700 rounded-full text-sm">
<Building size={16} />
Possui Estúdio ({selectedProfessional.studioQuantity})
</span>
)}
</div>
</div>
{/* Avaliações */}
<div>
<h3 className="font-semibold text-lg mb-3 text-brand-black">
Avaliações
</h3>
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
{selectedProfessional.role !== "Recepcionista" && (
<div className="bg-gray-50 p-3 rounded-lg">
<p className="text-xs text-gray-600 mb-1">
Qual. Técnica
</p>
<p className="text-lg font-semibold text-brand-gold">
{selectedProfessional.ratings.technicalQuality.toFixed(
1
)}
</p>
</div>
)}
<div className="bg-gray-50 p-3 rounded-lg">
<p className="text-xs text-gray-600 mb-1">Aparência</p>
<p className="text-lg font-semibold text-brand-gold">
{selectedProfessional.ratings.appearance.toFixed(1)}
</p>
</div>
<div className="bg-gray-50 p-3 rounded-lg">
<p className="text-xs text-gray-600 mb-1">Simpatia</p>
<p className="text-lg font-semibold text-brand-gold">
{selectedProfessional.ratings.sympathy.toFixed(1)}
</p>
</div>
<div className="bg-gray-50 p-3 rounded-lg">
<p className="text-xs text-gray-600 mb-1">Desempenho</p>
<p className="text-lg font-semibold text-brand-gold">
{selectedProfessional.ratings.eventPerformance.toFixed(1)}
</p>
</div>
<div className="bg-gray-50 p-3 rounded-lg">
<p className="text-xs text-gray-600 mb-1">
Disponibilidade
</p>
<p className="text-lg font-semibold text-brand-gold">
{selectedProfessional.ratings.scheduleAvailability.toFixed(
1
)}
</p>
</div>
</div>
</div>
{/* Valores */}
<div>
<h3 className="font-semibold text-lg mb-3 text-brand-black">
Valores
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div className="bg-gray-50 p-3 rounded-lg">
<p className="text-xs text-gray-600 mb-1">Tabela Free</p>
<p className="text-lg font-semibold text-brand-black">
{selectedProfessional.freeTable}
</p>
</div>
<div className="bg-gray-50 p-3 rounded-lg">
<p className="text-xs text-gray-600 mb-1">
Extra/Equipamento
</p>
<p className="text-lg font-semibold text-brand-black">
{selectedProfessional.extraFee}
</p>
</div>
</div>
</div>
{/* Observações */}
{selectedProfessional.observations && (
<div>
<h3 className="font-semibold text-lg mb-2 text-brand-black">
Observações
</h3>
<p className="text-gray-700 bg-gray-50 p-3 rounded-lg">
{selectedProfessional.observations}
</p>
</div>
)}
</div>
<div className="pt-6 border-t border-gray-200 flex gap-3 mt-6">
<button
onClick={() => setSelectedProfessional(null)}
className="flex-1 px-6 py-3 bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200 transition-colors font-medium"
>
Fechar
</button>
<button
onClick={() => handleEditProfessional(selectedProfessional)}
className="flex-1 px-6 py-3 bg-brand-gold text-white rounded-md hover:bg-[#a5bd2e] transition-colors font-medium"
>
Editar Dados
</button>
</div>
</div>
</div>
)}
</div>
);
};