Working on fixing sleep timetable & add gitea workflows
This commit is contained in:
@@ -1,61 +0,0 @@
|
||||
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
||||
import React, { useMemo } from 'react';
|
||||
import { useStats } from '../../hooks/useStats';
|
||||
import { timeToMinutes } from '../../utils/time';
|
||||
const Timetable = ({ from_date, to_date, day_start, day_end }) => {
|
||||
const { timetable, isLoading } = useStats(from_date, to_date, day_start, day_end);
|
||||
const days = useMemo(() => {
|
||||
const start = new Date(from_date);
|
||||
const end = new Date(to_date);
|
||||
const result = [];
|
||||
let current = new Date(start);
|
||||
while (current <= end) {
|
||||
result.push(current.toISOString().split('T')[0]);
|
||||
current.setDate(current.getDate() + 1);
|
||||
}
|
||||
return result;
|
||||
}, [from_date, to_date]);
|
||||
const timeSteps = useMemo(() => {
|
||||
return Array.from({ length: 24 }, (_, i) => i * 60);
|
||||
}, []);
|
||||
if (isLoading)
|
||||
return _jsx("div", { className: "text-center p-4", children: "Loading timetable..." });
|
||||
if (!timetable?.slots || timetable.slots.length === 0) {
|
||||
return _jsx("div", { className: "text-center p-4 text-gray-500", children: "No data available for this period." });
|
||||
}
|
||||
const getDayName = (dateStr) => {
|
||||
return new Date(dateStr).toLocaleDateString('en-US', { weekday: 'short' });
|
||||
};
|
||||
const formatTimeLabel = (mins) => {
|
||||
const hours = Math.floor(mins / 60);
|
||||
const minutes = mins % 60;
|
||||
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
|
||||
};
|
||||
const isSleepingInInterval = (date, intervalStart, intervalEnd) => {
|
||||
return timetable.slots.some((slot) => {
|
||||
if (slot.date !== date || !slot.sleeping)
|
||||
return false;
|
||||
let sStart = timeToMinutes(slot.slot_start);
|
||||
let sEnd = timeToMinutes(slot.slot_end);
|
||||
if (sEnd <= sStart) {
|
||||
const overlap1 = Math.max(sStart, intervalStart) < Math.min(1440, intervalEnd);
|
||||
const overlap2 = Math.max(0, intervalStart) < Math.min(sEnd, intervalEnd);
|
||||
return overlap1 || overlap2;
|
||||
}
|
||||
return Math.max(sStart, intervalStart) < Math.min(sEnd, intervalEnd);
|
||||
});
|
||||
};
|
||||
const currentMins = new Date().getHours() * 60 + new Date().getMinutes();
|
||||
return (_jsxs("div", { className: "bg-white p-4 rounded-xl shadow-sm border border-gray-200 overflow-x-auto", children: [_jsx("h3", { className: "text-lg font-semibold mb-4 text-gray-800", children: "Sleep Timetable" }), _jsxs("div", { className: "min-w-[600px] flex flex-col gap-px", children: [_jsxs("div", { className: "grid", style: {
|
||||
gridTemplateColumns: `4rem repeat(${days.length}, minmax(40px, 1fr))`
|
||||
}, children: [_jsx("div", { className: "h-8" }), " ", days.map((day) => (_jsx("div", { className: "text-center text-[10px] font-medium text-gray-500 uppercase flex items-center justify-center h-8", children: getDayName(day) }, day)))] }), _jsx("div", { className: "relative", children: _jsxs("div", { className: "grid", style: {
|
||||
gridTemplateColumns: `4rem repeat(${days.length}, minmax(40px, 1fr))`
|
||||
}, children: [timeSteps.map((step) => (_jsxs(React.Fragment, { children: [_jsx("div", { className: "text-[10px] text-gray-400 flex items-center justify-end pr-2 h-8", children: step % 120 === 0 ? formatTimeLabel(step) : '' }), days.map((day) => {
|
||||
const sleeping = isSleepingInInterval(day, step, step + 60);
|
||||
return (_jsx("div", { className: `h-8 w-full ${sleeping ? 'bg-green-500' : 'bg-gray-50'}` }, `${day}-${step}`));
|
||||
})] }, step))), _jsx("div", { className: "absolute left-[4rem] right-0 h-0.5 bg-red-500 pointer-events-none z-10", style: {
|
||||
top: `${(currentMins / 1440) * 100}%`,
|
||||
transform: 'translateY(-50%)'
|
||||
} })] }) })] })] }));
|
||||
};
|
||||
export default Timetable;
|
||||
@@ -49,16 +49,14 @@ describe('SleepTimetable', () => {
|
||||
expect(screen.getByText(/SUN/i)).toBeInTheDocument(); // 2025-04-20 is Sunday
|
||||
expect(screen.getByText(/MON/i)).toBeInTheDocument(); // 2025-04-21 is Monday
|
||||
|
||||
// Check for time labels (every 2 hours: 00, 02, 04...)
|
||||
// Check for time labels (every 1 hour: 00, 01, 02...)
|
||||
expect(screen.getByText('00:00')).toBeInTheDocument();
|
||||
expect(screen.getByText('01:00')).toBeInTheDocument();
|
||||
expect(screen.getByText('02:00')).toBeInTheDocument();
|
||||
expect(screen.getByText('03:00')).toBeInTheDocument();
|
||||
expect(screen.getByText('04:00')).toBeInTheDocument();
|
||||
expect(screen.getByText('22:00')).toBeInTheDocument();
|
||||
|
||||
// Verify non-labeled hours (every hour that isn't %120 == 0)
|
||||
// We can't easily check for empty text nodes in Vitest/RTL without more precision
|
||||
// but we can check that 01:00 is NOT present as a text label
|
||||
expect(screen.queryByText('01:00')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('23:00')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('colors sleeping intervals correctly', () => {
|
||||
|
||||
@@ -13,12 +13,12 @@ const Timetable: React.FC<TimetableProps> = ({ from_date, to_date, day_start, da
|
||||
const { timetable, isLoading } = useStats(from_date, to_date, day_start, day_end);
|
||||
|
||||
const days = useMemo(() => {
|
||||
const start = new Date(from_date);
|
||||
const end = new Date(to_date);
|
||||
const start = new Date(from_date + 'T00:00:00');
|
||||
const end = new Date(to_date + 'T23:59:59');
|
||||
const result: string[] = [];
|
||||
let current = new Date(start);
|
||||
while (current <= end) {
|
||||
result.push(current.toISOString().split('T')[0]);
|
||||
result.push(current.toLocaleDateString('en-CA'));
|
||||
current.setDate(current.getDate() + 1);
|
||||
}
|
||||
return result;
|
||||
@@ -51,6 +51,7 @@ const Timetable: React.FC<TimetableProps> = ({ from_date, to_date, day_start, da
|
||||
let sEnd = timeToMinutes(slot.slot_end);
|
||||
|
||||
if (sEnd <= sStart) {
|
||||
// Handle wrap-around within a single slot if it happens (though unlikely for 15m segments)
|
||||
const overlap1 = Math.max(sStart, intervalStart) < Math.min(1440, intervalEnd);
|
||||
const overlap2 = Math.max(0, intervalStart) < Math.min(sEnd, intervalEnd);
|
||||
return overlap1 || overlap2;
|
||||
@@ -94,7 +95,7 @@ const Timetable: React.FC<TimetableProps> = ({ from_date, to_date, day_start, da
|
||||
<React.Fragment key={step}>
|
||||
{/* Time Label */}
|
||||
<div className="text-[10px] text-gray-400 flex items-center justify-end pr-2 h-8">
|
||||
{step % 120 === 0 ? formatTimeLabel(step) : ''}
|
||||
{step % 60 === 0 ? formatTimeLabel(step) : ''}
|
||||
</div>
|
||||
|
||||
{/* Grid Cells */}
|
||||
@@ -110,20 +111,20 @@ const Timetable: React.FC<TimetableProps> = ({ from_date, to_date, day_start, da
|
||||
</React.Fragment>
|
||||
))}
|
||||
|
||||
{/* Red Marker Line */}
|
||||
|
||||
<div
|
||||
className="absolute left-[4rem] right-0 h-0.5 bg-red-500 pointer-events-none z-10"
|
||||
style={{
|
||||
top: `${(currentMins / 1440) * 100}%`,
|
||||
transform: 'translateY(-50%)'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
{/* Red Marker Line */}
|
||||
|
||||
<div
|
||||
className="absolute left-[4rem] right-0 h-0.5 bg-red-500 pointer-events-none z-10"
|
||||
style={{
|
||||
top: `${(currentMins / 1440) * 100}%`,
|
||||
transform: 'translateY(-50%)'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Timetable;
|
||||
|
||||
@@ -12,8 +12,10 @@ import { useSettings } from '../../hooks/useSettings';
|
||||
const StatsPage: React.FC = () => {
|
||||
const { settings, update } = useSettings();
|
||||
|
||||
const from_date = new Date(new Date().setDate(new Date().getDate() - settings.statsInterval)).toISOString().split('T')[0];
|
||||
const to_date = new Date().toISOString().split('T')[0];
|
||||
const now = new Date();
|
||||
const to_date = now.toLocaleDateString('en-CA'); // YYYY-MM-DD in local time
|
||||
const from_date = new Date(now.setDate(now.getDate() - settings.statsInterval)).toLocaleDateString('en-CA');
|
||||
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto space-y-8 pb-12">
|
||||
|
||||
Reference in New Issue
Block a user