@@ -233,11 +233,15 @@ export default function CragDetailScreen() {
233233 const groups : { label : string ; dateKey : string ; windows : typeof windows ; bestRating ?: string ; weatherCode ?: number } [ ] = [ ] ;
234234 const seen = new Map < string , typeof windows > ( ) ;
235235
236- // Group optimal windows by day
236+ // Group optimal windows by day, attaching hourly data to each window
237237 for ( const w of windows ) {
238238 const key = getDateKey ( w . startTime ) ;
239239 if ( ! seen . has ( key ) ) seen . set ( key , [ ] ) ;
240- seen . get ( key ) ! . push ( w ) ;
240+ const windowHours = hourly . filter ( ( h : any ) => {
241+ const ht = new Date ( h . time ) . getTime ( ) ;
242+ return ht >= new Date ( w . startTime ) . getTime ( ) && ht < new Date ( w . endTime ) . getTime ( ) ;
243+ } ) ;
244+ seen . get ( key ) ! . push ( { ...w , hours : windowHours } ) ;
241245 }
242246
243247 // Build groups from daily forecast to include all days
@@ -1135,6 +1139,82 @@ export default function CragDetailScreen() {
11351139 ) ;
11361140}
11371141
1142+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1143+ function ExpandableWindow ( { window : w , colors, units, t } : { window : any ; colors : ( typeof Colors ) [ "light" ] ; units : any ; t : any } ) {
1144+ const [ expanded , setExpanded ] = useState ( false ) ;
1145+ const wl = getRatingLabel ( w . avgFrictionScore ) ;
1146+ const wc = getRatingColors ( wl ) ;
1147+ const hours = w . hours || [ ] ;
1148+ const tf = units ?. timeFormat || "24h" ;
1149+
1150+ return (
1151+ < View >
1152+ < TouchableOpacity
1153+ style = { styles . windowRow }
1154+ onPress = { ( ) => setExpanded ( ! expanded ) }
1155+ activeOpacity = { 0.7 }
1156+ >
1157+ < View style = { [ styles . windowDot , { backgroundColor : wc ?. solid || colors . muted } ] } />
1158+ < Text style = { [ styles . windowTime , { color : colors . text } ] } > { fmtTimeRange ( w . startTime , w . endTime , tf ) } </ Text >
1159+ { wc && wl && (
1160+ < View style = { [ styles . smallBadge , { backgroundColor : wc . bg } ] } >
1161+ < Text style = { [ styles . smallBadgeText , { color : wc . text } ] } > { t ( `ratings.${ wl ! . toLowerCase ( ) } ` , wl ) } </ Text >
1162+ </ View >
1163+ ) }
1164+ < Ionicons
1165+ name = { expanded ? "chevron-up" : "chevron-down" }
1166+ size = { 14 }
1167+ color = { colors . muted }
1168+ style = { { marginLeft : "auto" } }
1169+ />
1170+ </ TouchableOpacity >
1171+ { expanded && hours . length > 0 && (
1172+ < View style = { { paddingLeft : Spacing . md , paddingTop : 2 } } >
1173+ { hours . map ( ( h : any , i : number ) => {
1174+ const score = h . frictionScore ?? h . friction ;
1175+ const rl = getRatingLabel ( score ) ;
1176+ const rc = getRatingColors ( rl ) ;
1177+ const temp = h . temp_c ?? h . temperature_c ;
1178+ const wind = h . wind_kph ?? h . windSpeed_kph ;
1179+ const windDir = h . wind_direction ;
1180+ return (
1181+ < View key = { i } style = { [ styles . hourlyRow , {
1182+ backgroundColor : rl === "Great" ? "rgba(34,197,94,0.06)" : rl === "Good" ? "rgba(59,130,246,0.04)" : "transparent" ,
1183+ borderRadius : BorderRadius . sm ,
1184+ paddingHorizontal : Spacing . xs ,
1185+ marginVertical : 1 ,
1186+ } ] } >
1187+ < Text style = { [ styles . hourlyTime , { color : colors . text , fontSize : 11 } ] } > { fmtHour ( h . time , tf ) } </ Text >
1188+ < WeatherIcon code = { h . weatherCode } size = "small" />
1189+ < Text style = { [ styles . hourlyValue , { color : colors . text , fontSize : 11 } ] } >
1190+ { formatTemperature ( convertTemperature ( temp , "celsius" , units . temperature ) , units . temperature , 0 ) }
1191+ </ Text >
1192+ < Text style = { [ styles . hourlyValue , { color : colors . muted , fontSize : 11 } ] } > { h . humidity } %</ Text >
1193+ < View style = { { flexDirection : "row" , alignItems : "center" } } >
1194+ < Text style = { [ styles . hourlyValue , { color : colors . muted , fontSize : 11 } ] } >
1195+ { formatWindSpeed ( convertWindSpeed ( wind , "kmh" , units . windSpeed ) , units . windSpeed , 0 ) }
1196+ </ Text >
1197+ { windDir != null && (
1198+ < >
1199+ < Text style = { [ styles . hourlyValue , { color : colors . muted , fontSize : 11 , transform : [ { rotate : `${ getWindArrowRotation ( windDir ) } deg` } ] , marginLeft : 2 } ] } > ↑</ Text >
1200+ < Text style = { [ styles . hourlyValue , { color : colors . muted , fontSize : 9 , marginLeft : 1 } ] } > { getWindCardinal ( windDir ) } </ Text >
1201+ </ >
1202+ ) }
1203+ </ View >
1204+ { rc && rl && (
1205+ < View style = { [ styles . smallBadge , { backgroundColor : rc . bg , marginLeft : "auto" } ] } >
1206+ < Text style = { [ styles . smallBadgeText , { color : rc . text , fontSize : 9 } ] } > { t ( `ratings.${ rl ! . toLowerCase ( ) } ` , rl ) } </ Text >
1207+ </ View >
1208+ ) }
1209+ </ View >
1210+ ) ;
1211+ } ) }
1212+ </ View >
1213+ ) }
1214+ </ View >
1215+ ) ;
1216+ }
1217+
11381218// eslint-disable-next-line @typescript-eslint/no-explicit-any
11391219function FoldableWindowDay ( { label, windows, isHighlighted, isToday, bestRating, colors, units, t } : {
11401220 label : string ; windows : any [ ] ; isHighlighted : boolean ; isToday : boolean ;
@@ -1205,21 +1285,9 @@ function FoldableWindowDay({ label, windows, isHighlighted, isToday, bestRating,
12051285 { t ( "dialog.noOptimalHoursDay" , "No good climbing windows on this day." ) }
12061286 </ Text >
12071287 ) : (
1208- windows . map ( ( w : any , i : number ) => {
1209- const wl = getRatingLabel ( w . avgFrictionScore ) ;
1210- const wc = getRatingColors ( wl ) ;
1211- return (
1212- < View key = { i } style = { styles . windowRow } >
1213- < View style = { [ styles . windowDot , { backgroundColor : wc ?. solid || colors . muted } ] } />
1214- < Text style = { [ styles . windowTime , { color : colors . text } ] } > { fmtTimeRange ( w . startTime , w . endTime , units ?. timeFormat || "24h" ) } </ Text >
1215- { wc && wl && (
1216- < View style = { [ styles . smallBadge , { backgroundColor : wc . bg } ] } >
1217- < Text style = { [ styles . smallBadgeText , { color : wc . text } ] } > { t ( `ratings.${ wl ! . toLowerCase ( ) } ` , wl ) } </ Text >
1218- </ View >
1219- ) }
1220- </ View >
1221- ) ;
1222- } )
1288+ windows . map ( ( w : any , i : number ) => (
1289+ < ExpandableWindow key = { i } window = { w } colors = { colors } units = { units } t = { t } />
1290+ ) )
12231291 ) }
12241292 </ View >
12251293 ) }
0 commit comments