Improvements todo created and small bugfixes
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useStats } from '../../hooks/useStats';
|
||||
import { formatMinutesAfter, getToday } from '../../utils/time';
|
||||
import { formatMinutesAfter, getToday, getLocalOffsetFromReference } from '../../utils/time';
|
||||
import {
|
||||
LineChart,
|
||||
Line,
|
||||
@@ -32,12 +32,13 @@ const BedtimePlot: React.FC<WakeupBedtimeProps> = ({ from_date, to_date, wakeup_
|
||||
const daysAgo = Math.floor(
|
||||
(new Date(getToday()).getTime() - new Date(day.date).getTime()) / (1000 * 60 * 60 * 24)
|
||||
);
|
||||
const offsetFromRef = day.bedtime_minutes !== null ? getLocalOffsetFromReference(day.bedtime_minutes, bedtime_ref) : 0;
|
||||
return {
|
||||
date: daysAgo === 0 ? 'Today' : `${daysAgo}d ago`,
|
||||
minutes: day.bedtime_minutes ?? 0,
|
||||
minutes: offsetFromRef,
|
||||
time:
|
||||
day.bedtime_minutes !== null
|
||||
? formatMinutesAfter(day.bedtime_minutes, bedtime_ref)
|
||||
? formatMinutesAfter(offsetFromRef, bedtime_ref)
|
||||
: 'N/A',
|
||||
};
|
||||
}).reverse();
|
||||
|
||||
@@ -62,69 +62,70 @@ const Timetable: React.FC<TimetableProps> = ({ from_date, to_date, day_start, da
|
||||
};
|
||||
|
||||
const currentMins = new Date().getHours() * 60 + new Date().getMinutes();
|
||||
const rowHeightPx = 32; // h-8 is 2rem, which is 32px in standard Tailwind config
|
||||
|
||||
return (
|
||||
<div className="bg-white p-4 rounded-xl shadow-sm border border-gray-200 overflow-x-auto">
|
||||
<h3 className="text-lg font-semibold mb-4 text-gray-800">Sleep Timetable</h3>
|
||||
<div className="min-w-[600px] flex flex-col gap-px">
|
||||
{/* Header Row */}
|
||||
<div
|
||||
className="grid"
|
||||
style={{
|
||||
gridTemplateColumns: `4rem repeat(${days.length}, minmax(40px, 1fr))`
|
||||
}}
|
||||
>
|
||||
<div className="h-8" /> {/* Empty corner cell */}
|
||||
{days.map((day) => (
|
||||
<div key={day} className="text-center text-[10px] font-medium text-gray-500 uppercase flex items-center justify-center h-8">
|
||||
{getDayName(day)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
return (
|
||||
<div className="bg-white p-4 rounded-xl shadow-sm border border-gray-200 overflow-x-auto">
|
||||
<h3 className="text-lg font-semibold mb-4 text-gray-800">Sleep Timetable</h3>
|
||||
<div className="min-w-[600px] flex flex-col gap-px">
|
||||
{/* Header Row */}
|
||||
<div
|
||||
className="grid"
|
||||
style={{
|
||||
gridTemplateColumns: `4rem repeat(${days.length}, minmax(40px, 1fr))`
|
||||
}}
|
||||
>
|
||||
<div className="h-8" /> {/* Empty corner cell */}
|
||||
{days.map((day) => (
|
||||
<div key={day} className="text-center text-[10px] font-medium text-gray-500 uppercase flex items-center justify-center h-8">
|
||||
{getDayName(day)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Time Grid Container */}
|
||||
<div className="relative">
|
||||
<div
|
||||
className="grid"
|
||||
style={{
|
||||
gridTemplateColumns: `4rem repeat(${days.length}, minmax(40px, 1fr))`
|
||||
}}
|
||||
>
|
||||
{/* Time Rows */}
|
||||
{timeSteps.map((step) => (
|
||||
<React.Fragment key={step}>
|
||||
{/* Time Label */}
|
||||
<div className="text-[10px] text-gray-400 flex items-center justify-end pr-2 h-8">
|
||||
{step % 60 === 0 ? formatTimeLabel(step) : ''}
|
||||
</div>
|
||||
|
||||
{/* Grid Cells */}
|
||||
{days.map((day) => {
|
||||
const sleeping = isSleepingInInterval(day, step, step + 60);
|
||||
return (
|
||||
<div
|
||||
key={`${day}-${step}`}
|
||||
className={`h-8 w-full ${sleeping ? 'bg-green-500' : 'bg-gray-50'}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
))}
|
||||
{/* Time Grid Container */}
|
||||
<div className="relative">
|
||||
<div
|
||||
className="grid"
|
||||
style={{
|
||||
gridTemplateColumns: `4rem repeat(${days.length}, minmax(40px, 1fr))`
|
||||
}}
|
||||
>
|
||||
{/* Time Rows */}
|
||||
{timeSteps.map((step) => (
|
||||
<React.Fragment key={step}>
|
||||
{/* Time Label */}
|
||||
<div className="text-[10px] text-gray-400 flex items-center justify-end pr-2 h-8">
|
||||
{step % 60 === 0 ? formatTimeLabel(step) : ''}
|
||||
</div>
|
||||
|
||||
{/* Grid Cells */}
|
||||
{days.map((day) => {
|
||||
const sleeping = isSleepingInInterval(day, step, step + 60);
|
||||
return (
|
||||
<div
|
||||
key={`${day}-${step}`}
|
||||
className={`h-8 w-full ${sleeping ? 'bg-green-500' : 'bg-gray-50'}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</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) * (24 * rowHeightPx)}px`,
|
||||
transform: 'translateY(-50%)'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Timetable;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useStats } from '../../hooks/useStats';
|
||||
import { formatMinutesAfter, getToday } from '../../utils/time';
|
||||
import { formatMinutesAfter, getToday, getLocalOffsetFromReference } from '../../utils/time';
|
||||
import {
|
||||
LineChart,
|
||||
Line,
|
||||
@@ -32,12 +32,13 @@ const WakeupPlot: React.FC<WakeupBedtimeProps> = ({ from_date, to_date, wakeup_r
|
||||
const daysAgo = Math.floor(
|
||||
(new Date(getToday()).getTime() - new Date(day.date).getTime()) / (1000 * 60 * 60 * 24)
|
||||
);
|
||||
const offsetFromRef = day.wakeup_minutes !== null ? getLocalOffsetFromReference(day.wakeup_minutes, wakeup_ref) : 0;
|
||||
return {
|
||||
date: daysAgo === 0 ? 'Today' : `${daysAgo}d ago`,
|
||||
minutes: day.wakeup_minutes ?? 0,
|
||||
minutes: offsetFromRef,
|
||||
time:
|
||||
day.wakeup_minutes !== null
|
||||
? formatMinutesAfter(day.wakeup_minutes, wakeup_ref)
|
||||
? formatMinutesAfter(offsetFromRef, wakeup_ref)
|
||||
: 'N/A',
|
||||
};
|
||||
}).reverse();
|
||||
@@ -70,11 +71,11 @@ const WakeupPlot: React.FC<WakeupBedtimeProps> = ({ from_date, to_date, wakeup_r
|
||||
domain={['dataMin - 30', 'dataMax + 30']}
|
||||
/>
|
||||
<Tooltip
|
||||
formatter={(value: any) => [
|
||||
formatMinutesAfter(value ?? 0, wakeup_ref),
|
||||
'Wakeup',
|
||||
] as any}
|
||||
labelFormatter={(label) => label}
|
||||
formatter={(value: any) => [
|
||||
formatMinutesAfter(value ?? 0, wakeup_ref),
|
||||
'Wakeup',
|
||||
] as any}
|
||||
labelFormatter={(label) => label}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
|
||||
@@ -22,6 +22,16 @@ export function formatMinutesAfter(minutes: number | null, reference: string = "
|
||||
return `${String(hours).padStart(2, "0")}:${String(mins).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust minutes by the local timezone offset
|
||||
* @param utcMinutes - minutes from UTC midnight
|
||||
* @returns adjusted minutes for local time
|
||||
*/
|
||||
export function adjustForTimezone(utcMinutes: number): number {
|
||||
const offsetInMinutes = new Date().getTimezoneOffset();
|
||||
return utcMinutes - offsetInMinutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Date to datetime-local input value format (local time)
|
||||
* @param date - Date object
|
||||
@@ -111,6 +121,17 @@ export function timeToMinutes(timeStr: string): number {
|
||||
return hours * 60 + minutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the offset from a reference time in local minutes
|
||||
* @param utcMinutesFromMidnight - minutes from UTC midnight
|
||||
* @param referenceHHMM - reference time as HH:MM string
|
||||
* @returns local offset from reference in minutes
|
||||
*/
|
||||
export function getLocalOffsetFromReference(utcMinutesFromMidnight: number, referenceHHMM: string): number {
|
||||
const localMinsFromMidnight = adjustForTimezone(utcMinutesFromMidnight);
|
||||
return localMinsFromMidnight - timeToMinutes(referenceHHMM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert total minutes to a human-readable string (e.g., "1h 30m" or "45m")
|
||||
* @param totalMinutes - Total number of minutes
|
||||
|
||||
Reference in New Issue
Block a user