v0.3.4 까지 누적된 dogfood UX 결함 hotfix. 사용자 직접 보고 3건 (inbox 재진입, 회고 탈출, 이동 modal 중복) + 동반 갭 4건 (count stale, 필터 잔류, 초기 로드 불일치, 배너 컨텍스트 누수). 데이터/마이그레이션 변경 없음 (스키마 v8). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
81 lines
2.8 KiB
TypeScript
81 lines
2.8 KiB
TypeScript
import React from 'react';
|
|
import { useInbox } from '../store.js';
|
|
import { NoteCard } from './NoteCard.js';
|
|
|
|
interface Props {
|
|
period: 'daily' | 'weekly' | 'monthly';
|
|
}
|
|
|
|
const periodLabel: Record<Props['period'], string> = {
|
|
daily: '일간',
|
|
weekly: '주간',
|
|
monthly: '월간'
|
|
};
|
|
|
|
export function ReviewView({ period }: Props): React.ReactElement {
|
|
const reviewData = useInbox((s) => s.reviewData);
|
|
const setView = useInbox((s) => s.setView);
|
|
const backButton = (
|
|
<button
|
|
onClick={() => setView('inbox')}
|
|
style={{
|
|
background: 'transparent',
|
|
border: 'none',
|
|
fontSize: 14,
|
|
cursor: 'pointer',
|
|
color: '#0a4b80',
|
|
padding: 0
|
|
}}
|
|
>
|
|
← 돌아가기
|
|
</button>
|
|
);
|
|
if (!reviewData) {
|
|
return (
|
|
<div style={{ padding: 16 }}>
|
|
<div style={{ marginBottom: 12 }}>{backButton}</div>
|
|
<div style={{ fontSize: 13, color: '#666' }}>불러오는 중…</div>
|
|
</div>
|
|
);
|
|
}
|
|
const max = reviewData.tagCounts[0]?.count ?? 1;
|
|
return (
|
|
<div style={{ padding: 16 }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
|
|
{backButton}
|
|
<h2 style={{ fontSize: 18, margin: 0 }}>{periodLabel[period]} 회고</h2>
|
|
</div>
|
|
<div style={{ marginTop: 8, fontSize: 13, color: '#444' }}>
|
|
총 {reviewData.totalCount}건
|
|
</div>
|
|
<section style={{ marginTop: 16 }}>
|
|
<h3 style={{ fontSize: 14, marginBottom: 4 }}>태그 분포</h3>
|
|
{reviewData.tagCounts.length === 0 && (
|
|
<div style={{ fontSize: 12, color: '#888' }}>태그 없음</div>
|
|
)}
|
|
{reviewData.tagCounts.slice(0, 10).map((t) => (
|
|
<div key={t.tag} style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 2 }}>
|
|
<span style={{ fontSize: 12, width: 80 }}>{t.tag}</span>
|
|
<div style={{ flex: 1, background: '#eee', height: 8, borderRadius: 2 }}>
|
|
<div style={{ width: `${(t.count / max) * 100}%`, background: '#4ec5b8', height: 8, borderRadius: 2 }} />
|
|
</div>
|
|
<span style={{ fontSize: 12, color: '#666', width: 30, textAlign: 'right' }}>{t.count}</span>
|
|
</div>
|
|
))}
|
|
</section>
|
|
<section style={{ marginTop: 16 }}>
|
|
<h3 style={{ fontSize: 14, marginBottom: 4 }}>마감 진행</h3>
|
|
<div style={{ fontSize: 13, color: '#444' }}>
|
|
완료 {reviewData.dueProgress.passed} / {reviewData.dueProgress.total} · 대기 {reviewData.dueProgress.pending}
|
|
</div>
|
|
</section>
|
|
<section style={{ marginTop: 16 }}>
|
|
<h3 style={{ fontSize: 14, marginBottom: 4 }}>최근 노트 ({reviewData.recentNotes.length})</h3>
|
|
{reviewData.recentNotes.map((n) => (
|
|
<NoteCard key={n.id} note={n} mode="inbox" onUpdated={() => {}} />
|
|
))}
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|