Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/api/memo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import axios from 'axios';

const API_URL = import.meta.env.VITE_API_URL as string;
const CONTENT_ID = 1;

export async function createMemo(memoText: string, timestamp: number): Promise<void> {
await axios.post(`${API_URL}/memo/${CONTENT_ID}`, { memoText, timestamp });
}

export async function getMemos(): Promise<{ memoText: string; timestamp: number }[]> {
const res = await axios.get(`${API_URL}/memo/${CONTENT_ID}`);
return res.data.data ?? [];
}
71 changes: 46 additions & 25 deletions src/components/recording/MemoTab.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,58 @@
import { useState } from 'react';
import type { MemoEntry } from '@/types/recording';

interface MemoTabProps {
memos: MemoEntry[];
onSubmit: (text: string) => Promise<void>;
}

export default function MemoTab({ memos }: MemoTabProps) {
if (memos.length === 0) {
return (
<div className="px-[32px] pt-[35px]">
<p className="text-[16px] leading-normal font-medium text-black">
✍️ 중요한 포인트인가요?
<br />
여기에 기록하세요.
</p>
<p className="mt-[24px] ml-5 text-[15px] text-[#727272]">
메모를 여기에 시작해보세요!
</p>
</div>
);
}
export default function MemoTab({ memos, onSubmit }: MemoTabProps) {
const [text, setText] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);

const handleKeyDown = async (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
const trimmed = text.trim();
if (!trimmed) return;
setIsSubmitting(true);
await onSubmit(trimmed);
setText('');
setIsSubmitting(false);
}
};

return (
<div className="flex flex-col gap-[16px] px-[35px] pt-[26px]">
{memos.map((memo, index) => (
<div key={index}>
<p className="text-[16px] leading-normal font-bold text-[#c4c4c4]">
{memo.timestamp}
</p>
<p className="text-[16px] leading-normal whitespace-pre-wrap text-black">
{memo.content}
</p>
<div className="flex h-full flex-col px-[32px] pt-[35px] pb-[24px]">
<p className="text-[16px] leading-normal font-medium text-black">
✍️ 중요한 포인트인가요?
<br />
여기에 기록하세요.
</p>

{memos.length > 0 && (
<div className="mt-[24px] flex flex-col gap-[16px]">
{memos.map((memo, index) => (
<div key={index}>
<p className="text-[16px] leading-normal font-bold text-[#c4c4c4]">
{memo.timestamp}
</p>
<p className="text-[16px] leading-normal whitespace-pre-wrap text-black">
{memo.content}
</p>
</div>
))}
</div>
))}
)}

<textarea
className="mt-[24px] flex-1 resize-none text-[15px] text-black outline-none placeholder:text-[#727272]"
placeholder="메모를 여기에 시작해보세요!"
value={text}
onChange={(e) => setText(e.target.value)}
onKeyDown={handleKeyDown}
disabled={isSubmitting}
/>
</div>
);
}
4 changes: 3 additions & 1 deletion src/components/recording/RightPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ type Tab = 'summary' | 'memo';
interface RightPanelProps {
memos: MemoEntry[];
summaries: string[];
onMemoSubmit: (text: string) => Promise<void>;
defaultTab?: Tab;
}

export default function RightPanel({
memos,
summaries,
onMemoSubmit,
defaultTab = 'summary',
}: RightPanelProps) {
const [activeTab, setActiveTab] = useState<Tab>(defaultTab);
Expand Down Expand Up @@ -54,7 +56,7 @@ export default function RightPanel({

{/* 탭 콘텐츠 */}
<div className="flex-1 overflow-y-auto">
{activeTab === 'summary' ? <SummaryTab summaries={summaries} /> : <MemoTab memos={memos} />}
{activeTab === 'summary' ? <SummaryTab summaries={summaries} /> : <MemoTab memos={memos} onSubmit={onMemoSubmit} />}
</div>
</div>
);
Expand Down
12 changes: 10 additions & 2 deletions src/pages/RecordingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import RightPanel from '@/components/recording/RightPanel';
import RecordingBar from '@/components/recording/RecordingBar';
import { useSocket } from '@/hooks/useSocket';
import { createAudioCapture, type AudioCapture } from '@/utils/audioCapture';
import type { TranscriptEntry } from '@/types/recording';
import type { TranscriptEntry, MemoEntry } from '@/types/recording';
import { createMemo } from '@/api/memo';

const DAY_NAMES = ['일', '월', '화', '수', '목', '금', '토'];

Expand All @@ -33,6 +34,7 @@ function formatElapsed(ms: number): string {
export default function RecordingPage() {
const [entries, setEntries] = useState<TranscriptEntry[]>([]);
const [summaries, setSummaries] = useState<string[]>([]);
const [memos, setMemos] = useState<MemoEntry[]>([]);
const [isRecording, setIsRecording] = useState(false);
const [isPaused, setIsPaused] = useState(false);

Expand Down Expand Up @@ -113,6 +115,12 @@ export default function RecordingPage() {
setEntries([]);
}, [disconnect]);

const handleMemoSubmit = useCallback(async (text: string) => {
const elapsed = Math.floor(getElapsed() / 1000);
await createMemo(text, elapsed);
setMemos((prev) => [...prev, { timestamp: formatElapsed(elapsed * 1000), content: text }]);
}, [getElapsed]);

return (
<div className="flex h-screen w-full">
<Sidebar />
Expand All @@ -131,7 +139,7 @@ export default function RecordingPage() {
<div className="flex-1 overflow-y-auto">
<TranscriptArea entries={entries} />
</div>
<RightPanel memos={[]} summaries={summaries} defaultTab="summary" />
<RightPanel memos={memos} summaries={summaries} onMemoSubmit={handleMemoSubmit} defaultTab="summary" />
</div>
</div>

Expand Down
Loading