feat(chronik): P6 — Dichteleiste zeigt, WANN die Box aktiv war (§4.5)
Ampel / ampel (push) Failing after 25s
Ampel / ampel (push) Failing after 25s
Der Zugewinn gegenueber einer reinen Liste: Ein stiller Tag und eine durchgearbeitete
Nacht sehen in einer Liste gleich aus — man muss scrollen, um es zu merken. Die
Dichteleiste macht das Muster sichtbar, bevor man den ersten Eintrag liest.
48 Stufen ueber den gezeigten Zeitraum, Achsenbeschriftung an beiden Enden, und beim
Filtern zeigt sie nur die Treffer (dann in kraeftigerem Ton).
Bewusst eigenes SVG-freies Markup statt einer Diagramm-Bibliothek: Es sind Rechtecke
ueber einer Zeitachse. Recharts dafuer zu laden waere 95 kB fuer etwas, das in 30
Zeilen passt — genau der Reflex, der in P0 das Startbuendel aufgeblaeht hatte.
Eine Kleinigkeit mit Absicht: Ein einzelner Eintrag bekommt 12 % Mindesthoehe. Ohne
sie saehe ein ruhiger Tag aus wie gar keine Aktivitaet — genau die Verwechslung, die
die Leiste verhindern soll. Dafuer gibt es einen eigenen Test.
Verifiziert gegen die echte Box:
150 Eintraege vom 02.08. bis 28.08., 25 von 48 Stufen belegt, Hoehen gestaffelt
Filter "update" -> 30 von 150, Leiste zeigt nur noch die Treffer und wird
hervorgehoben, Adresse /chronik?q=update
59 Tests gruen (5 neue) · ESLint 0 Fehler · Einstieg 133 400 B gzip / Budget 140 000.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3249c8e942
commit
33032f8fb0
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as B,M as T,j as e,N as p,W as g,b as n,J as D,T as G,z as L,O as $,f as R,k as F,u as I,r as q,P as y,Q as v,t as w,X as U,C as k,n as _,q as u}from"./index-Dd1yR_F_.js";import{S}from"./SectionLabel-DJUlupBf.js";import{C as K}from"./clock-BtwDQhLF.js";import{E as O}from"./external-link-CX_p2Ium.js";import{S as Q}from"./shield-ZD0AzbFC.js";/**
|
||||
import{c as B,M as T,j as e,N as p,W as g,b as n,J as D,T as G,z as L,O as $,f as R,k as F,u as I,r as q,P as y,Q as v,t as w,X as U,C as k,n as _,q as u}from"./index-l6kyKCL9.js";import{S}from"./SectionLabel-zEOM9wM3.js";import{C as K}from"./clock-QfyF1ur_.js";import{E as O}from"./external-link-ClFd8iN1.js";import{S as Q}from"./shield-CVm2T_f5.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as W,z as G,a2 as H,a3 as T,u as F,k as O,r as u,j as e,b as N,L as m,C as E,$ as P,a1 as z,a4 as R,X as I,T as B,G as V,n as $,a5 as _,q}from"./index-Dd1yR_F_.js";import{S}from"./SectionLabel-DJUlupBf.js";import{R as U}from"./refresh-cw-c_vCbXLa.js";import{A as Q}from"./arrow-right-BQzOIyyQ.js";import{C as K}from"./clock-BtwDQhLF.js";import{C as M}from"./chevron-down-BP_KoZ0O.js";import{H as Z}from"./hammer-Bhn7N9HF.js";/**
|
||||
import{c as W,z as G,a2 as H,a3 as T,u as F,k as O,r as u,j as e,b as N,L as m,C as E,$ as P,a1 as z,a4 as R,X as I,T as B,G as V,n as $,a5 as _,q}from"./index-l6kyKCL9.js";import{S}from"./SectionLabel-zEOM9wM3.js";import{R as U}from"./refresh-cw-BV1qZMSR.js";import{A as Q}from"./arrow-right-DSSXd-8y.js";import{C as K}from"./clock-QfyF1ur_.js";import{C as M}from"./chevron-down-C5keVSfm.js";import{H as Z}from"./hammer-Cl-GywsD.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+31
File diff suppressed because one or more lines are too long
-31
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as It,r as a,j as E,at as be,W as In,au as An,$ as _n,av as Mn,aw as ht,z as Tn,n as Ln,T as Fn,b as jn}from"./index-Dd1yR_F_.js";import{r as At}from"./index-C6cwCDNv.js";import{C as $n,a as Wn}from"./cloud-download-iKq6uLmJ.js";import{R as Un}from"./refresh-cw-c_vCbXLa.js";import{S as Bn}from"./search-304843k8.js";import{F as Vn}from"./file-text-CBWKyLdT.js";/**
|
||||
import{c as It,r as a,j as E,at as be,W as In,au as An,$ as _n,av as Mn,aw as ht,z as Tn,n as Ln,T as Fn,b as jn}from"./index-l6kyKCL9.js";import{r as At}from"./index-GVsa8kqq.js";import{C as $n,a as Wn}from"./cloud-download-YEWUSwEY.js";import{R as Un}from"./refresh-cw-BV1qZMSR.js";import{S as Bn}from"./search-C1Zs6OOf.js";import{F as Vn}from"./file-text-nO0jfgun.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as f,r as m,D as G,F as H,j as e,t as I,B as M,I as y,G as z,b as S,L as B,J as _,K as E,C as P}from"./index-Dd1yR_F_.js";import{A as u}from"./arrow-right-BQzOIyyQ.js";import{C as K}from"./chevron-down-BP_KoZ0O.js";/**
|
||||
import{c as f,r as m,D as G,F as H,j as e,t as I,B as M,I as y,G as z,b as S,L as B,J as _,K as E,C as P}from"./index-l6kyKCL9.js";import{A as u}from"./arrow-right-DSSXd-8y.js";import{C as K}from"./chevron-down-C5keVSfm.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as u,j as e,r as M,t as C,Y as z,Z as k,_ as y,Q as v,$ as L,a0 as G,W as D,b as f,a1 as T}from"./index-Dd1yR_F_.js";import{a as w,L as B,C as P,Z as E}from"./zap-Dk2Mubv-.js";import{L as R}from"./layers-D93tY13T.js";import{S as I}from"./shield-ZD0AzbFC.js";/**
|
||||
import{c as u,j as e,r as M,t as C,Y as z,Z as k,_ as y,Q as v,$ as L,a0 as G,W as D,b as f,a1 as T}from"./index-l6kyKCL9.js";import{a as w,L as B,C as P,Z as E}from"./zap-CnZftvep.js";import{L as R}from"./layers-BFKjGswj.js";import{S as I}from"./shield-CVm2T_f5.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as R,$ as O,r as u,n as z,j as e,b as S,L as D,G as T,X as Q,a3 as Se,u as De,k as Ce,z as Ke,A as ze,a6 as Ee,a7 as xe,C as Ae,T as Le,a4 as Pe,R as We,q as $e}from"./index-Dd1yR_F_.js";import{S as Re}from"./SectionLabel-DJUlupBf.js";import{C as ee}from"./code-xml-DazFbjTe.js";import{H as X}from"./hammer-Bhn7N9HF.js";import{R as ge}from"./refresh-cw-c_vCbXLa.js";import{F as fe}from"./file-text-CBWKyLdT.js";import{L as Be}from"./layers-D93tY13T.js";import{C as U}from"./chevron-down-BP_KoZ0O.js";import{C as he}from"./clock-BtwDQhLF.js";/**
|
||||
import{c as R,$ as O,r as u,n as z,j as e,b as S,L as D,G as T,X as Q,a3 as Se,u as De,k as Ce,z as Ke,A as ze,a6 as Ee,a7 as xe,C as Ae,T as Le,a4 as Pe,R as We,q as $e}from"./index-l6kyKCL9.js";import{S as Re}from"./SectionLabel-zEOM9wM3.js";import{C as ee}from"./code-xml-QQtsFWSM.js";import{H as X}from"./hammer-Cl-GywsD.js";import{R as ge}from"./refresh-cw-BV1qZMSR.js";import{F as fe}from"./file-text-nO0jfgun.js";import{L as Be}from"./layers-BFKjGswj.js";import{C as U}from"./chevron-down-C5keVSfm.js";import{C as he}from"./clock-QfyF1ur_.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{u,ag as m,k as x,j as s,y as n,ah as b,b as p,n as f,q as h}from"./index-Dd1yR_F_.js";function y(){const d=u(),{data:t=[]}=m(),{showAlert:l,dialogElement:o}=x();async function i(e){try{await f(`/api/jobs/${e}/cancel`,{method:"POST"}),d.invalidateQueries({queryKey:h.jobs})}catch(c){l("Fehler",c.message)}}const a=t.filter(e=>e.state==="running"||e.state==="queued"),r=t.filter(e=>e.state!=="running"&&e.state!=="queued").slice(-3);return a.length===0&&r.length===0?null:s.jsxs("div",{className:"space-y-3 mc-card p-4",children:[s.jsx("div",{className:"text-[11px] font-semibold text-muted-foreground uppercase tracking-wider",children:"Aktive Downloads"}),a.map(e=>s.jsxs("div",{className:"space-y-1.5 p-3 rounded-xl bg-background/20 border border-border/40",children:[s.jsxs("div",{className:"flex justify-between items-center text-xs",children:[s.jsx("span",{className:"font-semibold truncate max-w-[250px]",children:e.label}),s.jsxs("div",{className:"flex items-center gap-3",children:[s.jsxs("span",{className:"text-muted-foreground font-mono",children:[e.progress??0,"% • ",n(e.done_bytes),"/",n(e.total_bytes),e.eta_s?` • ETA ${b(e.eta_s)}`:""]}),s.jsx("button",{onClick:()=>i(e.id),className:"text-[10px] text-red-400 hover:text-red-300 font-semibold border border-red-500/25 bg-red-500/5 px-2 py-0.5 rounded transition-all cursor-pointer",children:"Abbrechen"})]})]}),s.jsx("div",{className:"h-1.5 overflow-hidden rounded-full bg-muted",children:s.jsx("div",{className:"h-full rounded-full bg-primary transition-all duration-500",style:{width:`${e.progress??0}%`}})})]},e.id)),r.map(e=>s.jsxs("div",{className:"flex justify-between items-center text-xs text-muted-foreground px-1",children:[s.jsx("span",{className:"truncate",children:e.label}),s.jsx("span",{className:p("font-semibold text-[10px] px-1.5 py-0.5 rounded uppercase font-mono",e.state==="done"?"bg-emerald-500/10 text-emerald-400":"bg-amber-500/10 text-amber-400"),children:e.state})]},e.id)),o]})}export{y as J};
|
||||
import{u,ag as m,k as x,j as s,y as n,ah as b,b as p,n as f,q as h}from"./index-l6kyKCL9.js";function y(){const d=u(),{data:t=[]}=m(),{showAlert:l,dialogElement:o}=x();async function i(e){try{await f(`/api/jobs/${e}/cancel`,{method:"POST"}),d.invalidateQueries({queryKey:h.jobs})}catch(c){l("Fehler",c.message)}}const a=t.filter(e=>e.state==="running"||e.state==="queued"),r=t.filter(e=>e.state!=="running"&&e.state!=="queued").slice(-3);return a.length===0&&r.length===0?null:s.jsxs("div",{className:"space-y-3 mc-card p-4",children:[s.jsx("div",{className:"text-[11px] font-semibold text-muted-foreground uppercase tracking-wider",children:"Aktive Downloads"}),a.map(e=>s.jsxs("div",{className:"space-y-1.5 p-3 rounded-xl bg-background/20 border border-border/40",children:[s.jsxs("div",{className:"flex justify-between items-center text-xs",children:[s.jsx("span",{className:"font-semibold truncate max-w-[250px]",children:e.label}),s.jsxs("div",{className:"flex items-center gap-3",children:[s.jsxs("span",{className:"text-muted-foreground font-mono",children:[e.progress??0,"% • ",n(e.done_bytes),"/",n(e.total_bytes),e.eta_s?` • ETA ${b(e.eta_s)}`:""]}),s.jsx("button",{onClick:()=>i(e.id),className:"text-[10px] text-red-400 hover:text-red-300 font-semibold border border-red-500/25 bg-red-500/5 px-2 py-0.5 rounded transition-all cursor-pointer",children:"Abbrechen"})]})]}),s.jsx("div",{className:"h-1.5 overflow-hidden rounded-full bg-muted",children:s.jsx("div",{className:"h-full rounded-full bg-primary transition-all duration-500",style:{width:`${e.progress??0}%`}})})]},e.id)),r.map(e=>s.jsxs("div",{className:"flex justify-between items-center text-xs text-muted-foreground px-1",children:[s.jsx("span",{className:"truncate",children:e.label}),s.jsx("span",{className:p("font-semibold text-[10px] px-1.5 py-0.5 rounded uppercase font-mono",e.state==="done"?"bg-emerald-500/10 text-emerald-400":"bg-amber-500/10 text-amber-400"),children:e.state})]},e.id)),o]})}export{y as J};
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
import{O as h,u as g,P as p,r as d,j as e,b as x,T as j,S as N,n as v,U as m,V as w,q as f}from"./index-Dd1yR_F_.js";import{E as k}from"./external-link-CX_p2Ium.js";import{R as y}from"./refresh-cw-c_vCbXLa.js";function K(){const{data:t}=h(),u=g(),a=t!=null&&t.box_console_url?p(t.box_console_url):void 0,n=t==null?void 0:t.box_console_reachable,[o,i]=d.useState(!1),[c,l]=d.useState("");async function b(){i(!0),l("");try{const s=await v("/api/maintenance/restart",{method:"POST",body:JSON.stringify({service:"box-console"})}),r=s.ok?"Konsolen-Dienst neu gestartet — einen Moment, dann lädt das Terminal.":`Neustart fehlgeschlagen: ${s.err||"Unbekannter Fehler"}`;l(r),m(s.ok?"erfolg":"fehler",r),w(u,f.agentStatus,f.services)}catch(s){const r=`Neustart fehlgeschlagen: ${(s==null?void 0:s.message)||s}`;l(r),m("fehler",r)}finally{i(!1)}}return e.jsxs("div",{className:"flex h-full flex-col gap-4",children:[e.jsxs("div",{className:"flex flex-wrap items-end justify-between gap-3",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text font-space text-2xl font-bold tracking-tight text-transparent",children:"Konsole"}),e.jsx("p",{className:"flex items-center gap-2 text-sm text-muted-foreground",children:"Direkte Shell auf der Box — wie ein SSH-Fenster, mitten im Browser."})]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsxs("span",{className:"flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground",children:[e.jsx("span",{className:x("h-2 w-2 rounded-full",n?"bg-emerald-500 animate-pulse":"bg-amber-500")}),n?"online":"offline"]}),a&&e.jsxs("a",{href:a,target:"_blank",rel:"noopener",className:"flex h-8 items-center gap-1.5 rounded-lg border border-border/60 bg-background/20 px-3 text-xs font-semibold text-muted-foreground transition-all hover:border-primary/50 hover:text-foreground",children:[e.jsx(k,{className:"h-3.5 w-3.5"})," In neuem Tab"]})]})]}),a?e.jsxs("div",{className:"relative min-h-[58vh] flex-1 overflow-hidden rounded-2xl border border-border/60 bg-black/50 shadow-lg shadow-black/25",children:[n===!1&&e.jsxs("div",{className:"absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 bg-black/70 text-center",children:[e.jsx(j,{className:"h-8 w-8 text-amber-400"}),e.jsx("div",{className:"text-sm font-semibold text-amber-300",children:"Konsole nicht erreichbar"}),e.jsxs("p",{className:"max-w-sm text-[11px] leading-normal text-muted-foreground",children:["Der Terminal-Dienst (",e.jsx("code",{className:"font-mono text-primary",children:"box-console"}),") läuft gerade nicht."]}),e.jsxs("button",{onClick:b,disabled:o,className:"flex h-9 items-center gap-1.5 rounded-lg border border-amber-500/40 bg-amber-500/10 px-4 text-[11px] font-bold uppercase tracking-wide text-amber-300 transition-all hover:bg-amber-500/20 cursor-pointer disabled:opacity-50",children:[e.jsx(y,{className:x("h-3.5 w-3.5",o&&"animate-spin")})," Dienst neu starten"]}),c&&e.jsx("p",{className:"max-w-sm text-[11px] text-muted-foreground",children:c})]}),e.jsx("iframe",{src:a,title:"Box-Konsole",className:"h-full w-full border-0",style:{minHeight:"58vh"}})]}):e.jsxs("div",{className:"flex min-h-[58vh] flex-1 items-center justify-center rounded-2xl border border-border/60 bg-background/20 text-xs text-muted-foreground",children:[e.jsx(N,{className:"mr-2 h-4 w-4"})," Lade Konsole…"]})]})}export{K as KonsoleView};
|
||||
import{O as h,u as g,P as p,r as d,j as e,b as x,T as j,S as N,n as v,U as m,V as w,q as f}from"./index-l6kyKCL9.js";import{E as k}from"./external-link-ClFd8iN1.js";import{R as y}from"./refresh-cw-BV1qZMSR.js";function K(){const{data:t}=h(),u=g(),a=t!=null&&t.box_console_url?p(t.box_console_url):void 0,n=t==null?void 0:t.box_console_reachable,[o,i]=d.useState(!1),[c,l]=d.useState("");async function b(){i(!0),l("");try{const s=await v("/api/maintenance/restart",{method:"POST",body:JSON.stringify({service:"box-console"})}),r=s.ok?"Konsolen-Dienst neu gestartet — einen Moment, dann lädt das Terminal.":`Neustart fehlgeschlagen: ${s.err||"Unbekannter Fehler"}`;l(r),m(s.ok?"erfolg":"fehler",r),w(u,f.agentStatus,f.services)}catch(s){const r=`Neustart fehlgeschlagen: ${(s==null?void 0:s.message)||s}`;l(r),m("fehler",r)}finally{i(!1)}}return e.jsxs("div",{className:"flex h-full flex-col gap-4",children:[e.jsxs("div",{className:"flex flex-wrap items-end justify-between gap-3",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text font-space text-2xl font-bold tracking-tight text-transparent",children:"Konsole"}),e.jsx("p",{className:"flex items-center gap-2 text-sm text-muted-foreground",children:"Direkte Shell auf der Box — wie ein SSH-Fenster, mitten im Browser."})]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsxs("span",{className:"flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground",children:[e.jsx("span",{className:x("h-2 w-2 rounded-full",n?"bg-emerald-500 animate-pulse":"bg-amber-500")}),n?"online":"offline"]}),a&&e.jsxs("a",{href:a,target:"_blank",rel:"noopener",className:"flex h-8 items-center gap-1.5 rounded-lg border border-border/60 bg-background/20 px-3 text-xs font-semibold text-muted-foreground transition-all hover:border-primary/50 hover:text-foreground",children:[e.jsx(k,{className:"h-3.5 w-3.5"})," In neuem Tab"]})]})]}),a?e.jsxs("div",{className:"relative min-h-[58vh] flex-1 overflow-hidden rounded-2xl border border-border/60 bg-black/50 shadow-lg shadow-black/25",children:[n===!1&&e.jsxs("div",{className:"absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 bg-black/70 text-center",children:[e.jsx(j,{className:"h-8 w-8 text-amber-400"}),e.jsx("div",{className:"text-sm font-semibold text-amber-300",children:"Konsole nicht erreichbar"}),e.jsxs("p",{className:"max-w-sm text-[11px] leading-normal text-muted-foreground",children:["Der Terminal-Dienst (",e.jsx("code",{className:"font-mono text-primary",children:"box-console"}),") läuft gerade nicht."]}),e.jsxs("button",{onClick:b,disabled:o,className:"flex h-9 items-center gap-1.5 rounded-lg border border-amber-500/40 bg-amber-500/10 px-4 text-[11px] font-bold uppercase tracking-wide text-amber-300 transition-all hover:bg-amber-500/20 cursor-pointer disabled:opacity-50",children:[e.jsx(y,{className:x("h-3.5 w-3.5",o&&"animate-spin")})," Dienst neu starten"]}),c&&e.jsx("p",{className:"max-w-sm text-[11px] text-muted-foreground",children:c})]}),e.jsx("iframe",{src:a,title:"Box-Konsole",className:"h-full w-full border-0",style:{minHeight:"58vh"}})]}):e.jsxs("div",{className:"flex min-h-[58vh] flex-1 items-center justify-center rounded-2xl border border-border/60 bg-background/20 text-xs text-muted-foreground",children:[e.jsx(N,{className:"mr-2 h-4 w-4"})," Lade Konsole…"]})]})}export{K as KonsoleView};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as E,u as he,a as qe,r as v,j as e,C as Y,L as Fe,b as k,d as Re,q as V,R as Ie,B as re,e as Pe,E as ze,f as le,g as Be,h as Ae,i as Oe,k as Ge,l as ee,X as xe,H as be,T as se,s as Te,m as He,n as q,o as U,p as Qe,t as Ue,v as Ve,w as We,x as Ze,y as Je,z as Xe,A as Ye}from"./index-Dd1yR_F_.js";import{J as et}from"./JobsBar-CnTUCk_C.js";import{C as tt}from"./code-xml-DazFbjTe.js";import{C as ue,Z as ie,L as rt,a as st}from"./zap-Dk2Mubv-.js";import{S as ge}from"./search-304843k8.js";import{P as nt}from"./power-DIQA5x8I.js";import{C as $e}from"./chevron-down-BP_KoZ0O.js";import{L as at}from"./layers-D93tY13T.js";/**
|
||||
import{c as E,u as he,a as qe,r as v,j as e,C as Y,L as Fe,b as k,d as Re,q as V,R as Ie,B as re,e as Pe,E as ze,f as le,g as Be,h as Ae,i as Oe,k as Ge,l as ee,X as xe,H as be,T as se,s as Te,m as He,n as q,o as U,p as Qe,t as Ue,v as Ve,w as We,x as Ze,y as Je,z as Xe,A as Ye}from"./index-l6kyKCL9.js";import{J as et}from"./JobsBar-HflSaqor.js";import{C as tt}from"./code-xml-QQtsFWSM.js";import{C as ue,Z as ie,L as rt,a as st}from"./zap-CnZftvep.js";import{S as ge}from"./search-C1Zs6OOf.js";import{P as nt}from"./power-DcJoPDeQ.js";import{C as $e}from"./chevron-down-C5keVSfm.js";import{L as at}from"./layers-BFKjGswj.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
import{j as e}from"./index-Dd1yR_F_.js";function n({icon:t,children:s}){return e.jsxs("p",{className:"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/60",children:[e.jsx(t,{className:"h-3.5 w-3.5"})," ",s]})}export{n as S};
|
||||
import{j as e}from"./index-l6kyKCL9.js";function n({icon:t,children:s}){return e.jsxs("p",{className:"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/60",children:[e.jsx(t,{className:"h-3.5 w-3.5"})," ",s]})}export{n as S};
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
var E=r=>{throw TypeError(r)};var O=(r,e,t)=>e.has(r)||E("Cannot "+t);var a=(r,e,t)=>(O(r,e,"read from private field"),t?t.call(r):e.get(r)),y=(r,e,t)=>e.has(r)?E("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(r):e.set(r,t),v=(r,e,t,n)=>(O(r,e,"write to private field"),n?n.call(r,t):e.set(r,t),t),j=(r,e,t)=>(O(r,e,"access private method"),t);import{a8 as Q,a9 as A,aa as M,ab as B,ac as P,u as J,r as f,ad as F,ae as I,c as T,af as U,j as s,J as D,T as H,b as K,L,Q as V,q as _,n as R}from"./index-Dd1yR_F_.js";import{J as z}from"./JobsBar-CnTUCk_C.js";var c,g,o,h,m,w,C,q,G=(q=class extends Q{constructor(e,t){super();y(this,m);y(this,c);y(this,g);y(this,o);y(this,h);v(this,c,e),this.setOptions(t),this.bindMethods(),j(this,m,w).call(this)}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(e){var n;const t=this.options;this.options=a(this,c).defaultMutationOptions(e),A(this.options,t)||a(this,c).getMutationCache().notify({type:"observerOptionsUpdated",mutation:a(this,o),observer:this}),t!=null&&t.mutationKey&&this.options.mutationKey&&M(t.mutationKey)!==M(this.options.mutationKey)?this.reset():((n=a(this,o))==null?void 0:n.state.status)==="pending"&&a(this,o).setOptions(this.options)}onUnsubscribe(){var e;this.hasListeners()||(e=a(this,o))==null||e.removeObserver(this)}onMutationUpdate(e){j(this,m,w).call(this),j(this,m,C).call(this,e)}getCurrentResult(){return a(this,g)}reset(){var e;(e=a(this,o))==null||e.removeObserver(this),v(this,o,void 0),j(this,m,w).call(this),j(this,m,C).call(this)}mutate(e,t){var n;return v(this,h,t),(n=a(this,o))==null||n.removeObserver(this),v(this,o,a(this,c).getMutationCache().build(a(this,c),this.options)),a(this,o).addObserver(this),a(this,o).execute(e)}},c=new WeakMap,g=new WeakMap,o=new WeakMap,h=new WeakMap,m=new WeakSet,w=function(){var t;const e=((t=a(this,o))==null?void 0:t.state)??B();v(this,g,{...e,isPending:e.status==="pending",isSuccess:e.status==="success",isError:e.status==="error",isIdle:e.status==="idle",mutate:this.mutate,reset:this.reset})},C=function(e){P.batch(()=>{var t,n,l,x,d,p,k,i;if(a(this,h)&&this.hasListeners()){const u=a(this,g).variables,N=a(this,g).context,S={client:a(this,c),meta:this.options.meta,mutationKey:this.options.mutationKey};if((e==null?void 0:e.type)==="success"){try{(n=(t=a(this,h)).onSuccess)==null||n.call(t,e.data,u,N,S)}catch(b){Promise.reject(b)}try{(x=(l=a(this,h)).onSettled)==null||x.call(l,e.data,null,u,N,S)}catch(b){Promise.reject(b)}}else if((e==null?void 0:e.type)==="error"){try{(p=(d=a(this,h)).onError)==null||p.call(d,e.error,u,N,S)}catch(b){Promise.reject(b)}try{(i=(k=a(this,h)).onSettled)==null||i.call(k,void 0,e.error,u,N,S)}catch(b){Promise.reject(b)}}}this.listeners.forEach(u=>{u(a(this,g))})})},q);function W(r,e){const t=J(),[n]=f.useState(()=>new G(t,r));f.useEffect(()=>{n.setOptions(r)},[n,r]);const l=f.useSyncExternalStore(f.useCallback(d=>n.subscribe(P.batchCalls(d)),[n]),()=>n.getCurrentResult(),()=>n.getCurrentResult()),x=f.useCallback((d,p)=>{n.mutate(d,p).catch(F)},[n]);if(l.error&&I(n.options.throwOnError,[l.error]))throw l.error;return{...l,mutate:x,mutateAsync:l.mutate}}/**
|
||||
var E=r=>{throw TypeError(r)};var O=(r,e,t)=>e.has(r)||E("Cannot "+t);var a=(r,e,t)=>(O(r,e,"read from private field"),t?t.call(r):e.get(r)),y=(r,e,t)=>e.has(r)?E("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(r):e.set(r,t),v=(r,e,t,n)=>(O(r,e,"write to private field"),n?n.call(r,t):e.set(r,t),t),j=(r,e,t)=>(O(r,e,"access private method"),t);import{a8 as Q,a9 as A,aa as M,ab as B,ac as P,u as J,r as f,ad as F,ae as I,c as T,af as U,j as s,J as D,T as H,b as K,L,Q as V,q as _,n as R}from"./index-l6kyKCL9.js";import{J as z}from"./JobsBar-HflSaqor.js";var c,g,o,h,m,w,C,q,G=(q=class extends Q{constructor(e,t){super();y(this,m);y(this,c);y(this,g);y(this,o);y(this,h);v(this,c,e),this.setOptions(t),this.bindMethods(),j(this,m,w).call(this)}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(e){var n;const t=this.options;this.options=a(this,c).defaultMutationOptions(e),A(this.options,t)||a(this,c).getMutationCache().notify({type:"observerOptionsUpdated",mutation:a(this,o),observer:this}),t!=null&&t.mutationKey&&this.options.mutationKey&&M(t.mutationKey)!==M(this.options.mutationKey)?this.reset():((n=a(this,o))==null?void 0:n.state.status)==="pending"&&a(this,o).setOptions(this.options)}onUnsubscribe(){var e;this.hasListeners()||(e=a(this,o))==null||e.removeObserver(this)}onMutationUpdate(e){j(this,m,w).call(this),j(this,m,C).call(this,e)}getCurrentResult(){return a(this,g)}reset(){var e;(e=a(this,o))==null||e.removeObserver(this),v(this,o,void 0),j(this,m,w).call(this),j(this,m,C).call(this)}mutate(e,t){var n;return v(this,h,t),(n=a(this,o))==null||n.removeObserver(this),v(this,o,a(this,c).getMutationCache().build(a(this,c),this.options)),a(this,o).addObserver(this),a(this,o).execute(e)}},c=new WeakMap,g=new WeakMap,o=new WeakMap,h=new WeakMap,m=new WeakSet,w=function(){var t;const e=((t=a(this,o))==null?void 0:t.state)??B();v(this,g,{...e,isPending:e.status==="pending",isSuccess:e.status==="success",isError:e.status==="error",isIdle:e.status==="idle",mutate:this.mutate,reset:this.reset})},C=function(e){P.batch(()=>{var t,n,l,x,d,p,k,i;if(a(this,h)&&this.hasListeners()){const u=a(this,g).variables,N=a(this,g).context,S={client:a(this,c),meta:this.options.meta,mutationKey:this.options.mutationKey};if((e==null?void 0:e.type)==="success"){try{(n=(t=a(this,h)).onSuccess)==null||n.call(t,e.data,u,N,S)}catch(b){Promise.reject(b)}try{(x=(l=a(this,h)).onSettled)==null||x.call(l,e.data,null,u,N,S)}catch(b){Promise.reject(b)}}else if((e==null?void 0:e.type)==="error"){try{(p=(d=a(this,h)).onError)==null||p.call(d,e.error,u,N,S)}catch(b){Promise.reject(b)}try{(i=(k=a(this,h)).onSettled)==null||i.call(k,void 0,e.error,u,N,S)}catch(b){Promise.reject(b)}}}this.listeners.forEach(u=>{u(a(this,g))})})},q);function W(r,e){const t=J(),[n]=f.useState(()=>new G(t,r));f.useEffect(()=>{n.setOptions(r)},[n,r]);const l=f.useSyncExternalStore(f.useCallback(d=>n.subscribe(P.batchCalls(d)),[n]),()=>n.getCurrentResult(),()=>n.getCurrentResult()),x=f.useCallback((d,p)=>{n.mutate(d,p).catch(F)},[n]);if(l.error&&I(n.options.throwOnError,[l.error]))throw l.error;return{...l,mutate:x,mutateAsync:l.mutate}}/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as v,j as e,b as c,r as l,T as W,L as ae,E as ve,n as b,Q as le,Y as oe,X as de,J as Se,u as _e,k as Ce,x as ze,as as Fe,ag as Le,am as Ue,V as P,t as Ae,y as q,q as N}from"./index-Dd1yR_F_.js";import{R as F}from"./refresh-cw-c_vCbXLa.js";import{F as Pe}from"./file-text-CBWKyLdT.js";import{S as Be}from"./search-304843k8.js";import{S as ce}from"./shield-ZD0AzbFC.js";import{A as ne}from"./arrow-right-BQzOIyyQ.js";import{C as Re}from"./clock-BtwDQhLF.js";import{E as $e}from"./external-link-CX_p2Ium.js";import{C as De,a as Ee}from"./cloud-download-iKq6uLmJ.js";import{P as Oe}from"./power-DIQA5x8I.js";/**
|
||||
import{c as v,j as e,b as c,r as l,T as W,L as ae,E as ve,n as b,Q as le,Y as oe,X as de,J as Se,u as _e,k as Ce,x as ze,as as Fe,ag as Le,am as Ue,V as P,t as Ae,y as q,q as N}from"./index-l6kyKCL9.js";import{R as F}from"./refresh-cw-BV1qZMSR.js";import{F as Pe}from"./file-text-nO0jfgun.js";import{S as Be}from"./search-C1Zs6OOf.js";import{S as ce}from"./shield-CVm2T_f5.js";import{A as ne}from"./arrow-right-DSSXd-8y.js";import{C as Re}from"./clock-QfyF1ur_.js";import{E as $e}from"./external-link-ClFd8iN1.js";import{C as De,a as Ee}from"./cloud-download-YEWUSwEY.js";import{P as Oe}from"./power-DcJoPDeQ.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
var to=Object.defineProperty;var eo=(e,n,r)=>n in e?to(e,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[n]=r;var Bn=(e,n,r)=>eo(e,typeof n!="symbol"?n+"":n,r);import{c as no,r as W,an as ro,ao as io,j as $,ap as oo,z as ao,A as so,aq as uo,n as lo,b as Ae,L as Ke,a1 as co,ar as fo}from"./index-Dd1yR_F_.js";import{i as Ft,c as Xn,a as Yn,b as ho,o as po,m as Zn,d as Kn}from"./string-DoZi9Vij.js";import{R as go}from"./refresh-cw-c_vCbXLa.js";import{S as yo}from"./search-304843k8.js";import{F as _o}from"./file-text-CBWKyLdT.js";/**
|
||||
var to=Object.defineProperty;var eo=(e,n,r)=>n in e?to(e,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[n]=r;var Bn=(e,n,r)=>eo(e,typeof n!="symbol"?n+"":n,r);import{c as no,r as W,an as ro,ao as io,j as $,ap as oo,z as ao,A as so,aq as uo,n as lo,b as Ae,L as Ke,a1 as co,ar as fo}from"./index-l6kyKCL9.js";import{i as Ft,c as Xn,a as Yn,b as ho,o as po,m as Zn,d as Kn}from"./string-DoZi9Vij.js";import{R as go}from"./refresh-cw-BV1qZMSR.js";import{S as yo}from"./search-C1Zs6OOf.js";import{F as _o}from"./file-text-nO0jfgun.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as r}from"./index-Dd1yR_F_.js";/**
|
||||
import{c as r}from"./index-l6kyKCL9.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as o}from"./index-Dd1yR_F_.js";/**
|
||||
import{c as o}from"./index-l6kyKCL9.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c}from"./index-Dd1yR_F_.js";/**
|
||||
import{c}from"./index-l6kyKCL9.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as a}from"./index-Dd1yR_F_.js";/**
|
||||
import{c as a}from"./index-l6kyKCL9.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as e}from"./index-Dd1yR_F_.js";/**
|
||||
import{c as e}from"./index-l6kyKCL9.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as a}from"./index-Dd1yR_F_.js";/**
|
||||
import{c as a}from"./index-l6kyKCL9.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as e}from"./index-Dd1yR_F_.js";/**
|
||||
import{c as e}from"./index-l6kyKCL9.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as a}from"./index-Dd1yR_F_.js";/**
|
||||
import{c as a}from"./index-l6kyKCL9.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
-1
@@ -1 +0,0 @@
|
||||
import{aA as r}from"./index-Dd1yR_F_.js";var o=r();export{o as r};
|
||||
+1
@@ -0,0 +1 @@
|
||||
import{aA as r}from"./index-l6kyKCL9.js";var o=r();export{o as r};
|
||||
+1
File diff suppressed because one or more lines are too long
+3
-3
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as a}from"./index-Dd1yR_F_.js";/**
|
||||
import{c as a}from"./index-l6kyKCL9.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as o}from"./index-Dd1yR_F_.js";/**
|
||||
import{c as o}from"./index-l6kyKCL9.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as e}from"./index-Dd1yR_F_.js";/**
|
||||
import{c as e}from"./index-l6kyKCL9.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c}from"./index-Dd1yR_F_.js";/**
|
||||
import{c}from"./index-l6kyKCL9.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as a}from"./index-Dd1yR_F_.js";/**
|
||||
import{c as a}from"./index-l6kyKCL9.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as a}from"./index-Dd1yR_F_.js";/**
|
||||
import{c as a}from"./index-l6kyKCL9.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
Vendored
+2
-2
@@ -7,8 +7,8 @@
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<title>Mission Control 2.0</title>
|
||||
<script type="module" crossorigin src="/assets/index-Dd1yR_F_.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-mvq7yy8w.css">
|
||||
<script type="module" crossorigin src="/assets/index-l6kyKCL9.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-d4K0juid.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { describe, expect, it } from "vitest"
|
||||
import { render } from "@testing-library/react"
|
||||
import { Dichteleiste } from "./Dichteleiste"
|
||||
import type { ChronikItem } from "@/lib/api"
|
||||
|
||||
function eintrag(ts: number): ChronikItem {
|
||||
return { id: ts, ts, subject: "x", text: "y", source: "test", priority: "normal" }
|
||||
}
|
||||
|
||||
/** Die Balken der Leiste (ohne die Achsen-Beschriftung darunter). */
|
||||
function balken(container: HTMLElement): HTMLElement[] {
|
||||
return [...container.querySelectorAll('[role="img"] > div')] as HTMLElement[]
|
||||
}
|
||||
|
||||
describe("Dichteleiste", () => {
|
||||
it("zeigt nichts, wenn es nichts zu zeigen gibt", () => {
|
||||
const { container } = render(<Dichteleiste items={[]} />)
|
||||
expect(container).toBeEmptyDOMElement()
|
||||
})
|
||||
|
||||
it("verteilt die Einträge über die volle Breite", () => {
|
||||
const basis = 1_800_000_000
|
||||
const items = [eintrag(basis), eintrag(basis + 43_200), eintrag(basis + 86_400)]
|
||||
const { container } = render(<Dichteleiste items={items} />)
|
||||
const b = balken(container)
|
||||
expect(b).toHaveLength(48)
|
||||
// Erster, mittlerer und letzter Eimer sind belegt — dazwischen ist es leer.
|
||||
expect(b[0].style.height).not.toBe("2px")
|
||||
expect(b[47].style.height).not.toBe("2px")
|
||||
expect(b[5].style.height).toBe("2px")
|
||||
})
|
||||
|
||||
// Ohne Mindesthöhe sähe ein ruhiger Tag aus wie gar keine Aktivität — genau die
|
||||
// Verwechslung, die die Leiste verhindern soll.
|
||||
it("macht auch einen einzelnen Eintrag sichtbar", () => {
|
||||
const { container } = render(<Dichteleiste items={[eintrag(1_800_000_000)]} />)
|
||||
const belegt = balken(container).filter((d) => d.style.height !== "2px")
|
||||
expect(belegt).toHaveLength(1)
|
||||
expect(parseFloat(belegt[0].style.height)).toBeGreaterThanOrEqual(12)
|
||||
})
|
||||
|
||||
it("beschriftet sich für Screenreader mit Anzahl und Zeitraum", () => {
|
||||
const basis = 1_800_000_000
|
||||
const { container } = render(<Dichteleiste items={[eintrag(basis), eintrag(basis + 86_400)]} />)
|
||||
const label = container.querySelector('[role="img"]')!.getAttribute("aria-label")!
|
||||
expect(label).toMatch(/2 Einträge/)
|
||||
expect(label).toMatch(/zwischen .* und /)
|
||||
})
|
||||
|
||||
it("hebt die Leiste hervor, solange gefiltert wird", () => {
|
||||
const items = [eintrag(1_800_000_000)]
|
||||
const normal = render(<Dichteleiste items={items} />).container
|
||||
const gefiltert = render(<Dichteleiste items={items} aktiv />).container
|
||||
const klasse = (c: HTMLElement) =>
|
||||
balken(c).find((d) => d.style.height !== "2px")!.className
|
||||
expect(klasse(normal)).toMatch(/primary\/40/)
|
||||
expect(klasse(gefiltert)).toMatch(/primary\/70/)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,74 @@
|
||||
import { useMemo } from "react"
|
||||
import type { ChronikItem } from "@/lib/api"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
// Aktivitäts-Dichte über der Chronik (v3-Umbau P6, Spezifikation §4.5).
|
||||
//
|
||||
// Der eigentliche Zugewinn gegenüber einer reinen Liste: Sie zeigt, WANN die Box aktiv
|
||||
// war, bevor man liest, WAS sie getan hat. Ein stiller Tag und eine durchgearbeitete
|
||||
// Nacht sehen in einer Liste gleich aus — man muss scrollen, um es zu merken.
|
||||
//
|
||||
// Bewusst eigenes SVG statt einer Diagramm-Bibliothek: Es sind Rechtecke über einer
|
||||
// Zeitachse. Recharts dafür zu laden wäre 95 kB für etwas, das in 30 Zeilen passt —
|
||||
// und genau dieser Reflex hat in P0 das Startbündel aufgebläht.
|
||||
|
||||
const STUNDE = 3_600
|
||||
const EIMER = 48 // 48 Stufen über den gezeigten Zeitraum
|
||||
|
||||
export function Dichteleiste({ items, aktiv }: {
|
||||
items: ChronikItem[]
|
||||
/** Farbe für den Fall, dass gefiltert wird — dann zeigt die Leiste nur die Treffer. */
|
||||
aktiv?: boolean
|
||||
}) {
|
||||
const { balken, von, bis, max } = useMemo(() => {
|
||||
if (!items.length) return { balken: [] as number[], von: 0, bis: 0, max: 0 }
|
||||
// Die Chronik kommt neueste zuerst; für die Achse brauchen wir beide Enden.
|
||||
const zeiten = items.map((i) => i.ts)
|
||||
const von = Math.min(...zeiten)
|
||||
const bis = Math.max(...zeiten)
|
||||
const spanne = Math.max(bis - von, STUNDE)
|
||||
const balken = new Array(EIMER).fill(0)
|
||||
for (const t of zeiten) {
|
||||
const i = Math.min(EIMER - 1, Math.floor(((t - von) / spanne) * EIMER))
|
||||
balken[i]++
|
||||
}
|
||||
return { balken, von, bis, max: Math.max(...balken, 1) }
|
||||
}, [items])
|
||||
|
||||
if (balken.length === 0) return null
|
||||
|
||||
const zeitpunkt = (ts: number) =>
|
||||
new Date(ts * 1000).toLocaleDateString("de-DE", {
|
||||
day: "2-digit", month: "2-digit",
|
||||
...(bis - von < 2 * 86_400 ? { hour: "2-digit", minute: "2-digit" } : {}),
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<div
|
||||
className="flex h-8 items-end gap-px"
|
||||
role="img"
|
||||
aria-label={`Aktivitätsdichte: ${items.length} Einträge zwischen ${zeitpunkt(von)} und ${zeitpunkt(bis)}`}
|
||||
>
|
||||
{balken.map((n, i) => (
|
||||
<div
|
||||
key={i}
|
||||
title={n === 1 ? "1 Eintrag" : `${n} Einträge`}
|
||||
className={cn(
|
||||
"min-w-px flex-1 rounded-t-sm transition-colors",
|
||||
n === 0 ? "bg-border/25" : aktiv ? "bg-primary/70" : "bg-primary/40",
|
||||
)}
|
||||
// Auch ein einzelner Eintrag muss sichtbar sein — sonst sieht ein ruhiger
|
||||
// Tag aus wie gar nichts.
|
||||
style={{ height: n === 0 ? "2px" : `${Math.max(12, (n / max) * 100)}%` }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-between font-mono text-[9px] text-muted-foreground/60">
|
||||
<span>{zeitpunkt(von)}</span>
|
||||
<span className="tabular-nums">{items.length} Einträge</span>
|
||||
<span>{zeitpunkt(bis)}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { useMemo, useState } from "react"
|
||||
import { AlarmClock, Archive, Bell, HeartPulse, History, Inbox, Loader2, MessageCircle, Moon, Radar, RotateCcw, Search, ShieldAlert, Sparkles, type LucideIcon } from "lucide-react"
|
||||
import { api, type ChronikItem, type Snapshot } from "@/lib/api"
|
||||
import { useNavigate, useSearch } from "@tanstack/react-router"
|
||||
import { Dichteleiste } from "@/features/chronik/Dichteleiste"
|
||||
import { useChronik, useZeitmaschine, useQueryClient, qk } from "@/lib/queries"
|
||||
import { useDialog } from "@/lib/useDialog"
|
||||
import { cn } from "@/lib/utils"
|
||||
@@ -90,6 +91,9 @@ export function ChronikView() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Zeigt, WANN die Box aktiv war — bevor man liest, WAS sie getan hat (§4.5). */}
|
||||
{!isLoading && items.length > 0 && <Dichteleiste items={items} aktiv={!!q} />}
|
||||
|
||||
{isLoading && (
|
||||
<div className="flex items-center gap-2 rounded-2xl border border-border/60 bg-card/30 p-6 text-xs text-muted-foreground">
|
||||
<Loader2 className="h-4 w-4 animate-spin" /> Chronik wird geladen …
|
||||
|
||||
Reference in New Issue
Block a user