diff --git a/web/src/images/map_marker.svg b/web/src/images/map_marker.svg new file mode 100644 index 0000000..4289ae7 --- /dev/null +++ b/web/src/images/map_marker.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/web/src/pages/CreateOrphanage.tsx b/web/src/pages/CreateOrphanage.tsx new file mode 100644 index 0000000..1033e2b --- /dev/null +++ b/web/src/pages/CreateOrphanage.tsx @@ -0,0 +1,210 @@ +import React, { useState, FormEvent, ChangeEvent } from "react"; +import { useNavigate } from "react-router-dom"; +import { FiPlus } from "react-icons/fi"; +import { GoogleMap, useJsApiLoader, Marker } from '@react-google-maps/api'; +import { toast } from 'react-toastify'; + +import mapMarkerImg from '../images/map_marker.svg'; +import '../styles/create-orphanage.css'; +import Sidebar from "../components/Sidebar"; +import api from "../services/api"; + +export default function CreateOrphanage() { + const { isLoaded } = useJsApiLoader({ + id: 'google-map-script', + googleMapsApiKey: process.env.REACT_APP_MAPS_API_KEY! + }); + + const navigate = useNavigate(); + + const [position, setPosition] = useState({ lat: 0, lng: 0 }); + const [name, setName] = useState(''); + const [about, setAbout] = useState(''); + const [instructions, setInstructions] = useState(''); + const [opening_hours, setOpeningHours] = useState(''); + const [open_on_weekends, setOpenOnWeekends] = useState(true); + const [phone, setPhone] = useState(''); + const [images, setImages] = useState([]); + const [previewImages, setPreviewImages] = useState([]); + + function handleMapClick(event: google.maps.MapMouseEvent) { + const { latLng } = event; + if (latLng) { + setPosition({ + lat: latLng.lat(), + lng: latLng.lng(), + }); + } + } + + function handleSelectImages(event: ChangeEvent) { + if (!event.target.files) { + return; + } + const selectedImages = Array.from(event.target.files); + setImages(selectedImages); + + const selectedImagesPreview = selectedImages.map(image => { + return URL.createObjectURL(image); + }); + + setPreviewImages(selectedImagesPreview); + } + + async function handleSubmit(event: FormEvent) { + event.preventDefault(); + + if (!name.trim()) { + toast.error('O campo "Nome" é obrigatório.'); + return; + } + if (!about.trim()) { + toast.error('O campo "Sobre" é obrigatório.'); + return; + } + if (!opening_hours.trim()) { + toast.error('O campo "Horário de funcionamento" é obrigatório.'); + return; + } + if (position.lat === 0) { + toast.error('Por favor, selecione a localização do orfanato no mapa.'); + return; + } + if (images.length === 0) { + toast.error('Por favor, adicione pelo menos uma foto do orfanato.'); + return; + } + if (!phone.trim()) { + toast.error('O campo "Número de Whatsapp" é obrigatório.'); + return; + } + + const { lat, lng } = position; + + const data = new FormData(); + data.append('name', name); + data.append('about', about); + data.append('latitude', String(lat)); + data.append('longitude', String(lng)); + data.append('instructions', instructions); + data.append('opening_hours', opening_hours); + data.append('open_on_weekends', String(open_on_weekends)); + data.append('phone', phone); + + images.forEach(image => { + data.append('images', image); + }); + + try { + await api.post('orphanages', data); + toast.success('Cadastro realizado com sucesso!'); + navigate('/app'); + } catch (error) { + toast.error('Erro ao realizar o cadastro. Tente novamente.'); + console.error('Failed to create orphanage:', error); + } + } + + if (!isLoaded) { + return Loading Map...; + } + + return ( + + + + + + Dados + + + {position.lat !== 0 && ( + + )} + + + + Nome + setName(e.target.value)} /> + + + + Sobre Máximo de 300 caracteres + setAbout(e.target.value)} /> + + + + Número de Whatsapp + setPhone(e.target.value)} + /> + + + + Fotos + + {previewImages.map(image => ( + + ))} + + + + + + + + + + Visitação + + Instruções + setInstructions(e.target.value)} /> + + + + Horário de funcionamento + setOpeningHours(e.target.value)} /> + + + + Atende fim de semana + + setOpenOnWeekends(true)} + > + Sim + + setOpenOnWeekends(false)} + > + Não + + + + + + + Confirmar + + + + + ); +} \ No newline at end of file diff --git a/web/src/pages/Orphanage.tsx b/web/src/pages/Orphanage.tsx new file mode 100644 index 0000000..3e87c14 --- /dev/null +++ b/web/src/pages/Orphanage.tsx @@ -0,0 +1,150 @@ +import { useEffect, useState } from "react"; +import { FaWhatsapp } from "react-icons/fa"; +import { FiClock, FiInfo } from "react-icons/fi"; +import { useParams } from 'react-router-dom'; +import { GoogleMap, useJsApiLoader, Marker } from "@react-google-maps/api"; + +import mapMarkerImg from '../images/map_marker.svg'; +import '../styles/orphanage.css'; +import Sidebar from '../components/Sidebar'; +import api from "../services/api"; + +interface Orphanage { + latitude: number; + longitude: number; + name: string; + about: string; + instructions: string; + opening_hours: string; + open_on_weekends: boolean; + phone: string; + images: Array<{ + id: number; + url: string; + }>; +} + +export default function Orphanage() { + const { isLoaded } = useJsApiLoader({ + id: 'google-map-script', + googleMapsApiKey: process.env.REACT_APP_MAPS_API_KEY! + }); + + const { id } = useParams(); + const [orphanage, setOrphanage] = useState(); + const [activeImageIndex, setActiveImageIndex] = useState(0); + + useEffect(() => { + api.get(`orphanages/${id}`).then(response => { + setOrphanage(response.data); + }); + }, [id]); + + if (!orphanage || !isLoaded) { + return Carregando...; + } + + function formatPhoneNumberForWhatsApp(phoneNumber: string) { + const digitsOnly = phoneNumber.replace(/\D/g, ''); + return `55${digitsOnly}`; + } + + return ( + + + + + + + + {orphanage.images.map((image, index) => ( + setActiveImageIndex(index)} + > + + + ))} + + + + {orphanage.name} + {orphanage.about} + + + + + + + + + + + Instruções para visita + {orphanage.instructions} + + + + + Segunda à Sexta + {orphanage.opening_hours} + + {orphanage.open_on_weekends ? ( + + + Atendemos + fim de semana + + ) : ( + + + Não atendemos + fim de semana + + )} + + + + + + Entrar em contato + + + + + + + + ); +} \ No newline at end of file diff --git a/web/src/styles/create-orphanage.css b/web/src/styles/create-orphanage.css new file mode 100644 index 0000000..87c5b00 --- /dev/null +++ b/web/src/styles/create-orphanage.css @@ -0,0 +1,175 @@ +#page-create-orphanage { + display: flex; +} + +#page-create-orphanage main { + flex: 1; +} + +form.create-orphanage-form { + width: 700px; + margin: 64px auto; + + background: #FFFFFF; + border: 1px solid #D3E2E5; + border-radius: 20px; + + padding: 64px 80px; + + overflow: hidden; +} + +form.create-orphanage-form .leaflet-container { + border-radius: 20px; + border: 1px solid #D3E2E5; + margin-bottom: 40px; +} + +form.create-orphanage-form fieldset { + border: 0; +} + +form.create-orphanage-form fieldset + fieldset { + margin-top: 80px; +} + +form.create-orphanage-form fieldset legend { + width: 100%; + + font-size: 32px; + line-height: 34px; + color: #5C8599; + font-weight: 700; + + border-bottom: 1px solid #D3E2E5; + margin-bottom: 40px; + padding-bottom: 24px; +} + +form.create-orphanage-form .input-block { + margin-top: 24px; +} + +form.create-orphanage-form .input-block label { + display: flex; + color: #8FA7B3; + margin-bottom: 8px; + line-height: 24px; +} + +form.create-orphanage-form .input-block label span { + font-size: 14px; + color: #8FA7B3; + margin-left: 24px; + line-height: 24px; +} + +form.create-orphanage-form .input-block input, +form.create-orphanage-form .input-block textarea { + width: 100%; + background: #F5F8FA; + border: 1px solid #D3E2E5; + border-radius: 20px; + outline: none; + color: #5C8599; +} + +form.create-orphanage-form .input-block input { + height: 64px; + padding: 0 16px; +} + +form.create-orphanage-form .input-block textarea { + min-height: 120px; + max-height: 240px; + resize: vertical; + padding: 16px; + line-height: 28px; +} + +/* --- NEW STYLES FOR IMAGE UPLOAD --- */ +form.create-orphanage-form .input-block input[type="file"] { + display: none; +} + +form.create-orphanage-form .input-block .images-container { + display: grid; + grid-template-columns: repeat(5, 1fr); + grid-gap: 16px; +} + +form.create-orphanage-form .input-block .images-container img { + width: 100%; + height: 96px; + object-fit: cover; + border-radius: 20px; +} + +form.create-orphanage-form .input-block .images-container .new-image { + height: 96px; + background: #F5F8FA; + border: 1px dashed #96D2F0; + border-radius: 20px; + cursor: pointer; + + display: flex; + justify-content: center; + align-items: center; +} +/* --- END NEW STYLES --- */ + + +form.create-orphanage-form .input-block .button-select { + display: grid; + grid-template-columns: 1fr 1fr; +} + +form.create-orphanage-form .input-block .button-select button { + height: 64px; + background: #F5F8FA; + border: 1px solid #D3E2E5; + color: #5C8599; + cursor: pointer; +} + +form.create-orphanage-form .input-block .button-select button.active { + background: #EDFFF6; + border: 1px solid #A1E9C5; + color: #37C77F; +} + +form.create-orphanage-form .input-block .button-select button:first-child { + border-radius: 20px 0px 0px 20px; +} + +form.create-orphanage-form .input-block .button-select button:last-child { + border-radius: 0 20px 20px 0; + border-left: 0; +} + +form.create-orphanage-form button.confirm-button { + margin-top: 64px; + + width: 100%; + height: 64px; + border: 0; + cursor: pointer; + background: #3CDC8C; + border-radius: 20px; + color: #FFFFFF; + font-weight: 800; + + display: flex; + justify-content: center; + align-items: center; + + transition: background-color 0.2s; +} + +form.create-orphanage-form button.confirm-button svg { + margin-right: 16px; +} + +form.create-orphanage-form button.confirm-button:hover { + background: #36CF82; +} \ No newline at end of file diff --git a/web/src/styles/orphanage.css b/web/src/styles/orphanage.css new file mode 100644 index 0000000..83b0e51 --- /dev/null +++ b/web/src/styles/orphanage.css @@ -0,0 +1,168 @@ +#page-orphanage { + display: flex; + min-height: 100vh; +} + +#page-orphanage main { + flex: 1; +} + +.orphanage-details { + width: 700px; + margin: 64px auto; + + background: #FFFFFF; + border: 1px solid #D3E2E5; + border-radius: 20px; + + overflow: hidden; +} + +.orphanage-details > img { + width: 100%; + height: 300px; + object-fit: cover; +} + +.orphanage-details .images { + display: grid; + grid-template-columns: repeat(6 ,1fr); + column-gap: 16px; + + margin: 16px 40px 0; +} + +.orphanage-details .images button { + border: 0; + height: 88px; + background: none; + cursor: pointer; + border-radius: 20px; + overflow: hidden; + outline: none; + + opacity: 0.6; +} + +.orphanage-details .images button.active { + opacity: 1; +} + +.orphanage-details .images button img { + width: 100%; + height: 88px; + object-fit: cover; + +} + +.orphanage-details .orphanage-details-content { + padding: 80px; +} + +.orphanage-details .orphanage-details-content h1 { + color: #4D6F80; + font-size: 54px; + line-height: 54px; + margin-bottom: 8px; +} + +.orphanage-details .orphanage-details-content p { + line-height: 28px; + color: #5C8599; + margin-top: 24px; +} + +.orphanage-details .orphanage-details-content .map-container { + margin-top: 64px; + background: #E6F7FB; + border: 1px solid #B3DAE2; + border-radius: 20px; +} + +.orphanage-details .orphanage-details-content .map-container footer { + padding: 20px 0; + text-align: center; +} + +.orphanage-details .orphanage-details-content .map-container footer a { + line-height: 24px; + color: #0089A5; + text-decoration: none; +} + +.orphanage-details .orphanage-details-content .map-container .leaflet-container { + border-bottom: 1px solid #DDE3F0; + border-radius: 20px; +} + +.orphanage-details .orphanage-details-content hr { + width: 100%; + height: 1px; + border: 0; + background: #D3E2E6; + margin: 64px 0; +} + +.orphanage-details .orphanage-details-content h2 { + font-size: 36px; + line-height: 46px; + color: #4D6F80; +} + +.orphanage-details .orphanage-details-content .open-details { + margin-top: 24px; + + display: grid; + grid-template-columns: 1fr 1fr; + column-gap: 20px; +} + +.orphanage-details .orphanage-details-content .open-details div { + padding: 32px 24px; + border-radius: 20px; + line-height: 28px; +} + +.orphanage-details .orphanage-details-content .open-details div svg { + display: block; + margin-bottom: 20px; +} + +.orphanage-details .orphanage-details-content .open-details div.hour { + background: linear-gradient(149.97deg, #E6F7FB 8.13%, #FFFFFF 92.67%); + border: 1px solid #B3DAE2; + color: #5C8599; +} + +.orphanage-details .orphanage-details-content .open-details div.open-on-weekends { + background: linear-gradient(154.16deg, #EDFFF6 7.85%, #FFFFFF 91.03%); + border: 1px solid #A1E9C5; + color: #37C77F; +} + +.orphanage-details .orphanage-details-content button.contact-button { + margin-top: 64px; + + width: 100%; + height: 64px; + border: 0; + cursor: pointer; + background: #3CDC8C; + border-radius: 20px; + color: #FFFFFF; + font-weight: 800; + + display: flex; + justify-content: center; + align-items: center; + + transition: background-color 0.2s; +} + +.orphanage-details .orphanage-details-content button.contact-button svg { + margin-right: 16px; +} + +.orphanage-details .orphanage-details-content button.contact-button:hover { + background: #36CF82; +}
Carregando...
{orphanage.about}
{orphanage.instructions}