theorose49 e3b5a874b3
All checks were successful
build-and-push / build (push) Successful in 31s
feat: 전 화면 반응형(모바일) + 인턴 직급 + 초과근무 탭 제거 + 로그아웃 URL + 로고 정렬
- 모바일 셸: 하단 탭바 + 더보기 드로어, 사이드바/탑바 반응형, safe-area, 폼/모달/테이블 반응형
- 근무: 유저 초과근무 탭 제거(관리자만 집계), 승인 관리 초과근무 섹션 제거
- 직급 인턴 추가, 직책 필드 제거, 부서는 관리자 설정→드롭다운(기존)
- 로그아웃: /me의 LOGOUT_URL 사용(SSO 완전 로그아웃), 회사 로고 가운데 정렬
- 디바이스 등록(FCM)·계정 메뉴·계정 설정 (이전 커밋 포함)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 10:55:54 +09:00

106 lines
4.7 KiB
TypeScript

import { NavLink } from "react-router-dom";
import {
LayoutDashboard, Clock, FolderKanban, Coins, CheckSquare, Calculator,
Wallet, Users, Settings, FolderCog, Inbox, UserCircle, ClipboardList,
type LucideIcon,
} from "lucide-react";
import { useQuery } from "@tanstack/react-query";
import { getNav, getApprovals } from "@/lib/api";
import { useAuth } from "@/context/Auth";
import { SpinLogo } from "./SpinLogo";
import { WorkStatusMenu } from "./WorkStatusMenu";
import { classNames } from "@/lib/format";
import type { NavItem } from "@/types";
export const ICONS: Record<string, LucideIcon> = {
LayoutDashboard, Clock, FolderKanban, Coins, CheckSquare, Calculator, Wallet, Users, Settings, FolderCog,
Inbox, UserCircle, ClipboardList,
};
export function Sidebar({ collapsed = false, className }: { collapsed?: boolean; className?: string }) {
const { isAdmin } = useAuth();
const navQ = useQuery({ queryKey: ["nav"], queryFn: getNav, staleTime: 5 * 60_000 });
const apprQ = useQuery({ queryKey: ["approvals-count"], queryFn: getApprovals, enabled: isAdmin, staleTime: 60_000 });
const approvalCount = (apprQ.data?.leave.length ?? 0) + (apprQ.data?.overtime.length ?? 0);
const items = navQ.data ?? [];
const sections = Array.from(new Set(items.map((i) => i.section)));
return (
<aside
className={classNames(
"shrink-0 bg-navy-sidebar text-white flex flex-col h-screen sticky top-0 transition-[width] duration-200 ease-out",
collapsed ? "w-[68px]" : "w-60",
className
)}
>
<div className={classNames("pt-6 pb-4", collapsed ? "px-0 flex justify-center" : "px-5")}>
<SpinLogo variant="light" markOnly={collapsed} />
</div>
{/* 출근/퇴근/휴식/미팅/이동 빠른 상태 설정 (로고 바로 아래) */}
<div className={classNames("pb-3", collapsed ? "px-2" : "px-5")}>
<WorkStatusMenu collapsed={collapsed} />
</div>
<nav className={classNames("flex-1 overflow-y-auto overflow-x-hidden pb-4", collapsed ? "px-2 space-y-2" : "px-3 space-y-4")}>
{sections.map((section, si) => (
<div key={section}>
{collapsed
? si > 0 && <div className="mx-2 my-2 h-px bg-white/10" />
: <div className="px-3 text-[10px] uppercase tracking-widest text-white/35 mb-1.5">{section}</div>}
<div className="space-y-0.5">
{items.filter((i) => i.section === section).map((item: NavItem) => {
const Icon = ICONS[item.icon] ?? LayoutDashboard;
return (
<NavLink
key={item.key}
to={item.path}
end={item.path === "/"}
title={collapsed ? item.label : undefined}
className={({ isActive }) =>
classNames(
"relative flex items-center rounded-control text-sm font-medium transition-colors",
collapsed ? "justify-center h-10" : "gap-3 px-3 py-2",
isActive ? "bg-white/10 text-white" : "text-white/60 hover:text-white hover:bg-white/5"
)
}
>
<Icon size={18} strokeWidth={2} />
{!collapsed && <span className="flex-1">{item.label}</span>}
{item.key === "approvals" && approvalCount > 0 && (
collapsed ? (
<span className="absolute top-1.5 right-1.5 w-2 h-2 rounded-full bg-[#C99A2E]" />
) : (
<span className="font-num text-[11px] font-bold bg-[#C99A2E] text-[#1A1305] rounded-pill min-w-[20px] h-5 px-1.5 flex items-center justify-center">
{approvalCount}
</span>
)
)}
</NavLink>
);
})}
</div>
</div>
))}
</nav>
{!collapsed && (
<div className="px-5 py-4 border-t border-white/10 flex flex-col items-center">
<div className="text-[10px] uppercase tracking-widest text-white/40 mb-2 text-center">Powered by</div>
{/* Logo sits directly on the rail (sidebar shares the logo's #03143F
navy), cropped 1px left / 3px bottom and scaled up. Centered. */}
<div className="overflow-hidden mx-auto" style={{ width: 143, height: 37 }}>
<img
src="/special-partners.jpg"
alt="Special Partners"
className="block"
style={{ height: 40, width: "auto", maxWidth: "none", marginLeft: -1 }}
/>
</div>
</div>
)}
</aside>
);
}