init: SR System — Helpdesk & ITAM Portal
This commit is contained in:
@@ -0,0 +1,315 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import {
|
||||
LayoutDashboard,
|
||||
Ticket,
|
||||
Link2,
|
||||
Monitor,
|
||||
FileText,
|
||||
Plus,
|
||||
Lightbulb,
|
||||
ChevronDown,
|
||||
PlusCircle,
|
||||
ArrowUpRight,
|
||||
ArrowDownLeft,
|
||||
History,
|
||||
BookOpen,
|
||||
Users,
|
||||
ClipboardCheck,
|
||||
CalendarDays,
|
||||
Settings,
|
||||
CheckSquare,
|
||||
Briefcase,
|
||||
} from 'lucide-react';
|
||||
import { useAuthStore } from '@/stores';
|
||||
|
||||
/**
|
||||
* SR 시스템의 메인 사이드바 컴포넌트
|
||||
* ITAM 하위 메뉴를 포함하도록 고도화되었습니다.
|
||||
*/
|
||||
const Sidebar = ({ isCollapsed }: { isCollapsed?: boolean }) => {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const [isItamOpen, setIsItamOpen] = useState(pathname.startsWith('/itam'));
|
||||
const user = useAuthStore((s) => s.user);
|
||||
const role = user?.roleCode;
|
||||
const isMgmt = role === 'MANAGER' || role === 'ADMIN';
|
||||
|
||||
const menuItems = [
|
||||
{ name: 'Dashboard', href: '/', icon: LayoutDashboard },
|
||||
{ name: 'SR Issues', href: '/issues', icon: Ticket },
|
||||
{ name: 'Dispatch', href: '/dispatch', icon: Link2 },
|
||||
];
|
||||
|
||||
const itamSubItems = [
|
||||
{ name: 'Asset Hub', href: '/itam', icon: Monitor },
|
||||
{ name: 'Register', href: '/itam/register', icon: PlusCircle },
|
||||
{ name: 'Check-out', href: '/itam/checkout', icon: ArrowUpRight },
|
||||
{ name: 'Check-in', href: '/itam/checkin', icon: ArrowDownLeft },
|
||||
{ name: 'History', href: '/itam', icon: History },
|
||||
];
|
||||
|
||||
const projects = [
|
||||
{ name: 'Samsung Pay SI', dot: 'bg-dot-green' },
|
||||
{ name: 'LG CNS SM', dot: 'bg-dot-orange' },
|
||||
{ name: 'Kakao Maintenance', dot: 'bg-dot-blue' },
|
||||
];
|
||||
|
||||
return (
|
||||
<aside
|
||||
className={`bg-surface border-r border-border flex flex-col justify-between flex-shrink-0 transition-all duration-300 relative z-20 overflow-hidden ${
|
||||
isCollapsed ? 'w-[64px]' : 'w-[250px]'
|
||||
}`}
|
||||
>
|
||||
<div className="flex-1 overflow-y-auto no-scrollbar">
|
||||
{/* 로고 영역 */}
|
||||
<div className={`h-16 flex items-center border-b border-border transition-all sticky top-0 bg-surface z-10 ${isCollapsed ? 'justify-center' : 'px-6'}`}>
|
||||
<div className="w-6 h-6 rounded-full bg-gradient-to-tr from-primary to-purple-400 flex-shrink-0 shadow-lg shadow-primary/20"></div>
|
||||
{!isCollapsed && (
|
||||
<h1 className="font-outfit font-semibold text-lg text-text-main tracking-wide ml-3 whitespace-nowrap">SR System.</h1>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 메인 메뉴 */}
|
||||
<div className={`py-6 ${isCollapsed ? 'px-1' : 'px-4'}`}>
|
||||
{!isCollapsed && (
|
||||
<p className="text-[10px] font-bold text-text-muted tracking-[0.12em] mb-4 px-2 uppercase whitespace-nowrap">Main Menu</p>
|
||||
)}
|
||||
<nav className="space-y-1">
|
||||
{menuItems.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const isActive = pathname === item.href;
|
||||
return (
|
||||
<Link
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
title={isCollapsed ? item.name : ''}
|
||||
className={`flex items-center rounded-xl font-bold text-xs transition-all ${
|
||||
isCollapsed ? 'justify-center py-3' : 'px-3 py-2.5'
|
||||
} ${
|
||||
isActive
|
||||
? 'bg-primary text-white shadow-lg shadow-primary/20'
|
||||
: 'text-text-muted hover:bg-gray-50 hover:text-text-main'
|
||||
}`}
|
||||
>
|
||||
<Icon className={`w-4.5 h-4.5 flex-shrink-0 ${isCollapsed ? '' : 'mr-3'}`} />
|
||||
{!isCollapsed && <span className="whitespace-nowrap">{item.name}</span>}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Projects link (management only) */}
|
||||
{isMgmt && (
|
||||
<Link
|
||||
href="/projects"
|
||||
className={`flex items-center rounded-xl font-bold text-xs transition-all ${
|
||||
isCollapsed ? 'justify-center py-3' : 'px-3 py-2.5'
|
||||
} ${
|
||||
pathname === '/projects'
|
||||
? 'bg-primary text-white shadow-lg shadow-primary/20'
|
||||
: 'text-text-muted hover:bg-gray-50 hover:text-text-main'
|
||||
}`}
|
||||
>
|
||||
<Briefcase className={`w-4.5 h-4.5 flex-shrink-0 ${isCollapsed ? '' : 'mr-3'}`} />
|
||||
{!isCollapsed && <span className="whitespace-nowrap">Projects</span>}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{/* ITAM Group Menu */}
|
||||
<div className="space-y-1">
|
||||
<button
|
||||
onClick={() => !isCollapsed && setIsItamOpen(!isItamOpen)}
|
||||
className={`w-full flex items-center justify-between rounded-xl font-bold text-xs transition-all ${
|
||||
isCollapsed ? 'justify-center py-3' : 'px-3 py-2.5'
|
||||
} ${
|
||||
pathname.startsWith('/itam') && !isCollapsed
|
||||
? 'text-primary'
|
||||
: 'text-text-muted hover:bg-gray-50 hover:text-text-main'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Monitor className={`w-4.5 h-4.5 flex-shrink-0 ${isCollapsed ? '' : 'mr-3'}`} />
|
||||
{!isCollapsed && <span className="whitespace-nowrap">ITAM (Assets)</span>}
|
||||
</div>
|
||||
{!isCollapsed && (
|
||||
<ChevronDown className={`w-3.5 h-3.5 transition-transform duration-300 ${isItamOpen ? 'rotate-180' : ''}`} />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* ITAM Sub Items */}
|
||||
{isItamOpen && !isCollapsed && (
|
||||
<div className="pl-6 space-y-1 mt-1 animate-in slide-in-from-top-2 duration-300">
|
||||
{itamSubItems.map((sub) => {
|
||||
const SubIcon = sub.icon;
|
||||
const isActive = pathname === sub.href;
|
||||
return (
|
||||
<Link
|
||||
key={sub.name}
|
||||
href={sub.href}
|
||||
className={`flex items-center px-3 py-2 rounded-lg text-[11px] font-bold transition-all ${
|
||||
isActive
|
||||
? 'text-primary bg-indigo-50/50'
|
||||
: 'text-text-muted hover:text-text-main hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<SubIcon className="w-3.5 h-3.5 mr-2.5" />
|
||||
<span className="whitespace-nowrap">{sub.name}</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Link
|
||||
href="/work-reports"
|
||||
className={`flex items-center rounded-xl font-bold text-xs transition-all ${
|
||||
isCollapsed ? 'justify-center py-3' : 'px-3 py-2.5'
|
||||
} ${
|
||||
pathname === '/work-reports'
|
||||
? 'bg-primary text-white shadow-lg shadow-primary/20'
|
||||
: 'text-text-muted hover:bg-gray-50 hover:text-text-main'
|
||||
}`}
|
||||
>
|
||||
<FileText className={`w-4.5 h-4.5 flex-shrink-0 ${isCollapsed ? '' : 'mr-3'}`} />
|
||||
{!isCollapsed && <span className="whitespace-nowrap">Work Reports</span>}
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/daily-reports"
|
||||
className={`flex items-center rounded-xl font-bold text-xs transition-all ${
|
||||
isCollapsed ? 'justify-center py-3' : 'px-3 py-2.5'
|
||||
} ${
|
||||
pathname === '/daily-reports'
|
||||
? 'bg-primary text-white shadow-lg shadow-primary/20'
|
||||
: 'text-text-muted hover:bg-gray-50 hover:text-text-main'
|
||||
}`}
|
||||
>
|
||||
<ClipboardCheck className={`w-4.5 h-4.5 flex-shrink-0 ${isCollapsed ? '' : 'mr-3'}`} />
|
||||
{!isCollapsed && <span className="whitespace-nowrap">Daily Report</span>}
|
||||
</Link>
|
||||
|
||||
{isMgmt && (
|
||||
<Link
|
||||
href="/work-reports/review"
|
||||
className={`flex items-center rounded-xl font-bold text-xs transition-all ${
|
||||
isCollapsed ? 'justify-center py-3' : 'px-3 py-2.5'
|
||||
} ${
|
||||
pathname === '/work-reports/review'
|
||||
? 'bg-primary text-white shadow-lg shadow-primary/20'
|
||||
: 'text-text-muted hover:bg-gray-50 hover:text-text-main'
|
||||
}`}
|
||||
>
|
||||
<CheckSquare className={`w-4.5 h-4.5 flex-shrink-0 ${isCollapsed ? '' : 'mr-3'}`} />
|
||||
{!isCollapsed && <span className="whitespace-nowrap">Report Review</span>}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{role === 'ADMIN' && (
|
||||
<Link
|
||||
href="/settings/codes"
|
||||
className={`flex items-center rounded-xl font-bold text-xs transition-all ${
|
||||
isCollapsed ? 'justify-center py-3' : 'px-3 py-2.5'
|
||||
} ${
|
||||
pathname === '/settings/codes'
|
||||
? 'bg-primary text-white shadow-lg shadow-primary/20'
|
||||
: 'text-text-muted hover:bg-gray-50 hover:text-text-main'
|
||||
}`}
|
||||
>
|
||||
<Settings className={`w-4.5 h-4.5 flex-shrink-0 ${isCollapsed ? '' : 'mr-3'}`} />
|
||||
{!isCollapsed && <span className="whitespace-nowrap">Settings</span>}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<Link
|
||||
href="/calendar"
|
||||
className={`flex items-center rounded-xl font-bold text-xs transition-all ${
|
||||
isCollapsed ? 'justify-center py-3' : 'px-3 py-2.5'
|
||||
} ${
|
||||
pathname === '/calendar'
|
||||
? 'bg-primary text-white shadow-lg shadow-primary/20'
|
||||
: 'text-text-muted hover:bg-gray-50 hover:text-text-main'
|
||||
}`}
|
||||
>
|
||||
<CalendarDays className={`w-4.5 h-4.5 flex-shrink-0 ${isCollapsed ? '' : 'mr-3'}`} />
|
||||
{!isCollapsed && <span className="whitespace-nowrap">Calendar</span>}
|
||||
</Link>
|
||||
|
||||
{isMgmt && (
|
||||
<Link
|
||||
href="/employees"
|
||||
className={`flex items-center rounded-xl font-bold text-xs transition-all ${
|
||||
isCollapsed ? 'justify-center py-3' : 'px-3 py-2.5'
|
||||
} ${
|
||||
pathname === '/employees'
|
||||
? 'bg-primary text-white shadow-lg shadow-primary/20'
|
||||
: 'text-text-muted hover:bg-gray-50 hover:text-text-main'
|
||||
}`}
|
||||
>
|
||||
<Users className={`w-4.5 h-4.5 flex-shrink-0 ${isCollapsed ? '' : 'mr-3'}`} />
|
||||
{!isCollapsed && <span className="whitespace-nowrap">Employees</span>}
|
||||
</Link>
|
||||
)}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* 내 프로젝트 */}
|
||||
<div className={`border-t border-border mt-4 pt-6 ${isCollapsed ? 'px-1 py-2' : 'px-4 py-2'}`}>
|
||||
<div className={`flex items-center justify-between mb-4 ${isCollapsed ? 'justify-center' : 'px-2'}`}>
|
||||
{!isCollapsed ? (
|
||||
<>
|
||||
<p className="text-[10px] font-bold text-text-muted tracking-[0.12em] uppercase whitespace-nowrap">My Projects</p>
|
||||
<button onClick={() => router.push('/dispatch')} className="text-text-muted hover:text-text-main">
|
||||
<Plus className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<p className="text-[10px] font-bold text-text-muted uppercase">MY</p>
|
||||
)}
|
||||
</div>
|
||||
<nav className="space-y-1">
|
||||
{projects.map((project) => (
|
||||
<Link
|
||||
key={project.name}
|
||||
href="/dispatch"
|
||||
title={isCollapsed ? project.name : ''}
|
||||
className={`flex items-center hover:bg-gray-50 rounded-xl text-xs font-bold text-text-muted transition-colors ${
|
||||
isCollapsed ? 'justify-center py-2' : 'px-3 py-2'
|
||||
}`}
|
||||
>
|
||||
<span className={`w-2 h-2 rounded-full flex-shrink-0 ${project.dot} ${isCollapsed ? '' : 'mr-3'}`}></span>
|
||||
{!isCollapsed && <span className="whitespace-nowrap">{project.name}</span>}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 하단 정보 카드 */}
|
||||
<div className={`${isCollapsed ? 'p-2' : 'p-6'}`}>
|
||||
{isCollapsed ? (
|
||||
<button className="w-10 h-10 mx-auto bg-primary rounded-xl flex items-center justify-center text-white shadow-soft hover:bg-primary-hover transition-colors">
|
||||
<Lightbulb className="w-5 h-5" />
|
||||
</button>
|
||||
) : (
|
||||
<div className="bg-indigo-50/50 rounded-2xl p-4 border border-indigo-100/50 text-center relative overflow-hidden group">
|
||||
<div className="absolute -top-4 -right-4 w-12 h-12 bg-primary/10 rounded-full opacity-50 blur-xl group-hover:scale-150 transition-transform duration-700"></div>
|
||||
<div className="w-8 h-8 mx-auto bg-white rounded-full flex items-center justify-center shadow-sm mb-3">
|
||||
<span className="text-lg"><Lightbulb className="w-4.5 h-4.5 text-primary" /></span>
|
||||
</div>
|
||||
<h4 className="font-bold text-xs text-slate-800 mb-1.5 whitespace-nowrap">Need Assistance?</h4>
|
||||
<p className="text-[10px] text-slate-500 leading-relaxed mb-4">현장 장비 부족이나 기술적 이슈는 즉시 보고하세요.</p>
|
||||
<button onClick={() => router.push('/issues')} className="w-full py-2 bg-white rounded-xl text-[10px] font-bold text-primary shadow-sm hover:shadow-md transition-all border border-indigo-100">
|
||||
HQ에 긴급 메시지
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
Reference in New Issue
Block a user