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
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>
|
|
);
|
|
}
|