feat(web): add routine preview timeline in editor
- Visual horizontal bar chart showing step durations proportionally - Color-coded segments with golden-angle hue distribution - Time markers (start/end) and step legend - Updates live as steps are added, removed, or reordered
This commit is contained in:
parent
d3b55a2135
commit
c5c800077c
220
web/public/sw.js
220
web/public/sw.js
File diff suppressed because one or more lines are too long
@ -255,6 +255,86 @@ export function RoutineEditor({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Timeline Preview */}
|
||||||
|
{steps.length > 0 && totalMinutes > 0 && (
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-2" style={{ color: 'var(--cm-text-secondary)' }}>
|
||||||
|
Timeline Preview
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
className="rounded-xl border p-3"
|
||||||
|
style={{ backgroundColor: 'var(--cm-surface-card)', borderColor: 'var(--cm-border)' }}
|
||||||
|
>
|
||||||
|
{/* Bar segments */}
|
||||||
|
<div className="flex rounded-lg overflow-hidden h-8 mb-2">
|
||||||
|
{steps.map((step, idx) => {
|
||||||
|
const pct = totalMinutes > 0 ? (step.durationMinutes / totalMinutes) * 100 : 0;
|
||||||
|
const hue = (idx * 137) % 360; // golden-angle distribution for distinct colors
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={step.id}
|
||||||
|
className="flex items-center justify-center text-[10px] font-medium truncate px-1"
|
||||||
|
style={{
|
||||||
|
width: `${Math.max(pct, 2)}%`,
|
||||||
|
backgroundColor: `hsl(${hue}, 60%, 45%)`,
|
||||||
|
color: '#fff',
|
||||||
|
borderRight: idx < steps.length - 1 ? '1px solid var(--cm-bg-elevated)' : undefined,
|
||||||
|
}}
|
||||||
|
title={`${step.label || `Step ${idx + 1}`}: ${step.durationMinutes}m`}
|
||||||
|
>
|
||||||
|
{pct > 10 ? (step.label || `#${idx + 1}`) : ''}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
{/* Time markers */}
|
||||||
|
<div className="flex justify-between text-[10px]" style={{ color: 'var(--cm-text-tertiary)' }}>
|
||||||
|
<span>0:00</span>
|
||||||
|
{(() => {
|
||||||
|
let cumulative = 0;
|
||||||
|
return steps.slice(0, -1).map((step, idx) => {
|
||||||
|
cumulative += step.durationMinutes;
|
||||||
|
const pct = (cumulative / totalMinutes) * 100;
|
||||||
|
const h = Math.floor(cumulative / 60);
|
||||||
|
const m = cumulative % 60;
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
key={step.id}
|
||||||
|
className="absolute"
|
||||||
|
style={{ left: `${pct}%`, transform: 'translateX(-50%)' }}
|
||||||
|
>
|
||||||
|
{h > 0 ? `${h}:${String(m).padStart(2, '0')}` : `${m}m`}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})()}
|
||||||
|
<span>
|
||||||
|
{Math.floor(totalMinutes / 60) > 0
|
||||||
|
? `${Math.floor(totalMinutes / 60)}:${String(totalMinutes % 60).padStart(2, '0')}`
|
||||||
|
: `${totalMinutes}m`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{/* Step legend */}
|
||||||
|
<div className="flex flex-wrap gap-x-3 gap-y-1 mt-2">
|
||||||
|
{steps.map((step, idx) => {
|
||||||
|
const hue = (idx * 137) % 360;
|
||||||
|
return (
|
||||||
|
<div key={step.id} className="flex items-center gap-1">
|
||||||
|
<div
|
||||||
|
className="w-2 h-2 rounded-sm"
|
||||||
|
style={{ backgroundColor: `hsl(${hue}, 60%, 45%)` }}
|
||||||
|
/>
|
||||||
|
<span className="text-[10px]" style={{ color: 'var(--cm-text-tertiary)' }}>
|
||||||
|
{step.label || `Step ${idx + 1}`} ({step.durationMinutes}m)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Save as template toggle */}
|
{/* Save as template toggle */}
|
||||||
<label className="flex items-center gap-2 cursor-pointer">
|
<label className="flex items-center gap-2 cursor-pointer">
|
||||||
<input
|
<input
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user