diff --git a/src/renderer/inbox/App.tsx b/src/renderer/inbox/App.tsx index 2451227..ae1cdcb 100644 --- a/src/renderer/inbox/App.tsx +++ b/src/renderer/inbox/App.tsx @@ -9,6 +9,7 @@ import { PendingBanner } from './components/PendingBanner.js'; import { OllamaBanner } from './components/OllamaBanner.js'; import { RecoveryToast } from './components/RecoveryToast.js'; import { TagUndoToast } from './components/TagUndoToast.js'; +import { ExpiryBanner } from './components/ExpiryBanner.js'; export function App(): React.ReactElement { const { @@ -77,6 +78,7 @@ export function App(): React.ReactElement { onDismiss={() => { markRecoveryDismissed(); setRecoveryDismissed(true); }} /> + {tagFilter !== null && (
s.expiredCandidates); + const snoozeUntilMs = useInbox((s) => s.expiredSnoozeUntilMs); + const trashExpiredBatch = useInbox((s) => s.trashExpiredBatch); + const snoozeExpired = useInbox((s) => s.snoozeExpired); + + // Q5=A: 0건 / snooze 활성 시 collapse + if (candidates.length === 0) return null; + if (snoozeUntilMs !== null && Date.now() < snoozeUntilMs) return null; + + return void trashExpiredBatch(ids)} + onSnooze={() => snoozeExpired()} + />; +} + +interface InnerProps { + candidates: Array<{ + id: string; + aiTitle: string | null; + rawText: string; + dueDate: string | null; + tags: Array<{ name: string }> + }>; + onTrash: (ids: string[]) => void; + onSnooze: () => void; +} + +function ExpiryBannerInner({ candidates, onTrash, onSnooze }: InnerProps): React.ReactElement { + const [expanded, setExpanded] = useState(true); + const [selected, setSelected] = useState>(new Set()); + + // candidates 가 변하면 selected 의 stale id 정리 + useEffect(() => { + const valid = new Set(candidates.map((c) => c.id)); + setSelected((prev) => { + const next = new Set(); + for (const id of prev) if (valid.has(id)) next.add(id); + return next; + }); + }, [candidates]); + + const allSelected = candidates.length > 0 && candidates.every((c) => selected.has(c.id)); + const someSelected = selected.size > 0 && !allSelected; + + function toggleAll() { + if (allSelected) setSelected(new Set()); + else setSelected(new Set(candidates.map((c) => c.id))); + } + + function toggle(id: string) { + setSelected((prev) => { + const next = new Set(prev); + if (next.has(id)) next.delete(id); + else next.add(id); + return next; + }); + } + + return ( +
+
+ 오늘 기준 만료 {candidates.length}개 + + +
+ {expanded && ( + <> + +
+ {candidates.map((n) => ( + + ))} +
+ + + )} +
+ ); +}