import { memo, useState } from 'react' import { useNavigate } from 'react-router' import { Alert, Box, Button, Chip, CircularProgress, Dialog, DialogActions, DialogContent, DialogTitle, Drawer, IconButton, Switch, Tooltip, Typography, } from '@mui/material' import { ArrowLeft, Plus, Trash2 } from 'lucide-react' import { useProperties, useUpdateProperty, useRemoveProperty } from '../../hooks/useProperties' import { useMatchesByProperty } from '../../hooks/useMatches' import { useLatentNeedById } from '../../hooks/useLatentNeeds' import { PropertyDetailView } from '../../components/supply' import { PublicNeedDetail, OfferWizard } from '../../components/anfragencenter' import { NEED_TO_LATENT_NEED } from '../../lib/needToLatentNeedMap' import type { Property } from '../../domain/property' import type { Match } from '../../domain/match' const ASSET_LABELS: Record = { OFFICE: 'Büro', RETAIL: 'Einzelhandel', LIGHT_INDUSTRIAL: 'Gewerbe', LOGISTICS: 'Logistik', PRODUCTION: 'Produktion', MIXED: 'Gemischt', } const GRID_COLS = '2fr 1fr 80px 100px 110px 120px 80px 48px' function formatDate(iso?: string) { if (!iso) return '–' return new Date(iso).toLocaleDateString('de-CH', { day: '2-digit', month: '2-digit', year: 'numeric' }) } function matchColor(score: number): string { if (score >= 80) return '#1a7a4a' if (score >= 60) return '#d97706' return '#dc2626' } // ─── Suchabo dialog ──────────────────────────────────────────────────────── function MatchListItem({ match, onSelect, }: { match: Match onSelect: (needId: string) => void }) { const latentNeedId = NEED_TO_LATENT_NEED[match.needId] ?? null const { data: latentNeed } = useLatentNeedById(latentNeedId) const companyName = latentNeed?.tenantCompany ?? match.needId return ( {companyName} {latentNeed && ( {latentNeed.desiredLocation} )} {latentNeedId ? ( ) : ( )} ) } interface SuchaboDialogProps { propertyId: string matches: Match[] onClose: () => void } function SuchaboDialogContent({ matches }: SuchaboDialogProps) { const [selectedNeedId, setSelectedNeedId] = useState( matches.length === 1 ? matches[0].needId : null, ) const latentNeedId = selectedNeedId ? (NEED_TO_LATENT_NEED[selectedNeedId] ?? null) : null const { data: latentNeed, isLoading } = useLatentNeedById(latentNeedId) const showList = !selectedNeedId if (showList) { return ( {matches.map(m => ( ))} ) } if (isLoading) { return ( ) } if (!latentNeed) { return ( Suchabo nicht gefunden. ) } return ( {matches.length > 1 && ( )} ) } // ─── Row ─────────────────────────────────────────────────────────────────── interface RowProps { p: Property isLast: boolean onDetail: (id: string) => void onToggleStatus: (p: Property) => void onDelete: (p: Property) => void onChipClick: (propertyId: string, matches: Match[]) => void updatePending: boolean removePending: boolean } const ListingRow = memo(function ListingRow({ p, isLast, onDetail, onToggleStatus, onDelete, onChipClick, updatePending, removePending, }: RowProps) { const { data: matches = [] } = useMatchesByProperty(p.id) const topScore = matches.length > 0 ? Math.max(...matches.map(m => m.matchScore)) : 0 return ( onDetail(p.id)} sx={{ display: 'grid', gridTemplateColumns: GRID_COLS, px: 2, py: 1.25, alignItems: 'center', borderBottom: isLast ? 'none' : '1px solid #f1f5f9', cursor: 'pointer', '&:hover': { bgcolor: '#f8fafc' }, transition: 'background 0.1s', }} > {/* Title + type */} {p.title} {/* City */} {p.location.city} {/* Area */} {p.areaSqm.toLocaleString('de-CH')} m² {/* Rent */} CHF {p.rentPricePerSqm.toLocaleString('de-CH')} {/* Match count */} e.stopPropagation()}> {matches.length > 0 ? ( onChipClick(p.id, matches)} sx={{ height: 20, fontSize: '0.65rem', fontWeight: 600, bgcolor: `${matchColor(topScore)}18`, color: matchColor(topScore), cursor: 'pointer', '&:hover': { opacity: 0.85 }, }} /> ) : ( )} {/* Created */} {formatDate(p.createdAt)} {/* Status toggle */} e.stopPropagation()}> onToggleStatus(p)} disabled={updatePending} sx={{ '& .MuiSwitch-thumb': { width: 14, height: 14 } }} /> {/* Delete */} e.stopPropagation()}> onDelete(p)} disabled={removePending} sx={{ color: '#94a3b8', '&:hover': { color: '#ef4444' } }} > ) }) // ─── Page ───────────────────────────────────────────────────────────────── export default function MyListings() { const navigate = useNavigate() const { data: listings = [], isLoading } = useProperties({ sourceType: 'DIRECT' }) const updateProperty = useUpdateProperty() const removeProperty = useRemoveProperty() const [detailId, setDetailId] = useState(null) const [confirmDelete, setConfirmDelete] = useState(null) const [actionError, setActionError] = useState(null) const [suchaboDialog, setSuchaboDialog] = useState<{ propertyId: string matches: Match[] } | null>(null) function handleToggleStatus(p: Property) { const next = p.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE' updateProperty.mutate( { id: p.id, input: { status: next } }, { onError: () => setActionError('Status konnte nicht geändert werden.') }, ) } function handleDelete(p: Property) { removeProperty.mutate(p.id, { onSuccess: () => setConfirmDelete(null), onError: () => { setActionError('Inserat konnte nicht gelöscht werden.') setConfirmDelete(null) }, }) } return ( Inserate Direkt erstellte Inserate — unabhängig vom Portfolio {actionError && ( setActionError(null)}> {actionError} )} {isLoading ? ( ) : listings.length === 0 ? ( Noch keine Inserate Erstellen Sie Ihr erstes direktes Inserat — ohne vollständiges Objekt im Portfolio. ) : ( {/* Header */} {['Inserat', 'Ort', 'Fläche', 'Preis/m²/J', 'Suchabos', 'Erstellt', 'Aktiv', ''].map(h => ( {h} ))} {listings.map((p, i) => ( setSuchaboDialog({ propertyId: pid, matches: m })} updatePending={updateProperty.isPending} removePending={removeProperty.isPending} /> ))} )} {/* Detail / edit drawer */} setDetailId(null)} slotProps={{ paper: { sx: { width: { xs: '100%', sm: 560 } } } }} > {detailId && ( setDetailId(null)} hideTabs={['Matchability', 'Marktsignale']} /> )} {/* Suchabo dialog */} setSuchaboDialog(null)} maxWidth="sm" fullWidth slotProps={{ paper: { sx: { maxHeight: '85vh' } } }} > Suchabos {suchaboDialog && ( {suchaboDialog.matches.length} Suchabo{suchaboDialog.matches.length !== 1 ? 's' : ''} matchen dieses Inserat )} {suchaboDialog && ( setSuchaboDialog(null)} /> )} {/* Delete confirmation */} setConfirmDelete(null)} maxWidth="xs" fullWidth> Inserat löschen? {confirmDelete?.title} wird unwiderruflich gelöscht. {/* OfferWizard — triggered by PublicNeedDetail's "Angebot erstellen" button */} ) }