1- import React from 'react' ;
1+ import React , { useState } from 'react' ;
22
3- import { Heading , Select , StatisticCard } from '@douglasneuroinformatics/libui/components' ;
3+ import { Dialog , Heading , Select , StatisticCard } from '@douglasneuroinformatics/libui/components' ;
44import { useTheme , useTranslation } from '@douglasneuroinformatics/libui/hooks' ;
55import type { Theme } from '@douglasneuroinformatics/libui/hooks' ;
66import { ClipboardDocumentIcon , DocumentTextIcon , UserIcon , UsersIcon } from '@heroicons/react/24/solid' ;
77import type { AppSubjectName } from '@opendatacapture/schemas/core' ;
8- import { createFileRoute , redirect } from '@tanstack/react-router' ;
8+ import { createFileRoute , redirect , useNavigate } from '@tanstack/react-router' ;
9+ import { AnimatePresence , motion } from 'motion/react' ;
910import { Area , AreaChart , CartesianGrid , ResponsiveContainer , Tooltip , XAxis , YAxis } from 'recharts' ;
1011
1112import { PageHeader } from '@/components/PageHeader' ;
13+ import { useInstrumentInfoQuery } from '@/hooks/useInstrumentInfoQuery' ;
1214import { summaryQueryOptions , useSummaryQuery } from '@/hooks/useSummaryQuery' ;
1315import { useAppStore } from '@/store' ;
1416
@@ -19,6 +21,9 @@ const RouteComponent = () => {
1921 const { t } = useTranslation ( ) ;
2022 const [ theme ] = useTheme ( ) ;
2123 const summaryQuery = useSummaryQuery ( { params : { groupId : currentGroup ?. id } } ) ;
24+ const navigate = useNavigate ( ) ;
25+ const [ isLookupOpen , setIsLookupOpen ] = useState ( false ) ;
26+ const instrumentInfoQuery = useInstrumentInfoQuery ( ) ;
2227
2328 const chartColors = {
2429 records : {
@@ -90,6 +95,19 @@ const RouteComponent = () => {
9095 } ) ;
9196 }
9297
98+ const instrumentData = currentGroup
99+ ? instrumentInfoQuery . data ?. filter ( ( instrument ) => {
100+ return currentGroup . accessibleInstrumentIds . includes ( instrument . id ) ;
101+ } )
102+ : instrumentInfoQuery . data ;
103+
104+ const instrumentInfo = instrumentData ?. map ( ( record ) => {
105+ return {
106+ kind : record . kind ,
107+ title : record . details . title
108+ } ;
109+ } ) ;
110+
93111 // should never happen, as data is ensured in loader, but avoid crashing the app if someone changes this
94112 if ( ! summaryQuery . data ) {
95113 return null ;
@@ -145,9 +163,14 @@ const RouteComponent = () => {
145163 value = { summaryQuery . data . counts . users }
146164 />
147165 </ div >
148- < div
166+ < button
149167 className = "group transform transition-all duration-300 hover:scale-105"
150168 data-testid = "statistic-subjects"
169+ onClick = { ( ) => {
170+ void navigate ( {
171+ to : '/datahub'
172+ } ) ;
173+ } }
151174 >
152175 < StatisticCard
153176 icon = {
@@ -159,21 +182,64 @@ const RouteComponent = () => {
159182 } ) }
160183 value = { summaryQuery . data . counts . subjects }
161184 />
162- </ div >
185+ </ button >
163186 < div
164187 className = "group transform transition-all duration-300 hover:scale-105"
165188 data-testid = "statistic-instruments"
166189 >
167- < StatisticCard
168- icon = {
169- < ClipboardDocumentIcon className = "h-12 w-12 text-amber-600 transition-transform duration-300 group-hover:scale-110 dark:text-amber-400" />
170- }
171- label = { t ( {
172- en : 'Total Instruments' ,
173- fr : "Nombre d'instruments"
174- } ) }
175- value = { summaryQuery . data . counts . instruments }
176- />
190+ < Dialog open = { isLookupOpen } onOpenChange = { setIsLookupOpen } >
191+ < Dialog . Trigger className = "grow" >
192+ < StatisticCard
193+ icon = {
194+ < ClipboardDocumentIcon className = "h-12 w-12 text-amber-600 transition-transform duration-300 group-hover:scale-110 dark:text-amber-400" />
195+ }
196+ label = { t ( {
197+ en : 'Total Instruments' ,
198+ fr : "Nombre d'instruments"
199+ } ) }
200+ value = { summaryQuery . data . counts . instruments }
201+ > </ StatisticCard >
202+ </ Dialog . Trigger >
203+ < Dialog . Content data-spotlight-type = "subject-lookup-modal" data-testid = "datahub-subject-lookup-dialog" >
204+ < Dialog . Header >
205+ < Dialog . Title >
206+ { t ( {
207+ en : 'Available Instruments' ,
208+ fr : 'Les instruments'
209+ } ) }
210+ </ Dialog . Title >
211+ </ Dialog . Header >
212+ < ul className = "flex flex-col gap-5" >
213+ < AnimatePresence mode = "popLayout" >
214+ < div className = "flex justify-between gap-4 font-bold" >
215+ < p >
216+ { t ( {
217+ en : 'Title' ,
218+ fr : 'Titre'
219+ } ) }
220+ </ p > { ' ' }
221+ < p > { t ( { en : 'Kind' } ) } </ p >
222+ </ div >
223+ { instrumentInfo ?. map ( ( instrument , i ) => {
224+ return (
225+ < motion . li
226+ layout
227+ animate = { { opacity : 1 , y : 0 } }
228+ exit = { { opacity : 0 } }
229+ initial = { { opacity : 0 } }
230+ key = { instrument . title }
231+ transition = { { bounce : 0.2 , delay : 0.15 * i , duration : 1.5 , type : 'spring' } }
232+ >
233+ < div className = "flex justify-between gap-4" >
234+ < p > { instrument . title } </ p > < p > { instrument . kind } </ p >
235+ </ div >
236+ </ motion . li >
237+ ) ;
238+ } ) }
239+ </ AnimatePresence >
240+ </ ul >
241+ </ Dialog . Content >
242+ </ Dialog >
177243 </ div >
178244 < div
179245 className = "group transform transition-all duration-300 hover:scale-105"
0 commit comments