import React, { useEffect, useState } from 'react'; import { getListings, collectListings, getListingsCollectStatus } from '../../../api'; import { tierMeta, formatMoney, formatRatio } from '../listingsUtils'; const DEAL_TYPES = ['전체', '전세', '반전세', '매매']; const ListingCard = ({ l }) => { const isSale = l.deal_type === '매매'; const tv = isSale ? l.valuation_tier : l.safety_tier; const meta = tierMeta(isSale ? 'valuation' : 'safety', tv); const price = l.price ?? l.deposit ?? l.amount; const flags = Array.isArray(l.regulation_flags) ? l.regulation_flags : []; const reasons = Array.isArray(l.reasons) ? l.reasons : []; return (
{tv && {meta.emoji} {meta.label}} {l.complex_name || l.name || l.dong || '(매물)'} {l.deal_type && {l.deal_type}} {l.dong && {l.dong}}
{price != null && {formatMoney(price)}} {l.area != null && {l.area}m²} {l.ratio != null && 비율 {formatRatio(l.ratio)}}
{flags.length > 0 && (
{flags.map((f, i) => {f})}
)} {reasons.length > 0 &&
{reasons.join(' · ')}
}
); }; const ListingsTab = () => { const [listings, setListings] = useState([]); const [dealType, setDealType] = useState('전체'); const [matchedOnly, setMatchedOnly] = useState(false); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [collectStatus, setCollectStatus] = useState(null); const [collecting, setCollecting] = useState(false); const load = async () => { setLoading(true); setError(''); try { const data = await getListings({ deal_type: dealType === '전체' ? undefined : dealType, matched_only: matchedOnly, size: 50, }); setListings(Array.isArray(data) ? data : (data.listings ?? [])); } catch (e) { setError(e?.message ?? String(e)); setListings([]); } finally { setLoading(false); } }; const loadStatus = async () => { try { setCollectStatus(await getListingsCollectStatus()); } catch { /* noop */ } }; useEffect(() => { load(); }, [dealType, matchedOnly]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { loadStatus(); }, []); const handleCollect = async () => { setCollecting(true); setError(''); const prevAt = collectStatus?.collected_at ?? null; try { await collectListings(); } catch (e) { setError(e?.message ?? String(e)); setCollecting(false); return; } let tries = 0; const poll = async () => { tries += 1; let st = null; try { st = await getListingsCollectStatus(); } catch { /* noop */ } if (st) setCollectStatus(st); const done = st && (st.collected_at !== prevAt || st.error); if (done || tries >= 12) { setCollecting(false); load(); return; } window.setTimeout(poll, 5000); }; window.setTimeout(poll, 5000); }; return (
{DEAL_TYPES.map((d) => ( ))}
{collectStatus && (

수집 상태: {collectStatus.status === 'never_run' ? '미실행' : collectStatus.status} {collectStatus.total_count != null && ` · 총 ${collectStatus.total_count}건`} {collectStatus.error && · 오류: {collectStatus.error}}

)} {error &&

{error}

} {loading ? (

불러오는 중…

) : listings.length === 0 ? (

조건에 맞는 매물이 없습니다. ‘수집 실행’으로 매물을 모아보세요.

) : (
{listings.map((l) => )}
)}
); }; export default ListingsTab;