initial commit

This commit is contained in:
parovin committed 2026-10-11 14:12:10 +03:00
commit 39fdd43d8e
127 files changed
+13919

No files matched your search

+80
View File
@@ -0,0 +1,80 @@
import { useMemo, useState } from "react";
import { WEEKDAY_LABELS, type WorkoutSummary } from "../api";
function daysInMonth(year: number, month: number) {
return new Date(year, month, 0).getDate();
}
export function AppCalendar({
value,
workouts = [],
onPick,
}: {
value: string;
workouts?: WorkoutSummary[];
onPick: (iso: string) => void;
}) {
const initial = new Date(value + "T12:00:00");
const [year, setYear] = useState(initial.getFullYear());
const [month, setMonth] = useState(initial.getMonth() + 1);
const byDate = useMemo(() => {
const map = new Map<string, WorkoutSummary>();
for (const w of workouts) map.set(w.scheduled_date, w);
return map;
}, [workouts]);
const firstWeekday = (new Date(year, month - 1, 1).getDay() + 6) % 7;
const totalDays = daysInMonth(year, month);
const today = new Date();
const todayIso = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}-${String(today.getDate()).padStart(2, "0")}`;
const shift = (delta: number) => {
const d = new Date(year, month - 1 + delta, 1);
setYear(d.getFullYear());
setMonth(d.getMonth() + 1);
};
const statusClass = (s: string) => {
if (s === "completed") return "done";
if (s === "in_progress" || s === "paused") return "active";
if (s === "abandoned") return "missed";
return "planned";
};
return (
<div className="calendar move-calendar">
<div className="cal-nav">
<button type="button" className="btn ghost" onClick={() => shift(-1)} aria-label="Предыдущий месяц">
‹
</button>
<h2>{new Date(year, month - 1, 1).toLocaleString("ru-RU", { month: "long", year: "numeric" })}</h2>
<button type="button" className="btn ghost" onClick={() => shift(1)} aria-label="Следующий месяц">
›
</button>
</div>
<div className="cal-grid head">
{WEEKDAY_LABELS.map((d) => (
<div key={d}>{d}</div>
))}
</div>
<div className="cal-grid">
{Array.from({ length: firstWeekday }).map((_, i) => (
<div key={`e${i}`} className="cal-cell empty" />
))}
{Array.from({ length: totalDays }).map((_, i) => {
const day = i + 1;
const key = `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
const w = byDate.get(key);
return (
<button
key={key}
type="button"
className={`cal-cell ${key === todayIso ? "today" : ""} ${key === value ? "picked" : ""} ${w ? statusClass(w.status) : ""}`}
onClick={() => onPick(key)}
>
<span>{day}</span>
{w && <i className="dot" />}
</button>
);
})}
</div>
</div>
);
}