@@ -25,6 +25,47 @@ const recentActivity = computed(() => overview.value?.recentEvents ?? [])
2525const perProject = computed (() => overview .value ?.perProject ?? [])
2626const projectCount = computed (() => overview .value ?.projects .total ?? 0 )
2727
28+ // ── Chart data ───────────────────────────────────────────────────────────
29+ // Theme-aligned hex (charts need concrete colours, not Tailwind tokens).
30+ const C = {
31+ primary: " #6366f1" ,
32+ open: " #3b82f6" ,
33+ inProgress: " #f59e0b" ,
34+ resolved: " #22c55e" ,
35+ closed: " #94a3b8" ,
36+ } as const
37+
38+ const volume = computed (() => overview .value ?.volume ?? [])
39+ const hasVolume = computed (() => volume .value .some ((v ) => v .count > 0 ))
40+ const volumeCategories = { count: { name: " Reports" , color: C .primary } }
41+ // vue-chrts x-formatter receives the data-array index; map it back to a
42+ // short MM-DD label.
43+ const volumeXFormatter = (i : number ): string => {
44+ const d = volume .value [i ]?.date
45+ return d ? d .slice (5 ) : " "
46+ }
47+
48+ const statusCounts = computed (() => overview .value ?.counts .byStatus )
49+ const STATUS_ORDER = [" open" , " in_progress" , " resolved" , " closed" ] as const
50+ const statusData = computed <number []>(() => STATUS_ORDER .map ((s ) => statusCounts .value ?.[s ] ?? 0 ))
51+ const hasStatus = computed (() => statusData .value .some ((n ) => n > 0 ))
52+ const statusCategories = {
53+ Open: { name: " Open" , color: C .open },
54+ " In progress" : { name: " In progress" , color: C .inProgress },
55+ Resolved: { name: " Resolved" , color: C .resolved },
56+ Closed: { name: " Closed" , color: C .closed },
57+ }
58+
59+ const topProjects = computed (() =>
60+ perProject .value
61+ .toSorted ((a , b ) => b .openCount - a .openCount )
62+ .slice (0 , 8 )
63+ .map ((p ) => ({ project: p .name , open: p .openCount })),
64+ )
65+ const hasTopProjects = computed (() => topProjects .value .some ((p ) => p .open > 0 ))
66+ const topProjectsCategories = { open: { name: " Open reports" , color: C .open } }
67+ const topProjectsXFormatter = (i : number ): string => topProjects .value [i ]?.project ?? " "
68+
2869const EVENT_LABEL: Record <string , string > = {
2970 status_changed: " changed status" ,
3071 priority_changed: " changed priority" ,
@@ -128,6 +169,66 @@ function describeEvent(e: AdminOverviewDTO["recentEvents"][number]): string {
128169 </NuxtLink >
129170 </div >
130171
172+ <!-- Insights -->
173+ <div v-if =" projectCount > 0" class =" space-y-4" >
174+ <div class =" rounded-xl border border-default bg-default" >
175+ <div class =" px-5 py-4 border-b border-default" >
176+ <h2 class =" text-sm font-semibold text-default tracking-tight" >Reports over time</h2 >
177+ <p class =" mt-0.5 text-sm text-muted" >Reports received per day, last 30 days.</p >
178+ </div >
179+ <div class =" p-5" >
180+ <AreaChart
181+ v-if =" hasVolume"
182+ :data =" volume"
183+ :height =" 240"
184+ :categories =" volumeCategories"
185+ :x-formatter =" volumeXFormatter"
186+ :x-num-ticks =" 6"
187+ />
188+ <div v-else class =" text-sm text-muted py-10 text-center" >No reports yet.</div >
189+ </div >
190+ </div >
191+
192+ <div class =" grid grid-cols-1 lg:grid-cols-2 gap-4" >
193+ <div class =" rounded-xl border border-default bg-default" >
194+ <div class =" px-5 py-4 border-b border-default" >
195+ <h2 class =" text-sm font-semibold text-default tracking-tight" >Status distribution</h2 >
196+ </div >
197+ <div class =" p-5 flex justify-center" >
198+ <DonutChart
199+ v-if =" hasStatus"
200+ :data =" statusData"
201+ :height =" 240"
202+ :categories =" statusCategories"
203+ :radius =" 4"
204+ :arc-width =" 24"
205+ />
206+ <div v-else class =" text-sm text-muted py-10 text-center" >No reports yet.</div >
207+ </div >
208+ </div >
209+
210+ <div class =" rounded-xl border border-default bg-default" >
211+ <div class =" px-5 py-4 border-b border-default" >
212+ <h2 class =" text-sm font-semibold text-default tracking-tight" >
213+ Top projects by open reports
214+ </h2 >
215+ </div >
216+ <div class =" p-5" >
217+ <BarChart
218+ v-if =" hasTopProjects"
219+ :data =" topProjects"
220+ :height =" 240"
221+ :categories =" topProjectsCategories"
222+ :y-axis =" ['open']"
223+ orientation =" horizontal"
224+ :x-formatter =" topProjectsXFormatter"
225+ />
226+ <div v-else class =" text-sm text-muted py-10 text-center" >No open reports yet.</div >
227+ </div >
228+ </div >
229+ </div >
230+ </div >
231+
131232 <!-- Two-column: recent reports + activity -->
132233 <div class =" grid grid-cols-1 lg:grid-cols-2 gap-4" >
133234 <div class =" rounded-xl border border-default bg-default" >
0 commit comments