feat: 일지 작성화면 및 일지 화면
This commit is contained in:
68
src/app/log/[id]/page.tsx
Normal file
68
src/app/log/[id]/page.tsx
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState, use } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { getHistory } from '@/lib/store';
|
||||||
|
import { Voyage } from '@/types';
|
||||||
|
|
||||||
|
export default function LogDetailPage({ params }: { params: Promise<{ id: string }> }) {
|
||||||
|
// Next.js 15: params is a Promise
|
||||||
|
const resolvedParams = use(params);
|
||||||
|
const router = useRouter();
|
||||||
|
const [log, setLog] = useState<Voyage | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const history = getHistory();
|
||||||
|
const found = history.find(item => item.id === resolvedParams.id);
|
||||||
|
if (found) setLog(found);
|
||||||
|
}, [resolvedParams.id]);
|
||||||
|
|
||||||
|
if (!log) return <div className="p-6 text-slate-500">Loading or Not Found...</div>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col flex-1 p-6 space-y-8 animate-in fade-in duration-300">
|
||||||
|
<header className="border-b border-slate-800 pb-4">
|
||||||
|
<Link href="/log" className="text-sm text-indigo-400 hover:text-indigo-300 mb-4 inline-block">← 목록으로</Link>
|
||||||
|
<h1 className="text-2xl font-bold text-white">{log.missionText}</h1>
|
||||||
|
<div className="flex gap-3 mt-2 text-sm text-slate-500">
|
||||||
|
<span>{new Date(log.startedAt).toLocaleString()}</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>{log.routeName} ({log.durationMinutes}m)</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="bg-slate-900/30 p-4 rounded-lg border border-slate-800/50">
|
||||||
|
<h3 className="text-xs font-bold text-slate-500 uppercase tracking-wider mb-2">결과 상태</h3>
|
||||||
|
<p className="text-lg text-indigo-100">
|
||||||
|
{log.status === 'completed' && '✅ 계획대로'}
|
||||||
|
{log.status === 'partial' && '🌓 부분 진행'}
|
||||||
|
{log.status === 'reoriented' && '🧭 방향 재설정'}
|
||||||
|
{log.status === 'aborted' && '🚨 조기 귀환'}
|
||||||
|
{log.status === 'in_progress' && '진행 중'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-6 sm:grid-cols-2">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xs font-bold text-slate-500 uppercase tracking-wider mb-2">확보한 것</h3>
|
||||||
|
<p className="text-slate-300 leading-relaxed bg-slate-900/20 p-3 rounded">{log.debriefProgress || '-'}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xs font-bold text-slate-500 uppercase tracking-wider mb-2">다음 행동</h3>
|
||||||
|
<p className="text-slate-300 leading-relaxed bg-slate-900/20 p-3 rounded">{log.nextAction || '-'}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{log.notes && (
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xs font-bold text-slate-500 uppercase tracking-wider mb-2">초기 메모</h3>
|
||||||
|
<p className="text-slate-400 text-sm italic">"{log.notes}"</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
62
src/app/log/page.tsx
Normal file
62
src/app/log/page.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { getHistory } from '@/lib/store';
|
||||||
|
import { Voyage, VoyageStatus } from '@/types';
|
||||||
|
|
||||||
|
export default function LogListPage() {
|
||||||
|
const [logs, setLogs] = useState<Voyage[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLogs(getHistory());
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getStatusLabel = (s: VoyageStatus) => {
|
||||||
|
switch(s) {
|
||||||
|
case 'completed': return '✅ 계획대로';
|
||||||
|
case 'partial': return '🌓 부분 진행';
|
||||||
|
case 'reoriented': return '🧭 방향 재설정';
|
||||||
|
case 'aborted': return '🚨 조기 귀환';
|
||||||
|
default: return '진행 중';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col flex-1 p-6">
|
||||||
|
<h1 className="text-xl font-bold text-slate-100 mb-6">나의 항해 기록</h1>
|
||||||
|
|
||||||
|
{logs.length === 0 ? (
|
||||||
|
<div className="flex flex-col items-center justify-center flex-1 py-20 text-slate-500 border border-dashed border-slate-800 rounded-xl">
|
||||||
|
<p className="mb-4">아직 기록된 항해가 없습니다.</p>
|
||||||
|
<Link href="/" className="text-indigo-400 hover:text-indigo-300 underline">
|
||||||
|
첫 항해 떠나기
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="grid gap-3">
|
||||||
|
{logs.map((log) => (
|
||||||
|
<Link
|
||||||
|
key={log.id}
|
||||||
|
href={`/log/${log.id}`}
|
||||||
|
className="block bg-slate-900/50 border border-slate-800 hover:border-slate-600 rounded-lg p-4 transition-colors"
|
||||||
|
>
|
||||||
|
<div className="flex justify-between items-start mb-1">
|
||||||
|
<span className="text-xs text-slate-500">
|
||||||
|
{new Date(log.startedAt).toLocaleDateString()}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs font-medium text-slate-400 bg-slate-800 px-1.5 py-0.5 rounded">
|
||||||
|
{getStatusLabel(log.status)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="font-semibold text-slate-200 truncate mb-1">
|
||||||
|
{log.missionText}
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs text-slate-500">{log.routeName} · {log.durationMinutes}min</p>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user