import { Ionicons } from '@expo/vector-icons';
import * as Clipboard from 'expo-clipboard';
import type { ReactNode } from 'react';
import { useCallback, useState } from 'react';
import {
ActivityIndicator,
Alert,
Linking,
Pressable,
ScrollView,
Text,
TextInput,
View,
} from 'react-native';
import {
LEGI_L151_36,
LEGI_L152_6,
OFF_MARKET_KEYWORDS,
PROSPECTION_CHECKLIST,
} from '@/constants/rechercheMarche';
import { UI } from '@/constants/uiTheme';
import { useNotesProspectionRecherche } from '@/hooks/useNotesProspectionRecherche';
import { formatPocketBaseError } from '@/utils/pocketbaseErrors';
const LBC = 'https://www.leboncoin.fr/';
const MOTEUR = 'https://www.moteurimmo.fr/';
function Collapsible({
title,
children,
defaultOpen,
}: {
title: string;
children: ReactNode;
defaultOpen?: boolean;
}) {
const [open, setOpen] = useState(Boolean(defaultOpen));
return (
setOpen((o) => !o)}
className="min-h-[52px] flex-row items-center justify-between px-4 py-3"
>
{title}
{open ? {children} : null}
);
}
export function OpportunitesTab() {
const { getState, saveChecklistItem, isLoading, isSaving } = useNotesProspectionRecherche();
const [expandedNoteId, setExpandedNoteId] = useState(null);
const [noteDraft, setNoteDraft] = useState('');
const persistItem = useCallback(
async (questionId: string, next: { done: boolean; note: string }) => {
try {
await saveChecklistItem({ questionId, data: next });
} catch (e) {
Alert.alert('Sauvegarde', formatPocketBaseError(e));
}
},
[saveChecklistItem],
);
const openNoteEditor = (questionId: string) => {
const st = getState(questionId);
setExpandedNoteId(questionId);
setNoteDraft(st.note);
};
const saveNoteFor = async (questionId: string) => {
const st = getState(questionId);
await persistItem(questionId, { done: st.done, note: noteDraft.trim() });
setExpandedNoteId(null);
};
const copyText = async (text: string) => {
try {
await Clipboard.setStringAsync(text);
Alert.alert('Copié', 'Collage dans Leboncoin ou Moteur Immo.');
} catch {
Alert.alert('Presse-papiers', 'Copie impossible sur cet appareil.');
}
};
if (isLoading) {
return (
);
}
return (
{OFF_MARKET_KEYWORDS.map((k) => (
{k.label}
{k.text}
void copyText(k.text)}
className="mt-3 min-h-[48px] items-center justify-center rounded-xl"
style={{ backgroundColor: UI.primary }}
>
Copier
))}
void Linking.openURL(LBC)}
className="min-h-[52px] flex-1 min-w-[140px] items-center justify-center rounded-2xl border-2"
style={{ borderColor: UI.warning, backgroundColor: '#FFFBEB' }}
>
Leboncoin
void Linking.openURL(MOTEUR)}
className="min-h-[52px] flex-1 min-w-[140px] items-center justify-center rounded-2xl border-2"
style={{ borderColor: UI.warning, backgroundColor: '#FFFBEB' }}
>
Moteur Immo
Astuce maison de ville
Surface terrain max 100 m² + surface habitable min 150 m² → maisons sans jardin, moins de concurrence,
idéal division.
Trier par ancienneté sur Moteur Immo. 40+ mois en ligne + baisses répétées = vendeur motivé. Décote possible
sous prix marché.
−10 % à −24 %
void Linking.openURL(MOTEUR)}
className="mt-4 min-h-[52px] items-center justify-center rounded-2xl"
style={{ backgroundColor: UI.danger }}
>
Moteur Immo — tri par ancienneté
Article L151-36
1 place de parking max par logement créé en zone bien desservie.
void Linking.openURL(LEGI_L151_36)}
className="mt-3 min-h-[48px] items-center justify-center rounded-xl bg-white"
>
Ouvrir sur Légifrance →
Article L152-6
Dans 500 m d'une gare ou métro → division sans obligation parking.
void Linking.openURL(LEGI_L152_6)}
className="mt-3 min-h-[48px] items-center justify-center rounded-xl bg-white"
>
Ouvrir sur Légifrance →
Coche après échange ; note la réponse pour ton dossier.
{PROSPECTION_CHECKLIST.map((item) => {
const st = getState(item.id);
const expanded = expandedNoteId === item.id;
return (
void persistItem(item.id, { done: !st.done, note: st.note })}
className="min-h-[48px] flex-row items-start gap-3"
>
{st.done ? ✓ : null}
{item.label}
{item.question}
(expanded ? setExpandedNoteId(null) : openNoteEditor(item.id))}
className="mt-3 min-h-[44px] justify-center rounded-xl px-3"
style={{ backgroundColor: '#F1F5F9' }}
>
{expanded ? 'Fermer la note' : st.note ? 'Modifier la note' : 'Ajouter une note'}
{expanded ? (
void saveNoteFor(item.id)}
disabled={isSaving}
className="mt-2 min-h-[48px] items-center justify-center rounded-xl"
style={{ backgroundColor: UI.primary }}
>
{isSaving ? (
) : (
Enregistrer la note
)}
) : st.note ? (
{st.note}
) : null}
);
})}
);
}