Skip to content

Commit 41543ae

Browse files
committed
feat: make optimal windows expandable with hourly detail on mobile
Each optimal window in the overview tab now has a chevron and can be tapped to reveal per-hour breakdown with temperature, humidity, wind, and rating — matching the web app's behavior. https://claude.ai/code/session_013VAW4BUvwvmyoUPqNFmvH8
1 parent b93ab1f commit 41543ae

1 file changed

Lines changed: 85 additions & 17 deletions

File tree

mobile/app/crag/[slug].tsx

Lines changed: 85 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -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
11391219
function 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

Comments
 (0)