Grapevine_Disease_Detection/VinEye/src/screens/GuidesScreen.tsx
Yanis 720dd34fdd add MyPlantsScreen + ScanDetailScreen + enriched admin + API mobile + project summary
Mobile:
- Replace LibraryScreen with MyPlantsScreen (date-grouped scan list, swipe actions, search, pull-to-refresh)
- Add ScanDetailScreen (immersive hero, confidence bar, cepage card, share/delete)
- Add DiseaseDetailScreen + GuideDetailScreen (hero pattern, animated entry)
- Add useScanDetail, useHistory (useCallback fix), dateGrouping utility
- Connect diseases/guides to admin API with cache + offline fallback
- Add NetworkContext, ToastContext, Skeleton loading components
- Extend ScanRecord type (isFavorite, location)
- Full i18n FR/EN for all new screens

Admin (vineye-admin):
- Enrich Disease/Guide Prisma schema (timeline, conditions, actions, sections)
- Enriched disease-form (7 sections) + guide-form (structured sections editor)
- Add mobile public API endpoints (diseases, guides by slug)
- Add Prisma migration + enriched seed data
- UI polish: sidebar, login, layout updates

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 03:19:39 +02:00

156 lines
4.7 KiB
TypeScript

import { useState } from "react";
import { View, ScrollView, RefreshControl, StyleSheet } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useNavigation } from "@react-navigation/native";
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { useTranslation } from "react-i18next";
import { Text } from "@/components/ui/text";
import SearchHeader from "@/components/home/SearchHeader";
import SearchSection from "@/components/home/SearchSection";
import AnimatedSegmentedControl from "@/components/guides/AnimatedSegmentedControl";
import SmallDiseaseCard from "@/components/ui/SmallDiseaseCard";
import GuideListItem from "@/components/ui/GuideListItem";
import { DiseaseCardSkeleton, GuideListItemSkeleton } from "@/components/ui/Skeleton";
import { useDiseases } from "@/hooks/useDiseases";
import { useGuides } from "@/hooks/useGuides";
import type { RootStackParamList } from "@/types/navigation";
type Nav = NativeStackNavigationProp<RootStackParamList>;
export default function GuidesScreen() {
const { t } = useTranslation();
const navigation = useNavigation<Nav>();
const insets = useSafeAreaInsets();
const [activeTab, setActiveTab] = useState(0);
const {
data: diseases,
isLoading: diseasesLoading,
isRefreshing: diseasesRefreshing,
refresh: refreshDiseases,
} = useDiseases();
const {
data: guides,
isLoading: guidesLoading,
isRefreshing: guidesRefreshing,
refresh: refreshGuides,
} = useGuides();
const tabs = [t("guides.tabDiseases"), t("guides.tabGuides")];
async function handleRefresh() {
if (activeTab === 0) await refreshDiseases();
else await refreshGuides();
}
const isRefreshing = activeTab === 0 ? diseasesRefreshing : guidesRefreshing;
const showDiseasesSkeleton = diseasesLoading && diseases.length === 0;
const showGuidesSkeleton = guidesLoading && guides.length === 0;
return (
<View style={styles.root}>
<ScrollView
contentContainerStyle={{
paddingTop: insets.top,
paddingBottom: insets.bottom + 100,
}}
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl
refreshing={isRefreshing}
onRefresh={handleRefresh}
tintColor="#2D6A4F"
progressViewOffset={insets.top}
/>
}
>
<SearchHeader />
<SearchSection />
<AnimatedSegmentedControl
tabs={tabs}
activeIndex={activeTab}
onTabChange={setActiveTab}
/>
{activeTab === 0 ? (
<View style={styles.grid}>
{showDiseasesSkeleton
? Array.from({ length: 4 }).map((_, i) => (
<View key={i} style={styles.gridItem}>
<DiseaseCardSkeleton style={{ height: 160 }} />
</View>
))
: diseases.map((disease, index) => (
<View key={disease.id} style={styles.gridItem}>
<SmallDiseaseCard
disease={disease}
onPress={() =>
navigation.navigate("DiseaseDetail", { diseaseId: disease.id })
}
index={index}
size="grid"
/>
</View>
))}
</View>
) : (
<View style={styles.guidesSection}>
<Text style={styles.sectionTitle}>{t("guides.tabGuides")}</Text>
<View style={styles.guidesList}>
{showGuidesSkeleton
? Array.from({ length: 3 }).map((_, i) => <GuideListItemSkeleton key={i} />)
: guides.map((guide, index) => (
<GuideListItem
key={guide.id}
guide={guide}
onPress={() =>
navigation.navigate("GuideDetail", { guideId: guide.id })
}
showSeparator={index < guides.length - 1}
index={index}
/>
))}
</View>
</View>
)}
</ScrollView>
</View>
);
}
const styles = StyleSheet.create({
root: {
flex: 1,
backgroundColor: "#F8F9FB",
},
grid: {
flexDirection: "row",
flexWrap: "wrap",
paddingHorizontal: 16,
gap: 12,
},
gridItem: {
width: "48%",
},
guidesSection: {
paddingHorizontal: 16,
},
sectionTitle: {
fontSize: 22,
fontWeight: "700",
color: "#1A1A1A",
marginBottom: 16,
paddingHorizontal: 4,
},
guidesList: {
backgroundColor: "#FFFFFF",
borderRadius: 16,
overflow: "hidden",
borderWidth: 1,
borderColor: "#F0F0F0",
},
});