recherche

This commit is contained in:
Bastien COIGNOUX
2026-05-04 21:52:51 +02:00
parent 432f8ce176
commit 2b8741de08
30 changed files with 2317 additions and 246 deletions

View File

@ -0,0 +1,168 @@
import { Ionicons } from '@expo/vector-icons';
import type { ComponentProps } from 'react';
import { useEffect, useState } from 'react';
import {
ActivityIndicator,
Alert,
Keyboard,
Linking,
Pressable,
ScrollView,
Text,
TextInput,
View,
} from 'react-native';
import {
dvfSearchUrl,
meilleursAgentsUrlForVille,
SECTOR_TOOLS,
type SectorTool,
} from '@/constants/rechercheMarche';
import { UI } from '@/constants/uiTheme';
import { useAnalyseSecteurForVille, useSaveAnalyseSecteur } from '@/hooks/useAnalysesSecteur';
import { formatPocketBaseError } from '@/utils/pocketbaseErrors';
type IonName = ComponentProps<typeof Ionicons>['name'];
function resolveToolUrl(tool: SectorTool, ville: string): string {
if (tool.id === 'ma') return meilleursAgentsUrlForVille(ville);
if (tool.id === 'dvf') return dvfSearchUrl(ville);
return tool.url;
}
export function SecteurTab() {
const [ville, setVille] = useState('');
const [notes, setNotes] = useState('');
const secteurQ = useAnalyseSecteurForVille(ville);
const saveMut = useSaveAnalyseSecteur();
useEffect(() => {
if (secteurQ.data?.notes != null) setNotes(secteurQ.data.notes);
}, [secteurQ.data?.id, secteurQ.data?.notes]);
const onOpenTool = async (tool: SectorTool) => {
const url = resolveToolUrl(tool, ville);
const ok = await Linking.canOpenURL(url);
if (!ok) {
Alert.alert('Lien', 'Impossible douvrir ce lien sur cet appareil.');
return;
}
void Linking.openURL(url);
};
const onAnalyser = () => {
const v = ville.trim();
if (!v) {
Alert.alert('Ville', 'Indique une ville ou une commune pour cadrer lanalyse.');
return;
}
Keyboard.dismiss();
Alert.alert(
'Secteur',
`Analyse pour « ${v} » : utilise les outils ci-dessous (données externes), puis consigne tes notes en bas de page.`,
);
};
const onSaveNotes = async () => {
const v = ville.trim();
if (!v) {
Alert.alert('Ville', 'Renseigne la ville avant de sauvegarder les notes.');
return;
}
try {
await saveMut.mutateAsync({ ville: v, notes });
} catch (e) {
Alert.alert('Erreur', formatPocketBaseError(e));
}
};
return (
<ScrollView className="flex-1 px-3 pt-2" contentContainerStyle={{ paddingBottom: 120 }} keyboardShouldPersistTaps="handled">
<Text className="text-base font-semibold" style={{ color: UI.text }}>
Ville / commune
</Text>
<TextInput
className="mt-2 rounded-2xl border-2 px-4 text-lg"
style={{ borderColor: UI.border, color: UI.text, minHeight: 52, backgroundColor: UI.card }}
placeholder="Ex. Lyon 3e, Bordeaux…"
placeholderTextColor={UI.textMuted}
value={ville}
onChangeText={setVille}
onSubmitEditing={onAnalyser}
/>
<Pressable
accessibilityRole="button"
onPress={onAnalyser}
className="mt-3 min-h-[52px] items-center justify-center rounded-2xl active:opacity-90"
style={{ backgroundColor: UI.primary }}
>
<Text className="text-lg font-bold text-white">Analyser</Text>
</Pressable>
<Text className="mb-2 mt-8 text-xl font-bold" style={{ color: UI.text }}>
Outils marché
</Text>
<Text className="mb-3 text-base" style={{ color: UI.textMuted }}>
Données externes ouverture dans le navigateur.
</Text>
{SECTOR_TOOLS.map((tool) => (
<Pressable
key={tool.id}
accessibilityRole="button"
onPress={() => void onOpenTool(tool)}
className="mb-3 flex-row items-center rounded-2xl border-2 bg-white p-4 active:opacity-90"
style={{ borderColor: UI.border }}
>
<View
className="mr-3 h-12 w-12 items-center justify-center rounded-xl"
style={{ backgroundColor: '#EFF6FF' }}
>
<Ionicons name={tool.icon as IonName} size={24} color={UI.primary} />
</View>
<View className="min-w-0 flex-1">
<Text className="text-lg font-bold" style={{ color: UI.text }}>
{tool.title}
</Text>
<Text className="mt-1 text-base leading-5" style={{ color: UI.textMuted }}>
{tool.description}
</Text>
<View className="mt-2 self-start rounded-full px-2 py-1" style={{ backgroundColor: '#E0E7FF' }}>
<Text className="text-xs font-bold" style={{ color: UI.primary }}>
Externe
</Text>
</View>
</View>
</Pressable>
))}
<Text className="mb-2 mt-4 text-xl font-bold" style={{ color: UI.text }}>
Notes secteur
</Text>
{secteurQ.isFetching ? <ActivityIndicator color={UI.primary} className="mb-2" /> : null}
<TextInput
className="min-h-[140px] rounded-2xl border-2 px-4 py-3 text-lg"
style={{ borderColor: UI.border, color: UI.text, backgroundColor: UI.card }}
multiline
textAlignVertical="top"
placeholder="Synthèse prix, tension, typologie…"
placeholderTextColor={UI.textMuted}
value={notes}
onChangeText={setNotes}
/>
<Pressable
accessibilityRole="button"
onPress={() => void onSaveNotes()}
disabled={saveMut.isPending}
className="mt-3 min-h-[52px] items-center justify-center rounded-2xl active:opacity-90"
style={{ backgroundColor: UI.success }}
>
{saveMut.isPending ? (
<ActivityIndicator color="#fff" />
) : (
<Text className="text-lg font-bold text-white">Sauvegarder</Text>
)}
</Pressable>
</ScrollView>
);
}