feat(V2-6차): 테마 토글 + Mermaid + 오디오 재생 + Dashboard 차트

[A] Dark/Light/6종 + Auto 테마 토글
- theme-mode-context.tsx: localStorage 영속 + prefers-color-scheme
- theme-provider.tsx: 컨텍스트 기반 동적 getTheme
- sidebar.tsx: MUI Select 토글 (PaletteIcon)
- 12개 locale theme.* 키 추가 (label/dark/light/auto/nord/solarized/catppuccin/dracula)

[B] DocumentEditor Mermaid + Markdown 렌더
- markdown-preview.tsx: react-markdown + remark-gfm + lazy mermaid
  - mermaid 코드 블록 자동 SVG 렌더
  - dark theme + d3ro 폰트
- document-editor.tsx: Edit/Preview Tabs

[C] 회의 오디오 재생
- meeting-audio-player.tsx: Supabase Storage signed URL (1h) → <audio controls>
- meetings/[id] AUDIO 카드 추가

[D] Dashboard 14일 추이 차트
- meetings-trend-chart.tsx: recharts LineChart (회의/문서)
- dashboard/page.tsx: 14일 데이터 집계 + 이번 주 stat 자동 계산

[W] fix: Emotion key 'd3ro-mui' → 'mui'
- 'd3ro-mui'에 숫자 3이 포함돼 Emotion 키 검증 실패
- dev SSR /login 500 에러 차단했음

deps:
- react-markdown@10, remark-gfm@4, mermaid@11, recharts@3
This commit is contained in:
윤찬 2026-04-11 08:57:27 +09:00
parent 162a59dc47
commit 07c6d13c99
24 changed files with 2220 additions and 63 deletions

View file

@ -4,7 +4,19 @@
// 대시보드 좌측 사이드바
import { usePathname, useRouter } from 'next/navigation'
import { Box, List, ListItem, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'
import {
Box,
FormControl,
InputLabel,
List,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
MenuItem,
Select,
type SelectChangeEvent
} from '@mui/material'
import DashboardIcon from '@mui/icons-material/Dashboard'
import MeetingRoomIcon from '@mui/icons-material/MeetingRoom'
import MicIcon from '@mui/icons-material/Mic'
@ -14,10 +26,13 @@ import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome'
import GroupsIcon from '@mui/icons-material/Groups'
import PaymentIcon from '@mui/icons-material/Payment'
import LogoutIcon from '@mui/icons-material/Logout'
import PaletteIcon from '@mui/icons-material/Palette'
import type { ThemeMode } from '@d3ro/core/types'
import { PhosphorText } from '@d3ro/ui/components/ds'
import { d3roPalette } from '@d3ro/ui/theme'
import { useI18n } from '@d3ro/i18n'
import { getSupabaseBrowserClient } from '@/lib/supabase-browser'
import { AVAILABLE_MODES, useThemeMode } from '@/components/providers/theme-mode-context'
interface NavItem {
key: string
@ -30,6 +45,11 @@ export function Sidebar(): React.ReactElement {
const router = useRouter()
const pathname = usePathname()
const { t } = useI18n()
const { mode, setMode } = useThemeMode()
const handleThemeChange = (event: SelectChangeEvent<ThemeMode>): void => {
setMode(event.target.value as ThemeMode)
}
const items: NavItem[] = [
{
@ -135,6 +155,31 @@ export function Sidebar(): React.ReactElement {
})}
</List>
<Box sx={{ px: 2, py: 1.5, borderTop: `1px solid ${d3roPalette.border.default}` }}>
<FormControl fullWidth size="small">
<InputLabel
id="theme-mode-label"
sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}
>
<PaletteIcon fontSize="inherit" />
{t('theme.label') ?? 'Theme'}
</InputLabel>
<Select<ThemeMode>
labelId="theme-mode-label"
id="theme-mode-select"
value={mode}
onChange={handleThemeChange}
label={t('theme.label') ?? 'Theme'}
>
{AVAILABLE_MODES.map((m) => (
<MenuItem key={m} value={m}>
{t(`theme.${m}`) ?? m}
</MenuItem>
))}
</Select>
</FormControl>
</Box>
<List sx={{ borderTop: `1px solid ${d3roPalette.border.default}` }}>
<ListItem disablePadding>
<ListItemButton onClick={() => void handleLogout()}>