human hand off bug

This commit is contained in:
debu-2 2026-10-06 18:27:06 +05:30
parent 223f846782
commit 6e7a0b9539
22 changed files with 1040 additions and 336 deletions

View file

@ -6,8 +6,8 @@ import {
CircleHelp,
Filter,
LayoutGrid,
List,
Mail,
MoreHorizontal,
RefreshCw,
Search,
Settings,
@ -46,7 +46,14 @@ import {
} from "../utils/inboxSignals";
import { buildReplyBodyHtml } from "../utils/emailReply";
const PAGE_SIZE = 25;
const PAGE_SIZE_KEY = "agmail_page_size";
const PAGE_SIZE_OPTIONS = [25, 50, 100];
function readPageSize() {
const stored = Number(window.localStorage.getItem(PAGE_SIZE_KEY));
return PAGE_SIZE_OPTIONS.includes(stored) ? stored : 50;
}
const CATEGORY_TABS = [
{ name: "Primary", icon: LayoutGrid },
{ name: "Updates", icon: Bell },
@ -57,10 +64,11 @@ const CATEGORY_TABS = [
];
function resultPage(result) {
if (Array.isArray(result)) return { items: result, nextCursor: null };
if (Array.isArray(result)) return { items: result, nextCursor: null, total: result.length };
return {
items: result.items || result.emails || result.results || [],
nextCursor: result.next_cursor || result.nextCursor || null,
total: Number(result.total || 0),
};
}
@ -112,6 +120,9 @@ export default function MailboxView({
const token = sessionStorage.getItem("agmail_access_token");
const [emails, setEmails] = useState([]);
const [nextCursor, setNextCursor] = useState(null);
const [total, setTotal] = useState(0);
const [pageSize, setPageSize] = useState(readPageSize);
const [pageMenuOpen, setPageMenuOpen] = useState(false);
const [cursor, setCursor] = useState(null);
const [cursorHistory, setCursorHistory] = useState([]);
const [selected, setSelected] = useState(null);
@ -140,7 +151,7 @@ export default function MailboxView({
const result = await listThreads(
{
mailbox,
limit: PAGE_SIZE,
limit: pageSize,
cursor,
q: filters.query.trim(),
is_read: filters.read === "all" ? undefined : filters.read === "read",
@ -153,11 +164,12 @@ export default function MailboxView({
const page = resultPage(result);
setEmails(page.items.map(threadToRow));
setNextCursor(page.nextCursor);
setTotal(page.total);
setStatus({ loading: false, error: "" });
} catch (error) {
setStatus({ loading: false, error: error.message });
}
}, [cursor, filters, mailbox, token]);
}, [cursor, filters, mailbox, pageSize, token]);
useEffect(() => {
const timeout = window.setTimeout(load, filters.query ? 250 : 0);
@ -250,6 +262,22 @@ export default function MailboxView({
});
}, [emails, onStats]);
useEffect(() => {
setCursor(null);
setCursorHistory([]);
setChecked({});
setPageMenuOpen(false);
}, [mailbox]);
useEffect(() => {
if (!pageMenuOpen) return undefined;
function closeMenu() {
setPageMenuOpen(false);
}
window.addEventListener("click", closeMenu);
return () => window.removeEventListener("click", closeMenu);
}, [pageMenuOpen]);
function changeFilter(name, value) {
setFilters((current) => ({ ...current, [name]: value }));
setCursor(null);
@ -580,6 +608,7 @@ export default function MailboxView({
setCursor(nextCursor);
setSelected(null);
setDetail(null);
setChecked({});
}
function previousPage() {
@ -589,11 +618,30 @@ export default function MailboxView({
setCursor(previous);
setSelected(null);
setDetail(null);
setChecked({});
}
function changePageSize(size) {
if (size === pageSize) {
setPageMenuOpen(false);
return;
}
window.localStorage.setItem(PAGE_SIZE_KEY, String(size));
setPageSize(size);
setCursor(null);
setCursorHistory([]);
setChecked({});
setPageMenuOpen(false);
}
const initials = `${account?.first_name?.[0] || ""}${account?.last_name?.[0] || ""}`.toUpperCase();
const rangeStart = listedEmails.length ? cursorHistory.length * PAGE_SIZE + 1 : 0;
const rangeEnd = cursorHistory.length * PAGE_SIZE + listedEmails.length;
const rangeStart = emails.length ? cursorHistory.length * pageSize + 1 : 0;
const rangeEnd = emails.length ? rangeStart + emails.length - 1 : 0;
const rangeLabel = emails.length === 0
? `0–0 of ${total}`
: `${rangeStart}–${rangeEnd} of ${total || rangeEnd}`;
const allSelected = listedEmails.length > 0 && selectedCount === listedEmails.length;
const someSelected = selectedCount > 0 && !allSelected;
return (
<section className="mailbox-workspace">
@ -652,10 +700,85 @@ export default function MailboxView({
<div className="mailbox-main-column">
<div className={`mailbox-shell${selected ? " detail-visible" : ""}`}>
<div className="mailbox-list-pane">
<div className="mailbox-list-bar" role="toolbar" aria-label="Mailbox controls">
<div className="mailbox-list-bar-actions">
<label className="mailbox-select-all">
<input
type="checkbox"
checked={allSelected}
ref={(node) => {
if (node) node.indeterminate = someSelected;
}}
aria-label={allSelected ? "Deselect all messages" : "Select all messages"}
onChange={toggleSelectAll}
/>
</label>
<button
type="button"
className={status.loading ? "is-busy" : ""}
title="Refresh"
aria-label="Refresh"
onClick={load}
>
<RefreshCw size={16} strokeWidth={1.75} />
</button>
<div className="mailbox-page-size-wrap" onClick={(event) => event.stopPropagation()}>
<button
type="button"
title="More options"
aria-label="More options"
aria-expanded={pageMenuOpen}
onClick={() => setPageMenuOpen((open) => !open)}
>
<MoreHorizontal size={16} strokeWidth={1.75} />
</button>
{pageMenuOpen && (
<div className="mailbox-page-size-menu" role="menu">
<p>Emails per page</p>
{PAGE_SIZE_OPTIONS.map((size) => (
<button
key={size}
type="button"
role="menuitemradio"
aria-checked={pageSize === size}
className={pageSize === size ? "active" : ""}
onClick={() => changePageSize(size)}
>
{size}
</button>
))}
</div>
)}
</div>
</div>
<div className="mailbox-list-bar-page">
<span className="mailbox-pagination-range">{rangeLabel}</span>
<button
type="button"
title="Newer"
aria-label="Previous page"
disabled={!cursorHistory.length || status.loading}
onClick={previousPage}
>
<ChevronLeft size={18} strokeWidth={1.75} />
</button>
<button
type="button"
title="Older"
aria-label="Next page"
disabled={!nextCursor || status.loading}
onClick={nextPage}
>
<ChevronRight size={18} strokeWidth={1.75} />
</button>
</div>
</div>
{selectedCount > 0 && (
<MailboxSelectionBar
count={selectedCount}
total={listedEmails.length}
hideSelectAll
onToggleAll={toggleSelectAll}
onArchive={() => runBulkAction("archive")}
onSpam={() => runBulkAction("spam")}
@ -755,32 +878,6 @@ export default function MailboxView({
))}
</div>
{!status.loading && (
<footer className="mailbox-pagination">
<span className="mailbox-pagination-range">
{listedEmails.length === 0
? "0 messages"
: `${rangeStart}–${rangeEnd}${nextCursor ? "" : ` · ${rangeEnd} loaded`}`}
</span>
<div className="mailbox-pagination-controls">
{listedEmails.length > 0 && (
<>
<button type="button" title="Previous page" aria-label="Previous page" disabled={!cursorHistory.length} onClick={previousPage}>
<ChevronLeft size={16} strokeWidth={1.75} />
</button>
<button type="button" title="Next page" aria-label="Next page" disabled={!nextCursor} onClick={nextPage}>
<ChevronRight size={16} strokeWidth={1.75} />
</button>
</>
)}
</div>
<div className="mailbox-pagination-density" aria-label="List density">
<button type="button" className="active" title="Comfortable density" aria-label="Comfortable density"><List size={15} strokeWidth={1.75} /></button>
<button type="button" title="Compact density" aria-label="Compact density"><LayoutGrid size={15} strokeWidth={1.75} /></button>
</div>
</footer>
)}
<MailDetail
email={detail}
thread={threadDetail}