Files
mdb/mdb-predator/app/index.tsx
Bastien COIGNOUX bd325fe456 init
2026-05-03 20:18:33 +02:00

147 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { router } from 'expo-router';
import { useMemo, useState } from 'react';
import {
Pressable,
ScrollView,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
import { runFinancialAgent } from '../src/agents/orchestrator';
import { analyzeDeal, RED_FLAG_MARGIN_PCT, type DealAnalysisInput } from '../src/core/dealAnalysis';
import { sharePurchaseOfferPdf } from '../src/services/offerPdf';
import { colors } from '../src/theme/colors';
const DEMO_PROPERTY_ID = 'local-demo';
export default function RedFlagDashboard() {
const [asking, setAsking] = useState('185000');
const [resale, setResale] = useState('268000');
const [surface, setSurface] = useState('88');
const [works, setWorks] = useState('42000');
const input: DealAnalysisInput = useMemo(
() => ({
resaleEstimateTtc: Number(resale.replace(/\s/g, '')) || 0,
surfaceM2: Number(surface.replace(',', '.')) || 1,
worksTotalEur: Number(works.replace(/\s/g, '')) || 0,
notaryRateOnPurchase: 0.077,
saleAgencyRateOnResale: 0.05,
miscAcquisitionEur: 2500,
miscSaleEur: 1800,
holdingMonths: 6,
holdingAnnualRateOnPrincipal: 0.055,
}),
[resale, surface, works],
);
const analysis = useMemo(() => analyzeDeal(input), [input]);
const askNum = Number(asking.replace(/\s/g, '')) || 0;
const fin = useMemo(
() => runFinancialAgent(input, askNum),
[input, askNum],
);
const red = analysis.isRedFlag(askNum);
return (
<ScrollView contentContainerStyle={styles.scroll}>
<View style={[styles.banner, red ? styles.bannerBad : styles.bannerOk]}>
<Text style={styles.bannerTitle}>{red ? 'RED FLAG' : 'GO'}</Text>
<Text style={styles.bannerSub}>
Marge nette à prix affiché : {(analysis.netMarginPct(askNum) * 100).toFixed(1)} %
{' — '}seuil {(RED_FLAG_MARGIN_PCT * 100).toFixed(0)} %
</Text>
<Text style={styles.bannerSub}>
Prix dachat max (algo) :{' '}
{fin.maxBuyingPriceEur.toLocaleString('fr-FR')}
</Text>
</View>
<Text style={styles.h}>Hypothèses deal</Text>
<Field label="Prix affiché / offre actuelle (€)" value={asking} onChange={setAsking} />
<Field label="Prix de revente estimé TTC (€)" value={resale} onChange={setResale} />
<Field label="Surface (m²)" value={surface} onChange={setSurface} />
<Field label="Travaux estimés (€)" value={works} onChange={setWorks} />
<Pressable style={styles.btn} onPress={() => router.push('/field')}>
<Text style={styles.btnText}>Field visit checklist</Text>
</Pressable>
<Pressable
style={[styles.btn, styles.btnGhost]}
onPress={() =>
void sharePurchaseOfferPdf({
propertyTitle: 'Bien cible',
address: 'À compléter',
maxBuyPriceEur: fin.maxBuyingPriceEur,
})
}
>
<Text style={styles.btnTextGhost}>One-click offer (PDF)</Text>
</Pressable>
<Text style={styles.foot}>
Agents SCOUT / ENGINEER / APIs : brancher Edge Functions + clés serveur.
Dossier checklist : id « {DEMO_PROPERTY_ID} ».
</Text>
</ScrollView>
);
}
function Field({
label,
value,
onChange,
}: {
label: string;
value: string;
onChange: (s: string) => void;
}) {
return (
<View style={{ marginBottom: 12 }}>
<Text style={styles.lab}>{label}</Text>
<TextInput
keyboardType="decimal-pad"
value={value}
onChangeText={onChange}
style={styles.inp}
placeholderTextColor={colors.muted}
/>
</View>
);
}
const styles = StyleSheet.create({
scroll: { padding: 16, paddingBottom: 48 },
banner: { borderRadius: 16, padding: 18, marginBottom: 20 },
bannerBad: { backgroundColor: '#3a121c' },
bannerOk: { backgroundColor: '#0f2a1c' },
bannerTitle: { color: colors.text, fontSize: 28, fontWeight: '900' },
bannerSub: { color: colors.muted, marginTop: 8, lineHeight: 20 },
h: { color: colors.text, fontSize: 16, fontWeight: '700', marginBottom: 10 },
lab: { color: colors.muted, fontSize: 12, marginBottom: 6 },
inp: {
borderWidth: 1,
borderColor: colors.border,
borderRadius: 10,
padding: 12,
color: colors.text,
backgroundColor: colors.card,
},
btn: {
backgroundColor: colors.accent,
paddingVertical: 14,
borderRadius: 12,
alignItems: 'center',
marginTop: 10,
},
btnGhost: {
backgroundColor: 'transparent',
borderWidth: 1,
borderColor: colors.border,
},
btnText: { color: '#fff', fontWeight: '700', fontSize: 16 },
btnTextGhost: { color: colors.text, fontWeight: '700', fontSize: 16 },
foot: { color: colors.muted, marginTop: 24, fontSize: 12, lineHeight: 18 },
});