From fb5d3817edd10e43cf3767e2c3ea84732aa355a0 Mon Sep 17 00:00:00 2001 From: pedrofedericoo Date: Fri, 17 Oct 2025 20:29:29 -0300 Subject: [PATCH] =?UTF-8?q?implementa=C3=A7=C3=A3o=20dos=20horarios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/doctors/DoctorForm.jsx | 452 +++++++++++------- .../doctors/HorariosDisponibilidade.jsx | 106 ++++ 2 files changed, 394 insertions(+), 164 deletions(-) create mode 100644 src/components/doctors/HorariosDisponibilidade.jsx diff --git a/src/components/doctors/DoctorForm.jsx b/src/components/doctors/DoctorForm.jsx index 8f335a71..e89df68e 100644 --- a/src/components/doctors/DoctorForm.jsx +++ b/src/components/doctors/DoctorForm.jsx @@ -1,56 +1,60 @@ -import React, { useState, useRef } from 'react'; -import { Link, useNavigate, useLocation } from 'react-router-dom'; -import './DoctorForm.css'; +import React, { useState, useRef } from "react"; +import { Link, useNavigate, useLocation } from "react-router-dom"; +import "./DoctorForm.css"; +import HorariosDisponibilidade from "../doctors/HorariosDisponibilidade"; function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { const navigate = useNavigate(); const location = useLocation(); const FormatTelefones = (valor) => { - const digits = String(valor).replace(/\D/g, '').slice(0, 11); + const digits = String(valor).replace(/\D/g, "").slice(0, 11); return digits - .replace(/(\d)/, '($1') - .replace(/(\d{2})(\d)/, '$1) $2') - .replace(/(\d)(\d{4})/, '$1 $2') - .replace(/(\d{4})(\d{4})/, '$1-$2'); + .replace(/(\d)/, "($1") + .replace(/(\d{2})(\d)/, "$1) $2") + .replace(/(\d)(\d{4})/, "$1 $2") + .replace(/(\d{4})(\d{4})/, "$1-$2"); }; const FormatCPF = (valor) => { - const digits = String(valor).replace(/\D/g, '').slice(0, 11); + const digits = String(valor).replace(/\D/g, "").slice(0, 11); return digits - .replace(/(\d{3})(\d)/, '$1.$2') - .replace(/(\d{3})(\d)/, '$1.$2') - .replace(/(\d{3})(\d{1,2})$/, '$1-$2'); + .replace(/(\d{3})(\d)/, "$1.$2") + .replace(/(\d{3})(\d)/, "$1.$2") + .replace(/(\d{3})(\d{1,2})$/, "$1-$2"); }; const validarCPF = (cpf) => { - const cpfLimpo = cpf.replace(/\D/g, ''); - + const cpfLimpo = cpf.replace(/\D/g, ""); + if (cpfLimpo.length !== 11) return false; - + if (/^(\d)\1+$/.test(cpfLimpo)) return false; - + let soma = 0; for (let i = 0; i < 9; i++) { soma += parseInt(cpfLimpo.charAt(i)) * (10 - i); } let resto = 11 - (soma % 11); let digito1 = resto === 10 || resto === 11 ? 0 : resto; - + soma = 0; for (let i = 0; i < 10; i++) { soma += parseInt(cpfLimpo.charAt(i)) * (11 - i); } resto = 11 - (soma % 11); let digito2 = resto === 10 || resto === 11 ? 0 : resto; - - return digito1 === parseInt(cpfLimpo.charAt(9)) && digito2 === parseInt(cpfLimpo.charAt(10)); + + return ( + digito1 === parseInt(cpfLimpo.charAt(9)) && + digito2 === parseInt(cpfLimpo.charAt(10)) + ); }; const [avatarUrl, setAvatarUrl] = useState(null); const [showRequiredModal, setShowRequiredModal] = useState(false); const [emptyFields, setEmptyFields] = useState([]); - const [cpfError, setCpfError] = useState(''); + const [cpfError, setCpfError] = useState(""); const nomeRef = useRef(null); const cpfRef = useRef(null); @@ -63,12 +67,13 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { dadosPessoais: true, contato: false, endereco: false, + horarios: false, }); const handleToggleCollapse = (section) => { - setCollapsedSections(prevState => ({ + setCollapsedSections((prevState) => ({ ...prevState, - [section]: !prevState[section] + [section]: !prevState[section], })); }; @@ -76,50 +81,49 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { const { name, value, type, checked, files } = e.target; if (value && emptyFields.includes(name)) { - setEmptyFields(prev => prev.filter(field => field !== name)); + setEmptyFields((prev) => prev.filter((field) => field !== name)); } - if (name === 'cpf' && cpfError) { - setCpfError(''); + if (name === "cpf" && cpfError) { + setCpfError(""); } - if (type === 'checkbox') { - setFormData(prev => ({ ...prev, [name]: checked })); - } else if (type === 'file') { - setFormData(prev => ({ ...prev, [name]: files[0] })); + if (type === "checkbox") { + setFormData((prev) => ({ ...prev, [name]: checked })); + } else if (type === "file") { + setFormData((prev) => ({ ...prev, [name]: files[0] })); - if (name === 'foto' && files[0]) { + if (name === "foto" && files[0]) { const reader = new FileReader(); reader.onloadend = () => { setAvatarUrl(reader.result); }; reader.readAsDataURL(files[0]); - } else if (name === 'foto' && !files[0]) { + } else if (name === "foto" && !files[0]) { setAvatarUrl(null); } - - } else if (name.includes('cpf')) { + } else if (name.includes("cpf")) { let cpfFormatado = FormatCPF(value); - setFormData(prev => ({ ...prev, [name]: cpfFormatado })); + setFormData((prev) => ({ ...prev, [name]: cpfFormatado })); - const cpfLimpo = cpfFormatado.replace(/\D/g, ''); + const cpfLimpo = cpfFormatado.replace(/\D/g, ""); if (cpfLimpo.length === 11) { if (!validarCPF(cpfFormatado)) { - setCpfError('CPF inválido'); + setCpfError("CPF inválido"); } else { - setCpfError(''); + setCpfError(""); } } - } else if (name.includes('phone')) { + } else if (name.includes("phone")) { let telefoneFormatado = FormatTelefones(value); - setFormData(prev => ({ ...prev, [name]: telefoneFormatado })); + setFormData((prev) => ({ ...prev, [name]: telefoneFormatado })); } else { - setFormData(prev => ({ ...prev, [name]: value })); + setFormData((prev) => ({ ...prev, [name]: value })); } }; const handleCepBlur = async () => { - const cep = formData.cep?.replace(/\D/g, ''); + const cep = formData.cep?.replace(/\D/g, ""); if (cep && cep.length === 8) { try { const response = await fetch(`https://viacep.com.br/ws/${cep}/json/`); @@ -127,49 +131,49 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { if (!data.erro) { setFormData((prev) => ({ ...prev, - street: data.logradouro || '', - neighborhood: data.bairro || '', - city: data.localidade || '', - state: data.uf || '' + street: data.logradouro || "", + neighborhood: data.bairro || "", + city: data.localidade || "", + state: data.uf || "", })); } else { setShowRequiredModal(true); - setEmptyFields(['cep']); + setEmptyFields(["cep"]); } } catch (error) { setShowRequiredModal(true); - setEmptyFields(['cep']); + setEmptyFields(["cep"]); } } }; const scrollToEmptyField = (fieldName) => { let fieldRef = null; - + switch (fieldName) { - case 'full_name': + case "full_name": fieldRef = nomeRef; - setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); + setCollapsedSections((prev) => ({ ...prev, dadosPessoais: true })); break; - case 'cpf': + case "cpf": fieldRef = cpfRef; - setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); + setCollapsedSections((prev) => ({ ...prev, dadosPessoais: true })); break; - case 'email': + case "email": fieldRef = emailRef; - setCollapsedSections(prev => ({ ...prev, contato: true })); + setCollapsedSections((prev) => ({ ...prev, contato: true })); break; - case 'phone_mobile': + case "phone_mobile": fieldRef = telefoneRef; - setCollapsedSections(prev => ({ ...prev, contato: true })); + setCollapsedSections((prev) => ({ ...prev, contato: true })); break; - case 'crm_uf': + case "crm_uf": fieldRef = crmUfRef; - setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); + setCollapsedSections((prev) => ({ ...prev, dadosPessoais: true })); break; - case 'crm': + case "crm": fieldRef = crmRef; - setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); + setCollapsedSections((prev) => ({ ...prev, dadosPessoais: true })); break; default: return; @@ -177,19 +181,20 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { setTimeout(() => { if (fieldRef.current) { - fieldRef.current.scrollIntoView({ - behavior: 'smooth', - block: 'center' + fieldRef.current.scrollIntoView({ + behavior: "smooth", + block: "center", }); fieldRef.current.focus(); - - fieldRef.current.style.border = '2px solid #dc3545'; - fieldRef.current.style.boxShadow = '0 0 0 0.2rem rgba(220, 53, 69, 0.25)'; - + + fieldRef.current.style.border = "2px solid #dc3545"; + fieldRef.current.style.boxShadow = + "0 0 0 0.2rem rgba(220, 53, 69, 0.25)"; + setTimeout(() => { if (fieldRef.current) { - fieldRef.current.style.border = ''; - fieldRef.current.style.boxShadow = ''; + fieldRef.current.style.border = ""; + fieldRef.current.style.boxShadow = ""; } }, 3000); } @@ -198,17 +203,17 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { const handleSubmit = async () => { const missingFields = []; - if (!formData.full_name) missingFields.push('full_name'); - if (!formData.cpf) missingFields.push('cpf'); - if (!formData.email) missingFields.push('email'); - if (!formData.phone_mobile) missingFields.push('phone_mobile'); - if (!formData.crm_uf) missingFields.push('crm_uf'); - if (!formData.crm) missingFields.push('crm'); + if (!formData.full_name) missingFields.push("full_name"); + if (!formData.cpf) missingFields.push("cpf"); + if (!formData.email) missingFields.push("email"); + if (!formData.phone_mobile) missingFields.push("phone_mobile"); + if (!formData.crm_uf) missingFields.push("crm_uf"); + if (!formData.crm) missingFields.push("crm"); if (missingFields.length > 0) { setEmptyFields(missingFields); setShowRequiredModal(true); - + setTimeout(() => { if (missingFields.length > 0) { scrollToEmptyField(missingFields[0]); @@ -217,20 +222,20 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { return; } - const cpfLimpo = formData.cpf.replace(/\D/g, ''); + const cpfLimpo = formData.cpf.replace(/\D/g, ""); if (cpfLimpo.length !== 11) { setShowRequiredModal(true); - setEmptyFields(['cpf']); - setCpfError('CPF deve ter 11 dígitos'); - setTimeout(() => scrollToEmptyField('cpf'), 500); + setEmptyFields(["cpf"]); + setCpfError("CPF deve ter 11 dígitos"); + setTimeout(() => scrollToEmptyField("cpf"), 500); return; } if (!validarCPF(formData.cpf)) { setShowRequiredModal(true); - setEmptyFields(['cpf']); - setCpfError('CPF inválido'); - setTimeout(() => scrollToEmptyField('cpf'), 500); + setEmptyFields(["cpf"]); + setCpfError("CPF inválido"); + setTimeout(() => scrollToEmptyField("cpf"), 500); return; } @@ -252,28 +257,33 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
Atenção
-

- {cpfError ? 'Problema com o CPF:' : 'Por favor, preencha:'} + {cpfError ? "Problema com o CPF:" : "Por favor, preencha:"}

{cpfError ? (

{cpfError}

) : ( <> - {!formData.full_name &&

- Nome

} + {!formData.full_name && ( +

- Nome

+ )} {!formData.cpf &&

- CPF

} - {!formData.email &&

- Email

} - {!formData.phone_mobile &&

- Telefone

} - {!formData.crm_uf &&

- Estado do CRM

} + {!formData.email && ( +

- Email

+ )} + {!formData.phone_mobile && ( +

- Telefone

+ )} + {!formData.crm_uf && ( +

- Estado do CRM

+ )} {!formData.crm &&

- CRM

} )} @@ -281,10 +291,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
-
@@ -297,13 +304,20 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { {/* DADOS PESSOAIS */}
-

handleToggleCollapse('dadosPessoais')}> +

handleToggleCollapse("dadosPessoais")} + > Dados Pessoais - {collapsedSections.dadosPessoais ? '▲' : '▼'} + {collapsedSections.dadosPessoais ? "▲" : "▼"}

-
+
@@ -314,13 +328,16 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { className="avatar-image" /> ) : ( -
- ☤ -
+
)}
- + - {formData.foto && {formData.foto.name}} + {formData.foto && ( + + {formData.foto.name} + + )}
-
- +
- {cpfError && ( -
+
{cpfError}
)} @@ -367,11 +401,11 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
-
- - + - + @@ -441,39 +484,50 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { {/* CONTATO */}
-

handleToggleCollapse('contato')}> +

handleToggleCollapse("contato")} + > Contato - {collapsedSections.contato ? '▲' : '▼'} + {collapsedSections.contato ? "▲" : "▼"}

-
+
-
-
- +
@@ -481,46 +535,119 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { {/* ENDEREÇO */}
-

handleToggleCollapse('endereco')}> +

handleToggleCollapse("endereco")} + > Endereço - {collapsedSections.endereco ? '▲' : '▼'} + {collapsedSections.endereco ? "▲" : "▼"}

-
+
- +
- +
- +
- +
- +
- +
- +
+ {/* HORÁRIOS */} +
+

handleToggleCollapse("horarios")} + > + Horários + + {collapsedSections.horarios ? "▲" : "▼"} + +

+ +
+ { + console.log("Disponibilidades atualizadas:", dados); + // Se quiser salvar no formData: + // setFormData(prev => ({ ...prev, disponibilidades: dados })); + }} + /> +
+
+ +
+ {/* BOTÕES DE AÇÃO */}
- + - +
-
); } -export default DoctorForm; \ No newline at end of file +export default DoctorForm; diff --git a/src/components/doctors/HorariosDisponibilidade.jsx b/src/components/doctors/HorariosDisponibilidade.jsx new file mode 100644 index 00000000..eac2c93b --- /dev/null +++ b/src/components/doctors/HorariosDisponibilidade.jsx @@ -0,0 +1,106 @@ +import React, { useState } from "react"; + +const diasDaSemana = [ + "segunda", + "terca", + "quarta", + "quinta", + "sexta", + "sabado", + "domingo" +]; + +export default function HorariosDisponibilidade({ onChange }) { + const [disponibilidades, setDisponibilidades] = useState( + diasDaSemana.map(dia => ({ + weekday: dia, + slots: [{ start_time: "09:00", end_time: "17:00" }], + ativo: false, + })) + ); + + function handleToggleDia(index) { + const updated = [...disponibilidades]; + updated[index].ativo = !updated[index].ativo; + setDisponibilidades(updated); + onChange?.(updated); + } + + function handleSlotChange(indexDia, indexSlot, campo, valor) { + const updated = [...disponibilidades]; + updated[indexDia].slots[indexSlot][campo] = valor; + setDisponibilidades(updated); + onChange?.(updated); + } + + function handleAddSlot(indexDia) { + const updated = [...disponibilidades]; + updated[indexDia].slots.push({ start_time: "", end_time: "" }); + setDisponibilidades(updated); + onChange?.(updated); + } + + function handleRemoveSlot(indexDia, indexSlot) { + const updated = [...disponibilidades]; + updated[indexDia].slots.splice(indexSlot, 1); + setDisponibilidades(updated); + onChange?.(updated); + } + + return ( +
+ {disponibilidades.map((dia, i) => ( +
+
+ + handleToggleDia(i)} + /> +
+ + {dia.ativo && ( +
+ {dia.slots.map((slot, j) => ( +
+ handleSlotChange(i, j, "start_time", e.target.value)} + className="border rounded p-1" + /> + até + handleSlotChange(i, j, "end_time", e.target.value)} + className="border rounded p-1" + /> + {dia.slots.length > 1 && ( + + )} +
+ ))} + +
+ )} +
+ ))} +
+ ); +}