// Posteingang — E-Mail inbox: read mail, convert rental requests, link to tenants, reply. // Two view styles (Liste / Karten) so the look can be compared. const { useState, useEffect } = React; // Icons const ICON_ENVELOPE = 'M3 6a1 1 0 011-1h16a1 1 0 011 1v12a1 1 0 01-1 1H4a1 1 0 01-1-1V6zM3 7l9 6 9-6'; const ICON_REPLY = 'M9 17l-5-5 5-5M4 12h11a4 4 0 014 4v2'; const ICON_LINK = 'M10 13a5 5 0 007 0l2-2a5 5 0 00-7-7l-1 1M14 11a5 5 0 00-7 0l-2 2a5 5 0 007 7l1-1'; const ICON_ARCHIVE = 'M3 6h18v3H3zM5 9v10a1 1 0 001 1h12a1 1 0 001-1V9M9 13h6'; const ICON_TRASH = 'M3 6h18M8 6V4a1 1 0 011-1h6a1 1 0 011 1v2M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6M10 11v6M14 11v6'; const ICON_BOLT = 'M13 2L3 14h8l-1 8 10-12h-8l1-8z'; const ICON_STAR = 'M12 2.5l2.9 6 6.6.9-4.8 4.6 1.2 6.5L12 18.2 6.1 20.5l1.2-6.5L2.5 9.4l6.6-.9z'; // Map a free-text equipment hint to an actual equipment id (best effort). function matchEquipmentId(hint, equipment) { if (!equipment || !equipment.length) return ''; if (!hint) return equipment[0].id; const h = hint.toLowerCase(); const direct = equipment.find(e => h.includes(e.name.toLowerCase()) || e.name.toLowerCase().includes(h)); if (direct) return direct.id; const byKind = (re) => { const e = equipment.find(x => re.test((x.name + ' ' + x.cat + ' ' + x.kind).toLowerCase())); return e && e.id; }; if (/funk/.test(h)) return byKind(/funk|ew100|sennheiser/) || equipment[0].id; if (/mikro|mic|gesang/.test(h)) return byKind(/sm58|mikro|shure/) || equipment[0].id; if (/lautsprech|box|^pa\b|pa |beschall|sound|musik|anlage/.test(h)) return byKind(/jbl|lautsprech|eon/) || equipment[0].id; if (/misch|mixer|pult/.test(h)) return byKind(/mg10|yamaha|misch/) || equipment[0].id; if (/anh\u00e4ng|trailer|umzug|transport|kasten/.test(h)) return byKind(/anh\u00e4nger/) || equipment[0].id; return equipment[0].id; } // Relative date label vs the app's fixed today. function mailDate(dateISO, time) { const today = todayISO(); if (dateISO === today) return time || 'Heute'; const d = fromISO(dateISO), tdy = fromISO(today); const diff = Math.round((tdy - d) / 86400000); if (diff === 1) return 'Gestern'; if (diff > 1 && diff < 7) return DE_DAYS[d.getDay()].slice(0, 2); return `${String(d.getDate()).padStart(2, '0')}.${String(d.getMonth() + 1).padStart(2, '0')}.`; } const initials = (name) => name.split(/\s+/).filter(Boolean).slice(0, 2).map(w => w[0]).join('').toUpperCase(); // Stable-ish avatar tint from name const AVATAR_TINTS = ['#007AFF', '#34C759', '#FF9500', '#AF52DE', '#FF2D55', '#5856D6', '#30B0C7']; const tintFor = (s) => AVATAR_TINTS[[...s].reduce((a, c) => a + c.charCodeAt(0), 0) % AVATAR_TINTS.length]; function Avatar({ name, size = 42 }) { const c = tintFor(name); return (
{initials(name)}
); } // ───────────────────────────────────────────────────────────── // Inbox list — two visual styles // ───────────────────────────────────────────────────────────── function InboxList({ emails, rentals, style, onOpen }) { const t = useTheme(); if (emails.length === 0) { return
Keine E-Mails in diesem Filter.
; } // ── Apple-Mail-style list ── if (style === 'liste') { return (
{emails.map((m, i) => { const linked = m.linkedRentalId && rentals.find(r => r.id === m.linkedRentalId); return ( onOpen(m)} scale={0.99}>
{/* unread dot */}
{m.unread &&
}
{m.from}
{mailDate(m.dateISO, m.time)}
{m.subject}
{m.body.split('\n')[0]}
{m.kind === 'request' && !m.linkedRentalId && ( Anfrage )} {linked && ✓ {linked.tenantName.split(' ')[0]}} {m.replied && Beantwortet} {m.starred && }
); })}
); } // ── Request-focused cards ── return (
{emails.map(m => { const isReq = m.kind === 'request' && !m.linkedRentalId; const linked = m.linkedRentalId && rentals.find(r => r.id === m.linkedRentalId); return ( onOpen(m)} scale={0.98}>
{isReq && (
NEUE MIET-ANFRAGE {m.unread && }
)}
{m.from}
{mailDate(m.dateISO, m.time)}
{m.subject}
{isReq && m.parsed ? (
{m.parsed.equipmentHint && {m.parsed.equipmentHint}} {m.parsed.startISO && {fmtRange(m.parsed.startISO, m.parsed.endISO)}} {m.parsed.deliveryAddress && Lieferung}
) : (
{m.body.split('\n').filter(Boolean)[0]}
)}
{linked && ✓ Verknüpft · {linked.tenantName.split(' ')[0]}} {m.replied && Beantwortet}
{isReq ? 'Bearbeiten' : 'Lesen'}
); })}
); } function Tag({ children, color, soft }) { const t = useTheme(); return ( {children} ); } function Chip({ children, icon }) { const t = useTheme(); return ( {icon && {icon}}{children} ); } // ───────────────────────────────────────────────────────────── // Email detail // ───────────────────────────────────────────────────────────── function EmailDetail({ email, equipment, rentals, onBack, onConvert, onReply, onLink, onArchive, onDelete, onToggleStar, toast }) { const t = useTheme(); const m = email; const isReq = m.kind === 'request'; const linked = m.linkedRentalId && rentals.find(r => r.id === m.linkedRentalId); const eqGuess = isReq && m.parsed ? equipment.find(e => e.id === matchEquipmentId(m.parsed.equipmentHint, equipment)) : null; return (
{/* top bar */}
onToggleStar(m)} scale={0.85}>
onArchive(m)} scale={0.85}>
onDelete(m)} scale={0.85}>
{/* subject + sender */}
{m.subject}
{m.from}
{m.fromEmail}
{fmtDateDE(m.dateISO)}{m.time ? ' · ' + m.time : ''}
{/* linked banner */} {linked && (
Verknüpft mit {linked.tenantName} · {linked.equipmentName}
)} {/* parsed request summary */} {isReq && m.parsed && (
ERKANNTE ANFRAGE
{m.parsed.startISO && 1 ? 'e' : ''}`}/>} {m.parsed.purpose && } {m.parsed.deliveryAddress && } {m.parsed.phone && }
)} {/* body */}
{m.body}
{m.replied && (
Du hast auf diese E-Mail bereits geantwortet.
)}
{/* action bar */}
{isReq && !m.linkedRentalId && ( onConvert(m)} scale={0.98} style={{ marginBottom: 8 }}>
In Vermietung umwandeln
)}
onReply(m)} scale={0.97} style={{ flex: 1 }}>
Antworten
onLink(m)} scale={0.95}>
{linked ? 'Ändern' : 'Verknüpfen'}
); } function SummaryRow({ label, value, sub, hint }) { const t = useTheme(); return (
{label}
{value}
{sub &&
{sub}
} {hint &&
{hint}
}
); } // ───────────────────────────────────────────────────────────── // Reply composer sheet // ───────────────────────────────────────────────────────────── function ReplySheet({ open, email, onClose, onSend }) { const t = useTheme(); const [text, setText] = useState(''); useEffect(() => { if (open && email) { const first = email.from.split(' ')[0]; setText(`Hallo ${first},\n\nvielen Dank für Ihre Nachricht.\n\n\n\nViele Grüße\nRentFlow Manager`); } }, [open, email && email.id]); if (!email) return null; const quick = [ 'Gerne — der Termin ist bei uns frei. ✅', 'Leider ist das Equipment in dem Zeitraum schon vergeben.', 'Anbei unser Angebot. Bei Fragen melden Sie sich gerne.', ]; return (
Antwort
Abbrechen
{/* quick replies */}
{quick.map((q, i) => ( setText(prev => { const sig = '\n\nViele Grüße\nRentFlow Manager'; const base = prev.replace(sig, ''); return base.replace(/\n+$/, '') + '\n\n' + q + sig; })} scale={0.96}>
{q.length > 28 ? q.slice(0, 26) + '…' : q}
))}