import React, { useState, useEffect } from 'react';
import {
Calendar,
Clock,
User,
Phone,
Mail,
MessageCircle,
Edit2,
Trash2,
Plus,
ChevronLeft,
ChevronRight,
CheckCircle2,
AlertCircle,
LayoutGrid,
List,
Loader2
} from 'lucide-react';
const API_URL = 'https://capi.orthostudioamaya.com/api.php';
export default function App() {
const [appointments, setAppointments] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]);
const [viewMode, setViewMode] = useState('day'); // 'day' | 'month'
// Estados para Modales
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
const [rescheduleData, setRescheduleData] = useState(null);
// Estado para notificaciones UI
const [toast, setToast] = useState(null);
// Formulario nuevo
const [formData, setFormData] = useState({
patientName: '', phone: '', email: '', time: '08:00', type: 'Valoración Inicial'
});
// --- Cargar datos desde la API al iniciar ---
useEffect(() => {
fetchAppointments();
}, []);
const fetchAppointments = async () => {
setIsLoading(true);
try {
const response = await fetch(API_URL);
if (!response.ok) throw new Error('Error en la respuesta de la API');
const data = await response.json();
// Si la API devuelve un array, lo guardamos. Si devuelve un objeto de error o está vacío, ponemos array vacío.
if (Array.isArray(data)) {
setAppointments(data);
} else {
setAppointments([]);
}
} catch (error) {
console.error("Error obteniendo citas:", error);
showToast("Error al conectar con la base de datos", "error");
} finally {
setIsLoading(false);
}
};
const showToast = (message, type = 'success') => {
setToast({ message, type });
setTimeout(() => setToast(null), 4000);
};
// --- Lógica de Fechas ---
const changeDate = (amount) => {
const currentDate = new Date(selectedDate + 'T00:00:00');
if (viewMode === 'day') {
currentDate.setDate(currentDate.getDate() + amount);
} else {
currentDate.setMonth(currentDate.getMonth() + amount);
}
setSelectedDate(currentDate.toISOString().split('T')[0]);
};
const formatDateLabel = (dateString) => {
const options = viewMode === 'day'
? { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }
: { year: 'numeric', month: 'long' };
const date = new Date(dateString + 'T00:00:00'); // Forzar zona horaria local
return date.toLocaleDateString('es-HN', options);
};
// --- Operaciones CRUD (Conectadas a la API) ---
// CREAR CITA (POST)
const handleAddAppointment = async (e) => {
e.preventDefault();
const newAptData = { ...formData, date: selectedDate };
try {
const response = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newAptData)
});
if (response.ok) {
await fetchAppointments(); // Recargar datos desde la BD
setIsAddModalOpen(false);
// Generar link de WhatsApp para Confirmación
const msg = `¡Hola ${formData.patientName}! Te confirmamos tu cita en Orthostudio Amaya para el ${formatDateLabel(selectedDate)} a las ${formData.time}.`;
sendWhatsApp(formData.phone, msg);
showToast('Cita agendada exitosamente');
setFormData({ patientName: '', phone: '', email: '', time: '08:00', type: 'Valoración Inicial' });
} else {
const errorData = await response.json();
showToast(errorData.error || 'Error al guardar la cita', 'error');
}
} catch (error) {
console.error("Error:", error);
showToast("Error de conexión al guardar", "error");
}
};
// ELIMINAR CITA (DELETE)
const handleDelete = async (id, patientName, phone) => {
if (window.confirm(`¿Estás seguro de cancelar la cita de ${patientName}?`)) {
try {
// Corrección: Enviamos el ID directamente en la URL para que PHP lo lea sin problemas
const response = await fetch(`${API_URL}?id=${id}`, {
method: 'DELETE'
});
if (response.ok) {
await fetchAppointments(); // Recargar datos
const msg = `Hola ${patientName}, te informamos que tu cita en Orthostudio Amaya ha sido cancelada. Por favor contáctanos para reprogramar.`;
sendWhatsApp(phone, msg);
showToast('Cita cancelada exitosamente');
} else {
showToast('No se pudo cancelar la cita', 'error');
}
} catch (error) {
console.error("Error:", error);
showToast("Error de conexión al cancelar", "error");
}
}
};
// REPROGRAMAR CITA (PUT)
const handleRescheduleSubmit = async (e) => {
e.preventDefault();
try {
const response = await fetch(API_URL, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: rescheduleData.id,
date: rescheduleData.newDate,
time: rescheduleData.newTime
})
});
if (response.ok) {
await fetchAppointments(); // Recargar datos
const msg = `Hola ${rescheduleData.patientName}, te confirmamos que tu cita en Orthostudio Amaya ha sido REPROGRAMADA para el ${formatDateLabel(rescheduleData.newDate)} a las ${rescheduleData.newTime}.`;
sendWhatsApp(rescheduleData.phone, msg);
setRescheduleData(null);
showToast('Cita reprogramada exitosamente');
} else {
showToast('No se pudo reprogramar la cita', 'error');
}
} catch (error) {
console.error("Error:", error);
showToast("Error de conexión al actualizar", "error");
}
};
// --- Notificaciones ---
const sendWhatsApp = (phone, message) => {
const url = `https://wa.me/${phone.replace(/\D/g, '')}?text=${encodeURIComponent(message)}`;
window.open(url, '_blank');
};
// Filtrar citas del día seleccionado
const todaysAppointments = appointments.filter(apt => apt.date === selectedDate);
// --- Renderizado de la Vista Mensual ---
const renderMonthView = () => {
const currentDateObj = new Date(selectedDate + 'T00:00:00');
const year = currentDateObj.getFullYear();
const month = currentDateObj.getMonth();
const firstDay = new Date(year, month, 1).getDay(); // 0 (Dom) - 6 (Sáb)
const daysInMonth = new Date(year, month + 1, 0).getDate();
const days = [];
// Espacios vacíos previos
for (let i = 0; i < firstDay; i++) {
days.push(
);
}
// Días del mes
for (let d = 1; d <= daysInMonth; d++) {
const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
const dayAppointments = appointments.filter(a => a.date === dateStr);
days.push(
{ setSelectedDate(dateStr); setViewMode('day'); }}
className={`p-2 border border-gray-100 min-h-[100px] md:min-h-[120px] cursor-pointer hover:bg-blue-50 transition group flex flex-col ${dateStr === selectedDate ? 'bg-blue-50 ring-2 ring-inset ring-[#0284c7]' : 'bg-white'}`}>
{d}
{dayAppointments.length > 0 && {dayAppointments.length}}
{dayAppointments.slice(0, 3).map(apt => (
{apt.time} {apt.patientName.split(' ')[0]}
))}
{dayAppointments.length > 3 && (
+{dayAppointments.length - 3} más
)}
);
}
const weekdays = ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb'];
return (
{weekdays.map(day => (
{day}
))}
{days}
);
};
return (
{/* Toast Notification */}
{toast && (
{toast.type === 'success' ?
:
}
{toast.message}
)}
{/* Header */}
Orthostudio Amaya | Agenda en la Nube
{/* Contenido Principal: Lista de Citas */}
{/* Navegador de Fechas y Vistas */}
{formatDateLabel(selectedDate)}
{viewMode === 'day' && !isLoading && (
{todaysAppointments.length} {todaysAppointments.length === 1 ? 'cita programada' : 'citas programadas'}
)}
{/* Renderizado condicional basado en el estado de carga y vista */}
{isLoading ? (
Conectando con la base de datos...
) : viewMode === 'day' ? (
{todaysAppointments.length === 0 ? (
No hay citas para este día
Disfruta el tiempo libre o agrega una nueva cita.
) : (
todaysAppointments.map((apt) => (
{/* Hora */}
{apt.time}
{/* Info Paciente */}
{apt.patientName}
{apt.type}
{apt.phone}
{apt.email || 'Sin correo'}
{/* Acciones */}
))
)}
) : (
renderMonthView()
)}
{/* Sidebar Informativo */}
Automatización
El sistema genera el enlace de WhatsApp listo para enviarse cada vez que programas o cancelas una cita.
🟢 Sistema Activo y Sincronizado
{/* Modal Agregar Cita */}
{isAddModalOpen && (
Agendar Nueva Cita
)}
{/* Modal Reprogramar Cita */}
{rescheduleData && (
Reprogramar Cita
Paciente: {rescheduleData.patientName}
)}
);
}