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:
saravanakumardb1 2026-02-28 13:38:48 -08:00
parent d3b55a2135
commit c5c800077c
2 changed files with 80 additions and 220 deletions

File diff suppressed because one or more lines are too long

View File

@ -255,6 +255,86 @@ export function RoutineEditor({
</button>
</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 */}
<label className="flex items-center gap-2 cursor-pointer">
<input