diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0229e78 --- /dev/null +++ b/.env.example @@ -0,0 +1,12 @@ +# Configuração da API do Mapbox +# Obtenha sua chave gratuita em: https://account.mapbox.com/ + +# Para usar: +# 1. Copie este arquivo para .env.local +# 2. Substitua YOUR_MAPBOX_TOKEN_HERE pela sua chave real +# 3. Atualize o arquivo services/mapboxService.ts com sua chave + +VITE_MAPBOX_TOKEN=YOUR_MAPBOX_TOKEN_HERE + +# Nota: A chave já está configurada diretamente no código para demonstração +# Em produção, use variáveis de ambiente como acima diff --git a/App.tsx b/App.tsx index 81353b7..d29f4ea 100644 --- a/App.tsx +++ b/App.tsx @@ -8,7 +8,7 @@ import { CalendarPage } from "./pages/Calendar"; import { TeamPage } from "./pages/Team"; import { FinancePage } from "./pages/Finance"; import { SettingsPage } from "./pages/Settings"; -import { AlbumsPage } from "./pages/Albums"; +import { InspirationPage } from "./pages/Inspiration"; import { PrivacyPolicy } from "./pages/PrivacyPolicy"; import { TermsOfUse } from "./pages/TermsOfUse"; import { LGPD } from "./pages/LGPD"; @@ -47,8 +47,8 @@ const AppContent: React.FC = () => { case "request-event": return ; - case "uploads": - return ; + case "inspiration": + return ; case "calendar": return ; @@ -62,9 +62,6 @@ const AppContent: React.FC = () => { case "settings": return ; - case "albums": - return ; - default: return ; } @@ -91,10 +88,10 @@ const AppContent: React.FC = () => {

Serviços

    -
  • Fotografia de Eventos
  • -
  • Álbuns Personalizados
  • -
  • Gestão de Formaturas
  • -
  • Cobertura Completa
  • +
  • Fotografia de Formatura
  • +
  • Baile de Gala
  • +
  • Cerimônia de Colação
  • +
  • Ensaios de Turma
