169 lines
5.6 KiB
TypeScript
169 lines
5.6 KiB
TypeScript
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 d’ouvrir 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 l’analyse.');
|
||
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>
|
||
);
|
||
}
|