22 * Main Dashboard component - orchestrates data fetching and child components
33 */
44
5+ import { add , isAfter } from "date-fns" ;
56import React from "react" ;
67import useSWR from "swr" ;
7- import { add , isAfter } from "date-fns" ;
88
99import { GearIcon , PauseIcon , PlayIcon } from "@components/icons" ;
1010import { NetworkGraph2d } from "@components/network-graph/network-graph-2d" ;
1111import { NetworkGraph3d } from "@components/network-graph/network-graph-3d" ;
1212
13- import { SettingsPanel , type ExpandedSections } from "./SettingsPanel " ;
13+ import { FieldReportsOverlay } from "./FieldReportsOverlay " ;
1414import { InfoPanel , type SelectedInfo } from "./InfoPanel" ;
15- import { TimelineBar } from "./TimelineBar" ;
1615import type { TimelineBucket } from "./sections" ;
16+ import { SettingsPanel , type ExpandedSections } from "./SettingsPanel" ;
17+ import { TimelineBar } from "./TimelineBar" ;
1718
19+ import { useFieldReports } from "@/hooks/dashboard" ;
1820import type { DataResponse } from "@/pages/api/data" ;
21+ import type { FieldReportsResponse } from "@/types" ;
1922import type { Voucher } from "@/types/voucher" ;
2023
2124// Fetcher function for SWR
@@ -29,10 +32,24 @@ const now = new Date();
2932
3033export function Dashboard ( ) {
3134 // Data fetching with SWR
32- const { data, error, isLoading } = useSWR < DataResponse > ( "/api/data" , fetcher , {
33- refreshInterval : 5 * 60 * 1000 ,
34- revalidateOnFocus : false ,
35- } ) ;
35+ const { data, error, isLoading } = useSWR < DataResponse > (
36+ "/api/data" ,
37+ fetcher ,
38+ {
39+ refreshInterval : 5 * 60 * 1000 ,
40+ revalidateOnFocus : false ,
41+ }
42+ ) ;
43+
44+ // Fetch field reports
45+ const { data : reportsData } = useSWR < FieldReportsResponse > (
46+ "/api/reports" ,
47+ fetcher ,
48+ {
49+ refreshInterval : 5 * 60 * 1000 ,
50+ revalidateOnFocus : false ,
51+ }
52+ ) ;
3653
3754 // Panel states
3855 const [ optionsOpen , setOptionsOpen ] = React . useState ( false ) ;
@@ -41,7 +58,9 @@ export function Dashboard() {
4158
4259 // Token filtering
4360 const [ selectedTokens , setSelectedTokens ] = React . useState < Voucher [ ] > ( [ ] ) ;
44- const [ filteredByToken , setFilteredByToken ] = React . useState < DataResponse [ "graphData" ] > ( {
61+ const [ filteredByToken , setFilteredByToken ] = React . useState <
62+ DataResponse [ "graphData" ]
63+ > ( {
4564 nodes : [ ] ,
4665 links : [ ] ,
4766 } ) ;
@@ -53,6 +72,7 @@ export function Dashboard() {
5372
5473 // Display options
5574 const [ showRecentOnly , setShowRecentOnly ] = React . useState ( true ) ;
75+ const [ showReports , setShowReports ] = React . useState ( false ) ;
5676
5777 // Physics settings - input values (immediate UI feedback)
5878 const [ chargeStrengthInput , setChargeStrengthInput ] = React . useState ( - 8 ) ;
@@ -69,12 +89,13 @@ export function Dashboard() {
6989 const [ copiedField , setCopiedField ] = React . useState < string | null > ( null ) ;
7090
7191 // Collapsible sections state
72- const [ expandedSections , setExpandedSections ] = React . useState < ExpandedSections > ( {
73- vouchers : true ,
74- animation : false ,
75- display : false ,
76- physics : false ,
77- } ) ;
92+ const [ expandedSections , setExpandedSections ] =
93+ React . useState < ExpandedSections > ( {
94+ vouchers : true ,
95+ animation : false ,
96+ display : false ,
97+ physics : false ,
98+ } ) ;
7899
79100 // Debounce physics updates
80101 React . useEffect ( ( ) => {
@@ -101,12 +122,15 @@ export function Dashboard() {
101122 const newGraphData = {
102123 nodes : data . graphData . nodes . filter ( ( node ) =>
103124 selectedTokens . some ( ( selectedToken ) =>
104- Object . keys ( node . usedVouchers ) . includes ( selectedToken . contract_address )
125+ Object . keys ( node . usedVouchers ) . includes (
126+ selectedToken . contract_address
127+ )
105128 )
106129 ) ,
107130 links : data . graphData . links . filter ( ( link ) =>
108131 selectedTokens . some (
109- ( selectedToken ) => selectedToken . contract_address === link . contract_address
132+ ( selectedToken ) =>
133+ selectedToken . contract_address === link . contract_address
110134 )
111135 ) ,
112136 } ;
@@ -186,7 +210,10 @@ export function Dashboard() {
186210 React . useEffect ( ( ) => {
187211 const handleKeyDown = ( e : KeyboardEvent ) => {
188212 // Ignore if user is typing in an input
189- if ( e . target instanceof HTMLInputElement || e . target instanceof HTMLTextAreaElement ) {
213+ if (
214+ e . target instanceof HTMLInputElement ||
215+ e . target instanceof HTMLTextAreaElement
216+ ) {
190217 return ;
191218 }
192219 if ( e . code === "Space" ) {
@@ -238,15 +265,39 @@ export function Dashboard() {
238265 nodes : filteredNodes ,
239266 links : activeLinks ,
240267 } ;
241- } , [ filteredByToken . links , filteredByToken . nodes , availableNodeIds , date , showRecentOnly ] ) ;
268+ } , [
269+ filteredByToken . links ,
270+ filteredByToken . nodes ,
271+ availableNodeIds ,
272+ date ,
273+ showRecentOnly ,
274+ ] ) ;
275+
276+ // Selected voucher addresses for filtering
277+ const selectedVoucherAddresses = React . useMemo (
278+ ( ) => new Set ( selectedTokens . map ( ( t ) => t . contract_address ) ) ,
279+ [ selectedTokens ]
280+ ) ;
281+
282+ // Field reports filtering
283+ const { visibleReports, dismissReport, resetDismissed } = useFieldReports ( {
284+ reports : reportsData ?. reports ?? [ ] ,
285+ currentDate : date ,
286+ selectedVoucherAddresses,
287+ maxVisible : 3 ,
288+ } ) ;
289+
290+ // Reset dismissed reports when animation restarts from beginning
291+ React . useEffect ( ( ) => {
292+ if ( date === dateRange . start ) {
293+ resetDismissed ( ) ;
294+ }
295+ } , [ date , dateRange . start , resetDismissed ] ) ;
242296
243297 // Callbacks
244- const toggleSection = React . useCallback (
245- ( section : keyof ExpandedSections ) => {
246- setExpandedSections ( ( prev ) => ( { ...prev , [ section ] : ! prev [ section ] } ) ) ;
247- } ,
248- [ ]
249- ) ;
298+ const toggleSection = React . useCallback ( ( section : keyof ExpandedSections ) => {
299+ setExpandedSections ( ( prev ) => ( { ...prev , [ section ] : ! prev [ section ] } ) ) ;
300+ } , [ ] ) ;
250301
251302 const handleNodeClick = React . useCallback ( ( node : any ) => {
252303 setSelectedInfo ( {
@@ -260,8 +311,10 @@ export function Dashboard() {
260311 } , [ ] ) ;
261312
262313 const handleLinkClick = React . useCallback ( ( link : any ) => {
263- const sourceId = typeof link . source === "object" ? link . source . id : link . source ;
264- const targetId = typeof link . target === "object" ? link . target . id : link . target ;
314+ const sourceId =
315+ typeof link . source === "object" ? link . source . id : link . source ;
316+ const targetId =
317+ typeof link . target === "object" ? link . target . id : link . target ;
265318 setSelectedInfo ( {
266319 type : "link" ,
267320 data : {
@@ -335,13 +388,15 @@ export function Dashboard() {
335388 { animate ? (
336389 < PauseIcon onClick = { ( ) => setAnimate ( false ) } />
337390 ) : (
338- < PlayIcon onClick = { ( ) => {
339- // If at or past end, reset to start before playing
340- if ( date >= dateRange . end ) {
341- setDate ( dateRange . start ) ;
342- }
343- setAnimate ( true ) ;
344- } } />
391+ < PlayIcon
392+ onClick = { ( ) => {
393+ // If at or past end, reset to start before playing
394+ if ( date >= dateRange . end ) {
395+ setDate ( dateRange . start ) ;
396+ }
397+ setAnimate ( true ) ;
398+ } }
399+ />
345400 ) }
346401 < GearIcon onClick = { ( ) => setOptionsOpen ( ( prev ) => ! prev ) } />
347402 </ div >
@@ -372,6 +427,8 @@ export function Dashboard() {
372427 setShowRecentOnly = { setShowRecentOnly }
373428 showTimelineBar = { showTimelineBar }
374429 setShowTimelineBar = { setShowTimelineBar }
430+ showReports = { showReports }
431+ setShowReports = { setShowReports }
375432 physicsInputs = { {
376433 chargeStrengthInput,
377434 linkDistanceInput,
@@ -408,6 +465,14 @@ export function Dashboard() {
408465 />
409466 ) }
410467
468+ { /* Field Reports Overlay */ }
469+ { showReports && (
470+ < FieldReportsOverlay
471+ visibleReports = { visibleReports }
472+ onDismiss = { dismissReport }
473+ />
474+ ) }
475+
411476 { /* Bottom Timeline Bar */ }
412477 { showTimelineBar && (
413478 < TimelineBar
0 commit comments