Skip to content

Alex #8785

@AlexanderPerez16

Description

@AlexanderPerez16

import React, { useEffect, useMemo, useState } from "react";
import { jsPDF } from "jspdf";
import autoTable from "jspdf-autotable";
import * as XLSX from "xlsx";

const DAY_LABELS = ["LUN", "MAR", "MIE", "JUE", "VIE", "SAB", "DOM"];
const HOURS = Array.from({ length: 20 }, (_, i) => 5 + i);

function startOfWeek(date) {
const d = new Date(date);
const day = (d.getDay() + 6) % 7;
const monday = new Date(d);
monday.setDate(d.getDate() - day);
monday.setHours(0, 0, 0, 0);
return monday;
}

function addDays(date, days) {
const d = new Date(date);
d.setDate(d.getDate() + days);
return d;
}

function fmtDM(d) {
const dd = String(d.getDate()).padStart(2, "0");
const mm = String(d.getMonth() + 1).padStart(2, "0");
return ${dd}/${mm};
}

function fmtRangeDM(weekStart) {
const a = fmtDM(weekStart);
const b = fmtDM(addDays(weekStart, 6));
return ${a}–${b};
}

function sameDay(a, b) {
return (
a.getFullYear() === b.getFullYear() &&
a.getMonth() === b.getMonth() &&
a.getDate() === b.getDate()
);
}

function parseUserDM(dm, baseDate) {
const [dd, mm] = dm.split("/").map((x) => parseInt(x, 10));
if (!dd || !mm) return null;
const d = new Date(baseDate);
d.setMonth(mm - 1, dd);
d.setHours(0, 0, 0, 0);
return isNaN(d.getTime()) ? null : d;
}

const CATEGORY_STYLES = {
CITA: {
badge: "bg-orange-500 text-white",
soft: "bg-orange-50 border-orange-200",
},
TRABAJO: {
badge: "bg-sky-400 text-white",
soft: "bg-sky-50 border-sky-200",
},
ESTUDIO: {
badge: "bg-green-500 text-white",
soft: "bg-green-50 border-green-200",
},
PERSONAL: {
badge: "bg-yellow-400 text-black",
soft: "bg-yellow-50 border-yellow-200",
},
};

const LS_KEY_FIXED = "horario-fixed-v1";
const LS_KEY_WEEK = (weekKey) => horario-week-${weekKey};

function weekKeyFromDate(d) {
const ws = startOfWeek(d);
return ${ws.getFullYear()}-${ws.getMonth() + 1}-${ws.getDate()};
}

