fix: /simplify 리뷰 — UX/코드 품질 10건 수정

- EditableSegment: readOnly prop 추가, hover 배경색 효과
- MeetingDetailTabs: useMemo(parseSegments), 이중 debounce 제거, cleanup
- MeetingChatPanel: sessionId deps, 에러 표시, key=timestamp
- ExportMenu: i18n 위반 수정 (하드코딩 → t())
- MeetingModePage: 에러 Snackbar 피드백 (startRecording, viewDetail)
- TranscriptTab: polish 실패 피드백
- 12개 locale i18n 키 5개 추가
This commit is contained in:
Yun Chan 2026-04-08 11:34:50 +09:00
parent 403985e2cd
commit f0e221058d
18 changed files with 179 additions and 49 deletions

View file

@ -1,7 +1,7 @@
// src/renderer/components/meeting/MeetingDetailTabs.tsx
// Phase 14.5: 풀스크린 탭 전환 컨테이너 — 전사 + 동적 문서 탭
import { useState, useEffect, useCallback, useRef } from 'react'
import { useState, useEffect, useCallback, useMemo } from 'react'
import {
Box,
Tabs,
@ -33,8 +33,6 @@ interface MeetingDetailTabsProps {
onBack: () => void
}
const AUTOSAVE_DELAY = 500
// 세그먼트 파싱: rawTranscript를 줄 단위로 분리
function parseSegments(
rawTranscript: string | null,
@ -78,17 +76,19 @@ export function MeetingDetailTabs({
}: MeetingDetailTabsProps): React.ReactElement {
const { t } = useI18n()
const [detail, setDetail] = useState<MeetingSessionDetail>(initialDetail)
const [documents, setDocuments] = useState<MeetingDocument[]>(initialDetail.documents ?? [])
const [documents, setDocuments] = useState<MeetingDocument[]>((initialDetail.documents ?? []).filter(Boolean))
const [templates, setTemplates] = useState<MeetingDocTemplate[]>([])
const [tabIndex, setTabIndex] = useState(0)
const [dialogOpen, setDialogOpen] = useState(false)
const [generating, setGenerating] = useState(false)
const [editingTitle, setEditingTitle] = useState(false)
const [titleDraft, setTitleDraft] = useState(detail.title ?? '')
const autoSaveTimers = useRef<Record<string, ReturnType<typeof setTimeout>>>({})
// 세그먼트 파싱 (rawTranscript 기반)
const segments = parseSegments(detail.rawTranscript, detail.editedTranscript)
const segments = useMemo(
() => parseSegments(detail.rawTranscript, detail.editedTranscript),
[detail.rawTranscript, detail.editedTranscript],
)
// 템플릿 로드
useEffect(() => {
@ -134,19 +134,16 @@ export function MeetingDetailTabs({
[detail.id],
)
// ── 문서 내용 자동저장 ──
// ── 문서 내용 자동저장 (DocumentTab 내부에 debounce 있음, 이중 래핑 제거) ──
const handleDocContentChange = useCallback(
(docId: string, content: string) => {
if (autoSaveTimers.current[docId]) clearTimeout(autoSaveTimers.current[docId])
autoSaveTimers.current[docId] = setTimeout(async () => {
await window.electronAPI.meetingMode.updateDocument({
documentId: docId,
content,
})
setDocuments((prev) =>
prev.map((d) => (d.id === docId ? { ...d, content } : d)),
)
}, AUTOSAVE_DELAY)
async (docId: string, content: string) => {
await window.electronAPI.meetingMode.updateDocument({
documentId: docId,
content,
})
setDocuments((prev) =>
prev.map((d) => (d.id === docId ? { ...d, content } : d)),
)
},
[],
)
@ -286,8 +283,8 @@ export function MeetingDetailTabs({
}}
>
<Tab label={t('meeting.transcriptTab')} />
{documents.map((doc) => (
<Tab key={doc.id} label={doc.title} />
{documents.filter(Boolean).map((doc) => (
<Tab key={doc.id} label={doc.title ?? t('meeting.untitled')} />
))}
<Tab
icon={<AddIcon sx={{ fontSize: 16 }} />}