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:
@@ -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>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user