From 65bcb6937466b74ef9ea31a75578807a5e272f78 Mon Sep 17 00:00:00 2001 From: theorose49 Date: Sun, 28 Jun 2026 09:38:51 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=A9=94=EC=9D=BC=ED=95=A8=C2=B7?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=82=AC=EC=A7=84=C2=B7=EA=B7=BC?= =?UTF-8?q?=EB=AC=B4=EC=83=81=ED=83=9C=20=EB=93=9C=EB=A1=AD=EB=8B=A4?= =?UTF-8?q?=EC=9A=B4=C2=B7=EC=A0=91=EC=9D=B4=EC=8B=9D=20=EC=82=AC=EC=9D=B4?= =?UTF-8?q?=EB=93=9C=EB=B0=94=20+=20#03143F=20=ED=8C=94=EB=A0=88=ED=8A=B8?= =?UTF-8?q?=20+=20=EC=9D=B8=EC=84=BC=ED=8B=B0=EB=B8=8C=20=EA=B2=8C?= =?UTF-8?q?=EC=9D=B4=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 브랜드 포인트컬러 #03143F로 팔레트 전면 재설정, 회사 로고 흰 wrap 제거+크롭, 로고 블렌딩 - 사이드바 접기/펼치기(localStorage), 로고 아래 근무상태 드롭다운(출근/퇴근/휴식/미팅/이동) - 대시보드 역할 무관 동일(회계/전사 위젯 제거) - 유저 근무화면 단순화(남은연차 소수점·기록·휴가/공가만), 관리자 근무관리(/admin/attendance) - 프로젝트: 관리자 전용 관리창(/admin/projects), 나의 업무는 본인 참여분 read-only - 메일함(/inbox)+탑바 벨(미확인), 프로필(부서·연락처·사진 업로드) - 인센티브 유저: BE/non-BE·환율 숨김, 할당량 세그먼트 게이지(지급완료→반영완료→반영중→예정, 할당량 화살표) Co-Authored-By: Claude Opus 4.8 (1M context) --- src/App.tsx | 6 ++ src/components/AppShell.tsx | 17 +++- src/components/Gantt.tsx | 2 +- src/components/Sidebar.tsx | 70 +++++++++---- src/components/SpinLogo.tsx | 24 +++-- src/components/Topbar.tsx | 55 +++++++++-- src/components/WorkStatusMenu.tsx | 109 ++++++++++++++++++++ src/components/ui.tsx | 2 +- src/index.css | 4 +- src/lib/api.ts | 35 ++++++- src/pages/Attendance.tsx | 88 +++++++---------- src/pages/Dashboard.tsx | 94 ++++++------------ src/pages/Inbox.tsx | 80 +++++++++++++++ src/pages/Incentive.tsx | 148 ++++++++++++++++------------ src/pages/Profile.tsx | 66 +++++++++---- src/pages/Projects.tsx | 18 ++-- src/pages/admin/Accounting.tsx | 2 +- src/pages/admin/AttendanceAdmin.tsx | 97 ++++++++++++++++++ src/pages/admin/Members.tsx | 2 +- src/pages/admin/ProjectsAdmin.tsx | 90 +++++++++++++++++ src/types.ts | 29 ++++++ tailwind.config.js | 11 ++- 22 files changed, 782 insertions(+), 267 deletions(-) create mode 100644 src/components/WorkStatusMenu.tsx create mode 100644 src/pages/Inbox.tsx create mode 100644 src/pages/admin/AttendanceAdmin.tsx create mode 100644 src/pages/admin/ProjectsAdmin.tsx diff --git a/src/App.tsx b/src/App.tsx index 644a187..50e3bd9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,7 +8,10 @@ import { ProjectsPage } from "@/pages/Projects"; import { ProjectDetailPage } from "@/pages/ProjectDetail"; import { IncentivePage } from "@/pages/Incentive"; import { ProfilePage } from "@/pages/Profile"; +import { InboxPage } from "@/pages/Inbox"; import { ApprovalsPage } from "@/pages/admin/Approvals"; +import { AttendanceAdminPage } from "@/pages/admin/AttendanceAdmin"; +import { ProjectsAdminPage } from "@/pages/admin/ProjectsAdmin"; import { IncentiveAdminPage } from "@/pages/admin/IncentiveAdmin"; import { AccountingPage } from "@/pages/admin/Accounting"; import { MembersPage } from "@/pages/admin/Members"; @@ -38,7 +41,10 @@ function Shell() { } /> } /> } /> + } /> } /> + } /> + } /> } /> } /> } /> diff --git a/src/components/AppShell.tsx b/src/components/AppShell.tsx index b44fdc7..bc6ac16 100644 --- a/src/components/AppShell.tsx +++ b/src/components/AppShell.tsx @@ -1,13 +1,26 @@ +import { useState } from "react"; import { Outlet } from "react-router-dom"; import { Sidebar } from "./Sidebar"; import { Topbar } from "./Topbar"; export function AppShell() { + // Sidebar collapse state, persisted so it survives reloads. + const [collapsed, setCollapsed] = useState( + () => localStorage.getItem("spin.sidebarCollapsed") === "1" + ); + const toggle = () => { + setCollapsed((c) => { + const next = !c; + localStorage.setItem("spin.sidebarCollapsed", next ? "1" : "0"); + return next; + }); + }; + return (
- +
- +
diff --git a/src/components/Gantt.tsx b/src/components/Gantt.tsx index 37479f8..f7280ac 100644 --- a/src/components/Gantt.tsx +++ b/src/components/Gantt.tsx @@ -61,7 +61,7 @@ export function Gantt({ tasks }: { tasks: ProjectTask[] }) { const s = parse(t.start), e = parse(t.end) || s + DAY; const left = ((s - min) / span) * 100; const width = Math.max(1.5, ((e - s) / span) * 100); - const color = LANE_COLOR[t.lane] ?? "#11224F"; + const color = LANE_COLOR[t.lane] ?? "#03143F"; return (
{t.title}
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index f0368df..7c2cf60 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,20 +1,23 @@ import { NavLink } from "react-router-dom"; import { LayoutDashboard, Clock, FolderKanban, Coins, CheckSquare, Calculator, - Wallet, Users, Settings, type LucideIcon, + 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"; const ICONS: Record = { - LayoutDashboard, Clock, FolderKanban, Coins, CheckSquare, Calculator, Wallet, Users, Settings, + LayoutDashboard, Clock, FolderKanban, Coins, CheckSquare, Calculator, Wallet, Users, Settings, FolderCog, + Inbox, UserCircle, ClipboardList, }; -export function Sidebar() { +export function Sidebar({ collapsed = false }: { collapsed?: boolean }) { 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 }); @@ -24,15 +27,27 @@ export function Sidebar() { const sections = Array.from(new Set(items.map((i) => i.section))); return ( -