Replace client-side state switching with proper Next.js routes: - /new - New scrape form - /jobs - Jobs list with table view - /jobs/[id] - Individual job details and logs - /analytics - Analytics overview (completed jobs) - /analytics/[id] - Analytics for specific job Add JobsContext for shared state across routes. Update Sidebar to use next/link with pathname matching. Root page redirects to /new. Also adds partial job status styling to JobsView. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
75 lines
2.6 KiB
TypeScript
75 lines
2.6 KiB
TypeScript
'use client';
|
|
|
|
import Link from 'next/link';
|
|
import { usePathname } from 'next/navigation';
|
|
import { useJobs } from '@/contexts/JobsContext';
|
|
|
|
export default function Sidebar() {
|
|
const pathname = usePathname();
|
|
const { jobs } = useJobs();
|
|
|
|
const navItems = [
|
|
{
|
|
href: '/new',
|
|
icon: (
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
|
|
</svg>
|
|
),
|
|
label: 'New',
|
|
matchPaths: ['/new'],
|
|
},
|
|
{
|
|
href: '/jobs',
|
|
icon: (
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
|
</svg>
|
|
),
|
|
label: 'Jobs',
|
|
matchPaths: ['/jobs'],
|
|
badge: jobs.length > 0 ? jobs.length : undefined,
|
|
},
|
|
{
|
|
href: '/analytics',
|
|
icon: (
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
|
</svg>
|
|
),
|
|
label: 'Analytics',
|
|
matchPaths: ['/analytics'],
|
|
},
|
|
];
|
|
|
|
const isActive = (item: typeof navItems[0]) => {
|
|
// Check if current path starts with any of the match paths
|
|
return item.matchPaths.some(path => pathname.startsWith(path));
|
|
};
|
|
|
|
return (
|
|
<div className="w-20 bg-gray-900 flex flex-col items-center py-6 gap-2">
|
|
{navItems.map((item) => (
|
|
<Link
|
|
key={item.href}
|
|
href={item.href}
|
|
className={`relative w-14 h-14 rounded-xl flex flex-col items-center justify-center gap-1 transition-all ${
|
|
isActive(item)
|
|
? 'bg-blue-600 text-white shadow-lg'
|
|
: 'text-gray-400 hover:bg-gray-800 hover:text-white'
|
|
}`}
|
|
title={item.label}
|
|
>
|
|
{item.icon}
|
|
<span className="text-[10px] font-medium">{item.label}</span>
|
|
{item.badge !== undefined && (
|
|
<span className="absolute -top-1 -right-1 w-5 h-5 bg-red-500 text-white text-xs font-bold rounded-full flex items-center justify-center">
|
|
{item.badge > 99 ? '99+' : item.badge}
|
|
</span>
|
|
)}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|