right side

This commit is contained in:
debu-2 2026-10-04 00:44:58 +05:30
parent 7fb9e0d5d4
commit f289d4a4d6
2 changed files with 195 additions and 104 deletions

View file

@ -1,7 +1,6 @@
import { useState } from "react";
import {
Calendar,
Check,
ChevronDown,
ChevronRight,
FileSearch,
@ -18,12 +17,14 @@ import SenderAvatar from "./SenderAvatar";
import { inboxContext } from "../utils/inboxSignals";
const ICON = { size: 14, strokeWidth: 1.75 };
const AGENDA_SLOTS = 3;
const ATTENTION_SLOTS = 2;
const cards = [
{ key: "important", label: "Important emails", captionKey: "importantFromClients", captionSuffix: "from clients", icon: Mail, tone: "rose" },
{ key: "actionItems", label: "Action items", hint: "Need your response", icon: ListChecks, tone: "blue" },
{ key: "meetings", label: "Meetings mentioned", hint: "In your emails", icon: Calendar, tone: "green" },
{ key: "invoices", label: "Invoices & bills", hint: "This week", icon: Receipt, tone: "amber" },
{ key: "important", label: "Important emails", captionKey: "importantFromClients", captionSuffix: "from clients", emptyCaption: "No priority mail yet", icon: Mail, tone: "rose" },
{ key: "actionItems", label: "Action items", hint: "Need your response", emptyCaption: "Nothing pending", icon: ListChecks, tone: "blue" },
{ key: "meetings", label: "Meetings mentioned", hint: "In your emails", emptyCaption: "No meetings found", icon: Calendar, tone: "green" },
{ key: "invoices", label: "Invoices & bills", hint: "This week", emptyCaption: "No invoices detected", icon: Receipt, tone: "amber" },
];
function greeting(name) {
@ -32,6 +33,70 @@ function greeting(name) {
return `${period}, ${name}`;
}
function statCaption(card, stats) {
const count = stats[card.key] || 0;
if (count === 0) return card.emptyCaption || card.hint;
if (card.captionKey && stats[card.captionKey] > 0) {
return `${stats[card.captionKey]} ${card.captionSuffix}`;
}
return card.hint;
}
function padSlots(items, slots) {
const result = items.slice(0, slots).map((item) => ({ ...item, placeholder: false }));
while (result.length < slots) {
result.push({ placeholder: true, id: `slot-${result.length}` });
}
return result;
}
function AgendaPlaceholder({ index }) {
return (
<li className="agent-agenda-row agent-placeholder-row" aria-hidden={index > 0}>
<span className="agent-agenda-bar placeholder" aria-hidden="true" />
<div className="agent-placeholder-copy">
{index === 0 ? (
<>
<time>—</time>
<strong>No meetings in your emails</strong>
<small>Calendar events will appear here</small>
</>
) : (
<>
<span className="agent-placeholder-line" />
<span className="agent-placeholder-line wide" />
</>
)}
</div>
</li>
);
}
function AttentionPlaceholder({ index, showEmptyMessage }) {
return (
<li className="agent-attention-placeholder" aria-hidden={index > 0 && !showEmptyMessage}>
{showEmptyMessage && index === 0 ? (
<>
<span className="agent-placeholder-avatar muted" aria-hidden="true" />
<span className="agent-placeholder-copy">
<strong className="agent-placeholder-label">Nothing needs attention</strong>
<small>Threads awaiting reply will appear here</small>
</span>
</>
) : (
<>
<span className="agent-placeholder-avatar" aria-hidden="true" />
<span className="agent-placeholder-copy">
<span className="agent-placeholder-line wide" />
<span className="agent-placeholder-line narrow" />
</span>
<span className="agent-placeholder-age" aria-hidden="true" />
</>
)}
</li>
);
}
export default function AgAgentPanel({
account,
emails,
@ -45,9 +110,8 @@ export default function AgAgentPanel({
const [agentQuery, setAgentQuery] = useState("");
const [panelOpen, setPanelOpen] = useState(true);
const firstName = account?.first_name || "there";
const activeCards = cards.filter((card) => (stats[card.key] || 0) > 0);
const hasContext = needsReply.length > 0 || today.length > 0 || deadlines.length > 0;
const hasSignals = activeCards.length > 0 || hasContext;
const agendaSlots = padSlots(today, AGENDA_SLOTS);
const attentionSlots = padSlots(needsReply, ATTENTION_SLOTS);
function submitAgentQuery(event) {
event.preventDefault();
@ -80,138 +144,145 @@ export default function AgAgentPanel({
{panelOpen && (
<div className="agent-panel-body">
{!insight && hasSignals && (
{!insight && (
<div className="agent-panel-hello">
<strong>{greeting(firstName)}</strong>
<span>Here&apos;s what needs your attention today.</span>
</div>
)}
{!insight && !hasSignals && (
<div className="agent-caught-up">
<p className="agent-caught-up-lead"><Check size={15} strokeWidth={2} /> You&apos;re all caught up.</p>
<p>No urgent messages or outstanding actions.</p>
<small>Inbox is quiet right now.</small>
</div>
)}
{!insight && activeCards.length > 0 && (
{!insight && (
<div className="agent-stat-grid">
{activeCards.map((card) => {
{cards.map((card) => {
const Icon = card.icon;
const count = stats[card.key] || 0;
const caption = card.captionKey && stats[card.captionKey]
? `${stats[card.captionKey]} ${card.captionSuffix}`
: card.hint;
const isEmpty = count === 0;
return (
<button
key={card.key}
type="button"
className={`agent-stat agent-stat-${card.tone}`}
className={`agent-stat agent-stat-${card.tone}${isEmpty ? " agent-stat-empty" : ""}`}
onClick={() => onQuickAction(card.key)}
disabled={isEmpty}
aria-label={`${card.label}: ${count}`}
>
<span className="agent-stat-icon"><Icon {...ICON} /></span>
<strong>{count}</strong>
<span>{card.label}</span>
<small>{caption}</small>
<small>{statCaption(card, stats)}</small>
</button>
);
})}
</div>
)}
{!insight && focus && hasSignals && <p className="agent-panel-note">{focus}</p>}
{!insight && focus && <p className="agent-panel-note">{focus}</p>}
<div className="agent-quick">
<h3>Quick actions</h3>
<div>
<button type="button" className="agent-quick-btn agent-quick-btn-agent" onClick={() => onQuickAction("summarize")}>
<span className="agent-quick-icon"><AgentSpark /></span>
<span>Summarize inbox</span>
</button>
<button type="button" className="agent-quick-btn agent-quick-btn-agent" onClick={() => onQuickAction("draft")}>
<span className="agent-quick-icon"><PenLine {...ICON} /></span>
<span>Draft replies</span>
</button>
<button type="button" className="agent-quick-btn agent-quick-btn-info" onClick={() => onQuickAction("attachments")}>
<span className="agent-quick-icon"><FileSearch {...ICON} /></span>
<span>Find attachments</span>
</button>
<button type="button" className="agent-quick-btn agent-quick-btn-success" onClick={() => onQuickAction("actions")}>
<span className="agent-quick-icon"><ListChecks {...ICON} /></span>
<span>Show action items</span>
</button>
<h3>Quick actions</h3>
<div>
<button type="button" className="agent-quick-btn agent-quick-btn-agent" onClick={() => onQuickAction("summarize")}>
<span className="agent-quick-icon"><AgentSpark /></span>
<span>Summarize inbox</span>
</button>
<button type="button" className="agent-quick-btn agent-quick-btn-agent" onClick={() => onQuickAction("draft")}>
<span className="agent-quick-icon"><PenLine {...ICON} /></span>
<span>Draft replies</span>
</button>
<button type="button" className="agent-quick-btn agent-quick-btn-info" onClick={() => onQuickAction("attachments")}>
<span className="agent-quick-icon"><FileSearch {...ICON} /></span>
<span>Find attachments</span>
</button>
<button type="button" className="agent-quick-btn agent-quick-btn-success" onClick={() => onQuickAction("actions")}>
<span className="agent-quick-icon"><ListChecks {...ICON} /></span>
<span>Show action items</span>
</button>
</div>
</div>
</div>
{!insight && today.length > 0 && (
<section className="agent-context-block agent-agenda">
<div className="agent-context-head">
<h3>Today&apos;s agenda</h3>
<button type="button" className="agent-context-link">View calendar</button>
</div>
<ul>
{today.map((item) => (
<li key={`${item.time}-${item.label}`} className={`agent-agenda-row tone-${item.tone || "green"}`}>
<span className="agent-agenda-bar" aria-hidden="true" />
<div>
<time>{item.time}</time>
<strong>{item.label}</strong>
{item.venue && <small>{item.venue}</small>}
</div>
<ChevronRight size={14} strokeWidth={1.75} />
</li>
))}
</ul>
</section>
{!insight && (
<section className="agent-context-block agent-agenda">
<div className="agent-context-head">
<h3>Today&apos;s agenda</h3>
<button type="button" className="agent-context-link">View calendar</button>
</div>
<ul>
{agendaSlots.map((item, index) => (
item.placeholder ? (
<AgendaPlaceholder key={item.id} index={index} />
) : (
<li key={`${item.time}-${item.label}`} className={`agent-agenda-row tone-${item.tone || "green"}`}>
<span className="agent-agenda-bar" aria-hidden="true" />
<div>
<time>{item.time}</time>
<strong>{item.label}</strong>
{item.venue && <small>{item.venue}</small>}
</div>
<ChevronRight size={14} strokeWidth={1.75} />
</li>
)
))}
</ul>
</section>
)}
{!insight && needsReply.length > 0 && (
<section className="agent-context-block">
<h3>Needs attention</h3>
<ul className="agent-attention-list">
{needsReply.slice(0, 3).map((item) => (
<li key={item.id}>
<button type="button" onClick={() => onOpenEmail?.(item.id)}>
<SenderAvatar email={emails.find((entry) => entry.id === item.id) || { sender: { name: item.name } }} className="agent-attention-avatar" />
<span className="agent-attention-copy">
<span className="agent-attention-name">{item.name}</span>
<span className="agent-attention-meta">{item.status}</span>
</span>
<em>{item.age}</em>
</button>
</li>
))}
</ul>
</section>
{!insight && (
<section className="agent-context-block agent-attention-block">
<h3>Needs attention</h3>
<ul className="agent-attention-list">
{attentionSlots.map((item, index) => (
item.placeholder ? (
<AttentionPlaceholder
key={item.id}
index={index}
showEmptyMessage={needsReply.length === 0}
/>
) : (
<li key={item.id}>
<button type="button" onClick={() => onOpenEmail?.(item.id)}>
<SenderAvatar
email={emails.find((entry) => entry.id === item.id) || { sender: { name: item.name } }}
className="agent-attention-avatar"
/>
<span className="agent-attention-copy">
<span className="agent-attention-name">{item.name}</span>
<span className="agent-attention-meta">{item.status}</span>
</span>
<em>{item.age}</em>
</button>
</li>
)
))}
</ul>
</section>
)}
{!insight && deadlines.length > 0 && (
<section className="agent-context-block agent-deadlines">
<h3>Deadlines</h3>
<ul>
{deadlines.map((item) => (
<li key={`${item.label}-${item.due}`}>
<span>{item.label}</span>
<em>{item.due}</em>
</li>
))}
</ul>
</section>
<section className="agent-context-block agent-deadlines">
<h3>Deadlines</h3>
<ul>
{deadlines.map((item) => (
<li key={`${item.label}-${item.due}`}>
<span>{item.label}</span>
<em>{item.due}</em>
</li>
))}
</ul>
</section>
)}
<form className="agent-ask-input" onSubmit={submitAgentQuery}>
<input
value={agentQuery}
onChange={(event) => setAgentQuery(event.target.value)}
placeholder="Ask about your emails..."
/>
<button type="button" className="agent-ask-attach" aria-label="Attach context" title="Attach context">
<Paperclip size={15} strokeWidth={1.75} />
</button>
<button type="submit" className="agent-ask-send" aria-label="Send to agent" disabled={!agentQuery.trim()}>
<Send size={15} strokeWidth={1.75} />
</button>
<input
value={agentQuery}
onChange={(event) => setAgentQuery(event.target.value)}
placeholder="Ask about your emails..."
/>
<button type="button" className="agent-ask-attach" aria-label="Attach context" title="Attach context">
<Paperclip size={15} strokeWidth={1.75} />
</button>
<button type="submit" className="agent-ask-send" aria-label="Send to agent" disabled={!agentQuery.trim()}>
<Send size={15} strokeWidth={1.75} />
</button>
</form>
</div>
)}