diff --git a/MAPBOX_SETUP.md b/MAPBOX_SETUP.md new file mode 100644 index 0000000..52a2585 --- /dev/null +++ b/MAPBOX_SETUP.md @@ -0,0 +1,72 @@ +# 🗺️ Configuração do Mapbox + +## Token do Mapbox Inválido + +O sistema está configurado para usar a API do Mapbox, mas o token atual é inválido ou expirado. + +## Como Obter um Token Válido (GRATUITO) + +### 1. Crie uma conta no Mapbox +- Acesse: **https://account.mapbox.com/** +- Clique em **"Sign up"** (ou faça login se já tiver conta) +- É **100% gratuito** para até 50.000 requisições/mês + +### 2. Acesse a página de Tokens +- Após fazer login, vá para: **https://account.mapbox.com/access-tokens/** +- Ou clique no menu em **"Access tokens"** + +### 3. Crie um novo token +- Clique em **"Create a token"** +- Dê um nome (ex: "Photum Forms") +- Selecione os escopos necessários (deixe os padrões marcados) +- Clique em **"Create token"** + +### 4. Copie o token +- O token será algo como: `pk.eyJ1IjoiZXhhbXBsZSIsImEiOiJja...` +- **COPIE O TOKEN COMPLETO** + +### 5. Configure no Projeto + +Abra o arquivo **`services/mapboxService.ts`** e substitua o token na linha 26: + +```typescript +const MAPBOX_TOKEN = 'SEU_TOKEN_AQUI'; // Cole o token do Mapbox aqui +``` + +### 6. Salve e recarregue + +Após salvar o arquivo, o Vite recarregará automaticamente e o mapa funcionará! + +--- + +## Verificação + +Se tudo estiver correto, você verá: +- ✅ "Inicializando mapa Mapbox..." no console +- ✅ "Mapa criado com sucesso" no console +- ✅ Mapa interativo carregado na tela de criação de eventos + +Se houver erro: +- ❌ Verifique se copiou o token completo (incluindo `pk.`) +- ❌ Verifique se não há espaços extras antes/depois do token +- ❌ Certifique-se de que o token não expirou + +--- + +## Recursos do Mapbox no Sistema + +- 🔍 Busca de endereços com autocomplete +- 📍 Mapa interativo com pin arrastável +- 🌍 Geocoding e Reverse Geocoding +- 🗺️ Integração com Google Maps para compartilhamento + +## Limites Gratuitos + +O plano gratuito do Mapbox inclui: +- 50.000 requisições de geocoding/mês +- 50.000 carregamentos de mapa/mês +- Mais que suficiente para este projeto! + +--- + +**Precisa de ajuda?** Acesse a documentação: https://docs.mapbox.com/api/ diff --git a/components/EventForm.tsx b/components/EventForm.tsx index 5004aa2..87f1bec 100644 --- a/components/EventForm.tsx +++ b/components/EventForm.tsx @@ -4,11 +4,12 @@ import { EventType, EventStatus, Address } from '../types'; import { Input, Select } from './Input'; import { Button } from './Button'; import { MapPin, Upload, Plus, X, Check, FileText, ExternalLink, Search, CheckCircle, Building2, AlertCircle } from 'lucide-react'; -import { searchLocationWithGemini, GeoResult } from '../services/genaiService'; +import { searchMapboxLocation, MapboxResult, reverseGeocode } from '../services/mapboxService'; import { useAuth } from '../contexts/AuthContext'; import { useData } from '../contexts/DataContext'; import { UserRole } from '../types'; import { InstitutionForm } from './InstitutionForm'; +import { MapboxMap } from './MapboxMap'; interface EventFormProps { onCancel: () => void; @@ -21,8 +22,9 @@ export const EventForm: React.FC = ({ onCancel, onSubmit, initia const { institutions, getInstitutionsByUserId, addInstitution } = useData(); const [activeTab, setActiveTab] = useState<'details' | 'location' | 'briefing' | 'files'>('details'); const [addressQuery, setAddressQuery] = useState(''); - const [addressResults, setAddressResults] = useState([]); + const [addressResults, setAddressResults] = useState([]); const [isSearching, setIsSearching] = useState(false); + const [isGeocoding, setIsGeocoding] = useState(false); const [showToast, setShowToast] = useState(false); const [showInstitutionForm, setShowInstitutionForm] = useState(false); @@ -41,7 +43,16 @@ export const EventForm: React.FC = ({ onCancel, onSubmit, initia time: '', type: '', status: EventStatus.PLANNING, - address: { street: '', number: '', city: '', state: '', zip: '', mapLink: '' } as Address, + address: { + street: '', + number: '', + city: '', + state: '', + zip: '', + lat: -23.5505, + lng: -46.6333, + mapLink: '' + } as Address, briefing: '', contacts: [{ name: '', role: '', phone: '' }], files: [] as File[], @@ -55,22 +66,22 @@ export const EventForm: React.FC = ({ onCancel, onSubmit, initia : (isClientRequest ? "Solicitar Orçamento/Evento" : "Cadastrar Novo Evento"); const submitLabel = initialData ? "Salvar Alterações" : (isClientRequest ? "Enviar Solicitação" : "Criar Evento"); - // Address Autocomplete Logic using Gemini + // Address Autocomplete Logic using Mapbox useEffect(() => { const timer = setTimeout(async () => { - if (addressQuery.length > 4) { // Increased threshold to avoid spamming API + if (addressQuery.length > 3) { setIsSearching(true); - const results = await searchLocationWithGemini(addressQuery); + const results = await searchMapboxLocation(addressQuery); setAddressResults(results); setIsSearching(false); } else { setAddressResults([]); } - }, 800); // Increased debounce for API efficiency + }, 500); return () => clearTimeout(timer); }, [addressQuery]); - const handleAddressSelect = (addr: GeoResult) => { + const handleAddressSelect = (addr: MapboxResult) => { setFormData((prev: any) => ({ ...prev, address: { @@ -79,6 +90,8 @@ export const EventForm: React.FC = ({ onCancel, onSubmit, initia city: addr.city, state: addr.state, zip: addr.zip, + lat: addr.lat, + lng: addr.lng, mapLink: addr.mapLink } })); @@ -86,6 +99,72 @@ export const EventForm: React.FC = ({ onCancel, onSubmit, initia setAddressResults([]); }; + const handleMapLocationChange = async (lat: number, lng: number) => { + // Buscar endereço baseado nas coordenadas + const addressData = await reverseGeocode(lat, lng); + + if (addressData) { + setFormData((prev: any) => ({ + ...prev, + address: { + street: addressData.street, + number: addressData.number, + city: addressData.city, + state: addressData.state, + zip: addressData.zip, + lat: addressData.lat, + lng: addressData.lng, + mapLink: addressData.mapLink + } + })); + } else { + // Se não conseguir o endereço, atualiza apenas as coordenadas + setFormData((prev: any) => ({ + ...prev, + address: { + ...prev.address, + lat, + lng, + mapLink: `https://www.google.com/maps/search/?api=1&query=${lat},${lng}` + } + })); + } + }; + + // Geocoding quando o usuário digita o endereço manualmente + const handleManualAddressChange = async () => { + const { street, number, city, state } = formData.address; + + // Montar query de busca + const query = `${street} ${number}, ${city}, ${state}`.trim(); + + if (query.length < 5) return; // Endereço muito curto + + setIsGeocoding(true); + + try { + const results = await searchMapboxLocation(query); + + if (results.length > 0) { + const firstResult = results[0]; + + setFormData((prev: any) => ({ + ...prev, + address: { + ...prev.address, + lat: firstResult.lat, + lng: firstResult.lng, + mapLink: firstResult.mapLink + } + })); + } + } catch (error) { + console.error('Erro ao geocodificar endereço manual:', error); + } finally { + setIsGeocoding(false); + } + }; + const addContact = () => { setFormData((prev: any) => ({ ...prev, @@ -351,7 +430,7 @@ export const EventForm: React.FC = ({ onCancel, onSubmit, initia
= ({ onCancel, onSubmit, initia
- + { + setFormData({ ...formData, address: { ...formData.address, street: e.target.value } }); + }} + onBlur={handleManualAddressChange} + placeholder="Digite o nome da rua" + />
{ - const value = e.target.value.replace(/\D/g, ''); + const value = e.target.value; setFormData({ ...formData, address: { ...formData.address, number: value } }); }} + onBlur={handleManualAddressChange} type="text" inputMode="numeric" />
- - + { + setFormData({ ...formData, address: { ...formData.address, city: e.target.value } }); + }} + onBlur={handleManualAddressChange} + placeholder="Digite a cidade" + /> + { + const value = e.target.value.toUpperCase().slice(0, 2); + setFormData({ ...formData, address: { ...formData.address, state: value } }); + }} + onBlur={handleManualAddressChange} + placeholder="SP" + maxLength={2} + /> +
+ + {/* Mapa Interativo */} +
+ +
{formData.address.mapLink && (
- Localização verificada via Google Maps + Localização verificada via Mapbox Ver no mapa diff --git a/components/MapboxMap.tsx b/components/MapboxMap.tsx new file mode 100644 index 0000000..156363a --- /dev/null +++ b/components/MapboxMap.tsx @@ -0,0 +1,195 @@ +import React, { useEffect, useRef, useState } from 'react'; +import mapboxgl from 'mapbox-gl'; +import 'mapbox-gl/dist/mapbox-gl.css'; +import { MapPin, Target } from 'lucide-react'; + +interface MapboxMapProps { + initialLat?: number; + initialLng?: number; + onLocationChange?: (lat: number, lng: number) => void; + height?: string; +} + +export const MapboxMap: React.FC = ({ + initialLat = -23.5505, // São Paulo como padrão + initialLng = -46.6333, + onLocationChange, + height = '400px' +}) => { + const mapContainer = useRef(null); + const map = useRef(null); + const marker = useRef(null); + const [currentLat, setCurrentLat] = useState(initialLat); + const [currentLng, setCurrentLng] = useState(initialLng); + const [mapLoaded, setMapLoaded] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + if (!mapContainer.current || map.current) return; + + try { + console.log('🗺️ Inicializando mapa Mapbox...'); + // Configurar token + const token = import.meta.env.VITE_MAPBOX_TOKEN; + if (!token) { + setError('❌ Token do Mapbox não encontrado. Configure VITE_MAPBOX_TOKEN no arquivo .env.local'); + return; + } + mapboxgl.accessToken = token; + + // Inicializar mapa + map.current = new mapboxgl.Map({ + container: mapContainer.current, + style: 'mapbox://styles/mapbox/streets-v12', + center: [initialLng, initialLat], + zoom: 15 + }); + + console.log('✅ Mapa criado com sucesso'); + + // Adicionar controles de navegação + map.current.addControl(new mapboxgl.NavigationControl(), 'top-right'); + + // Adicionar controle de localização + map.current.addControl( + new mapboxgl.GeolocateControl({ + positionOptions: { + enableHighAccuracy: true + }, + trackUserLocation: true, + showUserHeading: true + }), + 'top-right' + ); + + // Criar marcador arrastável + marker.current = new mapboxgl.Marker({ + color: '#c5a059', + draggable: true + }) + .setLngLat([initialLng, initialLat]) + .addTo(map.current); + + // Evento quando o marcador é arrastado + marker.current.on('dragend', () => { + if (marker.current) { + const lngLat = marker.current.getLngLat(); + setCurrentLat(lngLat.lat); + setCurrentLng(lngLat.lng); + if (onLocationChange) { + onLocationChange(lngLat.lat, lngLat.lng); + } + } + }); + + // Evento de clique no mapa para mover o marcador + map.current.on('click', (e) => { + if (marker.current) { + marker.current.setLngLat([e.lngLat.lng, e.lngLat.lat]); + setCurrentLat(e.lngLat.lat); + setCurrentLng(e.lngLat.lng); + if (onLocationChange) { + onLocationChange(e.lngLat.lat, e.lngLat.lng); + } + } + }); + + map.current.on('load', () => { + setMapLoaded(true); + }); + + } catch (error: any) { + console.error('Erro ao inicializar mapa:', error); + const errorMsg = error?.message || String(error); + if (errorMsg.includes('token') || errorMsg.includes('Unauthorized') || errorMsg.includes('401')) { + setError('❌ Token do Mapbox inválido. Obtenha um token gratuito em https://account.mapbox.com/ e configure em services/mapboxService.ts'); + } else { + setError(`Erro ao carregar o mapa: ${errorMsg}`); + } + } + + // Cleanup + return () => { + if (marker.current) { + marker.current.remove(); + } + if (map.current) { + map.current.remove(); + map.current = null; + } + }; + }, []); // Executar apenas uma vez + + // Atualizar posição do marcador quando as coordenadas mudarem externamente + useEffect(() => { + if (marker.current && map.current && mapLoaded) { + marker.current.setLngLat([initialLng, initialLat]); + map.current.flyTo({ + center: [initialLng, initialLat], + zoom: 15, + duration: 1500 + }); + setCurrentLat(initialLat); + setCurrentLng(initialLng); + } + }, [initialLat, initialLng, mapLoaded]); + + const centerOnMarker = () => { + if (map.current && marker.current) { + const lngLat = marker.current.getLngLat(); + map.current.flyTo({ + center: [lngLat.lng, lngLat.lat], + zoom: 17, + duration: 1000 + }); + } + }; + + return ( +
+ {error && ( +
+ {error} +
+ )} + +
+ + {/* Info overlay - Responsivo */} +
+
+ +
+

Coordenadas

+

+ {currentLat.toFixed(4)}, {currentLng.toFixed(4)} +

+
+
+
+ + {/* Botão de centralizar - Responsivo */} + + + {/* Instruções - Responsivo */} +
+

💡 Como usar:

+
    +
  • Arraste o marcador para a posição exata
  • +
  • Clique no mapa para mover o marcador
  • +
  • Use os controles para zoom e navegação
  • +
+
+
+ ); +}; diff --git a/components/Navbar.tsx b/components/Navbar.tsx index a4d8d6b..7b79732 100644 --- a/components/Navbar.tsx +++ b/components/Navbar.tsx @@ -39,13 +39,11 @@ export const Navbar: React.FC = ({ onNavigate, currentPage }) => { case UserRole.EVENT_OWNER: return [ { name: 'Meus Eventos', path: 'dashboard' }, - { name: 'Solicitar Evento', path: 'request-event' }, - { name: 'Álbuns Entregues', path: 'albums' } + { name: 'Solicitar Evento', path: 'request-event' } ]; case UserRole.PHOTOGRAPHER: return [ { name: 'Eventos Designados', path: 'dashboard' }, - { name: 'Meus Uploads', path: 'uploads' }, { name: 'Agenda', path: 'calendar' } ]; default: diff --git a/contexts/DataContext.tsx b/contexts/DataContext.tsx index 4134503..7a4cb4c 100644 --- a/contexts/DataContext.tsx +++ b/contexts/DataContext.tsx @@ -1,6 +1,6 @@ import React, { createContext, useContext, useState, ReactNode } from 'react'; -import { EventData, EventStatus, EventType, Attachment, Institution } from '../types'; +import { EventData, EventStatus, EventType, Institution } from '../types'; // Initial Mock Data const INITIAL_INSTITUTIONS: Institution[] = [ @@ -41,10 +41,6 @@ const INITIAL_EVENTS: EventData[] = [ coverImage: 'https://picsum.photos/id/1059/800/400', contacts: [{ id: 'c1', name: 'Cerimonial Silva', role: 'Cerimonialista', phone: '9999-9999', email: 'c@teste.com'}], checklist: [], - attachments: [ - { name: 'Ensaio 1', size: '2mb', type: 'image/jpeg', url: 'https://images.unsplash.com/photo-1519741497674-611481863552?auto=format&fit=crop&w=400&q=80' }, - { name: 'Ensaio 2', size: '2mb', type: 'image/jpeg', url: 'https://images.unsplash.com/photo-1511285560982-1351cdeb9821?auto=format&fit=crop&w=400&q=80' } - ], ownerId: 'client-1', photographerIds: ['photographer-1'], institutionId: 'inst-1' @@ -67,7 +63,6 @@ const INITIAL_EVENTS: EventData[] = [ coverImage: 'https://picsum.photos/id/3/800/400', contacts: [], checklist: [], - attachments: [], ownerId: 'client-2', // Other client photographerIds: [] } @@ -80,7 +75,6 @@ interface DataContextType { updateEventStatus: (id: string, status: EventStatus) => void; assignPhotographer: (eventId: string, photographerId: string) => void; getEventsByRole: (userId: string, role: string) => EventData[]; - addAttachment: (eventId: string, attachment: Attachment) => void; addInstitution: (institution: Institution) => void; updateInstitution: (id: string, institution: Partial) => void; getInstitutionsByUserId: (userId: string) => Institution[]; @@ -126,15 +120,6 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({ children }) => return []; }; - const addAttachment = (eventId: string, attachment: Attachment) => { - setEvents(prev => prev.map(e => { - if (e.id === eventId) { - return { ...e, attachments: [...e.attachments, attachment] }; - } - return e; - })); - }; - const addInstitution = (institution: Institution) => { setInstitutions(prev => [...prev, institution]); }; @@ -161,7 +146,6 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({ children }) => updateEventStatus, assignPhotographer, getEventsByRole, - addAttachment, addInstitution, updateInstitution, getInstitutionsByUserId, diff --git a/index.html b/index.html index 2fc7e6a..983631b 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,11 @@ - PhotumFormaturas - Gestão de Eventos + PhotumFormaturas + + + + diff --git a/package-lock.json b/package-lock.json index 50aa41d..a8c6107 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,9 @@ "version": "0.0.0", "dependencies": { "@google/genai": "^1.30.0", + "@types/mapbox-gl": "^3.4.1", "lucide-react": "^0.554.0", + "mapbox-gl": "^3.16.0", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router-dom": "^7.9.6" @@ -834,6 +836,58 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mapbox/jsonlint-lines-primitives": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", + "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@mapbox/mapbox-gl-supported": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-3.0.0.tgz", + "integrity": "sha512-2XghOwu16ZwPJLOFVuIOaLbN0iKMn867evzXFyf0P22dqugezfJwLmdanAgU25ITvz1TvOfVP4jsDImlDJzcWg==", + "license": "BSD-3-Clause" + }, + "node_modules/@mapbox/point-geometry": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz", + "integrity": "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==", + "license": "ISC" + }, + "node_modules/@mapbox/tiny-sdf": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.7.tgz", + "integrity": "sha512-25gQLQMcpivjOSA40g3gO6qgiFPDpWRoMfd+G/GoppPIeP6JDaMMkMrEJnMZhKyyS6iKwVt5YKu02vCUyJM3Ug==", + "license": "BSD-2-Clause" + }, + "node_modules/@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==", + "license": "BSD-2-Clause" + }, + "node_modules/@mapbox/vector-tile": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-2.0.4.tgz", + "integrity": "sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg==", + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/point-geometry": "~1.1.0", + "@types/geojson": "^7946.0.16", + "pbf": "^4.0.1" + } + }, + "node_modules/@mapbox/whoots-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", + "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==", + "license": "ISC", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1211,6 +1265,36 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/geojson-vt": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@types/geojson-vt/-/geojson-vt-3.2.5.tgz", + "integrity": "sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/mapbox__point-geometry": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz", + "integrity": "sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==", + "license": "MIT" + }, + "node_modules/@types/mapbox-gl": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-3.4.1.tgz", + "integrity": "sha512-NsGKKtgW93B+UaLPti6B7NwlxYlES5DpV5Gzj9F75rK5ALKsqSk15CiEHbOnTr09RGbr6ZYiCdI+59NNNcAImg==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/node": { "version": "22.19.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz", @@ -1222,6 +1306,21 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/pbf": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.5.tgz", + "integrity": "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==", + "license": "MIT" + }, + "node_modules/@types/supercluster": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz", + "integrity": "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@vitejs/plugin-react": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.1.tgz", @@ -1392,6 +1491,12 @@ ], "license": "CC-BY-4.0" }, + "node_modules/cheap-ruler": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cheap-ruler/-/cheap-ruler-4.0.0.tgz", + "integrity": "sha512-0BJa8f4t141BYKQyn9NSQt1PguFQXMXwZiA5shfoaBYHAb2fFk2RAX+tiWMoQU+Agtzt3mdt0JtuyshAXqZ+Vw==", + "license": "ISC" + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1444,6 +1549,12 @@ "node": ">= 8" } }, + "node_modules/csscolorparser": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", + "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==", + "license": "MIT" + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -1470,6 +1581,12 @@ } } }, + "node_modules/earcut": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz", + "integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==", + "license": "ISC" + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -1679,6 +1796,18 @@ "node": ">=6.9.0" } }, + "node_modules/geojson-vt": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-4.0.2.tgz", + "integrity": "sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==", + "license": "ISC" + }, + "node_modules/gl-matrix": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", + "license": "MIT" + }, "node_modules/glob": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", @@ -1726,6 +1855,12 @@ "node": ">=14" } }, + "node_modules/grid-index": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grid-index/-/grid-index-1.1.0.tgz", + "integrity": "sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==", + "license": "ISC" + }, "node_modules/gtoken": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", @@ -1845,6 +1980,12 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/kdbush": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", + "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==", + "license": "ISC" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -1864,6 +2005,62 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/mapbox-gl": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.16.0.tgz", + "integrity": "sha512-rluV1Zp/0oHf1Y9BV+nePRNnKyTdljko3E19CzO5rBqtQaNUYS0ePCMPRtxOuWRwSdKp3f9NWJkOCjemM8nmjw==", + "license": "SEE LICENSE IN LICENSE.txt", + "workspaces": [ + "src/style-spec", + "test/build/typings" + ], + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/mapbox-gl-supported": "^3.0.0", + "@mapbox/point-geometry": "^1.1.0", + "@mapbox/tiny-sdf": "^2.0.6", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^2.0.4", + "@mapbox/whoots-js": "^3.1.0", + "@types/geojson": "^7946.0.16", + "@types/geojson-vt": "^3.2.5", + "@types/mapbox__point-geometry": "^0.1.4", + "@types/pbf": "^3.0.5", + "@types/supercluster": "^7.1.3", + "cheap-ruler": "^4.0.0", + "csscolorparser": "~1.0.3", + "earcut": "^3.0.1", + "geojson-vt": "^4.0.2", + "gl-matrix": "^3.4.4", + "grid-index": "^1.1.0", + "kdbush": "^4.0.2", + "martinez-polygon-clipping": "^0.7.4", + "murmurhash-js": "^1.0.0", + "pbf": "^4.0.1", + "potpack": "^2.0.0", + "quickselect": "^3.0.0", + "serialize-to-js": "^3.1.2", + "supercluster": "^8.0.1", + "tinyqueue": "^3.0.0" + } + }, + "node_modules/martinez-polygon-clipping": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/martinez-polygon-clipping/-/martinez-polygon-clipping-0.7.4.tgz", + "integrity": "sha512-jBEwrKtA0jTagUZj2bnmb4Yg2s4KnJGRePStgI7bAVjtcipKiF39R4LZ2V/UT61jMYWrTcBhPazexeqd6JAVtw==", + "license": "MIT", + "dependencies": { + "robust-predicates": "^2.0.4", + "splaytree": "^0.1.4", + "tinyqueue": "^1.2.0" + } + }, + "node_modules/martinez-polygon-clipping/node_modules/tinyqueue": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-1.2.3.tgz", + "integrity": "sha512-Qz9RgWuO9l8lT+Y9xvbzhPT2efIUIFd69N7eF7tJ9lnQl0iLj1M7peK7IoUGZL9DJHw9XftqLreccfxcQgYLxA==", + "license": "ISC" + }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -1894,6 +2091,12 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/murmurhash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", + "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==", + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -1995,6 +2198,18 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, + "node_modules/pbf": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-4.0.1.tgz", + "integrity": "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==", + "license": "BSD-3-Clause", + "dependencies": { + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -2045,6 +2260,24 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/potpack": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.1.0.tgz", + "integrity": "sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==", + "license": "ISC" + }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", + "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==", + "license": "MIT" + }, + "node_modules/quickselect": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", + "license": "ISC" + }, "node_modules/react": { "version": "19.2.0", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", @@ -2116,6 +2349,15 @@ "react-dom": ">=18" } }, + "node_modules/resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "license": "MIT", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, "node_modules/rimraf": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", @@ -2131,6 +2373,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/robust-predicates": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-2.0.4.tgz", + "integrity": "sha512-l4NwboJM74Ilm4VKfbAtFeGq7aEjWL+5kVFcmgFA2MrdnQWx9iE/tUGvxY5HyMI7o/WpSIUFLbC5fbeaHgSCYg==", + "license": "Unlicense" + }, "node_modules/rollup": { "version": "4.53.3", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", @@ -2209,6 +2457,15 @@ "semver": "bin/semver.js" } }, + "node_modules/serialize-to-js": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/serialize-to-js/-/serialize-to-js-3.1.2.tgz", + "integrity": "sha512-owllqNuDDEimQat7EPG0tH7JjO090xKNzUtYz6X+Sk2BXDnOCilDdNLwjWeFywG9xkJul1ULvtUQa9O4pUaY0w==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/set-cookie-parser": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", @@ -2258,6 +2515,12 @@ "node": ">=0.10.0" } }, + "node_modules/splaytree": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-0.1.4.tgz", + "integrity": "sha512-D50hKrjZgBzqD3FT2Ek53f2dcDLAQT8SSGrzj3vidNH5ISRgceeGVJ2dQIthKOuayqFXfFjXheHNo4bbt9LhRQ==", + "license": "MIT" + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -2354,6 +2617,15 @@ "node": ">=8" } }, + "node_modules/supercluster": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", + "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==", + "license": "ISC", + "dependencies": { + "kdbush": "^4.0.2" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -2371,6 +2643,12 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyqueue": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", + "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==", + "license": "ISC" + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", diff --git a/package.json b/package.json index 3486521..8345a5e 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,9 @@ }, "dependencies": { "@google/genai": "^1.30.0", + "@types/mapbox-gl": "^3.4.1", "lucide-react": "^0.554.0", + "mapbox-gl": "^3.16.0", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router-dom": "^7.9.6" diff --git a/pages/Albums.tsx b/pages/Albums.tsx deleted file mode 100644 index bf0e2ab..0000000 --- a/pages/Albums.tsx +++ /dev/null @@ -1,190 +0,0 @@ -import React, { useState } from 'react'; -import { Search, Filter, Calendar, MapPin, Image as ImageIcon, ExternalLink, Download, Share2 } from 'lucide-react'; -import { Button } from '../components/Button'; - -interface Album { - id: string; - eventName: string; - clientName: string; - date: string; - coverImage: string; - photoCount: number; - size: string; - status: 'delivered' | 'archived'; - link: string; -} - -const MOCK_ALBUMS: Album[] = [ - { - id: '1', - eventName: 'Casamento Juliana & Marcos', - clientName: 'Juliana Noiva', - date: '2024-10-15', - coverImage: 'https://images.unsplash.com/photo-1511795409834-ef04bbd61622?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80', - photoCount: 450, - size: '2.4 GB', - status: 'delivered', - link: '#' - }, - { - id: '2', - eventName: 'Formatura Medicina UFPR', - clientName: 'Comissão de Formatura', - date: '2024-09-20', - coverImage: 'https://images.unsplash.com/photo-1523580494863-6f3031224c94?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80', - photoCount: 1200, - size: '8.5 GB', - status: 'delivered', - link: '#' - }, - { - id: '3', - eventName: 'Aniversário 15 Anos Sofia', - clientName: 'Ana Paula (Mãe)', - date: '2024-08-05', - coverImage: 'https://images.unsplash.com/photo-1530103862676-de3c9a59af57?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80', - photoCount: 320, - size: '1.8 GB', - status: 'archived', - link: '#' - }, - { - id: '4', - eventName: 'Evento Corporativo TechSummit', - clientName: 'Tech Solutions Inc.', - date: '2024-11-01', - coverImage: 'https://images.unsplash.com/photo-1515187029135-18ee286d815b?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80', - photoCount: 580, - size: '3.1 GB', - status: 'delivered', - link: '#' - } -]; - -export const AlbumsPage: React.FC = () => { - const [searchTerm, setSearchTerm] = useState(''); - const [filter, setFilter] = useState<'all' | 'delivered' | 'archived'>('all'); - - const filteredAlbums = MOCK_ALBUMS.filter(album => { - const matchesSearch = album.eventName.toLowerCase().includes(searchTerm.toLowerCase()) || - album.clientName.toLowerCase().includes(searchTerm.toLowerCase()); - const matchesFilter = filter === 'all' || album.status === filter; - return matchesSearch && matchesFilter; - }); - - return ( -
-
-
-
-

Álbuns Entregues

-

Gerencie e compartilhe os álbuns finalizados com seus clientes.

-
- {/* Placeholder for future actions if needed */} -
- - {/* Filters & Search */} -
-
- - setSearchTerm(e.target.value)} - /> -
- -
- - - -
-
- - {/* Albums Grid */} -
- {filteredAlbums.map((album) => ( -
-
- {album.eventName} -
-
-

{album.eventName}

-

- {new Date(album.date).toLocaleDateString()} -

-
-
- - {album.status === 'delivered' ? 'Entregue' : 'Arquivado'} - -
-
- -
-
-
-

Cliente

-

{album.clientName}

-
-
-

Fotos

-

{album.photoCount}

-
-
- -
- {album.size} -
- - - -
-
-
-
- ))} -
- - {filteredAlbums.length === 0 && ( -
- -

Nenhum álbum encontrado

-

Tente ajustar seus filtros de busca.

-
- )} -
-
- ); -}; diff --git a/pages/Dashboard.tsx b/pages/Dashboard.tsx index 7fcf0a7..b77c320 100644 --- a/pages/Dashboard.tsx +++ b/pages/Dashboard.tsx @@ -4,19 +4,19 @@ import { UserRole, EventData, EventStatus, EventType } from '../types'; import { EventCard } from '../components/EventCard'; import { EventForm } from '../components/EventForm'; import { Button } from '../components/Button'; -import { PlusCircle, Search, CheckCircle, Clock, Upload, Edit, Users, Map, Image as ImageIcon, Building2 } from 'lucide-react'; +import { PlusCircle, Search, CheckCircle, Clock, Edit, Users, Map, Building2 } from 'lucide-react'; import { useAuth } from '../contexts/AuthContext'; import { useData } from '../contexts/DataContext'; import { STATUS_COLORS } from '../constants'; interface DashboardProps { - initialView?: 'list' | 'create' | 'uploads'; + initialView?: 'list' | 'create'; } export const Dashboard: React.FC = ({ initialView = 'list' }) => { const { user } = useAuth(); - const { events, getEventsByRole, addEvent, updateEventStatus, assignPhotographer, addAttachment, getInstitutionById } = useData(); - const [view, setView] = useState<'list' | 'create' | 'edit' | 'details' | 'uploads'>(initialView); + const { events, getEventsByRole, addEvent, updateEventStatus, assignPhotographer, getInstitutionById } = useData(); + const [view, setView] = useState<'list' | 'create' | 'edit' | 'details'>(initialView); const [searchTerm, setSearchTerm] = useState(''); const [selectedEvent, setSelectedEvent] = useState(null); const [activeFilter, setActiveFilter] = useState('all'); @@ -58,7 +58,6 @@ export const Dashboard: React.FC = ({ initialView = 'list' }) => id: Math.random().toString(36).substr(2, 9), status: initialStatus, checklist: [], - attachments: [], ownerId: isClient ? user.id : 'unknown', photographerIds: [] }; @@ -94,24 +93,6 @@ export const Dashboard: React.FC = ({ initialView = 'list' }) => } }; - const handleUploadPhoto = () => { - if (!selectedEvent) return; - // Mock Upload Action - const newPhoto = { - name: `Foto_${Date.now()}.jpg`, - size: '3.5MB', - type: 'image/jpeg', - url: `https://picsum.photos/id/${Math.floor(Math.random() * 100)}/400/400` - }; - addAttachment(selectedEvent.id, newPhoto); - // Force refresh of selectedEvent state from context source - const updated = events.find(e => e.id === selectedEvent.id); - if (updated) { - // manually inject the new attachment for immediate UI feedback if context isn't enough - setSelectedEvent({...updated, attachments: [...updated.attachments, newPhoto]}); - } - }; - // --- RENDERS PER ROLE --- const renderRoleSpecificHeader = () => { @@ -127,7 +108,7 @@ export const Dashboard: React.FC = ({ initialView = 'list' }) => return (

Eventos Designados

-

Gerencie seus trabalhos e realize uploads.

+

Gerencie seus trabalhos e visualize detalhes.

); } @@ -276,11 +257,6 @@ export const Dashboard: React.FC = ({ initialView = 'list' }) =>
{/* Actions Toolbar */}
- {user.role === UserRole.PHOTOGRAPHER && ( - - )} {(user.role === UserRole.BUSINESS_OWNER || user.role === UserRole.SUPERADMIN) && ( <>
)} - - {view === 'uploads' && ( -
- {/* Check if user came from 'details' of a selected event OR came from Navbar */} - {selectedEvent ? ( -
- - -
-
-

Galeria de Evento: {selectedEvent.name}

-

Gerencie as fotos e faça novos uploads.

-
- -
- - {/* Drag and Drop Area */} -
- -

Adicionar Novas Fotos

-

Clique aqui para simular o upload de uma nova imagem

-
- - {/* Gallery Grid */} -
-

- - Fotos do Evento ({selectedEvent.attachments.filter(a => a.type.startsWith('image')).length}) -

- - {selectedEvent.attachments.length > 0 ? ( -
- {selectedEvent.attachments.map((file, idx) => ( -
- {file.url ? ( - {file.name} - ) : ( -
- -
- )} -
- {file.name} -
-
- ))} -
- ) : ( -
-

Nenhuma foto carregada ainda.

-
- )} -
-
- ) : ( - // Logic when clicking "Meus Uploads" in navbar: Select an Event first -
-

Selecione um evento para gerenciar uploads

-
- {myEvents.map(event => ( -
setSelectedEvent(event)} - > -

{event.name}

-

{new Date(event.date).toLocaleDateString()}

-
- - {event.attachments.length} arquivos -
-
- ))} - {myEvents.length === 0 && ( -

Você não possui eventos designados no momento.

- )} -
-
- )} -
- )}
); diff --git a/pages/Inspiration.tsx b/pages/Inspiration.tsx new file mode 100644 index 0000000..25730f6 --- /dev/null +++ b/pages/Inspiration.tsx @@ -0,0 +1,181 @@ +import React, { useState } from 'react'; +import { Heart, Search, Filter } from 'lucide-react'; +import { Construction } from 'lucide-react'; + +const MOCK_GALLERIES = [ + { + id: 1, + title: 'Formatura Medicina UNICAMP 2024', + category: 'Medicina', + images: [ + 'https://images.unsplash.com/photo-1523050854058-8df90110c9f1?w=800', + 'https://images.unsplash.com/photo-1541339907198-e08756dedf3f?w=800', + 'https://images.unsplash.com/photo-1523240795612-9a054b0db644?w=800', + ], + likes: 234, + }, + { + id: 2, + title: 'Engenharia Civil - USP 2024', + category: 'Engenharia', + images: [ + 'https://images.unsplash.com/photo-1513542789411-b6a5d4f31634?w=800', + 'https://images.unsplash.com/photo-1511632765486-a01980e01a18?w=800', + 'https://images.unsplash.com/photo-1523050854058-8df90110c9f1?w=800', + ], + likes: 189, + }, + { + id: 3, + title: 'Direito PUC 2023', + category: 'Direito', + images: [ + 'https://images.unsplash.com/photo-1519389950473-47ba0277781c?w=800', + 'https://images.unsplash.com/photo-1521737711867-e3b97375f902?w=800', + 'https://images.unsplash.com/photo-1522202176988-66273c2fd55f?w=800', + ], + likes: 312, + }, + { + id: 4, + title: 'Arquitetura UNESP 2024', + category: 'Arquitetura', + images: [ + 'https://images.unsplash.com/photo-1528605248644-14dd04022da1?w=800', + 'https://images.unsplash.com/photo-1523050854058-8df90110c9f1?w=800', + 'https://images.unsplash.com/photo-1519337265831-281ec6cc8514?w=800', + ], + likes: 278, + }, +]; + +const CATEGORIES = ['Todas', 'Medicina', 'Engenharia', 'Direito', 'Arquitetura', 'Administração']; + +export const InspirationPage: React.FC = () => { + const [searchTerm, setSearchTerm] = useState(''); + const [selectedCategory, setSelectedCategory] = useState('Todas'); + + const filteredGalleries = MOCK_GALLERIES.filter((gallery) => { + const matchesSearch = gallery.title.toLowerCase().includes(searchTerm.toLowerCase()); + const matchesCategory = selectedCategory === 'Todas' || gallery.category === selectedCategory; + return matchesSearch && matchesCategory; + }); + + return ( +
+
+ {/* Header */} +
+

+ Galeria de Inspiração +

+

+ Explore álbuns de formaturas anteriores e inspire-se para criar o seu evento perfeito +

+
+ + {/* Search and Filter */} +
+ {/* Search Bar */} +
+ + setSearchTerm(e.target.value)} + /> +
+ + {/* Category Filter */} +
+ {CATEGORIES.map((category) => ( + + ))} +
+
+ + {/* Gallery Grid */} + {filteredGalleries.length > 0 ? ( +
+ {filteredGalleries.map((gallery) => ( +
+ {/* Main Image */} +
+ {gallery.title} +
+ + {/* Category Badge */} +
+ {gallery.category} +
+
+ + {/* Content */} +
+

+ {gallery.title} +

+ + {/* Thumbnail Preview */} +
+ {gallery.images.slice(1, 3).map((img, idx) => ( +
+ +
+ ))} +
+ +12 +
+
+ + {/* Footer */} +
+
+ + {gallery.likes} curtidas +
+ +
+
+
+ ))} +
+ ) : ( +
+

Nenhuma galeria encontrada

+
+ )} + + {/* Coming Soon Banner */} +
+ +

Em Breve: Mais Funcionalidades

+

+ Estamos trabalhando para trazer mais galerias, filtros avançados e a possibilidade de salvar seus favoritos! +

+
+
+
+ ); +}; diff --git a/pages/Team.tsx b/pages/Team.tsx index fb12bf6..4c395c6 100644 --- a/pages/Team.tsx +++ b/pages/Team.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Users, Camera, Mail, Phone, MapPin, Star, Plus, Search, Filter, User } from 'lucide-react'; +import { Users, Camera, Mail, Phone, MapPin, Star, Plus, Search, Filter, User, Upload, X } from 'lucide-react'; import { Button } from '../components/Button'; interface Photographer { @@ -108,7 +108,27 @@ export const TeamPage: React.FC = () => { phone: '', location: '', specialties: [] as string[], + avatar: '' }); + const [avatarFile, setAvatarFile] = useState(null); + const [avatarPreview, setAvatarPreview] = useState(''); + + const handleAvatarChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) { + setAvatarFile(file); + const reader = new FileReader(); + reader.onloadend = () => { + setAvatarPreview(reader.result as string); + }; + reader.readAsDataURL(file); + } + }; + + const removeAvatar = () => { + setAvatarFile(null); + setAvatarPreview(''); + }; const getStatusColor = (status: Photographer['status']) => { switch (status) { @@ -340,16 +360,67 @@ export const TeamPage: React.FC = () => {
{ e.preventDefault(); - alert('Fotógrafo adicionado com sucesso!\n\n' + JSON.stringify(newPhotographer, null, 2)); + alert('Fotógrafo adicionado com sucesso!\n\n' + JSON.stringify({...newPhotographer, avatarFile: avatarFile?.name}, null, 2)); setShowAddModal(false); setNewPhotographer({ name: '', email: '', phone: '', location: '', - specialties: [] + specialties: [], + avatar: '' }); + setAvatarFile(null); + setAvatarPreview(''); }}> +
+ +
+ {avatarPreview ? ( +
+ Preview + +
+ ) : ( +
+ +
+ )} +
+ +

JPG, PNG ou GIF (máx. 5MB)

+ {avatarFile && ( +

{avatarFile.name}

+ )} +
+
+
+