feat(FN-1064): replace agent sidebar with dropdown select in MailboxModal

- Replace agent sidebar layout with a compact dropdown select component
- Refactor MailboxModal.tsx to use dropdown instead of sidebar navigation
- Remove unused sidebar CSS styles and add dropdown-specific styles
- Update tests to cover dropdown behavior and interaction patterns
This commit is contained in:
gsxdsm
2026-04-07 13:37:56 -07:00
parent 814c4d2363
commit 55f5dde24d
3 changed files with 93 additions and 98 deletions

View File

@@ -510,64 +510,68 @@ export function MailboxModal({
{/* Agent Mailboxes Tab */}
{activeTab === "agents" && (
<div className="mailbox-agents" data-testid="mailbox-agents">
<div className="mailbox-agents-sidebar">
<div className="mailbox-agents-label">Select an agent</div>
{agents.length === 0 && (
<div className="mailbox-empty">
<Bot size={24} />
<p>No agents found</p>
{agents.length === 0 ? (
<div className="mailbox-empty">
<Bot size={32} />
<p>No agents found</p>
</div>
) : (
<>
<div className="mailbox-agents-dropdown">
<select
className="message-composer-select mailbox-agent-select"
value={selectedAgentId ?? ""}
onChange={(e) => setSelectedAgentId(e.target.value || null)}
data-testid="mailbox-agent-select"
>
<option value="">Select an agent</option>
{agents.map((agent) => (
<option key={agent.id} value={agent.id}>
{agent.name || agent.id}
</option>
))}
</select>
</div>
)}
{agents.map((agent) => (
<button
key={agent.id}
className={`mailbox-agent-btn ${selectedAgentId === agent.id ? "active" : ""}`}
onClick={() => setSelectedAgentId(agent.id)}
data-testid={`mailbox-agent-btn-${agent.id}`}
>
<Bot size={14} />
<span>{agent.name || agent.id}</span>
</button>
))}
</div>
<div className="mailbox-agents-content">
{!selectedAgentId && (
<div className="mailbox-empty">
<Bot size={32} />
<p>Select an agent to view their mailbox</p>
</div>
)}
{selectedAgentId && isLoading && !agentMailbox && <MailboxSkeleton />}
{agentMailbox && agentMailbox.messages.length === 0 && (
<div className="mailbox-empty">
<InboxIcon size={32} />
<p>No messages for this agent</p>
</div>
)}
{agentMailbox?.messages.map((msg) => (
<div
key={msg.id}
className={`mailbox-item ${!msg.read ? "unread" : ""}`}
onClick={() => handleOpenMessage(msg)}
data-testid={`mailbox-item-${msg.id}`}
>
<div className="mailbox-item-avatar">
{msg.fromType === "agent" ? <Bot size={16} /> : <User size={16} />}
</div>
<div className="mailbox-item-content">
<div className="mailbox-item-header">
<span className="mailbox-item-from">
{msg.fromType === "agent"
? participantLabel(msg.toId, msg.toType)
: participantLabel(msg.fromId, msg.fromType)}
</span>
<span className="mailbox-item-time">{formatTimestamp(msg.createdAt)}</span>
<div className="mailbox-agents-content">
{!selectedAgentId && (
<div className="mailbox-empty">
<Bot size={32} />
<p>Select an agent to view their mailbox</p>
</div>
<div className="mailbox-item-preview">{msg.content.slice(0, 80)}{msg.content.length > 80 ? "…" : ""}</div>
</div>
)}
{selectedAgentId && isLoading && !agentMailbox && <MailboxSkeleton />}
{agentMailbox && agentMailbox.messages.length === 0 && (
<div className="mailbox-empty">
<InboxIcon size={32} />
<p>No messages for this agent</p>
</div>
)}
{agentMailbox?.messages.map((msg) => (
<div
key={msg.id}
className={`mailbox-item ${!msg.read ? "unread" : ""}`}
onClick={() => handleOpenMessage(msg)}
data-testid={`mailbox-item-${msg.id}`}
>
<div className="mailbox-item-avatar">
{msg.fromType === "agent" ? <Bot size={16} /> : <User size={16} />}
</div>
<div className="mailbox-item-content">
<div className="mailbox-item-header">
<span className="mailbox-item-from">
{msg.fromType === "agent"
? participantLabel(msg.toId, msg.toType)
: participantLabel(msg.fromId, msg.fromType)}
</span>
<span className="mailbox-item-time">{formatTimestamp(msg.createdAt)}</span>
</div>
<div className="mailbox-item-preview">{msg.content.slice(0, 80)}{msg.content.length > 80 ? "…" : ""}</div>
</div>
</div>
))}
</div>
))}
</div>
</>
)}
</div>
)}
</>

View File

@@ -197,16 +197,31 @@ describe("MailboxModal", () => {
});
});
it("shows agent buttons in agents tab", async () => {
it("shows agent dropdown in agents tab", async () => {
render(<MailboxModal {...defaultProps} />);
fireEvent.click(screen.getByTestId("mailbox-tab-agents"));
await waitFor(() => {
expect(screen.getByTestId("mailbox-agent-btn-agent-001")).toBeDefined();
expect(screen.getByTestId("mailbox-agent-btn-agent-002")).toBeDefined();
expect(screen.getByTestId("mailbox-agent-select")).toBeDefined();
});
// Should have placeholder plus two agent options
const select = screen.getByTestId("mailbox-agent-select") as HTMLSelectElement;
expect(select.options.length).toBe(3); // placeholder + 2 agents
expect(select.options[0].textContent).toBe("Select an agent…");
expect(select.options[1].textContent).toBe("Test Agent 1");
expect(select.options[2].textContent).toBe("Test Agent 2");
});
it("shows Select an agent… placeholder in dropdown", async () => {
render(<MailboxModal {...defaultProps} />);
fireEvent.click(screen.getByTestId("mailbox-tab-agents"));
await waitFor(() => {
const select = screen.getByTestId("mailbox-agent-select") as HTMLSelectElement;
expect(select.value).toBe("");
expect(select.options[0].textContent).toBe("Select an agent…");
});
});
it("loads agent mailbox when agent is selected", async () => {
it("loads agent mailbox when selecting an agent from dropdown", async () => {
mockFetchAgentMailbox.mockResolvedValue({
ownerId: "agent-001",
ownerType: "agent",
@@ -216,14 +231,22 @@ describe("MailboxModal", () => {
render(<MailboxModal {...defaultProps} />);
fireEvent.click(screen.getByTestId("mailbox-tab-agents"));
await waitFor(() => {
expect(screen.getByTestId("mailbox-agent-btn-agent-001")).toBeDefined();
expect(screen.getByTestId("mailbox-agent-select")).toBeDefined();
});
fireEvent.click(screen.getByTestId("mailbox-agent-btn-agent-001"));
fireEvent.change(screen.getByTestId("mailbox-agent-select"), { target: { value: "agent-001" } });
await waitFor(() => {
expect(mockFetchAgentMailbox).toHaveBeenCalledWith("agent-001", undefined);
});
});
it("shows empty state when no agents exist", async () => {
render(<MailboxModal {...defaultProps} agents={[]} />);
fireEvent.click(screen.getByTestId("mailbox-tab-agents"));
await waitFor(() => {
expect(screen.getByText("No agents found")).toBeDefined();
});
});
it("opens message detail when clicking a message", async () => {
render(<MailboxModal {...defaultProps} />);
await waitFor(() => {

View File

@@ -21454,50 +21454,18 @@ html .column.drag-over * {
/* Agents tab */
.mailbox-agents {
display: flex;
gap: 16px;
flex-direction: column;
gap: 12px;
min-height: 300px;
}
.mailbox-agents-sidebar {
width: 200px;
.mailbox-agents-dropdown {
flex-shrink: 0;
display: flex;
flex-direction: column;
gap: 4px;
border-right: 1px solid var(--border);
padding-right: 12px;
}
.mailbox-agents-label {
font-size: 0.8rem;
color: var(--text-muted);
margin-bottom: 4px;
font-weight: 600;
}
.mailbox-agent-btn {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 10px;
border: none;
border-radius: 6px;
background: none;
color: var(--text-primary);
font-size: 0.85rem;
cursor: pointer;
text-align: left;
.mailbox-agent-select {
width: 100%;
transition: background-color 0.1s;
}
.mailbox-agent-btn:hover {
background: var(--bg-hover);
}
.mailbox-agent-btn.active {
background: var(--bg-active);
font-weight: 600;
max-width: 300px;
}
.mailbox-agents-content {