91 lines
3.3 KiB
TypeScript
91 lines
3.3 KiB
TypeScript
import { useLocation, useNavigate } from 'react-router';
|
|
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
|
|
|
/**
|
|
* Page flow uses path prefixes for dynamic routes.
|
|
* Matching is done via startsWith so /report/{any-id} matches '/report/'.
|
|
*/
|
|
const PAGE_FLOW = [
|
|
{ prefix: '/', label: '랜딩', exact: true },
|
|
{ prefix: '/report/', label: '마케팅 분석' },
|
|
{ prefix: '/plan/', label: '콘텐츠 기획' },
|
|
{ prefix: '/studio/', label: '콘텐츠 제작' },
|
|
{ prefix: '/channels', label: '채널 연결', exact: true },
|
|
{ prefix: '/distribute', label: '콘텐츠 배포', exact: true },
|
|
{ prefix: '/performance', label: '성과 관리', exact: true },
|
|
];
|
|
|
|
function findCurrentIndex(pathname: string): number {
|
|
return PAGE_FLOW.findIndex((p) =>
|
|
p.exact ? pathname === p.prefix : pathname.startsWith(p.prefix)
|
|
);
|
|
}
|
|
|
|
function getNavigatePath(prefix: string, currentPath: string): string {
|
|
// For dynamic routes, try to preserve the current report/plan ID
|
|
if (prefix === '/') return '/';
|
|
|
|
// Extract ID from current path (e.g., /report/abc123 → abc123)
|
|
const idMatch = currentPath.match(/\/(report|plan|studio|clinic)\/(.+)/);
|
|
const currentId = idMatch?.[2] || 'live';
|
|
|
|
if (prefix.endsWith('/')) {
|
|
return `${prefix}${currentId}`;
|
|
}
|
|
return prefix;
|
|
}
|
|
|
|
export default function PageNavigator() {
|
|
const location = useLocation();
|
|
const navigate = useNavigate();
|
|
|
|
const currentIndex = findCurrentIndex(location.pathname);
|
|
if (currentIndex === -1) return null;
|
|
|
|
const prev = currentIndex > 0 ? PAGE_FLOW[currentIndex - 1] : null;
|
|
const next = currentIndex < PAGE_FLOW.length - 1 ? PAGE_FLOW[currentIndex + 1] : null;
|
|
|
|
return (
|
|
<div
|
|
data-no-print
|
|
className="fixed bottom-6 left-1/2 -translate-x-1/2 z-50 flex items-center gap-1 bg-white/90 backdrop-blur-xl border border-slate-200 rounded-full px-2 py-2 shadow-[0_4px_20px_rgba(0,0,0,0.08)]"
|
|
>
|
|
{/* Back */}
|
|
<button
|
|
onClick={() => prev && navigate(getNavigatePath(prev.prefix, location.pathname))}
|
|
disabled={!prev}
|
|
className="flex items-center gap-2 px-3 py-2 rounded-full text-sm font-medium transition-all disabled:opacity-30 disabled:cursor-not-allowed hover:bg-slate-50 text-slate-600"
|
|
>
|
|
<ChevronLeft size={16} />
|
|
{prev && <span className="hidden sm:inline">{prev.label}</span>}
|
|
</button>
|
|
|
|
{/* Page Indicators */}
|
|
<div className="flex items-center gap-2 px-2">
|
|
{PAGE_FLOW.map((page, i) => (
|
|
<button
|
|
key={page.prefix}
|
|
onClick={() => navigate(getNavigatePath(page.prefix, location.pathname))}
|
|
title={page.label}
|
|
className={`rounded-full transition-all ${
|
|
i === currentIndex
|
|
? 'w-6 h-2 bg-[#0A1128]'
|
|
: 'w-2 h-2 bg-slate-300 hover:bg-slate-400'
|
|
}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
{/* Next */}
|
|
<button
|
|
onClick={() => next && navigate(getNavigatePath(next.prefix, location.pathname))}
|
|
disabled={!next}
|
|
className="flex items-center gap-2 px-3 py-2 rounded-full text-sm font-medium transition-all disabled:opacity-30 disabled:cursor-not-allowed hover:bg-slate-50 text-slate-600"
|
|
>
|
|
{next && <span className="hidden sm:inline">{next.label}</span>}
|
|
<ChevronRight size={16} />
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|