// Desktop Posteingang — list + reading pane + reply/convert/link. function matchEquipmentId(hint, equipment) { if (!hint) return null; const h = hint.toLowerCase(); let best = null; equipment.forEach((e) => { const hay = (e.name + ' ' + e.cat + ' ' + e.kind).toLowerCase(); if (hay.includes(h) || h.includes(e.name.toLowerCase().split(' ')[0])) best = e.id; }); if (!best) { if (h.includes('lautsprecher') || h.includes('box') || h.includes('pa') || h.includes('musik')) best = (equipment.find((e) => e.kind === 'Tontechnik') || {}).id; else if (h.includes('anhänger') || h.includes('umzug')) best = (equipment.find((e) => e.kind === 'Anhänger') || {}).id; else if (h.includes('mikro') || h.includes('funk')) best = (equipment.find((e) => /mikro|funk/i.test(e.cat)) || {}).id; } return best; } // lightweight request parsing (mirrors the mobile parser intent) const PARSED = { m1: { equipmentHint: 'JBL EON 712', startISO: '2026-06-13', endISO: '2026-06-14', purpose: 'Hochzeit', deliveryAddress: 'Seestr. 9, Herrsching', phone: '0170 2233445' }, m2: { equipmentHint: 'Anhänger 750kg', startISO: '2026-05-24', endISO: '2026-05-24', purpose: 'Umzug', deliveryAddress: '', phone: '' }, m3: { equipmentHint: 'Beschallungsanlage', startISO: '2026-07-18', endISO: '2026-07-19', purpose: 'Sommerfest', deliveryAddress: 'Sportplatz Germering', phone: '' }, m6: { equipmentHint: 'Funkmikrofone', startISO: '2026-06-05', endISO: '2026-06-05', purpose: 'Konferenz', deliveryAddress: '', phone: '' }, }; const withParsed = (emails) => emails.map((m) => m.kind === 'request' ? { ...m, parsed: PARSED[m.id] || null } : m); function ScreenInbox({ go, equipment, rentals, onConvert }) { const t = useTheme(); const toast = useToast(); const [emails, setEmails] = useState(() => withParsed(SEED_EMAILS)); const [filter, setFilter] = useState('alle'); const [selId, setSelId] = useState(null); const [reply, setReply] = useState(null); const [link, setLink] = useState(null); const [confirmDel, setConfirmDel] = useState(null); const [confirmBulk, setConfirmBulk] = useState(false); const [selectMode, setSelectMode] = useState(false); const [picked, setPicked] = useState(() => new Set()); const visible = emails.filter((m) => !m.archived).filter((m) => { if (filter === 'anfragen') return m.kind === 'request' && !m.linkedRentalId; if (filter === 'verknuepft') return !!m.linkedRentalId; if (filter === 'markiert') return m.starred; return true; }); const reqCount = emails.filter((m) => !m.archived && m.kind === 'request' && !m.linkedRentalId).length; const unreadCount = emails.filter((m) => !m.archived && m.unread).length; const selected = emails.find((m) => m.id === selId); const open = (m) => { setEmails((prev) => prev.map((x) => x.id === m.id ? { ...x, unread: false } : x)); setSelId(m.id); }; const sendReply = (m, text) => { setEmails((prev) => prev.map((x) => x.id === m.id ? { ...x, replied: true } : x)); toast('Antwort gesendet'); }; const doLink = (m, r) => { setEmails((prev) => prev.map((x) => x.id === m.id ? { ...x, linkedRentalId: r.id } : x)); toast(`Verknüpft mit ${r.tenantName}`); }; const doUnlink = (m) => { setEmails((prev) => prev.map((x) => x.id === m.id ? { ...x, linkedRentalId: null } : x)); toast('Verknüpfung aufgehoben'); }; const archive = (m) => { setEmails((prev) => prev.map((x) => x.id === m.id ? { ...x, archived: true } : x)); setSelId(null); toast('Archiviert'); }; const toggleStar = (m) => setEmails((prev) => prev.map((x) => x.id === m.id ? { ...x, starred: !x.starred } : x)); const del = (m) => { setEmails((prev) => prev.filter((x) => x.id !== m.id)); if (selId === m.id) setSelId(null); setConfirmDel(null); toast('E-Mail gelöscht'); }; const togglePick = (id) => setPicked((prev) => { const n = new Set(prev); n.has(id) ? n.delete(id) : n.add(id); return n; }); const delBulk = () => { const ids = picked; setEmails((prev) => prev.filter((x) => !ids.has(x.id))); if (selId && ids.has(selId)) setSelId(null); setPicked(new Set()); setSelectMode(false); setConfirmBulk(false); toast(`${ids.size} E-Mail${ids.size === 1 ? '' : 's'} gelöscht`); }; return ( <> {selectMode ? ( <> ) : ( <> )}
{/* list */}
{visible.length === 0 &&
Keine Nachrichten.
} {visible.map((m) => { const isReq = m.kind === 'request'; const sel = m.id === selId; const linked = m.linkedRentalId && rentals.find((r) => r.id === m.linkedRentalId); const checked = picked.has(m.id); const rowClick = selectMode ? () => togglePick(m.id) : () => open(m); return (
{selectMode && (
{checked && }
)}
{m.unread && }
{m.from}
{fromISO(m.dateISO).getDate()}. {DE_MONTHS_SHORT[fromISO(m.dateISO).getMonth()]}
{m.subject}
{m.body.split('\n').filter(Boolean)[0]}
{isReq && !m.linkedRentalId && ANFRAGE} {linked && ✓ {linked.tenantName.split(' ')[0]}} {m.replied && Beantwortet} {m.starred && }
{!selectMode && (
{ e.stopPropagation(); setConfirmDel(m); }} className="rf-inbox-del" style={{ position: 'absolute', bottom: 10, right: 10, width: 28, height: 28, borderRadius: 7, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', background: (t.red || '#FF3B30') + '15', opacity: 0, transition: 'opacity 120ms ease' }} onMouseEnter={(e) => { e.currentTarget.style.background = (t.red || '#FF3B30') + '28'; }} onMouseLeave={(e) => { e.currentTarget.style.background = (t.red || '#FF3B30') + '15'; }} >
)}
); })}
{/* reading pane */}
{selected ? ( setReply(selected)} onLink={() => setLink(selected)} onArchive={() => archive(selected)} onToggleStar={() => toggleStar(selected)} onDelete={() => setConfirmDel(selected)} go={go}/> ) : ( )}
setReply(null)} onSend={sendReply}/> setLink(null)} onPick={doLink} onUnlink={doUnlink}/> del(confirmDel)} onClose={() => setConfirmDel(null)} /> setConfirmBulk(false)} /> ); } function InboxDetail({ email, equipment, rentals, onConvert, onReply, onLink, onArchive, onToggleStar, onDelete, go }) { 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 ( <>
{m.subject}
{m.from}
{m.fromEmail}
{fmtDateDE(m.dateISO)} · {m.time}
{linked && (
Verknüpft mit {linked.tenantName} · {linked.equipmentName}
go('rentals', { rentalId: linked.id })}>
Öffnen ›
)} {isReq && m.parsed && (
ERKANNTE ANFRAGE
{m.parsed.startISO && } {m.parsed.purpose && } {m.parsed.deliveryAddress && } {m.parsed.phone && }
)}
{m.body}
{m.replied && (
Du hast auf diese E-Mail bereits geantwortet.
)} {/* actions */}
{isReq && !m.linkedRentalId && }
); } function SumRow({ label, value, sub, hint }) { const t = useTheme(); return (
{label}
{value}
{sub &&
{sub}
} {hint &&
{hint}
}
); } 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\n${DEFAULT_COMPANY.owner}\n${DEFAULT_COMPANY.name}`); } }, [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
An: {email.from} · {email.fromEmail}
Betreff: {email.subject.startsWith('Re:') ? email.subject : 'Re: ' + email.subject}
{quick.map((q, i) => ( setText((prev) => { const sig = `\n\nViele Grüße\n${DEFAULT_COMPANY.owner}\n${DEFAULT_COMPANY.name}`; const base = prev.replace(sig, ''); return base.replace(/\n+$/, '') + '\n\n' + q + sig; })} scale={0.96}>
{q.length > 32 ? q.slice(0, 30) + '…' : q}
))}