// desktop-shared.jsx — sidebar, top bar, primitives for the desktop variant // Relies on window.I (icons), window.ListingCard data helpers, etc. from mobile bundle. const dskMonogram = ( {/* tiny serif "I" mark — echoes Newsreader feel */} ); // ─── SIDEBAR ──────────────────────────────────────────────────────────── function Sidebar({ active = 'kauf', notifBadge = 0, onNav = () => {}, user = null, taskLimit = null, onNewSearch = null, }) { const items = [ { id: 'kauf', label: 'Kaufen', icon: }, { id: 'miete', label: 'Mieten', icon: }, { id: 'investor', label: 'Investor', icon: }, { id: 'merk', label: 'Merkliste', icon: }, { id: 'mitt', label: 'Mitteilungen', icon: , badge: notifBadge }, { id: 'entd', label: 'Entdecken', icon: }, ]; const admin = [ { id: 'profile', label: 'Profil', icon: }, ...(user?.isAdmin ? [{ id: 'jobs', label: 'Job-Monitor', icon: }] : []), { id: 'export', label: 'Exporte', icon: }, ]; const quota = taskLimit?.remaining_evaluations == null ? 'Unbegrenzt' : taskLimit.remaining_evaluations; return ( ); } function desktopQuotaPct(taskLimit) { const max = Number(taskLimit?.max_evaluations_per_day || 100); const remaining = taskLimit?.remaining_evaluations == null ? max : Number(taskLimit.remaining_evaluations || 0); if (!max) return 0; return Math.max(0, Math.min(100, (remaining / max) * 100)); } function SideLabel({ children }) { return (
{children}
); } function NavItem({ icon, label, badge, active, onClick }) { return ( ); } // ─── TOP BAR ──────────────────────────────────────────────────────────── function DTopBar({ crumb, title, subtitle, actions, notifBadge = 0, searchValue = '', onSearchChange, onSearchSubmit, onNotifications, searchPlaceholder = 'Adresse, Suchname oder Plattform-ID …', showSearch = true, compactActions = false, }) { const searchRef = React.useRef(null); React.useEffect(() => { if (!showSearch) return undefined; const onKey = (event) => { if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') { event.preventDefault(); searchRef.current?.focus(); } }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [showSearch]); const canSearch = showSearch && typeof onSearchChange === 'function'; const actionGap = compactActions ? 8 : 10; const actionSize = compactActions ? 'sm' : 'md'; return (
{crumb && (
{crumb.map((c, i) => ( {c} {i < crumb.length - 1 && } ))}
)}

{title}

{subtitle && (
{subtitle}
)}
{/* Search + actions */}
{showSearch && (
onSearchChange?.(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') onSearchSubmit?.(e.target.value); }} style={{ width: 220, border: 0, background: 'transparent', outline: 'none', fontFamily: 'var(--font-sans)', fontSize: 13, color: 'var(--ink)', minWidth: 0, }} /> {searchValue ? ( ) : ( ⌘K )}
)} } badge={notifBadge} onClick={onNotifications} title="Mitteilungen" size={actionSize} /> {actions}
); } // ─── BUTTON ───────────────────────────────────────────────────────────── function DButton({ kind = 'ghost', icon, children, onClick, badge, size = 'md', disabled = false, title }) { const padding = size === 'sm' ? '7px 11px' : '9px 14px'; const styles = { primary: { background: 'var(--ink)', color: '#fff', border: '1px solid var(--ink)' }, accent: { background: 'var(--primary)', color: '#fff', border: '1px solid var(--primary)' }, outline: { background: 'var(--surface)', color: 'var(--ink)', border: '1px solid var(--border-strong)' }, ghost: { background: 'var(--surface)', color: 'var(--ink-2)', border: '1px solid var(--border)' }, soft: { background: 'var(--surface-2)', color: 'var(--ink)', border: '1px solid transparent' }, icon: { background: 'transparent', color: 'var(--ink-2)', border: 'none', padding: 8 }, }; return ( ); } // ─── STAT TILE (desktop) ──────────────────────────────────────────────── function DStat({ value, label, delta, icon, accent, trend }) { return (
{label} {icon && {React.cloneElement(icon, { width: 15, height: 15 })}}
{value}
{delta && (
{trend === 'up' && } {trend === 'down' && } {delta}
)}
); } // ─── SMALL UI HELPERS ────────────────────────────────────────────────── function DChip({ children, tone = 'neutral', icon }) { const tones = { neutral: { bg: 'var(--surface-2)', c: 'var(--ink-2)' }, accent: { bg: 'var(--primary-soft)', c: 'var(--primary-ink)' }, success: { bg: 'var(--success-soft)', c: 'var(--success)' }, warn: { bg: 'var(--warn-soft)', c: 'var(--warn)' }, danger: { bg: 'var(--danger-soft)', c: 'var(--danger)' }, info: { bg: 'var(--info-soft)', c: 'var(--info)' }, ghost: { bg: 'transparent', c: 'var(--ink-muted)' }, }; const t = tones[tone]; return ( {icon && React.cloneElement(icon, { width: 12, height: 12 })} {children} ); } function DSectionHead({ title, subtitle, right }) { return (

{title}

{subtitle && (
{subtitle}
)}
{right}
); } function DCard({ children, padding = 16, style }) { return (
{children}
); } // ─── SCORE PILL (desktop, smaller than mobile ring) ───────────────────── function DScorePill({ score, size = 32 }) { const color = score >= 80 ? '#2F8F5A' : score >= 60 ? '#C28428' : '#C8331C'; return (
{score}
); } // ─── DESKTOP APP WRAPPER ──────────────────────────────────────────────── function DesktopFrame({ children, url = 'app.immobot.de', tab = 'ImmoBot' }) { return (
{children}
); } Object.assign(window, { Sidebar, DTopBar, DButton, DStat, DChip, DSectionHead, DCard, DScorePill, DesktopFrame, });