// screens-detail.jsx — Listing detail + AI evaluation full screen
function mobileRentalPersonalizationEnabled(user) {
const value = user?.rental_profile?.personalized_inquiry_enabled;
return !(value === false || value === 'false' || value === 0 || value === '0');
}
// ─── LISTING DETAIL ────────────────────────────────────────────────────
function ListingDetailScreen({ ctx, id, publicMode = false }) {
const {
listings = [], evaluations = {}, navigate = () => {}, back = () => { window.location.href = '/'; },
favs = new Set(), toggleFav = () => {}, contactAd = async () => {}, toggleSharing = () => {},
evaluateAd = () => {}, scrapeAdDetails = () => {}, inquiryPreview = null, busy = false,
} = ctx;
const idKey = String(id ?? '');
const investorItems = [
...(ctx.investor?.evaluations || []),
...(ctx.investor?.compare?.items || []),
...(ctx.portfolioAnalytics?.items || []),
];
const investorItem = investorItems.find(item => {
const li = item?.listing || {};
return String(item?.adId ?? '') === idKey
|| String(item?.id ?? '') === idKey
|| String(li.id ?? '') === idKey
|| String(li.adId ?? '') === idKey;
});
const l = listings.find(x => String(x.id) === idKey || String(x.adId) === idKey) || investorItem?.listing;
const eval_ = evaluations[idKey] || evaluations[id] || window.AI_EVALS?.[idKey] || window.AI_EVALS?.[id] || investorItem?.evaluation;
const [shared, setShared] = React.useState(!!l?.sharingEnabled);
const [shareUrl, setShareUrl] = React.useState(l?.shareUrl || null);
const [shareOpen, setShareOpen] = React.useState(false);
const [contactOpen, setContactOpen] = React.useState(false);
const isKauf = l?.type === 'kauf';
const personalizedInquiryEnabled = !isKauf && mobileRentalPersonalizationEnabled(ctx.user);
const messageKey = `immobot.mobile.contactMessage.${isKauf ? 'purchase' : 'rental'}`;
const cachedInquiryMessage = (!isKauf && personalizedInquiryEnabled) ? (l?.rentalInquiryMessage || '') : '';
const standardMessage = isKauf
? 'Sehr geehrte Damen und Herren,\n\nich habe Interesse an dieser Immobilie und möchte gerne einen Besichtigungstermin vereinbaren.'
: (ctx.user?.default_reply_message || 'Sehr geehrte Damen und Herren,\n\nich habe Interesse an dieser Mietwohnung und möchte gerne einen Besichtigungstermin vereinbaren.');
const defaultMessage = cachedInquiryMessage || standardMessage;
const [message, setMessage] = React.useState(() => cachedInquiryMessage || (isKauf ? localStorage.getItem(messageKey) : null) || standardMessage);
const [previewLoading, setPreviewLoading] = React.useState(false);
const pricePerArea = l?.pricePerSqm ? `${Number(l.pricePerSqm).toLocaleString('de-DE')} €/m²` : (l?.area ? `${Math.round(l.price / l.area).toLocaleString('de-DE')} €/m²` : '€/m² nicht verfügbar');
React.useEffect(() => {
if (!l) return;
if (!isKauf && personalizedInquiryEnabled) return;
localStorage.setItem(messageKey, message);
}, [l, isKauf, personalizedInquiryEnabled, messageKey, message]);
React.useEffect(() => {
if (!contactOpen || isKauf || personalizedInquiryEnabled) return;
setPreviewLoading(false);
setMessage(localStorage.getItem(messageKey) || defaultMessage);
}, [contactOpen, isKauf, personalizedInquiryEnabled, messageKey, defaultMessage]);
React.useEffect(() => {
setShared(!!l?.sharingEnabled);
setShareUrl(l?.shareUrl || null);
}, [l?.id, l?.sharingEnabled, l?.shareUrl]);
React.useEffect(() => {
if (!contactOpen || !l || isKauf || !personalizedInquiryEnabled || !inquiryPreview) return undefined;
if (cachedInquiryMessage) {
setMessage(cachedInquiryMessage);
setPreviewLoading(false);
return undefined;
}
let active = true;
setMessage('');
setPreviewLoading(true);
inquiryPreview(l.task_id, l.adId || l.id)
.then(result => {
if (active && result?.message) setMessage(result.message);
})
.catch(() => {
if (active) setMessage(defaultMessage);
})
.finally(() => {
if (active) setPreviewLoading(false);
});
return () => {
active = false;
};
}, [contactOpen, l?.task_id, l?.adId, l?.id, isKauf, personalizedInquiryEnabled, inquiryPreview, defaultMessage, cachedInquiryMessage]);
if (!l) return
Nicht gefunden
;
const isFav = !publicMode && favs.has(l.id);
const shareDataFor = (url) => ({
title: l.title || 'ImmoBot Anzeige',
text: `Schau dir diese Immobilie an: ${l.title || 'ImmoBot Anzeige'}`,
url,
});
const copyShareLink = async (url) => {
if (!url) return;
try {
await navigator.clipboard?.writeText(url);
alert('Share-Link wurde kopiert.');
} catch (error) {
const input = document.createElement('textarea');
input.value = url;
input.style.position = 'fixed';
input.style.left = '-9999px';
document.body.appendChild(input);
input.focus();
input.select();
document.execCommand('copy');
document.body.removeChild(input);
alert('Share-Link wurde kopiert.');
}
};
const openInquirySettings = () => {
sessionStorage.setItem('immobotScrollInquiryProfile', '1');
setContactOpen(false);
navigate('profile');
};
const openContactSheet = () => {
if (!isKauf && personalizedInquiryEnabled && cachedInquiryMessage) {
setMessage(cachedInquiryMessage);
setPreviewLoading(false);
} else if (!isKauf && personalizedInquiryEnabled && inquiryPreview) {
setMessage('');
setPreviewLoading(true);
} else {
setPreviewLoading(false);
}
setContactOpen(true);
};
const nativeShare = async (url) => {
if (!url || !navigator.share) return false;
await navigator.share(shareDataFor(url));
return true;
};
const ensureShareUrl = async () => {
if (shareUrl || l.shareUrl) return shareUrl || l.shareUrl;
const result = await toggleSharing(l.task_id, l.adId || l.id);
const url = result.share_url || l.shareUrl || shareUrl;
setShared(!!result.sharing_enabled);
setShareUrl(url);
return url;
};
const shareListing = async () => {
try {
const alreadyShareable = !!(shareUrl || l.shareUrl);
const url = await ensureShareUrl();
if (!url) return;
if (alreadyShareable) {
try {
if (await nativeShare(url)) return;
} catch (error) {
if (error?.name === 'AbortError') return;
}
}
setShareOpen(true);
} catch (error) {
const url = shareUrl || l.shareUrl;
if (url) setShareOpen(true);
}
};
const generatingText = previewLoading && personalizedInquiryEnabled && !isKauf;
return (
{/* Photo gallery with overlay back */}
{/* Back / fav buttons */}
{!publicMode &&
toggleFav(l.id)} style={{
width: 40, height: 40, borderRadius: 999, border: 0,
background: 'rgba(255,255,255,.95)', backdropFilter: 'blur(10px)',
display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
color: isFav ? '#C8331C' : 'var(--ink)',
}}>{isFav ? : }
}
{/* Body */}
{l.provisionsfrei &&
Provisionsfrei }
{fmtRelative(l.posted)}
{l.title}
{l.address}, {l.plz} {l.city} · {l.district}
{/* Price block */}
{isKauf ? 'Kaufpreis' : 'Warmmiete / Monat'}
{fmtPrice(l.price)}
{isKauf
? pricePerArea
: `inkl. ${l.nebenkosten} € NK · kalt ${l.kaltmiete} €`
}
{l.aiScore !== null && l.aiScore !== undefined &&
}
{/* Specs grid */}
} label="Wohnfläche" value={fmtArea(l.area)} />
} label="Zimmer" value={`${l.rooms} Zi.`} />
} label="Baujahr" value={l.baujahr} />
} label="Energie" value={`Klasse ${l.energieklasse}`} />
{isKauf && l.nebenkosten && } label="Hausgeld" value={`${l.nebenkosten} €/Mt.`} />}
{!isKauf && } label="Kaltmiete" value={`${l.kaltmiete} €`} />}
} label="Etage" value={l.floor} />
{/* AI evaluation card (if available) */}
{eval_ && (
!publicMode && navigate('aiEval', { id: l.id })}
style={{
marginTop: 20, padding: 16, borderRadius: 16, cursor: publicMode ? 'default' : 'pointer',
background: 'linear-gradient(135deg, var(--primary-soft) 0%, #FCEEDE 100%)',
border: '1px solid var(--primary-soft)',
display: 'flex', alignItems: 'center', gap: 14,
}}
>
KI-Bewertung
{eval_.recommendation} · Note {eval_.grade}
{eval_.summary}
{!publicMode &&
}
)}
{isKauf && !eval_ && !publicMode && (
Noch nicht bewertet
KI-Analyse jetzt starten
evaluateAd(l.task_id, l.adId || l.id)} className="btn btn-accent" style={{ padding: '8px 14px', fontSize: 12 }}>
Starten
)}
{/* Description */}
Beschreibung
{l.description}
{/* Features */}
Ausstattung
{l.features.map(f => (
{f}
))}
{/* Source */}
{/* Footer CTA */}
{!publicMode &&
{l.contacted ? 'Kontaktiert' : 'Anbieter kontaktieren'}
{eval_ && (
navigate('aiEval', { id: l.id })} className="btn btn-outline" style={{ padding: '14px 16px', fontSize: 15 }}>
)}
}
{!publicMode &&
setShareOpen(false)} title="Anzeige teilen" height="46%">
Diese Anzeige ist jetzt öffentlich teilbar. Sende den Link direkt an Freunde oder kopiere ihn.
{navigator.share &&
{
try { await nativeShare(shareUrl || l.shareUrl); setShareOpen(false); } catch (error) { if (error?.name !== 'AbortError') copyShareLink(shareUrl || l.shareUrl); }
}} className="btn btn-accent" style={{ width: '100%', justifyContent: 'center', padding: 14 }}>
Teilen…
}
Per WhatsApp teilen
Per Telegram teilen
copyShareLink(shareUrl || l.shareUrl)} className="btn btn-outline" style={{ width: '100%', justifyContent: 'center', padding: 14 }}>
Link kopieren
}
{!publicMode &&
setContactOpen(false)} title="Anbieter kontaktieren" height="58%">
}
);
}
function SharedListingScreen() {
const listing = window.REVAMP_SHARED_LISTING;
const evaluation = window.REVAMP_SHARED_EVALUATION;
if (!listing) return Inserat nicht gefunden
;
const ctx = {
listings: [listing],
evaluations: evaluation ? { [listing.id]: evaluation } : {},
favs: new Set(),
user: null,
back: () => { window.location.href = '/'; },
};
return ;
}
function Spec({ icon, label, value }) {
return (
);
}
function evalArray(primary, fallback = []) {
return Array.isArray(primary) ? primary : (Array.isArray(fallback) ? fallback : []);
}
function evalText(value) {
return String(value || '').replace(/\\r\\n/g, '\n').replace(/\\n/g, '\n').replace(/\\t/g, '\t');
}
function EvalSimpleList({ items, tone = 'neutral' }) {
const palette = tone === 'danger'
? { bg: 'var(--danger-soft)', fg: 'var(--danger)', icon: }
: tone === 'success'
? { bg: 'var(--success-soft)', fg: 'var(--success)', icon: }
: { bg: 'var(--surface-2)', fg: 'var(--ink-muted)', icon: };
return (
{items.map((item, i) => (
))}
);
}
// ─── AI EVALUATION FULL SCREEN ─────────────────────────────────────────
function AIEvalScreen({ ctx, id }) {
const { listings, navigate, back } = ctx;
const l = listings.find(x => x.id === id);
const eval_ = ctx.evaluations[id] || window.AI_EVALS?.[id];
if (!eval_) return Keine Bewertung verfügbar
;
const scoreColor = eval_.score >= 80 ? '#2F8F5A' : eval_.score >= 60 ? '#C28428' : '#C8331C';
const kpis = evalArray(eval_.kpis);
const scorecard = evalArray(eval_.scorecard);
const strengths = evalArray(eval_.strengths, eval_.key_strengths);
const risks = evalArray(eval_.risks, eval_.key_risks);
const assumptions = evalArray(eval_.assumptions);
const imageObservations = evalArray(eval_.image_observations, eval_.imageObservations);
const modelLabel = [eval_.provider === 'codex' ? 'Codex' : eval_.provider, eval_.model].filter(Boolean).join(' · ') || 'KI-Modell';
return (
alert('Geteilt: immobot.de/eval/' + l.sharedToken)}> }
/>
{/* Hero score */}
{eval_.recommendation}
{eval_.summary}
Bewertet am {fmtDateTime(eval_.evaluatedAt)} · {modelLabel}
{/* Listing summary card */}
navigate('listing', { id: l.id })} style={{ display: 'flex', gap: 12, padding: 10, cursor: 'pointer', alignItems: 'center' }}>
{l.title}
{fmtPrice(l.price)} · {l.district}, {l.city}
{/* KPIs */}
Kennzahlen
{kpis.map((k, i) => (
{k.label}
{k.value}
{k.delta && (
{k.delta}
)}
))}
{/* Scorecard */}
Scorecard
{scorecard.map((s, i) => (
))}
{strengths.length > 0 && (
Stärken
)}
{risks.length > 0 && (
Risiken
)}
{eval_.analysis && (
Vollständige Analyse
{evalText(eval_.analysis)}
)}
{imageObservations.length > 0 && (
Bildanalyse
)}
{assumptions.length > 0 && (
Annahmen
)}
{/* Comparables */}
{eval_.comparables && (
Vergleichsobjekte
{eval_.comparables.map((c, i) => (
{c.addr}
{c.area} m² · {Math.round(c.price/c.area).toLocaleString('de-DE')} €/m² · {c.dist} entfernt
{fmtPriceShort(c.price)}
))}
)}
{/* Disclaimer */}
Hinweis: Die KI-Analyse basiert auf öffentlich verfügbaren Daten und stellt keine Anlageberatung dar. Vor einem Kauf sollten weitere Prüfungen erfolgen.
);
}
Object.assign(window, { ListingDetailScreen, SharedListingScreen, AIEvalScreen, Spec });