// ============================================================================
// public/screens/inbox.jsx — Unified Inbox (Phase 6B)
// ============================================================================
function InboxScreen() {
const [threads, setThreads] = React.useState(null);
const [pagination, setPag] = React.useState({});
const [stats, setStats] = React.useState(null);
const [channel, setChannel] = React.useState("all");
const [status, setStatus] = React.useState("open");
const [search, setSearch] = React.useState("");
const [activeThread, setActiveThread] = React.useState(null);
const [messages, setMessages] = React.useState([]);
const [reply, setReply] = React.useState("");
const load = React.useCallback(async (page = 1) => {
const p = new URLSearchParams({ page, limit: 25, channel, status });
if (search) p.set("search", search);
const r = await VoaisAPI.get("/api/inbox?" + p.toString());
if (r.ok && r.data?.ok) { setThreads(r.data.threads); setPag(r.data.pagination); }
}, [channel, status, search]);
const loadStats = React.useCallback(async () => {
const r = await VoaisAPI.get("/api/inbox/stats");
if (r.ok && r.data?.ok) setStats(r.data);
}, []);
React.useEffect(() => { load(); loadStats(); }, [load, loadStats]);
const openThread = async (id) => {
const r = await VoaisAPI.get("/api/inbox/" + id);
if (r.ok && r.data?.ok) { setActiveThread(r.data.thread); setMessages(r.data.messages); loadStats(); }
};
const sendReply = async () => {
if (!reply.trim() || !activeThread) return;
await VoaisAPI.post("/api/inbox/" + activeThread.id + "/reply", { content: reply });
setReply(""); openThread(activeThread.id);
};
const updateThread = async (id, fields) => {
await VoaisAPI.patch("/api/inbox/" + id, fields);
if (activeThread?.id === id) openThread(id);
load();
};
if (threads === null) return ;
const channelIcon = { call: I.phone, whatsapp: I.whatsapp, email: I.email, sms: I.message, web: I.globe };
return (
{/* Left: thread list */}
{/* Channel tabs */}
{["all", "call", "whatsapp", "email"].map(ch => (
))}
{/* Search */}
{/* Thread list */}
{threads.map(t => {
const ChIcon = channelIcon[t.channel] || I.message;
const isActive = activeThread?.id === t.id;
return (
openThread(t.id)} style={{
padding: "12px 14px", cursor: "pointer", borderBottom: "1px solid var(--line)",
background: isActive ? "var(--accent-soft)" : t.unread_count > 0 ? "var(--surface-2)" : "transparent",
}}>
0 ? 600 : 400, flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{t.display_name || t.phone || t.email || "Unknown"}
{t.unread_count > 0 && {t.unread_count}}
{t.pinned ? : null}
{t.last_message_preview || "No messages"}
{t.intent && t.intent !== "unset" && {t.intent}}
{t.last_message_at ? timeAgoShort(t.last_message_at) : ""}
);
})}
{threads.length === 0 &&
No conversations found.
}
{/* Right: thread detail / empty state */}
{!activeThread ? (
Select a conversation
Pick a thread from the left to view messages.
) : (
<>
{/* Thread header */}
{activeThread.display_name || activeThread.phone || "Unknown"}
{activeThread.phone || ""} · {activeThread.channel}
updateThread(activeThread.id, { pinned: activeThread.pinned ? 0 : 1 })}>
{activeThread.pinned ? "Unpin" : "Pin"}
updateThread(activeThread.id, { status: "closed" })}>
Close
{/* Messages */}
{messages.map((m, i) => (
{m.from_role === "contact" || m.from_role === "caller" ? (activeThread.display_name || "Contact") : m.from_role === "ai" ? "AI" : "You"}
{m.content}
{m.created_at ? new Date(m.created_at).toLocaleTimeString("en-IN", { hour: "2-digit", minute: "2-digit" }) : ""}
{m.status && m.direction === "outbound" && {m.status === "read" ? "✓✓" : m.status === "delivered" ? "✓✓" : m.status === "sent" ? "✓" : ""}}
))}
{messages.length === 0 &&
No messages in this thread.
}
{/* Reply box */}
setReply(e.target.value)} onKeyDown={e => { if (e.key === "Enter") sendReply(); }}/>
} onClick={sendReply} disabled={!reply.trim()}>Send
>
)}
);
}
function timeAgoShort(d) { const s = Math.floor((Date.now()-new Date(d).getTime())/1000); if(s<60) return "now"; if(s<3600) return Math.floor(s/60)+"m"; if(s<86400) return Math.floor(s/3600)+"h"; return Math.floor(s/86400)+"d"; }
window.InboxScreen = InboxScreen;