feat: Melhorias na tabela de eventos e formulários

- Adiciona coluna de Cidade com ordenação independente na tabela de eventos
- Implementa ordenação clicável em todas as colunas (nome, tipo, data, horário, cidade, local, status)
- Remove dependência de estado para filtro de cidade
- Adiciona exibição de curso/turma no modal de detalhes do evento
- Torna campo curso/turma obrigatório no formulário de solicitação de evento
- Remove campo de imagem de capa do formulário
- Corrige importação de getActiveCoursesByInstitutionId no Dashboard
This commit is contained in:
João Vitor 2025-12-08 09:11:05 -03:00
parent de5ceea1f3
commit 8016a0298e
5 changed files with 362 additions and 157 deletions

View file

@ -84,7 +84,7 @@ export const EventFiltersBar: React.FC<EventFiltersBarProps> = ({
</label> </label>
<select <select
value={filters.state} value={filters.state}
onChange={(e) => onFilterChange({ ...filters, state: e.target.value, city: '' })} onChange={(e) => onFilterChange({ ...filters, state: e.target.value })}
className="px-3 py-2 border border-gray-300 rounded text-sm focus:outline-none focus:border-brand-gold transition-colors bg-white" className="px-3 py-2 border border-gray-300 rounded text-sm focus:outline-none focus:border-brand-gold transition-colors bg-white"
> >
<option value="">Todos os estados</option> <option value="">Todos os estados</option>
@ -105,8 +105,7 @@ export const EventFiltersBar: React.FC<EventFiltersBarProps> = ({
<select <select
value={filters.city} value={filters.city}
onChange={(e) => onFilterChange({ ...filters, city: e.target.value })} onChange={(e) => onFilterChange({ ...filters, city: e.target.value })}
disabled={!filters.state} className="px-3 py-2 border border-gray-300 rounded text-sm focus:outline-none focus:border-brand-gold transition-colors bg-white"
className="px-3 py-2 border border-gray-300 rounded text-sm focus:outline-none focus:border-brand-gold transition-colors bg-white disabled:bg-gray-100 disabled:cursor-not-allowed"
> >
<option value="">Todas as cidades</option> <option value="">Todas as cidades</option>
{availableCities.map((city) => ( {availableCities.map((city) => (

View file

@ -86,8 +86,6 @@ export const EventForm: React.FC<EventFormProps> = ({
briefing: "", briefing: "",
contacts: [{ name: "", role: "", phone: "" }], contacts: [{ name: "", role: "", phone: "" }],
files: [] as File[], files: [] as File[],
coverImage:
"https://images.unsplash.com/photo-1511795409834-ef04bbd61622?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80", // Default
institutionId: "", institutionId: "",
attendees: "", attendees: "",
courseId: "", courseId: "",
@ -248,6 +246,12 @@ export const EventForm: React.FC<EventFormProps> = ({
return; return;
} }
// Validate course selection
if (!formData.courseId) {
alert("Por favor, selecione um curso/turma antes de continuar.");
return;
}
// Show toast // Show toast
setShowToast(true); setShowToast(true);
// Call original submit after small delay for visual effect or immediately // Call original submit after small delay for visual effect or immediately
@ -543,7 +547,7 @@ export const EventForm: React.FC<EventFormProps> = ({
{formData.institutionId && ( {formData.institutionId && (
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-1 tracking-wide uppercase text-xs"> <label className="block text-sm font-medium text-gray-700 mb-1 tracking-wide uppercase text-xs">
Curso/Turma Curso/Turma <span className="text-red-500">*</span>
</label> </label>
{availableCourses.length === 0 ? ( {availableCourses.length === 0 ? (
@ -575,8 +579,9 @@ export const EventForm: React.FC<EventFormProps> = ({
courseId: e.target.value, courseId: e.target.value,
}) })
} }
required
> >
<option value="">Selecione um curso (opcional)</option> <option value="">Selecione um curso *</option>
{availableCourses.map((course) => ( {availableCourses.map((course) => (
<option key={course.id} value={course.id}> <option key={course.id} value={course.id}>
{course.name} - {course.graduationType} ( {course.name} - {course.graduationType} (
@ -587,52 +592,6 @@ export const EventForm: React.FC<EventFormProps> = ({
)} )}
</div> </div>
)} )}
{/* Cover Image Upload */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1 tracking-wide uppercase text-xs">
Imagem de Capa
</label>
<div className="relative border border-gray-300 rounded-sm p-2 flex items-center bg-white">
<input
type="file"
accept="image/*"
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
onChange={(e) => {
if (e.target.files && e.target.files[0]) {
const file = e.target.files[0];
const imageUrl = URL.createObjectURL(file);
setFormData({ ...formData, coverImage: imageUrl });
}
}}
/>
<div className="flex items-center justify-between w-full px-2">
<span className="text-sm text-gray-500 truncate max-w-[200px]">
{formData.coverImage &&
!formData.coverImage.startsWith("http")
? "Imagem selecionada"
: formData.coverImage
? "Imagem atual (URL)"
: "Clique para selecionar..."}
</span>
<div className="bg-gray-100 p-1.5 rounded hover:bg-gray-200">
<Upload size={16} className="text-gray-600" />
</div>
</div>
</div>
{formData.coverImage && (
<div className="mt-2 h-32 w-full rounded-sm overflow-hidden border border-gray-200 relative group">
<img
src={formData.coverImage}
alt="Preview"
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center text-white text-xs">
Visualização da Capa
</div>
</div>
)}
</div>
</div> </div>
<div className="flex flex-col sm:flex-row justify-end gap-3 sm:gap-0 mt-8"> <div className="flex flex-col sm:flex-row justify-end gap-3 sm:gap-0 mt-8">

View file

@ -1,6 +1,6 @@
import React from 'react'; import React, { useState, useMemo } from 'react';
import { EventData, EventStatus, UserRole } from '../types'; import { EventData, EventStatus, UserRole } from '../types';
import { Calendar, MapPin, Users, CheckCircle, Clock } from 'lucide-react'; import { Calendar, MapPin, Users, CheckCircle, Clock, ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react';
import { STATUS_COLORS } from '../constants'; import { STATUS_COLORS } from '../constants';
interface EventTableProps { interface EventTableProps {
@ -10,6 +10,9 @@ interface EventTableProps {
userRole: UserRole; userRole: UserRole;
} }
type SortField = 'name' | 'type' | 'date' | 'time' | 'city' | 'location' | 'status';
type SortOrder = 'asc' | 'desc' | null;
export const EventTable: React.FC<EventTableProps> = ({ export const EventTable: React.FC<EventTableProps> = ({
events, events,
onEventClick, onEventClick,
@ -17,6 +20,84 @@ export const EventTable: React.FC<EventTableProps> = ({
userRole userRole
}) => { }) => {
const canApprove = (userRole === UserRole.BUSINESS_OWNER || userRole === UserRole.SUPERADMIN); const canApprove = (userRole === UserRole.BUSINESS_OWNER || userRole === UserRole.SUPERADMIN);
const [sortField, setSortField] = useState<SortField | null>(null);
const [sortOrder, setSortOrder] = useState<SortOrder>(null);
const handleSort = (field: SortField) => {
if (sortField === field) {
// Se já está ordenando por este campo, alterna a ordem
if (sortOrder === 'asc') {
setSortOrder('desc');
} else if (sortOrder === 'desc') {
setSortOrder(null);
setSortField(null);
}
} else {
// Novo campo, começa com ordem ascendente
setSortField(field);
setSortOrder('asc');
}
};
const sortedEvents = useMemo(() => {
if (!sortField || !sortOrder) {
return events;
}
const sorted = [...events].sort((a, b) => {
let aValue: any;
let bValue: any;
switch (sortField) {
case 'name':
aValue = a.name.toLowerCase();
bValue = b.name.toLowerCase();
break;
case 'type':
aValue = a.type.toLowerCase();
bValue = b.type.toLowerCase();
break;
case 'date':
aValue = new Date(a.date + 'T00:00:00').getTime();
bValue = new Date(b.date + 'T00:00:00').getTime();
break;
case 'time':
aValue = a.time;
bValue = b.time;
break;
case 'city':
aValue = a.address.city.toLowerCase();
bValue = b.address.city.toLowerCase();
break;
case 'location':
aValue = `${a.address.city}, ${a.address.state}`.toLowerCase();
bValue = `${b.address.city}, ${b.address.state}`.toLowerCase();
break;
case 'status':
aValue = a.status.toLowerCase();
bValue = b.status.toLowerCase();
break;
default:
return 0;
}
if (aValue < bValue) return sortOrder === 'asc' ? -1 : 1;
if (aValue > bValue) return sortOrder === 'asc' ? 1 : -1;
return 0;
});
return sorted;
}, [events, sortField, sortOrder]);
const getSortIcon = (field: SortField) => {
if (sortField !== field) {
return <ArrowUpDown size={14} className="opacity-0 group-hover:opacity-50 transition-opacity" />;
}
if (sortOrder === 'asc') {
return <ArrowUp size={14} className="text-brand-gold" />;
}
return <ArrowDown size={14} className="text-brand-gold" />;
};
const formatDate = (date: string) => { const formatDate = (date: string) => {
const eventDate = new Date(date + 'T00:00:00'); const eventDate = new Date(date + 'T00:00:00');
@ -50,20 +131,59 @@ export const EventTable: React.FC<EventTableProps> = ({
Ações Ações
</th> </th>
)} )}
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider"> <th
Nome do Evento className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider cursor-pointer hover:bg-gray-100 transition-colors group"
onClick={() => handleSort('name')}
>
<div className="flex items-center gap-2">
Nome do Evento
{getSortIcon('name')}
</div>
</th> </th>
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider"> <th
Tipo className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider cursor-pointer hover:bg-gray-100 transition-colors group"
onClick={() => handleSort('type')}
>
<div className="flex items-center gap-2">
Tipo
{getSortIcon('type')}
</div>
</th> </th>
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider"> <th
Data className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider cursor-pointer hover:bg-gray-100 transition-colors group"
onClick={() => handleSort('date')}
>
<div className="flex items-center gap-2">
Data
{getSortIcon('date')}
</div>
</th> </th>
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider"> <th
Horário className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider cursor-pointer hover:bg-gray-100 transition-colors group"
onClick={() => handleSort('time')}
>
<div className="flex items-center gap-2">
Horário
{getSortIcon('time')}
</div>
</th> </th>
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider"> <th
Local className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider cursor-pointer hover:bg-gray-100 transition-colors group"
onClick={() => handleSort('city')}
>
<div className="flex items-center gap-2">
Cidade
{getSortIcon('city')}
</div>
</th>
<th
className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider cursor-pointer hover:bg-gray-100 transition-colors group"
onClick={() => handleSort('location')}
>
<div className="flex items-center gap-2">
Local
{getSortIcon('location')}
</div>
</th> </th>
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider"> <th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
Contatos Contatos
@ -71,13 +191,19 @@ export const EventTable: React.FC<EventTableProps> = ({
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider"> <th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
Equipe Equipe
</th> </th>
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider"> <th
Status className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider cursor-pointer hover:bg-gray-100 transition-colors group"
onClick={() => handleSort('status')}
>
<div className="flex items-center gap-2">
Status
{getSortIcon('status')}
</div>
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-gray-100"> <tbody className="divide-y divide-gray-100">
{events.map((event) => ( {sortedEvents.map((event) => (
<tr <tr
key={event.id} key={event.id}
onClick={() => onEventClick(event)} onClick={() => onEventClick(event)}
@ -115,6 +241,11 @@ export const EventTable: React.FC<EventTableProps> = ({
{event.time} {event.time}
</div> </div>
</td> </td>
<td className="px-4 py-3">
<div className="text-sm text-gray-700">
{event.address.city}
</div>
</td>
<td className="px-4 py-3"> <td className="px-4 py-3">
<div className="flex items-center text-sm text-gray-700"> <div className="flex items-center text-sm text-gray-700">
<MapPin size={14} className="mr-1.5 text-brand-gold flex-shrink-0" /> <MapPin size={14} className="mr-1.5 text-brand-gold flex-shrink-0" />
@ -164,7 +295,7 @@ export const EventTable: React.FC<EventTableProps> = ({
</table> </table>
</div> </div>
{events.length === 0 && ( {sortedEvents.length === 0 && (
<div className="text-center py-12 text-gray-500"> <div className="text-center py-12 text-gray-500">
<p>Nenhum evento encontrado.</p> <p>Nenhum evento encontrado.</p>
</div> </div>

View file

@ -35,6 +35,7 @@ export const Dashboard: React.FC<DashboardProps> = ({
updateEventStatus, updateEventStatus,
assignPhotographer, assignPhotographer,
getInstitutionById, getInstitutionById,
getActiveCoursesByInstitutionId,
} = useData(); } = useData();
const [view, setView] = useState<"list" | "create" | "edit" | "details">( const [view, setView] = useState<"list" | "create" | "edit" | "details">(
initialView initialView
@ -67,14 +68,10 @@ export const Dashboard: React.FC<DashboardProps> = ({
// Extract unique values for filters // Extract unique values for filters
const { availableStates, availableCities, availableTypes } = useMemo(() => { const { availableStates, availableCities, availableTypes } = useMemo(() => {
const states = [...new Set(myEvents.map(e => e.address.state))].sort(); const states = [...new Set(myEvents.map(e => e.address.state))].sort();
const cities = advancedFilters.state const cities = [...new Set(myEvents.map(e => e.address.city))].sort();
? [...new Set(myEvents
.filter(e => e.address.state === advancedFilters.state)
.map(e => e.address.city))].sort()
: [];
const types = [...new Set(myEvents.map(e => e.type))].sort(); const types = [...new Set(myEvents.map(e => e.type))].sort();
return { availableStates: states, availableCities: cities, availableTypes: types }; return { availableStates: states, availableCities: cities, availableTypes: types };
}, [myEvents, advancedFilters.state]); }, [myEvents]);
// Helper function to check time range // Helper function to check time range
const isInTimeRange = (time: string, range: string): boolean => { const isInTimeRange = (time: string, range: string): boolean => {
@ -451,6 +448,27 @@ export const Dashboard: React.FC<DashboardProps> = ({
{institution.type} {institution.type}
</p> </p>
{/* Course Information */}
{selectedEvent.courseId &&
(() => {
const course = getActiveCoursesByInstitutionId(
selectedEvent.institutionId
).find((c) => c.id === selectedEvent.courseId);
if (course) {
return (
<div className="bg-brand-gold/10 border border-brand-gold/30 rounded px-3 py-2 mb-3">
<p className="text-xs text-gray-500 uppercase tracking-wide mb-0.5">
Curso/Turma
</p>
<p className="text-sm font-semibold text-brand-black">
{course.name} - {course.graduationType} ({course.year}/{course.semester}º sem)
</p>
</div>
);
}
return null;
})()}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm">
<div> <div>
<p className="text-gray-500 text-xs uppercase tracking-wide"> <p className="text-gray-500 text-xs uppercase tracking-wide">

View file

@ -202,47 +202,49 @@ export const TeamPage: React.FC = () => {
const [selectedProfessional, setSelectedProfessional] = const [selectedProfessional, setSelectedProfessional] =
useState<Professional | null>(null); useState<Professional | null>(null);
const [showAddModal, setShowAddModal] = useState(false); const [showAddModal, setShowAddModal] = useState(false);
const [newProfessional, setNewProfessional] = useState<Partial<Professional>>({ const [newProfessional, setNewProfessional] = useState<Partial<Professional>>(
name: "", {
role: "Fotógrafo", name: "",
address: { role: "Fotógrafo",
street: "", address: {
number: "", street: "",
complement: "", number: "",
neighborhood: "", complement: "",
city: "", neighborhood: "",
state: "", city: "",
}, state: "",
whatsapp: "", },
cpfCnpj: "", whatsapp: "",
bankInfo: { cpfCnpj: "",
bank: "", bankInfo: {
agency: "", bank: "",
accountPix: "", agency: "",
}, accountPix: "",
hasCar: false, },
hasStudio: false, hasCar: false,
studioQuantity: 0, hasStudio: false,
cardType: "", studioQuantity: 0,
accountHolder: "", cardType: "",
observations: "", accountHolder: "",
ratings: { observations: "",
technicalQuality: 0, ratings: {
appearance: 0, technicalQuality: 0,
education: 0, appearance: 0,
sympathy: 0, education: 0,
eventPerformance: 0, sympathy: 0,
scheduleAvailability: 0, eventPerformance: 0,
average: 0, scheduleAvailability: 0,
}, average: 0,
freeTable: "", },
extraFee: "", freeTable: "",
email: "", extraFee: "",
specialties: [], email: "",
avatar: "", specialties: [],
}); avatar: "",
}
);
const [avatarFile, setAvatarFile] = useState<File | null>(null); const [avatarFile, setAvatarFile] = useState<File | null>(null);
const [avatarPreview, setAvatarPreview] = useState<string>(""); const [avatarPreview, setAvatarPreview] = useState<string>("");
@ -300,16 +302,21 @@ export const TeamPage: React.FC = () => {
const matchesSearch = const matchesSearch =
professional.name.toLowerCase().includes(searchTerm.toLowerCase()) || professional.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
professional.email.toLowerCase().includes(searchTerm.toLowerCase()); professional.email.toLowerCase().includes(searchTerm.toLowerCase());
const matchesRole = roleFilter === "all" || professional.role === roleFilter; const matchesRole =
roleFilter === "all" || professional.role === roleFilter;
const matchesStatus = const matchesStatus =
statusFilter === "all" || professional.status === statusFilter; statusFilter === "all" || professional.status === statusFilter;
return matchesSearch && matchesRole && matchesStatus; return matchesSearch && matchesRole && matchesStatus;
}); });
const stats = { const stats = {
photographers: MOCK_PROFESSIONALS.filter((p) => p.role === "Fotógrafo").length, photographers: MOCK_PROFESSIONALS.filter((p) => p.role === "Fotógrafo")
cinematographers: MOCK_PROFESSIONALS.filter((p) => p.role === "Cinegrafista").length, .length,
receptionists: MOCK_PROFESSIONALS.filter((p) => p.role === "Recepcionista").length, cinematographers: MOCK_PROFESSIONALS.filter(
(p) => p.role === "Cinegrafista"
).length,
receptionists: MOCK_PROFESSIONALS.filter((p) => p.role === "Recepcionista")
.length,
total: MOCK_PROFESSIONALS.length, total: MOCK_PROFESSIONALS.length,
}; };
@ -344,7 +351,9 @@ export const TeamPage: React.FC = () => {
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6"> <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<p className="text-sm text-gray-600 mb-1">Total de Cinegrafistas</p> <p className="text-sm text-gray-600 mb-1">
Total de Cinegrafistas
</p>
<p className="text-3xl font-bold text-brand-black"> <p className="text-3xl font-bold text-brand-black">
{stats.cinematographers} {stats.cinematographers}
</p> </p>
@ -355,7 +364,9 @@ export const TeamPage: React.FC = () => {
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6"> <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<p className="text-sm text-gray-600 mb-1">Total de Recepcionistas</p> <p className="text-sm text-gray-600 mb-1">
Total de Recepcionistas
</p>
<p className="text-3xl font-bold text-brand-black"> <p className="text-3xl font-bold text-brand-black">
{stats.receptionists} {stats.receptionists}
</p> </p>
@ -606,7 +617,10 @@ export const TeamPage: React.FC = () => {
required required
value={newProfessional.name} value={newProfessional.name}
onChange={(e) => onChange={(e) =>
setNewProfessional({ ...newProfessional, name: e.target.value }) setNewProfessional({
...newProfessional,
name: e.target.value,
})
} }
placeholder="Nome completo" 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" className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
@ -645,7 +659,10 @@ export const TeamPage: React.FC = () => {
required required
value={newProfessional.email} value={newProfessional.email}
onChange={(e) => onChange={(e) =>
setNewProfessional({ ...newProfessional, email: e.target.value }) setNewProfessional({
...newProfessional,
email: e.target.value,
})
} }
placeholder="email@exemplo.com" 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" className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
@ -661,7 +678,10 @@ export const TeamPage: React.FC = () => {
required required
value={newProfessional.whatsapp} value={newProfessional.whatsapp}
onChange={(e) => onChange={(e) =>
setNewProfessional({ ...newProfessional, whatsapp: e.target.value }) setNewProfessional({
...newProfessional,
whatsapp: e.target.value,
})
} }
placeholder="(00) 00000-0000" 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" className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
@ -678,7 +698,10 @@ export const TeamPage: React.FC = () => {
required required
value={newProfessional.cpfCnpj} value={newProfessional.cpfCnpj}
onChange={(e) => onChange={(e) =>
setNewProfessional({ ...newProfessional, cpfCnpj: e.target.value }) setNewProfessional({
...newProfessional,
cpfCnpj: e.target.value,
})
} }
placeholder="000.000.000-00 ou 00.000.000/0000-00" 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" className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
@ -704,7 +727,10 @@ export const TeamPage: React.FC = () => {
onChange={(e) => onChange={(e) =>
setNewProfessional({ setNewProfessional({
...newProfessional, ...newProfessional,
address: { ...newProfessional.address!, street: e.target.value }, address: {
...newProfessional.address!,
street: e.target.value,
},
}) })
} }
placeholder="Nome da rua" placeholder="Nome da rua"
@ -723,7 +749,10 @@ export const TeamPage: React.FC = () => {
onChange={(e) => onChange={(e) =>
setNewProfessional({ setNewProfessional({
...newProfessional, ...newProfessional,
address: { ...newProfessional.address!, number: e.target.value }, address: {
...newProfessional.address!,
number: e.target.value,
},
}) })
} }
placeholder="123" placeholder="123"
@ -743,7 +772,10 @@ export const TeamPage: React.FC = () => {
onChange={(e) => onChange={(e) =>
setNewProfessional({ setNewProfessional({
...newProfessional, ...newProfessional,
address: { ...newProfessional.address!, complement: e.target.value }, address: {
...newProfessional.address!,
complement: e.target.value,
},
}) })
} }
placeholder="Apto, Sala, etc" placeholder="Apto, Sala, etc"
@ -762,7 +794,10 @@ export const TeamPage: React.FC = () => {
onChange={(e) => onChange={(e) =>
setNewProfessional({ setNewProfessional({
...newProfessional, ...newProfessional,
address: { ...newProfessional.address!, neighborhood: e.target.value }, address: {
...newProfessional.address!,
neighborhood: e.target.value,
},
}) })
} }
placeholder="Nome do bairro" placeholder="Nome do bairro"
@ -783,7 +818,10 @@ export const TeamPage: React.FC = () => {
onChange={(e) => onChange={(e) =>
setNewProfessional({ setNewProfessional({
...newProfessional, ...newProfessional,
address: { ...newProfessional.address!, city: e.target.value }, address: {
...newProfessional.address!,
city: e.target.value,
},
}) })
} }
placeholder="Nome da cidade" placeholder="Nome da cidade"
@ -803,7 +841,10 @@ export const TeamPage: React.FC = () => {
onChange={(e) => onChange={(e) =>
setNewProfessional({ setNewProfessional({
...newProfessional, ...newProfessional,
address: { ...newProfessional.address!, state: e.target.value.toUpperCase() }, address: {
...newProfessional.address!,
state: e.target.value.toUpperCase(),
},
}) })
} }
placeholder="SP" placeholder="SP"
@ -831,7 +872,10 @@ export const TeamPage: React.FC = () => {
onChange={(e) => onChange={(e) =>
setNewProfessional({ setNewProfessional({
...newProfessional, ...newProfessional,
bankInfo: { ...newProfessional.bankInfo!, bank: e.target.value }, bankInfo: {
...newProfessional.bankInfo!,
bank: e.target.value,
},
}) })
} }
placeholder="Nome do banco" placeholder="Nome do banco"
@ -850,7 +894,10 @@ export const TeamPage: React.FC = () => {
onChange={(e) => onChange={(e) =>
setNewProfessional({ setNewProfessional({
...newProfessional, ...newProfessional,
bankInfo: { ...newProfessional.bankInfo!, agency: e.target.value }, bankInfo: {
...newProfessional.bankInfo!,
agency: e.target.value,
},
}) })
} }
placeholder="0000-0" placeholder="0000-0"
@ -869,7 +916,10 @@ export const TeamPage: React.FC = () => {
onChange={(e) => onChange={(e) =>
setNewProfessional({ setNewProfessional({
...newProfessional, ...newProfessional,
bankInfo: { ...newProfessional.bankInfo!, accountPix: e.target.value }, bankInfo: {
...newProfessional.bankInfo!,
accountPix: e.target.value,
},
}) })
} }
placeholder="Conta ou chave Pix" placeholder="Conta ou chave Pix"
@ -888,7 +938,10 @@ export const TeamPage: React.FC = () => {
required required
value={newProfessional.cardType} value={newProfessional.cardType}
onChange={(e) => onChange={(e) =>
setNewProfessional({ ...newProfessional, cardType: e.target.value }) setNewProfessional({
...newProfessional,
cardType: e.target.value,
})
} }
placeholder="Débito, Crédito, Pix, etc" 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" className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
@ -904,7 +957,10 @@ export const TeamPage: React.FC = () => {
required required
value={newProfessional.accountHolder} value={newProfessional.accountHolder}
onChange={(e) => onChange={(e) =>
setNewProfessional({ ...newProfessional, accountHolder: e.target.value }) setNewProfessional({
...newProfessional,
accountHolder: e.target.value,
})
} }
placeholder="Nome do titular" 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" className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
@ -926,7 +982,10 @@ export const TeamPage: React.FC = () => {
type="checkbox" type="checkbox"
checked={newProfessional.hasCar} checked={newProfessional.hasCar}
onChange={(e) => onChange={(e) =>
setNewProfessional({ ...newProfessional, hasCar: e.target.checked }) setNewProfessional({
...newProfessional,
hasCar: e.target.checked,
})
} }
className="w-4 h-4 text-brand-gold focus:ring-brand-gold border-gray-300 rounded" className="w-4 h-4 text-brand-gold focus:ring-brand-gold border-gray-300 rounded"
/> />
@ -942,7 +1001,10 @@ export const TeamPage: React.FC = () => {
type="checkbox" type="checkbox"
checked={newProfessional.hasStudio} checked={newProfessional.hasStudio}
onChange={(e) => onChange={(e) =>
setNewProfessional({ ...newProfessional, hasStudio: e.target.checked }) setNewProfessional({
...newProfessional,
hasStudio: e.target.checked,
})
} }
className="w-4 h-4 text-brand-gold focus:ring-brand-gold border-gray-300 rounded" className="w-4 h-4 text-brand-gold focus:ring-brand-gold border-gray-300 rounded"
/> />
@ -1096,7 +1158,10 @@ export const TeamPage: React.FC = () => {
required required
value={newProfessional.freeTable} value={newProfessional.freeTable}
onChange={(e) => onChange={(e) =>
setNewProfessional({ ...newProfessional, freeTable: e.target.value }) setNewProfessional({
...newProfessional,
freeTable: e.target.value,
})
} }
placeholder="R$ 800,00" 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" className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
@ -1111,7 +1176,10 @@ export const TeamPage: React.FC = () => {
type="text" type="text"
value={newProfessional.extraFee} value={newProfessional.extraFee}
onChange={(e) => onChange={(e) =>
setNewProfessional({ ...newProfessional, extraFee: e.target.value }) setNewProfessional({
...newProfessional,
extraFee: e.target.value,
})
} }
placeholder="R$ 150,00" 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" className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold"
@ -1128,7 +1196,10 @@ export const TeamPage: React.FC = () => {
<textarea <textarea
value={newProfessional.observations} value={newProfessional.observations}
onChange={(e) => onChange={(e) =>
setNewProfessional({ ...newProfessional, observations: e.target.value }) setNewProfessional({
...newProfessional,
observations: e.target.value,
})
} }
placeholder="Qualquer tipo de observação relevante" placeholder="Qualquer tipo de observação relevante"
rows={4} rows={4}
@ -1178,7 +1249,9 @@ export const TeamPage: React.FC = () => {
<h2 className="text-2xl font-serif font-bold text-brand-black mb-1"> <h2 className="text-2xl font-serif font-bold text-brand-black mb-1">
{selectedProfessional.name} {selectedProfessional.name}
</h2> </h2>
<p className="text-gray-600 mb-2">{selectedProfessional.role}</p> <p className="text-gray-600 mb-2">
{selectedProfessional.role}
</p>
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<Star <Star
size={18} size={18}
@ -1189,7 +1262,8 @@ export const TeamPage: React.FC = () => {
{selectedProfessional.ratings.average.toFixed(1)} {selectedProfessional.ratings.average.toFixed(1)}
</span> </span>
<span className="text-sm text-gray-500"> <span className="text-sm text-gray-500">
({selectedProfessional.eventsCompleted} eventos concluídos) ({selectedProfessional.eventsCompleted} eventos
concluídos)
</span> </span>
</div> </div>
<span <span
@ -1214,7 +1288,9 @@ export const TeamPage: React.FC = () => {
<div className="space-y-6"> <div className="space-y-6">
{/* Contato */} {/* Contato */}
<div> <div>
<h3 className="font-semibold text-lg mb-3 text-brand-black">Contato</h3> <h3 className="font-semibold text-lg mb-3 text-brand-black">
Contato
</h3>
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center text-gray-700"> <div className="flex items-center text-gray-700">
<Mail size={18} className="mr-3 text-brand-gold" /> <Mail size={18} className="mr-3 text-brand-gold" />
@ -1227,10 +1303,14 @@ export const TeamPage: React.FC = () => {
<div className="flex items-start text-gray-700"> <div className="flex items-start text-gray-700">
<MapPin size={18} className="mr-3 text-brand-gold mt-1" /> <MapPin size={18} className="mr-3 text-brand-gold mt-1" />
<span> <span>
{selectedProfessional.address.street}, {selectedProfessional.address.number} {selectedProfessional.address.street},{" "}
{selectedProfessional.address.complement && ` - ${selectedProfessional.address.complement}`} {selectedProfessional.address.number}
{selectedProfessional.address.complement &&
` - ${selectedProfessional.address.complement}`}
<br /> <br />
{selectedProfessional.address.neighborhood}, {selectedProfessional.address.city} - {selectedProfessional.address.state} {selectedProfessional.address.neighborhood},{" "}
{selectedProfessional.address.city} -{" "}
{selectedProfessional.address.state}
</span> </span>
</div> </div>
</div> </div>
@ -1238,7 +1318,9 @@ export const TeamPage: React.FC = () => {
{/* Recursos */} {/* Recursos */}
<div> <div>
<h3 className="font-semibold text-lg mb-3 text-brand-black">Recursos</h3> <h3 className="font-semibold text-lg mb-3 text-brand-black">
Recursos
</h3>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{selectedProfessional.hasCar && ( {selectedProfessional.hasCar && (
<span className="flex items-center gap-2 px-3 py-1 bg-green-100 text-green-700 rounded-full text-sm"> <span className="flex items-center gap-2 px-3 py-1 bg-green-100 text-green-700 rounded-full text-sm">
@ -1257,13 +1339,19 @@ export const TeamPage: React.FC = () => {
{/* Avaliações */} {/* Avaliações */}
<div> <div>
<h3 className="font-semibold text-lg mb-3 text-brand-black">Avaliações</h3> <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"> <div className="grid grid-cols-2 md:grid-cols-3 gap-3">
{selectedProfessional.role !== "Recepcionista" && ( {selectedProfessional.role !== "Recepcionista" && (
<div className="bg-gray-50 p-3 rounded-lg"> <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-xs text-gray-600 mb-1">
Qual. Técnica
</p>
<p className="text-lg font-semibold text-brand-gold"> <p className="text-lg font-semibold text-brand-gold">
{selectedProfessional.ratings.technicalQuality.toFixed(1)} {selectedProfessional.ratings.technicalQuality.toFixed(
1
)}
</p> </p>
</div> </div>
)} )}
@ -1286,9 +1374,13 @@ export const TeamPage: React.FC = () => {
</p> </p>
</div> </div>
<div className="bg-gray-50 p-3 rounded-lg"> <div className="bg-gray-50 p-3 rounded-lg">
<p className="text-xs text-gray-600 mb-1">Disponibilidade</p> <p className="text-xs text-gray-600 mb-1">
Disponibilidade
</p>
<p className="text-lg font-semibold text-brand-gold"> <p className="text-lg font-semibold text-brand-gold">
{selectedProfessional.ratings.scheduleAvailability.toFixed(1)} {selectedProfessional.ratings.scheduleAvailability.toFixed(
1
)}
</p> </p>
</div> </div>
</div> </div>
@ -1296,7 +1388,9 @@ export const TeamPage: React.FC = () => {
{/* Valores */} {/* Valores */}
<div> <div>
<h3 className="font-semibold text-lg mb-3 text-brand-black">Valores</h3> <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="grid grid-cols-1 md:grid-cols-2 gap-3">
<div className="bg-gray-50 p-3 rounded-lg"> <div className="bg-gray-50 p-3 rounded-lg">
<p className="text-xs text-gray-600 mb-1">Tabela Free</p> <p className="text-xs text-gray-600 mb-1">Tabela Free</p>
@ -1305,7 +1399,9 @@ export const TeamPage: React.FC = () => {
</p> </p>
</div> </div>
<div className="bg-gray-50 p-3 rounded-lg"> <div className="bg-gray-50 p-3 rounded-lg">
<p className="text-xs text-gray-600 mb-1">Extra/Equipamento</p> <p className="text-xs text-gray-600 mb-1">
Extra/Equipamento
</p>
<p className="text-lg font-semibold text-brand-black"> <p className="text-lg font-semibold text-brand-black">
{selectedProfessional.extraFee} {selectedProfessional.extraFee}
</p> </p>
@ -1316,7 +1412,9 @@ export const TeamPage: React.FC = () => {
{/* Observações */} {/* Observações */}
{selectedProfessional.observations && ( {selectedProfessional.observations && (
<div> <div>
<h3 className="font-semibold text-lg mb-2 text-brand-black">Observações</h3> <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"> <p className="text-gray-700 bg-gray-50 p-3 rounded-lg">
{selectedProfessional.observations} {selectedProfessional.observations}
</p> </p>