import { Ionicons } from '@expo/vector-icons'; import { router } from 'expo-router'; import { useEffect, useMemo, useState } from 'react'; import { Alert, Pressable, SectionList, StyleSheet, Text, View, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { PrimaryButton } from '../../src/components/PrimaryButton'; import { useApp } from '../../src/context/AppContext'; import type { DealSourceRow, DossierRow } from '../../src/data/types'; import { ensureNotificationPermission, notifyGradeADealLocal, useDealsSourcesGradeAAlerts, } from '../../src/hooks/useDealsSourcesGradeAAlerts'; import { colors } from '../../src/theme/colors'; type SectionRow = | { kind: 'deal'; deal: DealSourceRow } | { kind: 'dossier'; dossier: DossierRow }; export default function DossiersListScreen() { const insets = useSafeAreaInsets(); const app = useApp(); const [scoutBusy, setScoutBusy] = useState(false); const cloudNeedsAuth = app.runtimeMode === 'cloud' && !app.user && app.supabase; const needsSetup = app.runtimeMode === 'none'; const sortedDeals = useMemo( () => [...app.dealSources].sort( (a, b) => b.opportunity_score - a.opportunity_score, ), [app.dealSources], ); const sections = useMemo(() => { const dealRows: SectionRow[] = sortedDeals.map((deal) => ({ kind: 'deal' as const, deal, })); const dossierRows: SectionRow[] = app.dossiers.map((dossier) => ({ kind: 'dossier' as const, dossier, })); return [ { title: 'Flux opportunités (Scout)', data: dealRows }, { title: 'Mes dossiers', data: dossierRows }, ]; }, [sortedDeals, app.dossiers]); useDealsSourcesGradeAAlerts( app.supabase, app.user?.id, app.runtimeMode === 'cloud' && !!app.user && !!app.supabase, ); useEffect(() => { if (app.runtimeMode === 'cloud' && app.user) { void ensureNotificationPermission(); } }, [app.runtimeMode, app.user]); return ( {needsSetup ? ( Choisissez le mode hors-ligne sur l’accueil, ou configurez Supabase dans Réglages. router.replace('/')} /> ) : null} {cloudNeedsAuth ? ( Connectez-vous pour charger vos dossiers Supabase. router.push('/auth/login')} /> ) : null} item.kind === 'deal' ? `deal-${item.deal.id}` : `dossier-${item.dossier.id}-${index}` } stickySectionHeadersEnabled={false} contentContainerStyle={{ paddingHorizontal: 16, paddingBottom: insets.bottom + 100, }} ListHeaderComponent={ { if (!app.user) { router.push('/auth/login'); return; } setScoutBusy(true); const r = await app.runScoutSampleBatch(); setScoutBusy(false); if ('error' in r) { Alert.alert('Scout', r.error); return; } if (app.runtimeMode === 'local' && r.gradeA > 0) { notifyGradeADealLocal( `${r.gradeA} opportunité(s) Grade A (Scout simulé)`, ); } Alert.alert( 'Scout', `Insérés : ${r.inserted} — Grade A : ${r.gradeA}.`, ); }} /> Filtre : mots-clés succession / urgent / travaux important + prix/m² sous moyenne simulée (3500 €/m²). Cloud : RPC `scout_process_batch` après migration SQL. } renderSectionHeader={({ section: { title, data } }) => ( {title} {title.startsWith('Flux') ? ` (${data.length})` : ` (${data.length})`} )} renderItem={({ item }) => item.kind === 'deal' ? ( ) : ( ) } ListEmptyComponent={null} /> { if (app.runtimeMode === 'none') { router.replace('/'); return; } if (!app.user && app.runtimeMode === 'cloud') { router.push('/auth/login'); return; } const id = await app.createDossier(); if (id) router.push(`/dossier/${id}`); }} > ); } function DealSourceCard({ row }: { row: DealSourceRow }) { const pm = Math.round(row.price_per_m2_eur); const dotStyle = row.grade === 'A' ? styles.badgeA : row.grade === 'B' ? styles.badgeB : styles.badgeC; return ( Grade {row.grade} {row.opportunity_score.toFixed(0)} pts {row.title} {row.price_eur != null ? `${row.price_eur.toLocaleString('fr-FR')} € · ${row.surface_m2} m² · ${pm} €/m²` : `${row.surface_m2} m²`} {row.distress_keywords?.length ? ( Mots-clés : {row.distress_keywords.join(', ')} ) : null} {row.source_name ? ( Source : {row.source_name} ) : null} ); } function DossierRowCard({ row }: { row: DossierRow }) { const city = [row.postal_code, row.city].filter(Boolean).join(' '); return ( router.push(`/dossier/${row.id}`)} > {row.title} {city ? {city} : null} Statut : {row.status} ); } const styles = StyleSheet.create({ root: { flex: 1, backgroundColor: colors.bg }, banner: { marginHorizontal: 16, marginBottom: 12, padding: 14, borderRadius: 12, backgroundColor: colors.bgCard, borderWidth: 1, borderColor: colors.border, gap: 10, }, bannerText: { color: colors.text, lineHeight: 20 }, hint: { color: colors.textMuted, fontSize: 12, lineHeight: 17, marginTop: 10, }, sectionTitle: { color: colors.text, fontSize: 15, fontWeight: '800', marginTop: 8, marginBottom: 8, }, dealCard: { backgroundColor: '#121a24', borderRadius: 14, padding: 16, marginBottom: 12, borderWidth: 1, borderColor: colors.border, }, dealHead: { flexDirection: 'row', alignItems: 'center', marginBottom: 8, gap: 8 }, badgeDot: { width: 8, height: 8, borderRadius: 4 }, badgeA: { backgroundColor: '#3fb950' }, badgeB: { backgroundColor: '#d29922' }, badgeC: { backgroundColor: colors.textMuted }, badgeText: { color: colors.text, fontWeight: '900', fontSize: 13, marginRight: 4, }, score: { color: colors.textMuted, fontSize: 12, marginLeft: 'auto' }, kw: { color: colors.flash ?? '#7ee787', marginTop: 6, fontSize: 12 }, card: { backgroundColor: colors.bgCard, borderRadius: 14, padding: 16, marginBottom: 12, borderWidth: 1, borderColor: colors.border, }, cardTitle: { color: colors.text, fontSize: 17, fontWeight: '700' }, cardSub: { color: colors.textMuted, marginTop: 4 }, cardMeta: { color: colors.textMuted, marginTop: 8, fontSize: 12 }, fabWrap: { position: 'absolute', right: 20, }, fab: { width: 58, height: 58, borderRadius: 29, backgroundColor: colors.accent, alignItems: 'center', justifyContent: 'center', elevation: 4, shadowColor: '#000', shadowOpacity: 0.25, shadowRadius: 6, shadowOffset: { width: 0, height: 2 }, }, });