diff --git a/frontend/App.tsx b/frontend/App.tsx index 0cc417c..99e9e8f 100644 --- a/frontend/App.tsx +++ b/frontend/App.tsx @@ -89,8 +89,8 @@ const AppContent: React.FC = () => { className="h-24 sm:h-28 md:h-32 lg:h-36 mb-4 md:mb-6" />

- Eternizando momentos únicos com excelência e profissionalismo - desde 2020. + Eternizando momentos únicos com excelência e + profissionalismo desde 2020.

diff --git a/frontend/components/CourseForm.tsx b/frontend/components/CourseForm.tsx index 32d57b4..85b9f27 100644 --- a/frontend/components/CourseForm.tsx +++ b/frontend/components/CourseForm.tsx @@ -1,8 +1,8 @@ -import React, { useState } from 'react'; -import { Course, Institution } from '../types'; -import { Input, Select } from './Input'; -import { Button } from './Button'; -import { GraduationCap, X, Check, AlertCircle } from 'lucide-react'; +import React, { useState } from "react"; +import { Course, Institution } from "../types"; +import { Input, Select } from "./Input"; +import { Button } from "./Button"; +import { GraduationCap, X, Check, AlertCircle } from "lucide-react"; interface CourseFormProps { onCancel: () => void; @@ -13,52 +13,54 @@ interface CourseFormProps { } const GRADUATION_TYPES = [ - 'Bacharelado', - 'Licenciatura', - 'Tecnológico', - 'Especialização', - 'Mestrado', - 'Doutorado' + "Bacharelado", + "Licenciatura", + "Tecnológico", + "Especialização", + "Mestrado", + "Doutorado", ]; -export const CourseForm: React.FC = ({ - onCancel, - onSubmit, +export const CourseForm: React.FC = ({ + onCancel, + onSubmit, initialData, userId, - institutions + institutions, }) => { const currentYear = new Date().getFullYear(); - const [formData, setFormData] = useState>(initialData || { - name: '', - institutionId: '', - year: currentYear, - semester: 1, - graduationType: '', - createdAt: new Date().toISOString(), - createdBy: userId, - isActive: true, - }); + const [formData, setFormData] = useState>( + initialData || { + name: "", + institutionId: "", + year: currentYear, + semester: 1, + graduationType: "", + createdAt: new Date().toISOString(), + createdBy: userId, + isActive: true, + } + ); const [showToast, setShowToast] = useState(false); - const [error, setError] = useState(''); + const [error, setError] = useState(""); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - + // Validações if (!formData.name || formData.name.trim().length < 3) { - setError('Nome do curso deve ter pelo menos 3 caracteres'); + setError("Nome do curso deve ter pelo menos 3 caracteres"); return; } - + if (!formData.institutionId) { - setError('Selecione uma universidade'); + setError("Selecione uma universidade"); return; } - + if (!formData.graduationType) { - setError('Selecione o tipo de graduação'); + setError("Selecione o tipo de graduação"); return; } @@ -69,20 +71,21 @@ export const CourseForm: React.FC = ({ }; const handleChange = (field: keyof Course, value: any) => { - setFormData(prev => ({ ...prev, [field]: value })); - setError(''); // Limpa erro ao editar + setFormData((prev) => ({ ...prev, [field]: value })); + setError(""); // Limpa erro ao editar }; return (
- {/* Success Toast */} {showToast && (

Sucesso!

-

Curso cadastrado com sucesso.

+

+ Curso cadastrado com sucesso. +

)} @@ -93,7 +96,7 @@ export const CourseForm: React.FC = ({

- {initialData ? 'Editar Curso/Turma' : 'Cadastrar Curso/Turma'} + {initialData ? "Editar Curso/Turma" : "Cadastrar Curso/Turma"}

Registre as turmas disponíveis para eventos fotográficos @@ -109,11 +112,13 @@ export const CourseForm: React.FC = ({

- {/* Erro global */} {error && (
- +

{error}

)} @@ -123,23 +128,23 @@ export const CourseForm: React.FC = ({

Informações do Curso

- + handleChange('name', e.target.value)} + value={formData.name || ""} + onChange={(e) => handleChange("name", e.target.value)} required /> @@ -149,27 +154,29 @@ export const CourseForm: React.FC = ({ type="number" placeholder={currentYear.toString()} value={formData.year || currentYear} - onChange={(e) => handleChange('year', parseInt(e.target.value))} + onChange={(e) => handleChange("year", parseInt(e.target.value))} min={currentYear - 1} max={currentYear + 5} required /> - + ({ value: t, label: t }))} - value={formData.graduationType || ''} - onChange={(e) => handleChange('graduationType', e.target.value)} + options={GRADUATION_TYPES.map((t) => ({ value: t, label: t }))} + value={formData.graduationType || ""} + onChange={(e) => handleChange("graduationType", e.target.value)} required />
@@ -180,7 +187,7 @@ export const CourseForm: React.FC = ({ type="checkbox" id="isActive" checked={formData.isActive !== false} - onChange={(e) => handleChange('isActive', e.target.checked)} + onChange={(e) => handleChange("isActive", e.target.checked)} className="w-4 h-4 text-brand-gold border-gray-300 rounded focus:ring-brand-gold" />
- + setProfileData({ ...profileData, name: e.target.value })} + onChange={(e) => + setProfileData({ ...profileData, name: e.target.value }) + } className="w-full pl-11 pr-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#492E61] focus:border-transparent transition-all" placeholder="Seu nome completo" required @@ -608,11 +688,19 @@ export const Navbar: React.FC = ({ onNavigate, currentPage }) => { Email
- + setProfileData({ ...profileData, email: e.target.value })} + onChange={(e) => + setProfileData({ + ...profileData, + email: e.target.value, + }) + } className="w-full pl-11 pr-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#492E61] focus:border-transparent transition-all" placeholder="seu@email.com" required @@ -626,11 +714,19 @@ export const Navbar: React.FC = ({ onNavigate, currentPage }) => { Telefone
- + setProfileData({ ...profileData, phone: e.target.value })} + onChange={(e) => + setProfileData({ + ...profileData, + phone: e.target.value, + }) + } className="w-full pl-11 pr-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#492E61] focus:border-transparent transition-all" placeholder="(00) 00000-0000" /> @@ -656,8 +752,7 @@ export const Navbar: React.FC = ({ onNavigate, currentPage }) => {
- ) - } + )} ); }; diff --git a/frontend/contexts/DataContext.tsx b/frontend/contexts/DataContext.tsx index 4c0da99..f1b4e00 100644 --- a/frontend/contexts/DataContext.tsx +++ b/frontend/contexts/DataContext.tsx @@ -1,5 +1,11 @@ import React, { createContext, useContext, useState, ReactNode } from "react"; -import { EventData, EventStatus, EventType, Institution, Course } from "../types"; +import { + EventData, + EventStatus, + EventType, + Institution, + Course, +} from "../types"; // Initial Mock Data const INITIAL_INSTITUTIONS: Institution[] = [ @@ -37,7 +43,8 @@ const INITIAL_EVENTS: EventData[] = [ state: "RS", zip: "95670-000", }, - briefing: "Cerimônia de formatura com 120 formandos. Foco em fotos individuais e da turma.", + briefing: + "Cerimônia de formatura com 120 formandos. Foco em fotos individuais e da turma.", coverImage: "https://picsum.photos/id/1059/800/400", contacts: [ { @@ -67,7 +74,8 @@ const INITIAL_EVENTS: EventData[] = [ state: "SP", zip: "04551-000", }, - briefing: "Colação de grau solene. Capturar juramento e entrega de diplomas.", + briefing: + "Colação de grau solene. Capturar juramento e entrega de diplomas.", coverImage: "https://picsum.photos/id/3/800/400", contacts: [ { @@ -117,7 +125,8 @@ const INITIAL_EVENTS: EventData[] = [ state: "RS", zip: "90035-003", }, - briefing: "Defesa de tese em sala fechada. Fotos discretas da apresentação e banca.", + briefing: + "Defesa de tese em sala fechada. Fotos discretas da apresentação e banca.", coverImage: "https://picsum.photos/id/20/800/400", contacts: [ { @@ -196,7 +205,8 @@ const INITIAL_EVENTS: EventData[] = [ state: "SP", zip: "04578-000", }, - briefing: "Congresso com múltiplas salas. Cobrir palestrantes principais e stands.", + briefing: + "Congresso com múltiplas salas. Cobrir palestrantes principais e stands.", coverImage: "https://picsum.photos/id/50/800/400", contacts: [ { @@ -275,7 +285,8 @@ const INITIAL_EVENTS: EventData[] = [ state: "SP", zip: "01045-000", }, - briefing: "Festival com apresentações musicais e teatrais. Cobertura completa.", + briefing: + "Festival com apresentações musicais e teatrais. Cobertura completa.", coverImage: "https://picsum.photos/id/80/800/400", contacts: [], checklist: [], @@ -296,7 +307,8 @@ const INITIAL_EVENTS: EventData[] = [ state: "RS", zip: "91509-900", }, - briefing: "Defesa de dissertação. Registro da apresentação e momento da aprovação.", + briefing: + "Defesa de dissertação. Registro da apresentação e momento da aprovação.", coverImage: "https://picsum.photos/id/90/800/400", contacts: [], checklist: [], @@ -438,7 +450,8 @@ const INITIAL_EVENTS: EventData[] = [ state: "RS", zip: "95670-200", }, - briefing: "Formatura elegante em hotel. Cobertura completa da cerimônia e recepção.", + briefing: + "Formatura elegante em hotel. Cobertura completa da cerimônia e recepção.", coverImage: "https://picsum.photos/id/150/800/400", contacts: [ { @@ -467,7 +480,8 @@ const INITIAL_EVENTS: EventData[] = [ state: "RS", zip: "90540-000", }, - briefing: "Múltiplas defesas sequenciais. Fotos rápidas de cada apresentação.", + briefing: + "Múltiplas defesas sequenciais. Fotos rápidas de cada apresentação.", coverImage: "https://picsum.photos/id/160/800/400", contacts: [], checklist: [], @@ -488,7 +502,8 @@ const INITIAL_EVENTS: EventData[] = [ state: "RS", zip: "90040-000", }, - briefing: "Festival ao ar livre com várias bandas. Fotos de palco e público.", + briefing: + "Festival ao ar livre com várias bandas. Fotos de palco e público.", coverImage: "https://picsum.photos/id/170/800/400", contacts: [], checklist: [], @@ -651,7 +666,9 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({ const updateCourse = (id: string, updatedData: Partial) => { setCourses((prev) => - prev.map((course) => (course.id === id ? { ...course, ...updatedData } : course)) + prev.map((course) => + course.id === id ? { ...course, ...updatedData } : course + ) ); }; diff --git a/frontend/pages/CourseManagement.tsx b/frontend/pages/CourseManagement.tsx index ce4a20b..b858e12 100644 --- a/frontend/pages/CourseManagement.tsx +++ b/frontend/pages/CourseManagement.tsx @@ -1,41 +1,62 @@ -import React, { useState } from 'react'; -import { useData } from '../contexts/DataContext'; -import { useAuth } from '../contexts/AuthContext'; -import { UserRole, Course } from '../types'; -import { CourseForm } from '../components/CourseForm'; -import { GraduationCap, Plus, Building2, ChevronRight, Edit, Trash2, CheckCircle, XCircle } from 'lucide-react'; -import { Button } from '../components/Button'; +import React, { useState } from "react"; +import { useData } from "../contexts/DataContext"; +import { useAuth } from "../contexts/AuthContext"; +import { UserRole, Course } from "../types"; +import { CourseForm } from "../components/CourseForm"; +import { + GraduationCap, + Plus, + Building2, + ChevronRight, + Edit, + Trash2, + CheckCircle, + XCircle, +} from "lucide-react"; +import { Button } from "../components/Button"; export const CourseManagement: React.FC = () => { const { user } = useAuth(); - const { - institutions, - courses, - addCourse, + const { + institutions, + courses, + addCourse, updateCourse, - getCoursesByInstitutionId + getCoursesByInstitutionId, } = useData(); - - const [selectedInstitution, setSelectedInstitution] = useState(null); + + const [selectedInstitution, setSelectedInstitution] = useState( + null + ); const [showCourseForm, setShowCourseForm] = useState(false); - const [editingCourse, setEditingCourse] = useState(undefined); + const [editingCourse, setEditingCourse] = useState( + undefined + ); // Verificar se é admin - const isAdmin = user?.role === UserRole.SUPERADMIN || user?.role === UserRole.BUSINESS_OWNER; + const isAdmin = + user?.role === UserRole.SUPERADMIN || + user?.role === UserRole.BUSINESS_OWNER; if (!isAdmin) { return (

Acesso Negado

-

Apenas administradores podem acessar esta página.

+

+ Apenas administradores podem acessar esta página. +

); } - const selectedInstitutionData = institutions.find(inst => inst.id === selectedInstitution); - const institutionCourses = selectedInstitution ? getCoursesByInstitutionId(selectedInstitution) : []; + const selectedInstitutionData = institutions.find( + (inst) => inst.id === selectedInstitution + ); + const institutionCourses = selectedInstitution + ? getCoursesByInstitutionId(selectedInstitution) + : []; const handleAddCourse = () => { setEditingCourse(undefined); @@ -59,7 +80,7 @@ export const CourseManagement: React.FC = () => { semester: courseData.semester, graduationType: courseData.graduationType!, createdAt: new Date().toISOString(), - createdBy: user?.id || '', + createdBy: user?.id || "", isActive: courseData.isActive !== false, }; addCourse(newCourse); @@ -83,7 +104,8 @@ export const CourseManagement: React.FC = () => {

- Cadastre e gerencie os cursos/turmas disponíveis em cada universidade + Cadastre e gerencie os cursos/turmas disponíveis em cada + universidade

@@ -97,7 +119,7 @@ export const CourseManagement: React.FC = () => { }} onSubmit={handleSubmitCourse} initialData={editingCourse} - userId={user?.id || ''} + userId={user?.id || ""} institutions={institutions} /> @@ -115,7 +137,8 @@ export const CourseManagement: React.FC = () => { - {institutions.length} {institutions.length === 1 ? 'instituição' : 'instituições'} + {institutions.length}{" "} + {institutions.length === 1 ? "instituição" : "instituições"} @@ -141,29 +164,41 @@ export const CourseManagement: React.FC = () => { {institutions.length === 0 ? ( - + Nenhuma universidade cadastrada ) : ( institutions.map((institution) => { - const coursesCount = getCoursesByInstitutionId(institution.id).length; + const coursesCount = getCoursesByInstitutionId( + institution.id + ).length; const isSelected = selectedInstitution === institution.id; return ( setSelectedInstitution(isSelected ? null : institution.id)} + onClick={() => + setSelectedInstitution( + isSelected ? null : institution.id + ) + } >
{institution.name}
- {institution.address?.city}, {institution.address?.state} + {institution.address?.city},{" "} + {institution.address?.state}
@@ -172,20 +207,24 @@ export const CourseManagement: React.FC = () => { - 0 - ? 'bg-green-100 text-green-700' - : 'bg-gray-100 text-gray-500' - }`}> + 0 + ? "bg-green-100 text-green-700" + : "bg-gray-100 text-gray-500" + }`} + > {coursesCount} - @@ -211,7 +250,10 @@ export const CourseManagement: React.FC = () => { {selectedInstitutionData && (

- {institutionCourses.length} {institutionCourses.length === 1 ? 'curso cadastrado' : 'cursos cadastrados'} + {institutionCourses.length}{" "} + {institutionCourses.length === 1 + ? "curso cadastrado" + : "cursos cadastrados"}

)} @@ -293,13 +335,20 @@ export const CourseManagement: React.FC = () => { > {course.isActive ? ( <> - - Ativo + + + Ativo + ) : ( <> - Inativo + + Inativo + )} diff --git a/frontend/pages/Settings.tsx b/frontend/pages/Settings.tsx index ed2245d..fcb620d 100644 --- a/frontend/pages/Settings.tsx +++ b/frontend/pages/Settings.tsx @@ -1,534 +1,649 @@ -import React, { useState } from 'react'; -import { User, Mail, Phone, MapPin, Lock, Bell, Palette, Globe, Save, Camera, GraduationCap } from 'lucide-react'; -import { Button } from '../components/Button'; -import { useAuth } from '../contexts/AuthContext'; -import { UserRole } from '../types'; +import React, { useState } from "react"; +import { + User, + Mail, + Phone, + MapPin, + Lock, + Bell, + Palette, + Globe, + Save, + Camera, + GraduationCap, +} from "lucide-react"; +import { Button } from "../components/Button"; +import { useAuth } from "../contexts/AuthContext"; +import { UserRole } from "../types"; export const SettingsPage: React.FC = () => { - const { user } = useAuth(); - const isAdmin = user?.role === UserRole.SUPERADMIN || user?.role === UserRole.BUSINESS_OWNER; - const [activeTab, setActiveTab] = useState<'profile' | 'account' | 'notifications' | 'appearance' | 'courses'>('profile'); - const [profileData, setProfileData] = useState({ - name: 'João Silva', - email: 'joao.silva@photum.com', - phone: '(41) 99999-0000', - location: 'Curitiba, PR', - bio: 'Fotógrafo profissional especializado em eventos e formaturas há mais de 10 anos.', - avatar: 'https://i.pravatar.cc/150?img=68' - }); + const { user } = useAuth(); + const isAdmin = + user?.role === UserRole.SUPERADMIN || + user?.role === UserRole.BUSINESS_OWNER; + const [activeTab, setActiveTab] = useState< + "profile" | "account" | "notifications" | "appearance" | "courses" + >("profile"); + const [profileData, setProfileData] = useState({ + name: "João Silva", + email: "joao.silva@photum.com", + phone: "(41) 99999-0000", + location: "Curitiba, PR", + bio: "Fotógrafo profissional especializado em eventos e formaturas há mais de 10 anos.", + avatar: "https://i.pravatar.cc/150?img=68", + }); - const [notificationSettings, setNotificationSettings] = useState({ - emailNotifications: true, - pushNotifications: true, - smsNotifications: false, - eventReminders: true, - paymentAlerts: true, - teamUpdates: false - }); + const [notificationSettings, setNotificationSettings] = useState({ + emailNotifications: true, + pushNotifications: true, + smsNotifications: false, + eventReminders: true, + paymentAlerts: true, + teamUpdates: false, + }); - const [appearanceSettings, setAppearanceSettings] = useState({ - theme: 'light', - language: 'pt-BR', - dateFormat: 'DD/MM/YYYY', - currency: 'BRL' - }); + const [appearanceSettings, setAppearanceSettings] = useState({ + theme: "light", + language: "pt-BR", + dateFormat: "DD/MM/YYYY", + currency: "BRL", + }); - const handleSaveProfile = () => { - alert('Perfil atualizado com sucesso!'); - }; + const handleSaveProfile = () => { + alert("Perfil atualizado com sucesso!"); + }; - const handleSaveNotifications = () => { - alert('Configurações de notificações salvas!'); - }; + const handleSaveNotifications = () => { + alert("Configurações de notificações salvas!"); + }; - const handleSaveAppearance = () => { - alert('Configurações de aparência salvas!'); - }; + const handleSaveAppearance = () => { + alert("Configurações de aparência salvas!"); + }; - return ( -
-
- {/* Header */} -
-

- Configurações -

-

- Gerencie suas preferências e informações da conta -

-
- -
- {/* Mobile Tabs - Horizontal */} -
-
- -
-
- - {/* Desktop Sidebar - Vertical */} -
-
- -
-
- - {/* Content */} -
-
- {/* Profile Tab */} - {activeTab === 'profile' && ( -
-

Informações do Perfil

- -
-
-
- Avatar - -
-
-

{profileData.name}

-

{profileData.email}

- -
-
-
- -
-
- -
- - setProfileData({ ...profileData, name: e.target.value })} - className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold" - /> -
-
- -
- -
- - setProfileData({ ...profileData, email: e.target.value })} - className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold" - /> -
-
- -
- -
- - setProfileData({ ...profileData, phone: e.target.value })} - className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold" - /> -
-
- -
- -
- - setProfileData({ ...profileData, location: e.target.value })} - className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold" - /> -
-
- -
- -