// screens-home.jsx — home (listings list), map, listing detail function CategoryChips({ active, onChange, t }) { return (
{CATEGORIES.map(c => { const isActive = active === c.id; return ( ); })}
); } // Helper : extrait le titre/location selon le format (mock bilingue OU API string) function listingTitle(l, lang) { if (typeof l.title === 'object' && l.title !== null) return l.title[lang] || l.title.fr || ''; return l.title || ''; } function listingCity(l, lang) { // Priorité : array cities (V2), puis city (string), puis location (mock) if (Array.isArray(l.cities) && l.cities.length) { if (l.cities.length === 1) return l.cities[0]; return l.cities[0] + ' +' + (l.cities.length - 1); } if (l.city) return l.city; if (typeof l.location === 'object' && l.location !== null) return l.location[lang] || l.location.fr || ''; return ''; } function listingPrice(l) { if (l.price && typeof l.price === 'object') return l.price.amount; return l.rate; } function listingPriceUnit(l, t) { const unit = (l.price && l.price.unit) || 'hour'; if (unit === 'hour') return t('hour_short'); if (unit === 'day') return 'j'; return 'fc'; // forfait } function listingPhoto(l) { if (Array.isArray(l.photos) && l.photos.length) return l.photos[0]; return l.img || null; } function ListingCard({ listing, lang, t, onOpen }) { const photo = listingPhoto(listing); const price = listingPrice(listing); const title = listingTitle(listing, lang); const city = listingCity(listing, lang); return (
); } function HomeScreen({ lang, t, onOpenListing, onOpenMap, query, setQuery, onCreateListing, refreshKey }) { const [cat, setCat] = React.useState('all'); const [listings, setListings] = React.useState(null); // null = loading, [] = empty, [...] = data const [error, setError] = React.useState(null); const me = (window.TrustaAuth && window.TrustaAuth.getUser()) || null; const displayName = (me && me.name) ? me.name.split(' ')[0] : (lang === 'ka' ? 'მეგობარო' : 'ami'); const displayCity = (me && me.city) ? me.city : (lang === 'ka' ? 'საფრანგეთი' : 'France'); // Fetch annonces depuis l'API au montage + au changement de filtre catégorie React.useEffect(() => { if (!window.API_AVAILABLE) { setListings(LISTINGS); // fallback dev/preview return; } setListings(null); setError(null); const filters = {}; if (cat !== 'all') filters.category = cat; window.TrustaAPI.listListings(filters) .then(rows => setListings(rows || [])) .catch(err => { console.error('[HomeScreen] fetch failed', err); setError(err); setListings([]); }); }, [cat, refreshKey]); // Filtre client-side pour la recherche texte const filtered = (listings || []).filter(l => { if (!query) return true; const q = query.toLowerCase(); const title = listingTitle(l, lang).toLowerCase(); const city = listingCity(l, lang).toLowerCase(); return title.includes(q) || city.includes(q); }); return (
} />
setQuery(e.target.value)} placeholder={t('search_placeholder')} style={{ flex: 1, background: 'transparent', border: 'none', outline: 'none', color: '#fff', fontFamily: 'Inter, system-ui', fontSize: 14, }} />
{t('new_listings')}
{t('see_all')}
{listings === null && (
{lang === 'ka' ? 'იტვირთება…' : 'Chargement…'}
)} {listings !== null && filtered.map(l => )} {listings !== null && filtered.length === 0 && !error && (
{query ? (lang === 'ka' ? 'შედეგი ვერ მოიძებნა' : 'Aucun résultat') : (lang === 'ka' ? 'ჯერ არცერთი განცხადება არ არის' : 'Pas encore d\'annonces')}
{query ? (lang === 'ka' ? 'სცადეთ სხვა ძიება' : 'Essayez une autre recherche') : (lang === 'ka' ? 'იყავი პირველი, ვინც განცხადებას დადებს ჩვენს თემში' : 'Sois le premier à publier dans la communauté')}
{!query && onCreateListing && ( )}
)} {error && filtered.length === 0 && (
{lang === 'ka' ? 'შეცდომა. სცადეთ თავიდან.' : 'Erreur de chargement. Réessayez.'}
)}
); } function MapScreen({ lang, t, onBack, onOpenListing }) { const [selected, setSelected] = React.useState(LISTINGS[0]); return (
{/* faux map */}
{/* roads */} {/* river */}
} /> {/* Pins */} {[ { l: LISTINGS[0], x: '32%', y: '38%' }, { l: LISTINGS[1], x: '64%', y: '30%' }, { l: LISTINGS[2], x: '52%', y: '52%' }, { l: LISTINGS[3], x: '24%', y: '60%' }, { l: LISTINGS[4], x: '72%', y: '64%' }, { l: LISTINGS[5], x: '44%', y: '44%' }, ].map((p, i) => ( ))} {/* Bottom card */} {selected && (
)}
); } function ListingDetailScreen({ listing, lang, t, onBack, onOpenChat, onOpenProfile, onDeleted }) { const [photoIdx, setPhotoIdx] = React.useState(0); const [deleting, setDeleting] = React.useState(false); const [lightboxOpen, setLightboxOpen] = React.useState(false); const [showReport, setShowReport] = React.useState(false); const touchStartX = React.useRef(null); const me = window.TrustaAuth ? window.TrustaAuth.getUser() : null; const isMine = me && listing.user && me.id === listing.user.id; const handleDelete = async () => { const msg = lang === 'ka' ? 'წავშალო ეს განცხადება?' : 'Supprimer cette annonce ?'; if (!window.confirm(msg)) return; setDeleting(true); try { await window.TrustaAPI.deleteListing(listing.id); if (onDeleted) onDeleted(listing.id); } catch (e) { alert(window.errorMessage ? window.errorMessage(e, lang) : 'Erreur'); setDeleting(false); } }; // Adapte le listing à un format unifié (compatible mock + API) const title = listingTitle(listing, lang); const city = listingCity(listing, lang); const price = listingPrice(listing); const unit = listingPriceUnit(listing, t); const photos = (Array.isArray(listing.photos) && listing.photos.length) ? listing.photos : (listing.img ? [listing.img] : []); const description = (typeof listing.description === 'object' && listing.description !== null) ? (listing.description[lang] || listing.description.fr || '') : (listing.description || ''); // Poster : depuis l'API on a listing.user, depuis le mock on a MEMBERS[posterId] const poster = listing.user ? { id: listing.user.id, name: listing.user.name || (lang === 'ka' ? 'მომხმარებელი' : 'Utilisateur'), avatar: listing.user.photo || null, verified: !!listing.user.verified, rating: null, missions: null, } : (listing.posterId && window.MEMBERS && window.MEMBERS[listing.posterId]) ? { ...window.MEMBERS[listing.posterId], name: typeof window.MEMBERS[listing.posterId].name === 'object' ? window.MEMBERS[listing.posterId].name[lang] : window.MEMBERS[listing.posterId].name, } : { name: lang === 'ka' ? 'მომხმარებელი' : 'Utilisateur', avatar: null, verified: false, rating: null, missions: null }; return (
{/* Photo carousel */}
{ if (photos.length) setLightboxOpen(true); }} onTouchStart={(e) => { touchStartX.current = e.touches[0].clientX; }} onTouchEnd={(e) => { if (touchStartX.current === null || photos.length < 2) return; const dx = e.changedTouches[0].clientX - touchStartX.current; if (Math.abs(dx) > 50) { if (dx < 0) setPhotoIdx((i) => Math.min(i + 1, photos.length - 1)); else setPhotoIdx((i) => Math.max(i - 1, 0)); } touchStartX.current = null; }} style={{ position: 'absolute', inset: 0, background: photos.length ? `url(${photos[photoIdx]}) center/cover` : `linear-gradient(135deg, ${COLORS.card} 0%, rgba(212,175,55,0.06) 100%)`, cursor: photos.length ? 'zoom-in' : 'default', }} />
{!isMine && }
} />
{photos.length > 1 && (
{photoIdx + 1}/{photos.length}
)} {photos.length > 1 && (
{photos.map((_, i) => (
)}
{title}
{city}
{price !== null && price !== undefined ?
€{price}/{unit}
:
{t('to_negotiate') || 'À discuter'}
}
{/* Catégories (chips) */} {((Array.isArray(listing.categories) && listing.categories.length) || listing.category) && (
{(Array.isArray(listing.categories) && listing.categories.length ? listing.categories : [listing.category]).map(c => (
{t('cat_' + c)}
))}
)} {/* Villes desservies (si plusieurs) */} {Array.isArray(listing.cities) && listing.cities.length > 1 && (
{lang === 'ka' ? 'მომსახურების ქალაქები' : 'Villes desservies'}
{listing.cities.map((c, i) => (
{c}
))}
)} {description && (
{description}
)} {/* Poster */}
{t('posted_by')}
{!isMine && poster.id && ( )}
{/* Footer */}
{isMine ? ( <>
{lang === 'ka' ? 'ეს არის შენი განცხადება' : 'Ceci est votre annonce'}
) : ( <>
🛡 {t('protected_chat')}
onOpenChat(poster)}>{t('contact_chat')} )}
{/* Modal Signaler */} {showReport && window.ReportModal && ( setShowReport(false)} /> )} {/* Photo lightbox */} {lightboxOpen && photos.length > 0 && (
setLightboxOpen(false)} onTouchStart={(e) => { touchStartX.current = e.touches[0].clientX; }} onTouchEnd={(e) => { if (touchStartX.current === null) return; const dx = e.changedTouches[0].clientX - touchStartX.current; if (Math.abs(dx) > 50 && photos.length > 1) { e.stopPropagation(); if (dx < 0) setPhotoIdx((i) => Math.min(i + 1, photos.length - 1)); else setPhotoIdx((i) => Math.max(i - 1, 0)); } touchStartX.current = null; }} style={{ position: 'fixed', inset: 0, zIndex: 500, background: 'rgba(0,0,0,0.96)', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'zoom-out', }} > e.stopPropagation()} style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', userSelect: 'none', pointerEvents: 'none', }} /> {/* Close button */} {/* Counter */} {photos.length > 1 && (
{photoIdx + 1} / {photos.length}
)} {/* Prev / Next arrows (desktop) */} {photos.length > 1 && photoIdx > 0 && ( )} {photos.length > 1 && photoIdx < photos.length - 1 && ( )}
)}
); } window.HomeScreen = HomeScreen; window.MapScreen = MapScreen; window.ListingDetailScreen = ListingDetailScreen;