// 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 ? (
<>
{ setSelectMode(false); setPicked(new Set()); }}>Abbrechen
setConfirmBulk(true)}>{picked.size} löschen
>
) : (
<>
setSelectMode(true)}>Auswählen
toast('Postfach aktualisiert')}>Abrufen
>
)}
{/* 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 && (
)}
{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}
{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 && onConvert(m)}>In Mietvorgang umwandeln }
Antworten
{linked ? 'Verknüpfung ändern' : 'Mit Mieter verknüpfen'}
>
);
}
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 (
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}
))}
);
}
function LinkSheet({ open, email, rentals, equipment, onClose, onPick, onUnlink }) {
const t = useTheme();
if (!email) return null;
const suggested = rentals.filter((r) => r.email && email.fromEmail && r.email.toLowerCase() === email.fromEmail.toLowerCase());
const ordered = [...suggested, ...rentals.filter((r) => !suggested.includes(r))];
return (
Mit Mieter verknüpfen
E-Mail einem Mietvertrag zuordnen.
Fertig
{email.linkedRentalId &&
{ onUnlink(email); onClose(); }} scale={0.98} style={{ marginTop: 14 }}>Verknüpfung aufheben
}
{ordered.map((r) => {
const eq = equipment.find((e) => e.id === r.equipmentId);
const isSug = suggested.includes(r);
const isCur = email.linkedRentalId === r.id;
return (
{ onPick(email, r); onClose(); }} scale={0.98}>
{r.tenantName}
{isSug &&
passt }
{eq ? eq.name : r.equipmentName} · {fmtRange(r.start, r.end)}
);
})}
);
}
Object.assign(window, { ScreenInbox, matchEquipmentId, withParsed });