export default function HorarioSemanal() {
const [weekStart, setWeekStart] = useState(() => startOfWeek(new Date()));
const [category, setCategory] = useState("CITA");
const [dm, setDm] = useState("");
const [hInicio, setHInicio] = useState(5);
const [hFin, setHFin] = useState(6);
const [desc, setDesc] = useState("");
const [tipo, setTipo] = useState("VARIABLE");
const [msg, setMsg] = useState("");

const [fixedEvents, setFixedEvents] = useState([]);
const [weekEvents, setWeekEvents] = useState([]);

useEffect(() => {
try {
const raw = localStorage.getItem(LS_KEY_FIXED);
if (raw) setFixedEvents(JSON.parse(raw));
} catch {}
}, []);

useEffect(() => {
try {
const key = LS_KEY_WEEK(weekKeyFromDate(weekStart));
const raw = localStorage.getItem(key);
setWeekEvents(raw ? JSON.parse(raw) : []);
} catch {
setWeekEvents([]);
}
}, [weekStart]);

useEffect(() => {
try {
localStorage.setItem(LS_KEY_FIXED, JSON.stringify(fixedEvents));
} catch {}
}, [fixedEvents]);

useEffect(() => {
try {
const key = LS_KEY_WEEK(weekKeyFromDate(weekStart));
localStorage.setItem(key, JSON.stringify(weekEvents));
} catch {}
}, [weekEvents, weekStart]);

const daysOfWeek = useMemo(() => {
return Array.from({ length: 7 }, (_, i) => addDays(weekStart, i));
}, [weekStart]);

const rangeLabel = useMemo(() => fmtRangeDM(weekStart), [weekStart]);

const events = useMemo(() => {
const all = [...fixedEvents, ...weekEvents];
return all.filter((ev) => daysOfWeek.some((d) => sameDay(d, new Date(ev.date))));
}, [fixedEvents, weekEvents, daysOfWeek]);

function addEvent() {
setMsg("");
const d = parseUserDM(dm, weekStart);
if (!d) return setMsg("❗ Formato de fecha inválido");
const inWeek = daysOfWeek.some((wd) => sameDay(wd, d));
if (!inWeek) return setMsg("⚠️ Fecha fuera de la semana");

const hi = Number(hInicio);
const hf = Number(hFin);
if (hf <= hi) return setMsg("❗ Hora fin debe ser mayor a inicio");

const ev = {
  id: crypto.randomUUID(),
  type: tipo,
  category,
  date: d.toISOString(),
  start: hi,
  end: hf,
  desc: desc.trim() || "(Sin descripción)",
};

if (tipo === "FIJO") setFixedEvents((s) => [...s, ev]);
else setWeekEvents((s) => [...s, ev]);

setDesc("");

}

function deleteEvent(id, eventType) {
if (eventType === "FIJO") setFixedEvents((s) => s.filter((e) => e.id !== id));
else setWeekEvents((s) => s.filter((e) => e.id !== id));
}

const eventsByDay = useMemo(() => {
const map = new Map();
daysOfWeek.forEach((d, i) => map.set(i, []));
events.forEach((ev) => {
const d = new Date(ev.date);
const idx = daysOfWeek.findIndex((wd) => sameDay(wd, d));
if (idx >= 0) map.get(idx).push(ev);
});
for (const [k, arr] of map.entries()) {
arr.sort((a, b) => a.start - b.start);
}
return map;
}, [events, daysOfWeek]);

function exportExcel() {
const data = events.map(ev => ({
Categoría: ev.category,
Fecha: fmtDM(new Date(ev.date)),
Inicio: ${ev.start}:00,
Fin: ${ev.end}:00,
Descripción: ev.desc,
Tipo: ev.type
}));
const ws = XLSX.utils.json_to_sheet(data);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "Horario");
XLSX.writeFile(wb, "horario.xlsx");
}

function exportPDF() {
const doc = new jsPDF({ orientation: "landscape" });
doc.text("Horario Semanal", 14, 10);
autoTable(doc, {
head: [["Categoría", "Fecha", "Inicio", "Fin", "Descripción", "Tipo"]],
body: events.map(ev => [
ev.category,
fmtDM(new Date(ev.date)),
${ev.start}:00,
${ev.end}:00,
ev.desc,
ev.type
])
});
doc.save("horario.pdf");
}

return (



HORARIO SEMANAL


Semana: {rangeLabel}





Categoría

{Object.keys(CATEGORY_STYLES).map((key) => (
<button key={key} onClick={() => setCategory(key)}
className={px-2 py-1 rounded ${category === key ? CATEGORY_STYLES[key].badge : "bg-white border"}}>
{key}

))}



Fecha
<input value={dm} onChange={(e) => setDm(e.target.value)} placeholder="dd/mm" className="border rounded w-full" />


Inicio
<input type="number" value={hInicio} onChange={(e) => setHInicio(e.target.value)} min={5} max={24} className="border rounded w-full" />


Fin
<input type="number" value={hFin} onChange={(e) => setHFin(e.target.value)} min={5} max={24} className="border rounded w-full" />


Tipo
<select value={tipo} onChange={(e) => setTipo(e.target.value)} className="border rounded w-full">
Fijo
Variable




Descripción
<input value={desc} onChange={(e) => setDesc(e.target.value)} className="border rounded w-full" />

{msg &&
{msg}
}
Agregar

<section className="grid" style={{ gridTemplateColumns: "96px repeat(7, 1fr)" }}>
Hora

{daysOfWeek.map((d, i) => (

{DAY_LABELS[i]}
{fmtDM(d)}

))}
{HOURS.map((h) => (
<React.Fragment key={h}>
{String(h).padStart(2, "0")}:00

{daysOfWeek.map((_, dayIdx) => (

{eventsByDay.get(dayIdx)?.filter(ev => ev.start === h).map((ev) => {
const cat = CATEGORY_STYLES[ev.category] || CATEGORY_STYLES.PERSONAL;
return (
<div key={ev.id} className={absolute inset-0 rounded ${cat.soft}}>
<div className={${cat.badge} px-2 py-1 flex justify-between}>
{ev.category}
<button onClick={() => deleteEvent(ev.id, ev.type)} className="text-white font-bold">×

{ev.desc}


);
})}

))}
</React.Fragment>
))}


Exportar a Excel
Exportar a PDF


);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions