{/* Handtaget står mitt över strecket (mål) eller fyllningens kant (nu), samma x som fingret ger (v769).
Bara vid 0 och 100 % hålls det 4 px innanför kanten. */}
);
}
function GbuValTab({ label, value, on, tone, onSelect, onStep }){
const col = tone === 'sky' ? 'text-sky-200' : 'text-emerald-300';
const ring = tone === 'sky' ? 'ring-sky-400/70' : 'ring-emerald-400/70';
const empty = value === '' || value == null;
const step = (s) => (
);
return (
{on && step(-1)}
{on && step(1)}
);
}
/* Backendens plan ritad från nu till fönstrets slut. Inga egna tal: blocken är planens kvartar.
depQ = avresans kvart i fönstret (dag 0 = i dag), ritas bara inne i fönstret. */
function GbuTimeline({ ranges, from, to, depQ, depTime, tomLbl, colOf }){
const span = Math.max(1, to - from);
const xp = (q) => Math.max(0, Math.min(100, ((q - from) / span) * 100));
const ticks = [24, 48, 72, 96, 120, 144, 168].filter(q => q > from + 6 && q < to);
const depX = depQ != null && depQ > from && depQ <= to ? xp(depQ) : null;
const lbl = (q) => q % 96 === 0 ? tomLbl : String(Math.floor((q % 96) / 4)).padStart(2, '0');
return (
);
}
/* Det man ställer in en gång: ihopfällt med valt värde, förklaringarna i den öppnade raden. */
function GbuSetRow({ id, title, value, note, tag, tagBad, open, onToggle, children }){
return (
{open &&
{children}
}
);
}
function GbuToggleRow({ on, tone, title, desc, onClick }){
const onCls = tone === 'amber' ? 'border-amber-400/50 bg-amber-500/10' : 'border-emerald-400/50 bg-emerald-500/10';
const tCol = on ? (tone === 'amber' ? 'text-amber-200' : 'text-emerald-200') : 'text-slate-200';
return (
);
}
/* Två laddboxar: en flik per box, en box i taget. items: [{key, name, sub, col}]. */
function GbuBoxTabs({ items, sel, onSel }){
return (
{items.map((it, k) => { const on = k === sel; return (
); })}
);
}
/* Spara och Ångra i en rad ovanför bottenflikarna. Spärren (v697/v698) står i den gula ramen. */
function GbuSaveBar({ dirty, blocked, toast, onSave, onUndo }){
if (!dirty && !toast) return null;
const tc = toast ? ({ ok:'text-emerald-300', warn:'text-yellow-300', err:'text-rose-300' }[toast.kind] || 'text-slate-300') : 'text-slate-200';
return (
{dirty && blocked &&
Lägg till en avresedag i veckoschemat eller välj ett annat laddläge för att spara.