You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

164 lines
5.0 KiB
TypeScript

// frontend/components/mail/MailList.tsx — 가운데 메일 목록 (헤더 + 검색 + 행)
"use client";
import { Icon } from "@/components/Icon";
import type { EmailRow, MailAccount, Person } from "@/lib/types";
import { MailRow } from "./MailRow";
export function MailList({
title,
metaLabel,
count,
folder,
focusedUnread,
othersUnread,
onSelectTab,
query,
syncing,
emails,
people,
accounts,
selId,
hasSel,
selected,
onToggleSelect,
onClearSelected,
onBulk,
onQuery,
onSync,
onOpen,
onStar,
accColor,
}: {
title: string;
metaLabel: string;
count: number;
folder: string;
focusedUnread: number;
othersUnread: number;
onSelectTab: (slug: string) => void;
query: string;
syncing: boolean;
emails: EmailRow[];
people: Record<string, Person>;
accounts: Record<string, MailAccount>;
accColor: (id: string) => string;
selId: string | null;
hasSel: boolean;
selected: Set<string>;
onToggleSelect: (id: string) => void;
onClearSelected: () => void;
onBulk: (action: string) => void;
onQuery: (q: string) => void;
onSync: () => void;
onOpen: (id: string) => void;
onStar: (id: string) => void;
}) {
const selCount = selected.size;
// 받은편지함은 집중(Focused)/기타(Others) 탭으로 분할 — Gmail 기본/Outlook focused = 집중.
const showTabs = folder === "inbox" || folder === "others";
return (
<div className={"mlist" + (hasSel ? " has-sel" : "")}>
<div className="ml-head">
<div className="ml-titlerow">
<span className="ml-title">{title}</span>
<span className="ml-meta">
{metaLabel}
{count}
</span>
<button
className={"ml-sync" + (syncing ? " spin" : "")}
onClick={onSync}
aria-label="동기화"
>
<Icon name="refresh" />
{syncing ? "동기화 중" : "동기화"}
</button>
</div>
{showTabs && (
<div className="ml-cats" role="tablist" aria-label="받은편지함 분류">
<button
className={"ml-cat" + (folder === "inbox" ? " on" : "")}
role="tab"
aria-selected={folder === "inbox"}
onClick={() => onSelectTab("inbox")}
>
{focusedUnread > 0 && <span className="ml-cat-cnt">{focusedUnread}</span>}
</button>
<button
className={"ml-cat" + (folder === "others" ? " on" : "")}
role="tab"
aria-selected={folder === "others"}
onClick={() => onSelectTab("others")}
>
{othersUnread > 0 && <span className="ml-cat-cnt">{othersUnread}</span>}
</button>
</div>
)}
<div className="ml-search">
<Icon name="search" />
<input
value={query}
onChange={(e) => onQuery(e.target.value)}
placeholder="메일 검색…"
aria-label="메일 검색"
/>
</div>
</div>
{selCount > 0 && (
<div className="ml-bulk" role="toolbar" aria-label="선택 항목 작업">
<span className="ml-bulk-count">{selCount} </span>
<button className="ml-bulk-btn" onClick={() => onBulk("read")}>
</button>
<button className="ml-bulk-btn" onClick={() => onBulk("unread")}>
</button>
<button className="ml-bulk-btn" onClick={() => onBulk("archive")}>
</button>
<button className="ml-bulk-btn danger" onClick={() => onBulk("trash")}>
</button>
<button className="ml-bulk-btn ghost" onClick={onClearSelected} aria-label="선택 해제">
<Icon name="x" />
</button>
</div>
)}
<div className="ml-scroll" role="region" aria-label="메일 목록" tabIndex={0}>
{emails.map((m) => (
<div className={"ml-rowwrap" + (selected.has(m.id) ? " picked" : "")} key={m.id}>
<label className="ml-check" onClick={(e) => e.stopPropagation()}>
<input
type="checkbox"
checked={selected.has(m.id)}
onChange={() => onToggleSelect(m.id)}
aria-label="메일 선택"
/>
</label>
<MailRow
m={m}
person={people[m.from]}
account={accounts[m.account]}
accColor={accColor(m.account)}
on={selId === m.id}
onClick={() => onOpen(m.id)}
onStar={onStar}
/>
</div>
))}
{emails.length === 0 && (
<div className="rd-empty" style={{ height: "auto", padding: "60px 30px" }}>
<div className="e-ico">
<Icon name="inbox" />
</div>
<h3> </h3>
<p> .</p>
</div>
)}
</div>
</div>
);
}