@@ -4,6 +4,8 @@ import { useTranslation } from 'react-i18next';
44import { Button } from '@/components/ui/Button' ;
55import {
66 IconBot ,
7+ IconChevronDown ,
8+ IconChevronUp ,
79 IconKey ,
810 IconSatellite ,
911 IconShield ,
@@ -237,6 +239,7 @@ export function DashboardPage() {
237239
238240 const [ loading , setLoading ] = useState ( true ) ;
239241 const [ localEnvironmentOpen , setLocalEnvironmentOpen ] = useState ( false ) ;
242+ const [ localReadyItemsOpen , setLocalReadyItemsOpen ] = useState ( false ) ;
240243 const [ localEnvironmentLoading , setLocalEnvironmentLoading ] = useState ( false ) ;
241244 const [ localEnvironmentError , setLocalEnvironmentError ] = useState ( '' ) ;
242245 const [ localEnvironmentSnapshot , setLocalEnvironmentSnapshot ] =
@@ -339,6 +342,7 @@ export function DashboardPage() {
339342 if ( ! isDesktopMode ( ) ) {
340343 setLocalEnvironmentError ( '' ) ;
341344 setLocalEnvironmentSnapshot ( null ) ;
345+ setLocalReadyItemsOpen ( false ) ;
342346 return ;
343347 }
344348
@@ -350,6 +354,7 @@ export function DashboardPage() {
350354 try {
351355 const snapshot = await request ;
352356 setLocalEnvironmentSnapshot ( snapshot ) ;
357+ setLocalReadyItemsOpen ( false ) ;
353358 } catch ( error ) {
354359 const message = error instanceof Error ? error . message : t ( 'common.unknown_error' ) ;
355360 setLocalEnvironmentError ( message ) ;
@@ -496,6 +501,7 @@ export function DashboardPage() {
496501 } ) ) ;
497502 if ( response . snapshot ) {
498503 setLocalEnvironmentSnapshot ( response . snapshot ) ;
504+ setLocalReadyItemsOpen ( false ) ;
499505 } else {
500506 await fetchLocalEnvironment ( ) ;
501507 }
@@ -619,6 +625,8 @@ export function DashboardPage() {
619625 const localEnvironmentItems = buildLocalEnvironmentDisplayItems (
620626 localEnvironmentSnapshot ?. items ?? [ ]
621627 ) ;
628+ const localReadyItems = localEnvironmentItems . filter ( ( item ) => item . status === 'ready' ) ;
629+ const localIssueItems = localEnvironmentItems . filter ( ( item ) => item . status !== 'ready' ) ;
622630 const localRequiredReady = localRequiredItems . filter ( ( item ) => item . status === 'ready' ) . length ;
623631 const localOptionalUnavailable =
624632 localEnvironmentSnapshot ?. items . filter ( ( item ) => item . status === 'optionalUnavailable' ) . length ?? 0 ;
@@ -726,6 +734,53 @@ export function DashboardPage() {
726734 </ div >
727735 ) ;
728736
737+ const renderLocalDependencyItem = ( item : LocalDependencyItem ) => {
738+ const isRepairingItem =
739+ repairingTarget ?. itemId === item . id &&
740+ repairingTarget . actionId === item . repairActionId ;
741+ const itemRepairProgress =
742+ item . id === localRepairProgressItemId &&
743+ item . repairActionId &&
744+ localRepairProgress ?. actionId === item . repairActionId
745+ ? localRepairProgress
746+ : null ;
747+
748+ return (
749+ < div key = { item . id } className = { styles . localDependencyItem } >
750+ < div className = { styles . localDependencyMain } >
751+ < div className = { styles . localDependencyTitleRow } >
752+ < strong > { item . name } </ strong >
753+ < span className = { `${ styles . localStatusBadge } ${ getLocalStatusClass ( item . status ) } ` } >
754+ { getLocalStatusLabel ( item . status ) }
755+ </ span >
756+ </ div >
757+ < div className = { styles . localDependencyMeta } >
758+ { item . version && < span > { item . version } </ span > }
759+ { item . path && < span > { item . path } </ span > }
760+ </ div >
761+ < p > { item . detail } </ p >
762+ { item . recommendation && < small > { item . recommendation } </ small > }
763+ </ div >
764+ { item . repairActionId && localEnvironmentDesktopMode && (
765+ < Button
766+ type = "button"
767+ variant = "secondary"
768+ size = "sm"
769+ className = { styles . localEnvironmentRepairButton }
770+ onClick = { ( ) => handleRepairLocalDependency ( item ) }
771+ loading = { isRepairingItem }
772+ disabled = { repairingTarget !== null }
773+ >
774+ { isRepairingItem
775+ ? t ( 'dashboard.local_environment_repairing_button' )
776+ : t ( 'dashboard.local_environment_repair' ) }
777+ </ Button >
778+ ) }
779+ { itemRepairProgress && renderLocalRepairProgress ( itemRepairProgress ) }
780+ </ div >
781+ ) ;
782+ } ;
783+
729784 return (
730785 < div className = { styles . dashboard } >
731786 { /* Decorative background orbs */ }
@@ -905,55 +960,40 @@ export function DashboardPage() {
905960 ) }
906961 { localEnvironmentSnapshot && (
907962 < div className = { styles . localEnvironmentItems } >
908- { localEnvironmentItems . map ( ( item ) => {
909- const isRepairingItem =
910- repairingTarget ?. itemId === item . id &&
911- repairingTarget . actionId === item . repairActionId ;
912- const itemRepairProgress =
913- item . id === localRepairProgressItemId &&
914- item . repairActionId &&
915- localRepairProgress ?. actionId === item . repairActionId
916- ? localRepairProgress
917- : null ;
918- return (
919- < div key = { item . id } className = { styles . localDependencyItem } >
920- < div className = { styles . localDependencyMain } >
921- < div className = { styles . localDependencyTitleRow } >
922- < strong > { item . name } </ strong >
923- < span
924- className = { `${ styles . localStatusBadge } ${ getLocalStatusClass ( item . status ) } ` }
925- >
926- { getLocalStatusLabel ( item . status ) }
927- </ span >
928- </ div >
929- < div className = { styles . localDependencyMeta } >
930- { item . version && < span > { item . version } </ span > }
931- { item . path && < span > { item . path } </ span > }
932- </ div >
933- < p > { item . detail } </ p >
934- { item . recommendation && < small > { item . recommendation } </ small > }
935- </ div >
936- { item . repairActionId && localEnvironmentDesktopMode && (
937- < Button
938- type = "button"
939- variant = "secondary"
940- size = "sm"
941- className = { styles . localEnvironmentRepairButton }
942- onClick = { ( ) => handleRepairLocalDependency ( item ) }
943- loading = { isRepairingItem }
944- disabled = { repairingTarget !== null }
945- >
946- { isRepairingItem
947- ? t ( 'dashboard.local_environment_repairing_button' )
948- : t ( 'dashboard.local_environment_repair' ) }
949- </ Button >
963+ { localReadyItems . length > 0 && (
964+ < div className = { styles . localReadyGroup } >
965+ < button
966+ type = "button"
967+ className = { styles . localReadyToggle }
968+ aria-expanded = { localReadyItemsOpen }
969+ onClick = { ( ) => setLocalReadyItemsOpen ( ( open ) => ! open ) }
970+ >
971+ < span className = { styles . localReadyToggleText } >
972+ < strong >
973+ { t ( 'dashboard.local_environment_ready_group_title' , {
974+ count : localReadyItems . length ,
975+ } ) }
976+ </ strong >
977+ < span >
978+ { localReadyItemsOpen
979+ ? t ( 'dashboard.local_environment_ready_group_collapse' )
980+ : t ( 'dashboard.local_environment_ready_group_expand' ) }
981+ </ span >
982+ </ span >
983+ { localReadyItemsOpen ? (
984+ < IconChevronUp size = { 16 } />
985+ ) : (
986+ < IconChevronDown size = { 16 } />
950987 ) }
951- { itemRepairProgress && (
952- renderLocalRepairProgress ( itemRepairProgress )
953- ) }
954- </ div >
955- ) ;
956- } ) }
988+ </ button >
989+ { localReadyItemsOpen && (
990+ < div className = { styles . localReadyGroupItems } >
991+ { localReadyItems . map ( renderLocalDependencyItem ) }
992+ </ div >
993+ ) }
994+ </ div >
995+ ) }
996+ { localIssueItems . map ( renderLocalDependencyItem ) }
957997 </ div >
958998 ) }
959999 </ div >
0 commit comments