UI 전면 리디자인: Vivid Blue 모던 SaaS 스타일 전환
- globals.css: primary를 dark navy(11.2%)에서 vivid blue(#3B82F6)로 전환 - button.tsx: default variant를 진짜 CTA(bg-primary)로 변경 - login: AI 패턴 제거 (장식원, backdrop-blur, 무의미한 그라데이션) - dashboard: 그라데이션 배너 → 간결한 헤더, 카드별 고유 색상 - sidebar: 활성메뉴 좌측 인디케이터바, bg-primary/10 남용 제거 Made-with: Cursor
This commit is contained in:
parent
7910921c97
commit
dde073720a
|
|
@ -5,23 +5,13 @@ import { LoginHeader } from "@/components/auth/LoginHeader";
|
|||
import { LoginForm } from "@/components/auth/LoginForm";
|
||||
import { LoginFooter } from "@/components/auth/LoginFooter";
|
||||
|
||||
/**
|
||||
* 로그인 페이지 컴포넌트
|
||||
* 비즈니스 로직은 useLogin 훅에서 처리하고, UI 컴포넌트들을 조합하여 구성
|
||||
*/
|
||||
export default function LoginPage() {
|
||||
const { formData, isLoading, error, showPassword, handleInputChange, handleLogin, togglePasswordVisibility } =
|
||||
useLogin();
|
||||
|
||||
return (
|
||||
<div className="relative flex min-h-screen items-center justify-center bg-gradient-to-br from-background to-muted p-4">
|
||||
{/* 배경 장식 원 */}
|
||||
<div className="absolute inset-0 overflow-hidden">
|
||||
<div className="absolute -top-40 -right-40 h-80 w-80 rounded-full bg-primary/5" />
|
||||
<div className="absolute -bottom-40 -left-40 h-80 w-80 rounded-full bg-primary/5" />
|
||||
</div>
|
||||
|
||||
<div className="relative w-full max-w-md space-y-8">
|
||||
<div className="flex min-h-screen flex-col items-center justify-center bg-muted/40 p-4">
|
||||
<div className="w-full max-w-md space-y-6">
|
||||
<LoginHeader />
|
||||
|
||||
<LoginForm
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@
|
|||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { FileCheck, Menu, Users, Bell, FileText, Layout, Server, Shield, Calendar } from "lucide-react";
|
||||
import { FileCheck, Menu, Users, Bell, FileText, Layout, Server, Shield, Calendar, ArrowRight } from "lucide-react";
|
||||
|
||||
const quickAccessItems = [
|
||||
{ label: "결재함", icon: FileCheck, href: "/admin/approvalBox" },
|
||||
{ label: "메뉴 관리", icon: Menu, href: "/admin/menu" },
|
||||
{ label: "사용자 관리", icon: Users, href: "/admin/userMng" },
|
||||
{ label: "공지사항", icon: Bell, href: "/admin/system-notices" },
|
||||
{ label: "감사 로그", icon: FileText, href: "/admin/audit-log" },
|
||||
{ label: "화면 관리", icon: Layout, href: "/admin/screenMng" },
|
||||
{ label: "결재함", icon: FileCheck, href: "/admin/approvalBox", color: "text-blue-600 bg-blue-50" },
|
||||
{ label: "메뉴 관리", icon: Menu, href: "/admin/menu", color: "text-violet-600 bg-violet-50" },
|
||||
{ label: "사용자 관리", icon: Users, href: "/admin/userMng", color: "text-emerald-600 bg-emerald-50" },
|
||||
{ label: "공지사항", icon: Bell, href: "/admin/system-notices", color: "text-amber-600 bg-amber-50" },
|
||||
{ label: "감사 로그", icon: FileText, href: "/admin/audit-log", color: "text-rose-600 bg-rose-50" },
|
||||
{ label: "화면 관리", icon: Layout, href: "/admin/screenMng", color: "text-cyan-600 bg-cyan-50" },
|
||||
];
|
||||
|
||||
export default function MainHomePage() {
|
||||
|
|
@ -18,35 +18,35 @@ export default function MainHomePage() {
|
|||
const { user } = useAuth();
|
||||
|
||||
const userName = user?.userName || "사용자";
|
||||
const today = new Date();
|
||||
const dateStr = today.toLocaleDateString("ko-KR", { year: "numeric", month: "long", day: "numeric", weekday: "long" });
|
||||
|
||||
return (
|
||||
<div className="space-y-6 p-4 sm:p-6">
|
||||
{/* 환영 영역 */}
|
||||
<div className="rounded-xl bg-gradient-to-r from-primary/10 via-primary/5 to-transparent p-6 sm:p-8">
|
||||
<h1 className="text-xl font-bold sm:text-2xl">
|
||||
안녕하세요, {userName}님
|
||||
<div className="space-y-6 p-4 sm:p-6 lg:p-8">
|
||||
{/* 헤더 영역 */}
|
||||
<div className="flex flex-col gap-1">
|
||||
<h1 className="text-2xl font-bold tracking-tight sm:text-3xl">
|
||||
{userName}님, 좋은 하루 되세요
|
||||
</h1>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
오늘도 효율적인 업무를 시작하세요.
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">{dateStr}</p>
|
||||
</div>
|
||||
|
||||
{/* 퀵 액세스 카드 */}
|
||||
{/* 바로가기 */}
|
||||
<div>
|
||||
<h2 className="mb-4 text-lg font-semibold">바로가기</h2>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4">
|
||||
<h2 className="mb-3 text-base font-semibold text-foreground">바로가기</h2>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-6">
|
||||
{quickAccessItems.map((item) => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<button
|
||||
key={item.href}
|
||||
onClick={() => router.push(item.href)}
|
||||
className="group flex flex-col items-center gap-3 rounded-xl border bg-card p-4 shadow-sm transition-all hover:border-primary/30 hover:shadow-md sm:p-6"
|
||||
className="group flex flex-col items-center gap-2.5 rounded-lg border bg-card p-4 transition-all hover:shadow-md"
|
||||
>
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 text-primary transition-colors group-hover:bg-primary/20 sm:h-12 sm:w-12">
|
||||
<Icon className="h-5 w-5 sm:h-6 sm:w-6" />
|
||||
<div className={`flex h-10 w-10 items-center justify-center rounded-lg ${item.color} transition-transform group-hover:scale-105`}>
|
||||
<Icon className="h-5 w-5" />
|
||||
</div>
|
||||
<span className="text-xs font-medium sm:text-sm">{item.label}</span>
|
||||
<span className="text-xs font-medium text-foreground">{item.label}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
|
@ -54,11 +54,11 @@ export default function MainHomePage() {
|
|||
</div>
|
||||
|
||||
{/* 시스템 정보 */}
|
||||
<div className="rounded-xl border bg-card p-4 shadow-sm sm:p-6">
|
||||
<h2 className="mb-3 text-lg font-semibold">시스템 정보</h2>
|
||||
<div className="rounded-lg border bg-card p-4 sm:p-5">
|
||||
<h2 className="mb-3 text-base font-semibold text-foreground">시스템 정보</h2>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-muted">
|
||||
<div className="flex h-9 w-9 items-center justify-center rounded-md bg-muted">
|
||||
<Server className="h-4 w-4 text-muted-foreground" />
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -67,7 +67,7 @@ export default function MainHomePage() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-muted">
|
||||
<div className="flex h-9 w-9 items-center justify-center rounded-md bg-muted">
|
||||
<Shield className="h-4 w-4 text-muted-foreground" />
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -76,13 +76,13 @@ export default function MainHomePage() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-muted">
|
||||
<div className="flex h-9 w-9 items-center justify-center rounded-md bg-muted">
|
||||
<Calendar className="h-4 w-4 text-muted-foreground" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">오늘 날짜</p>
|
||||
<p className="text-sm font-medium">
|
||||
{new Date().toLocaleDateString("ko-KR", { year: "numeric", month: "long", day: "numeric" })}
|
||||
{today.toLocaleDateString("ko-KR", { year: "numeric", month: "long", day: "numeric" })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -65,28 +65,28 @@
|
|||
--radius-xl: calc(var(--radius) + 4px);
|
||||
}
|
||||
|
||||
/* ===== CSS Variables (shadcn/ui Official) ===== */
|
||||
/* ===== CSS Variables (Vivid Blue Theme) ===== */
|
||||
:root {
|
||||
/* Light Theme Colors - HSL Format */
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--foreground: 224 71% 4%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
--card-foreground: 224 71% 4%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
--popover-foreground: 224 71% 4%;
|
||||
--primary: 217.2 91.2% 59.8%;
|
||||
--primary-foreground: 0 0% 100%;
|
||||
--secondary: 220 14.3% 95.9%;
|
||||
--secondary-foreground: 220.9 39.3% 11%;
|
||||
--muted: 220 14.3% 95.9%;
|
||||
--muted-foreground: 220 8.9% 46.1%;
|
||||
--accent: 220 14.3% 95.9%;
|
||||
--accent-foreground: 220.9 39.3% 11%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 222.2 84% 4.9%;
|
||||
--destructive-foreground: 0 0% 100%;
|
||||
--border: 220 13% 91%;
|
||||
--input: 220 13% 91%;
|
||||
--ring: 217.2 91.2% 59.8%;
|
||||
|
||||
/* Success Colors (Emerald) */
|
||||
--success: 142 76% 36%;
|
||||
|
|
@ -111,37 +111,37 @@
|
|||
--radius: 0.5rem;
|
||||
|
||||
/* Sidebar Colors */
|
||||
--sidebar-background: 0 0% 98%;
|
||||
--sidebar-foreground: 240 5.3% 26.1%;
|
||||
--sidebar-primary: 240 5.9% 10%;
|
||||
--sidebar-primary-foreground: 0 0% 98%;
|
||||
--sidebar-accent: 240 4.8% 95.9%;
|
||||
--sidebar-accent-foreground: 240 5.9% 10%;
|
||||
--sidebar-background: 220 20% 97%;
|
||||
--sidebar-foreground: 224 71% 4%;
|
||||
--sidebar-primary: 217.2 91.2% 59.8%;
|
||||
--sidebar-primary-foreground: 0 0% 100%;
|
||||
--sidebar-accent: 220 14.3% 95.9%;
|
||||
--sidebar-accent-foreground: 220.9 39.3% 11%;
|
||||
--sidebar-border: 220 13% 91%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
}
|
||||
|
||||
/* ===== Dark Theme ===== */
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
--background: 224 71% 4%;
|
||||
--foreground: 210 20% 98%;
|
||||
--card: 224 71% 4%;
|
||||
--card-foreground: 210 20% 98%;
|
||||
--popover: 224 71% 4%;
|
||||
--popover-foreground: 210 20% 98%;
|
||||
--primary: 217.2 91.2% 59.8%;
|
||||
--primary-foreground: 0 0% 100%;
|
||||
--secondary: 215 27.9% 16.9%;
|
||||
--secondary-foreground: 210 20% 98%;
|
||||
--muted: 215 27.9% 16.9%;
|
||||
--muted-foreground: 217.9 10.6% 64.9%;
|
||||
--accent: 215 27.9% 16.9%;
|
||||
--accent-foreground: 210 20% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
--destructive-foreground: 210 20% 98%;
|
||||
--border: 215 27.9% 16.9%;
|
||||
--input: 215 27.9% 16.9%;
|
||||
--ring: 217.2 91.2% 59.8%;
|
||||
|
||||
/* Success Colors (Emerald) - Dark */
|
||||
--success: 142 76% 36%;
|
||||
|
|
@ -163,13 +163,13 @@
|
|||
--chart-5: 340 75% 55%;
|
||||
|
||||
/* Sidebar Colors - Dark */
|
||||
--sidebar-background: 222.2 84% 4.9%;
|
||||
--sidebar-foreground: 210 40% 98%;
|
||||
--sidebar-background: 224 71% 4%;
|
||||
--sidebar-foreground: 210 20% 98%;
|
||||
--sidebar-primary: 217.2 91.2% 59.8%;
|
||||
--sidebar-primary-foreground: 222.2 47.4% 11.2%;
|
||||
--sidebar-accent: 217.2 32.6% 17.5%;
|
||||
--sidebar-accent-foreground: 210 40% 98%;
|
||||
--sidebar-border: 217.2 32.6% 17.5%;
|
||||
--sidebar-primary-foreground: 0 0% 100%;
|
||||
--sidebar-accent: 215 27.9% 16.9%;
|
||||
--sidebar-accent-foreground: 210 20% 98%;
|
||||
--sidebar-border: 215 27.9% 16.9%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export function LoginForm({
|
|||
onTogglePassword,
|
||||
}: LoginFormProps) {
|
||||
return (
|
||||
<Card className="border-0 shadow-xl bg-card/80 backdrop-blur-sm">
|
||||
<Card className="border shadow-lg">
|
||||
<CardHeader className="space-y-1">
|
||||
<CardTitle className="text-center text-2xl">로그인</CardTitle>
|
||||
<CardDescription className="text-center">계정 정보를 입력해주세요</CardDescription>
|
||||
|
|
|
|||
|
|
@ -407,11 +407,11 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|||
return (
|
||||
<div key={menu.id}>
|
||||
<div
|
||||
className={`group flex min-h-[44px] sm:min-h-[40px] cursor-pointer items-center justify-between rounded-lg px-3 py-2 text-sm font-medium transition-colors duration-150 ease-in-out ${
|
||||
className={`group flex min-h-[44px] sm:min-h-[40px] cursor-pointer items-center justify-between rounded-md px-3 py-2 text-sm font-medium transition-colors duration-150 ease-in-out ${
|
||||
pathname === menu.url
|
||||
? "bg-primary/10 text-primary font-semibold"
|
||||
? "border-l-3 border-primary bg-primary/8 text-primary font-semibold"
|
||||
: isExpanded
|
||||
? "bg-accent text-foreground"
|
||||
? "bg-accent/60 text-foreground"
|
||||
: "text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
} ${level > 0 ? "ml-6" : ""}`}
|
||||
onClick={() => handleMenuClick(menu)}
|
||||
|
|
@ -435,9 +435,9 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|||
{menu.children?.map((child: any) => (
|
||||
<div
|
||||
key={child.id}
|
||||
className={`flex min-h-[44px] sm:min-h-[40px] cursor-pointer items-center rounded-lg px-3 py-2 text-sm transition-colors duration-150 hover:cursor-pointer ${
|
||||
className={`flex min-h-[44px] sm:min-h-[40px] cursor-pointer items-center rounded-md px-3 py-2 text-sm transition-colors duration-150 hover:cursor-pointer ${
|
||||
pathname === child.url
|
||||
? "bg-primary/10 text-primary font-semibold"
|
||||
? "border-l-3 border-primary bg-primary/8 text-primary font-semibold"
|
||||
: "text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
}`}
|
||||
onClick={() => handleMenuClick(child)}
|
||||
|
|
@ -552,7 +552,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|||
isMobile
|
||||
? (sidebarOpen ? "translate-x-0" : "-translate-x-full") + " fixed top-14 left-0 z-40 h-[calc(100vh-56px)]"
|
||||
: "relative z-auto h-screen translate-x-0"
|
||||
} flex w-[260px] sm:w-[220px] lg:w-[240px] flex-col border-r border-border bg-background transition-transform duration-300`}
|
||||
} flex w-[260px] sm:w-[220px] lg:w-[240px] flex-col border-r border-sidebar-border bg-sidebar transition-transform duration-300`}
|
||||
>
|
||||
{/* 사이드바 최상단 - 로고 (데스크톱에서만 표시) */}
|
||||
{!isMobile && (
|
||||
|
|
@ -563,7 +563,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|||
|
||||
{/* WACE 관리자: 현재 관리 회사 표시 */}
|
||||
{(user as ExtendedUserInfo)?.userType === "SUPER_ADMIN" && (
|
||||
<div className="mx-3 mt-3 rounded-lg border bg-gradient-to-r from-primary/10 to-primary/5 p-3">
|
||||
<div className="mx-3 mt-3 rounded-md border border-border bg-muted/50 p-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Building2 className="h-4 w-4 shrink-0 text-primary" />
|
||||
<div className="min-w-0 flex-1">
|
||||
|
|
@ -584,10 +584,10 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|||
{/* 관리자/사용자 메뉴 전환 */}
|
||||
<Button
|
||||
onClick={handleModeSwitch}
|
||||
className={`flex w-full items-center justify-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-colors duration-150 hover:cursor-pointer ${
|
||||
className={`flex w-full items-center justify-center gap-2 rounded-md px-3 py-2 text-sm font-medium transition-colors duration-150 hover:cursor-pointer ${
|
||||
isAdminMode
|
||||
? "border border-warning/20 bg-warning/10 text-warning hover:bg-warning/20"
|
||||
: "border border-primary/20 bg-primary/10 text-primary hover:bg-primary/20"
|
||||
? "border border-amber-200 bg-amber-50 text-amber-700 hover:bg-amber-100 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-400"
|
||||
: "border border-primary/20 bg-primary/5 text-primary hover:bg-primary/10"
|
||||
}`}
|
||||
>
|
||||
{isAdminMode ? (
|
||||
|
|
@ -607,7 +607,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|||
{(user as ExtendedUserInfo)?.userType === "SUPER_ADMIN" && (
|
||||
<Button
|
||||
onClick={() => { console.log("🔴 회사 선택 버튼 클릭!"); setShowCompanySwitcher(true); }}
|
||||
className="flex w-full items-center justify-center gap-2 rounded-lg border border-primary/20 bg-primary/10 px-3 py-2 text-sm font-medium text-primary transition-colors duration-150 hover:cursor-pointer hover:bg-primary/20"
|
||||
className="flex w-full items-center justify-center gap-2 rounded-md border border-primary/20 bg-primary/5 px-3 py-2 text-sm font-medium text-primary transition-colors duration-150 hover:cursor-pointer hover:bg-primary/10"
|
||||
>
|
||||
<Building2 className="h-4 w-4" />
|
||||
회사 선택
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@ const buttonVariants = cva(
|
|||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-background border border-foreground text-foreground shadow-xs hover:bg-muted/50",
|
||||
default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||
outline:
|
||||
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||
"border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||
secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
|
|
|
|||
Loading…
Reference in New Issue