File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
// 3단계 대시보드 시안의 좌측 메뉴. 아직 화면이 있는 것은 기관관리뿐이라
// 나머지 항목은 비활성(준비 중)으로 두고 자리만 잡는다 — 시안과 같은 골격을
// 먼저 세워 두면 다음 모듈(Mattermost 채팅 등)이 붙을 때 메뉴만 활성화하면 된다.
interface MenuItem {
key: string
label: string
enabled: boolean
icon: JSX.Element
}
const ICON_CLASS = 'h-4 w-4 shrink-0'
const MENU: MenuItem[] = [
{
key: 'dashboard',
label: 'Dashboard',
enabled: true,
icon: (
<svg className={ICON_CLASS} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<rect x="3" y="3" width="7" height="7" rx="1" />
<rect x="14" y="3" width="7" height="7" rx="1" />
<rect x="3" y="14" width="7" height="7" rx="1" />
<rect x="14" y="14" width="7" height="7" rx="1" />
</svg>
),
},
{
key: 'channels',
label: '채널관리',
enabled: true,
icon: (
<svg className={ICON_CLASS} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M9 3L7 21M17 3l-2 18M4 8h17M3 16h17" />
</svg>
),
},
{
key: 'orgs',
label: '기관관리',
enabled: true,
icon: (
<svg className={ICON_CLASS} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M3 21h18M5 21V5a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v16M9 8h2M9 12h2M9 16h2M15 10h4v11" />
</svg>
),
},
{
key: 'contacts',
label: '담당자관리',
enabled: true,
icon: (
<svg className={ICON_CLASS} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M16 14a4 4 0 1 0-8 0M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM4 21a8 8 0 0 1 16 0" />
</svg>
),
},
{
key: 'projects',
label: '사업관리',
enabled: true,
icon: (
<svg className={ICON_CLASS} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z" />
</svg>
),
},
{
key: 'rights-check',
label: '권리확인',
enabled: false,
icon: (
<svg className={ICON_CLASS} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M12 3l8 3v6c0 4.5-3.2 7.7-8 9-4.8-1.3-8-4.5-8-9V6l8-3z" />
<path d="M9 12l2 2 4-4" />
</svg>
),
},
{
key: 'rights-process',
label: '권리처리',
enabled: false,
icon: (
<svg className={ICON_CLASS} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M7 3h7l5 5v13a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z" />
<path d="M14 3v5h5M9 13h6M9 17h6" />
</svg>
),
},
{
key: 'files',
label: '자료실',
enabled: false,
icon: (
<svg className={ICON_CLASS} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M3 7h18v4H3zM5 11v9a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-9M10 15h4" />
</svg>
),
},
{
key: 'mattermost',
label: 'Mattermost',
enabled: false,
icon: (
<svg className={ICON_CLASS} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M21 12a8 8 0 1 1-4-6.9L21 4l-1.1 4A8 8 0 0 1 21 12z" />
</svg>
),
},
{
key: 'system',
label: '시스템관리',
enabled: true,
icon: (
<svg className={ICON_CLASS} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="12" cy="12" r="3" />
<path d="M19 12a7 7 0 0 0-.1-1.2l2-1.6-2-3.4-2.4 1a7 7 0 0 0-2-1.2L14 3h-4l-.5 2.6a7 7 0 0 0-2 1.2l-2.4-1-2 3.4 2 1.6a7 7 0 0 0 0 2.4l-2 1.6 2 3.4 2.4-1a7 7 0 0 0 2 1.2L10 21h4l.5-2.6a7 7 0 0 0 2-1.2l2.4 1 2-3.4-2-1.6c.06-.4.1-.8.1-1.2z" />
</svg>
),
},
]
interface SidebarProps {
activeKey?: string
onSelect?: (key: string) => void
collapsed?: boolean
onToggle?: () => void
}
export default function Sidebar({
activeKey = 'orgs',
onSelect,
collapsed = false,
onToggle,
}: SidebarProps) {
return (
<aside
className={`flex shrink-0 flex-col border-r border-gray-200 bg-white transition-all duration-200 ${
collapsed ? 'w-14' : 'w-56'
}`}
>
{collapsed ? (
<div className="flex justify-center pb-4 pt-5" title="ITN-HUB 사업관리시스템">
<span className="flex h-8 w-8 items-center justify-center rounded-md bg-gray-900 text-xs font-bold text-white">
IH
</span>
</div>
) : (
<div className="px-5 pb-4 pt-5">
<p className="text-lg font-bold tracking-tight">ITN-HUB</p>
<p className="text-xs text-gray-500">사업관리시스템</p>
</div>
)}
<nav aria-label="주 메뉴" className="flex-1">
<ul className="space-y-0.5 px-2">
{MENU.map((item) =>
item.enabled ? (
<li key={item.key}>
<a
href="/"
onClick={(e) => {
e.preventDefault()
onSelect?.(item.key)
}}
title={collapsed ? item.label : undefined}
aria-current={item.key === activeKey ? 'page' : undefined}
className={`flex items-center gap-3 rounded-md px-3 py-2 text-sm ${
collapsed ? 'justify-center px-0' : ''
} ${
item.key === activeKey
? 'bg-gray-100 font-medium text-gray-900'
: 'text-gray-700 hover:bg-gray-50'
}`}
>
{item.icon}
{!collapsed && item.label}
</a>
</li>
) : (
<li key={item.key}>
<span
title={collapsed ? `${item.label} (준비 중)` : '준비 중'}
aria-disabled="true"
className={`flex cursor-not-allowed items-center gap-3 rounded-md px-3 py-2 text-sm text-gray-400 ${
collapsed ? 'justify-center px-0' : ''
}`}
>
{item.icon}
{!collapsed && item.label}
</span>
</li>
),
)}
</ul>
</nav>
{onToggle && (
<div className={`border-t border-gray-100 p-2 ${collapsed ? 'flex justify-center' : ''}`}>
<button
type="button"
onClick={onToggle}
aria-label={collapsed ? '메뉴 펼치기' : '메뉴 접기'}
title={collapsed ? '메뉴 펼치기' : '메뉴 접기'}
className={`flex items-center gap-2 rounded-md p-2 text-sm text-gray-500 hover:bg-gray-100 ${
collapsed ? '' : 'w-full'
}`}
>
<svg
className="h-4 w-4"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
{collapsed ? (
<path d="M9 18l6-6-6-6" />
) : (
<path d="M15 18l-6-6 6-6" />
)}
</svg>
{!collapsed && '메뉴 접기'}
</button>
</div>
)}
</aside>
)
}