pop화면
This commit is contained in:
35
frontend/components/pop/dashboard/ActivityList.tsx
Normal file
35
frontend/components/pop/dashboard/ActivityList.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { ActivityItem } from "./types";
|
||||
|
||||
interface ActivityListProps {
|
||||
items: ActivityItem[];
|
||||
onMoreClick: () => void;
|
||||
}
|
||||
|
||||
export function ActivityList({ items, onMoreClick }: ActivityListProps) {
|
||||
return (
|
||||
<div className="pop-dashboard-card">
|
||||
<div className="pop-dashboard-card-header">
|
||||
<h3 className="pop-dashboard-card-title">최근 활동</h3>
|
||||
<button className="pop-dashboard-btn-more" onClick={onMoreClick}>
|
||||
전체보기
|
||||
</button>
|
||||
</div>
|
||||
<div className="pop-dashboard-activity-list">
|
||||
{items.map((item) => (
|
||||
<div key={item.id} className="pop-dashboard-activity-item">
|
||||
<span className="pop-dashboard-activity-time">{item.time}</span>
|
||||
<span className={`pop-dashboard-activity-dot ${item.category}`} />
|
||||
<div className="pop-dashboard-activity-content">
|
||||
<div className="pop-dashboard-activity-title">{item.title}</div>
|
||||
<div className="pop-dashboard-activity-desc">{item.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
24
frontend/components/pop/dashboard/DashboardFooter.tsx
Normal file
24
frontend/components/pop/dashboard/DashboardFooter.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
|
||||
interface DashboardFooterProps {
|
||||
companyName: string;
|
||||
version: string;
|
||||
emergencyContact: string;
|
||||
}
|
||||
|
||||
export function DashboardFooter({
|
||||
companyName,
|
||||
version,
|
||||
emergencyContact,
|
||||
}: DashboardFooterProps) {
|
||||
return (
|
||||
<footer className="pop-dashboard-footer">
|
||||
<span>© 2024 {companyName}</span>
|
||||
<span>Version {version}</span>
|
||||
<span>긴급연락: {emergencyContact}</span>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
96
frontend/components/pop/dashboard/DashboardHeader.tsx
Normal file
96
frontend/components/pop/dashboard/DashboardHeader.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Moon, Sun } from "lucide-react";
|
||||
import { WeatherInfo, UserInfo, CompanyInfo } from "./types";
|
||||
|
||||
interface DashboardHeaderProps {
|
||||
theme: "dark" | "light";
|
||||
weather: WeatherInfo;
|
||||
user: UserInfo;
|
||||
company: CompanyInfo;
|
||||
onThemeToggle: () => void;
|
||||
onUserClick: () => void;
|
||||
}
|
||||
|
||||
export function DashboardHeader({
|
||||
theme,
|
||||
weather,
|
||||
user,
|
||||
company,
|
||||
onThemeToggle,
|
||||
onUserClick,
|
||||
}: DashboardHeaderProps) {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [currentTime, setCurrentTime] = useState(new Date());
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
const timer = setInterval(() => {
|
||||
setCurrentTime(new Date());
|
||||
}, 1000);
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
const formatTime = (date: Date) => {
|
||||
const hours = String(date.getHours()).padStart(2, "0");
|
||||
const minutes = String(date.getMinutes()).padStart(2, "0");
|
||||
const seconds = String(date.getSeconds()).padStart(2, "0");
|
||||
return `${hours}:${minutes}:${seconds}`;
|
||||
};
|
||||
|
||||
const formatDate = (date: Date) => {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
return `${year}-${month}-${day}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="pop-dashboard-header">
|
||||
<div className="pop-dashboard-header-left">
|
||||
<div className="pop-dashboard-time-display">
|
||||
<div className="pop-dashboard-time-main">
|
||||
{mounted ? formatTime(currentTime) : "--:--:--"}
|
||||
</div>
|
||||
<div className="pop-dashboard-time-date">
|
||||
{mounted ? formatDate(currentTime) : "----.--.--"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pop-dashboard-header-right">
|
||||
{/* 테마 토글 */}
|
||||
<button
|
||||
className="pop-dashboard-theme-toggle"
|
||||
onClick={onThemeToggle}
|
||||
title="테마 변경"
|
||||
>
|
||||
{theme === "dark" ? <Moon size={16} /> : <Sun size={16} />}
|
||||
</button>
|
||||
|
||||
{/* 날씨 정보 */}
|
||||
<div className="pop-dashboard-weather">
|
||||
<span className="pop-dashboard-weather-temp">{weather.temp}</span>
|
||||
<span className="pop-dashboard-weather-desc">{weather.description}</span>
|
||||
</div>
|
||||
|
||||
{/* 회사 정보 */}
|
||||
<div className="pop-dashboard-company">
|
||||
<div className="pop-dashboard-company-name">{company.name}</div>
|
||||
<div className="pop-dashboard-company-sub">{company.subTitle}</div>
|
||||
</div>
|
||||
|
||||
{/* 사용자 배지 */}
|
||||
<button className="pop-dashboard-user-badge" onClick={onUserClick}>
|
||||
<div className="pop-dashboard-user-avatar">{user.avatar}</div>
|
||||
<div className="pop-dashboard-user-text">
|
||||
<div className="pop-dashboard-user-name">{user.name}</div>
|
||||
<div className="pop-dashboard-user-role">{user.role}</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
60
frontend/components/pop/dashboard/KpiBar.tsx
Normal file
60
frontend/components/pop/dashboard/KpiBar.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { KpiItem } from "./types";
|
||||
|
||||
interface KpiBarProps {
|
||||
items: KpiItem[];
|
||||
}
|
||||
|
||||
export function KpiBar({ items }: KpiBarProps) {
|
||||
const getStrokeDashoffset = (percentage: number) => {
|
||||
const circumference = 264; // 2 * PI * 42
|
||||
return circumference - (circumference * percentage) / 100;
|
||||
};
|
||||
|
||||
const formatValue = (value: number) => {
|
||||
if (value >= 1000) {
|
||||
return value.toLocaleString();
|
||||
}
|
||||
return value.toString();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="pop-dashboard-kpi-bar">
|
||||
{items.map((item) => (
|
||||
<div key={item.id} className="pop-dashboard-kpi-item">
|
||||
<div className="pop-dashboard-kpi-gauge">
|
||||
<svg viewBox="0 0 100 100" width="52" height="52">
|
||||
<circle
|
||||
className="pop-dashboard-kpi-gauge-bg"
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="42"
|
||||
/>
|
||||
<circle
|
||||
className={`pop-dashboard-kpi-gauge-fill kpi-color-${item.color}`}
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="42"
|
||||
strokeDasharray="264"
|
||||
strokeDashoffset={getStrokeDashoffset(item.percentage)}
|
||||
/>
|
||||
</svg>
|
||||
<span className={`pop-dashboard-kpi-gauge-text kpi-color-${item.color}`}>
|
||||
{item.percentage}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="pop-dashboard-kpi-info">
|
||||
<div className="pop-dashboard-kpi-label">{item.label}</div>
|
||||
<div className={`pop-dashboard-kpi-value kpi-color-${item.color}`}>
|
||||
{formatValue(item.value)}
|
||||
<span className="pop-dashboard-kpi-unit">{item.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
43
frontend/components/pop/dashboard/MenuGrid.tsx
Normal file
43
frontend/components/pop/dashboard/MenuGrid.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { MenuItem } from "./types";
|
||||
|
||||
interface MenuGridProps {
|
||||
items: MenuItem[];
|
||||
}
|
||||
|
||||
export function MenuGrid({ items }: MenuGridProps) {
|
||||
const router = useRouter();
|
||||
|
||||
const handleClick = (item: MenuItem) => {
|
||||
if (item.href === "#") {
|
||||
alert(`${item.title} 화면은 준비 중입니다.`);
|
||||
} else {
|
||||
router.push(item.href);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="pop-dashboard-menu-grid">
|
||||
{items.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className={`pop-dashboard-menu-card ${item.category}`}
|
||||
onClick={() => handleClick(item)}
|
||||
>
|
||||
<div className="pop-dashboard-menu-header">
|
||||
<div className="pop-dashboard-menu-title">{item.title}</div>
|
||||
<div className={`pop-dashboard-menu-count ${item.category}`}>
|
||||
{item.count}
|
||||
</div>
|
||||
</div>
|
||||
<div className="pop-dashboard-menu-desc">{item.description}</div>
|
||||
<div className="pop-dashboard-menu-status">{item.status}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
22
frontend/components/pop/dashboard/NoticeBanner.tsx
Normal file
22
frontend/components/pop/dashboard/NoticeBanner.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
|
||||
interface NoticeBannerProps {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export function NoticeBanner({ text }: NoticeBannerProps) {
|
||||
return (
|
||||
<div className="pop-dashboard-notice-banner">
|
||||
<div className="pop-dashboard-notice-label">공지</div>
|
||||
<div className="pop-dashboard-notice-content">
|
||||
<div className="pop-dashboard-notice-marquee">
|
||||
<span className="pop-dashboard-notice-text">{text}</span>
|
||||
<span className="pop-dashboard-notice-text">{text}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
31
frontend/components/pop/dashboard/NoticeList.tsx
Normal file
31
frontend/components/pop/dashboard/NoticeList.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { NoticeItem } from "./types";
|
||||
|
||||
interface NoticeListProps {
|
||||
items: NoticeItem[];
|
||||
onMoreClick: () => void;
|
||||
}
|
||||
|
||||
export function NoticeList({ items, onMoreClick }: NoticeListProps) {
|
||||
return (
|
||||
<div className="pop-dashboard-card">
|
||||
<div className="pop-dashboard-card-header">
|
||||
<h3 className="pop-dashboard-card-title">공지사항</h3>
|
||||
<button className="pop-dashboard-btn-more" onClick={onMoreClick}>
|
||||
더보기
|
||||
</button>
|
||||
</div>
|
||||
<div className="pop-dashboard-notice-list">
|
||||
{items.map((item) => (
|
||||
<div key={item.id} className="pop-dashboard-notice-item">
|
||||
<div className="pop-dashboard-notice-title">{item.title}</div>
|
||||
<div className="pop-dashboard-notice-date">{item.date}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
83
frontend/components/pop/dashboard/PopDashboard.tsx
Normal file
83
frontend/components/pop/dashboard/PopDashboard.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { DashboardHeader } from "./DashboardHeader";
|
||||
import { NoticeBanner } from "./NoticeBanner";
|
||||
import { KpiBar } from "./KpiBar";
|
||||
import { MenuGrid } from "./MenuGrid";
|
||||
import { ActivityList } from "./ActivityList";
|
||||
import { NoticeList } from "./NoticeList";
|
||||
import { DashboardFooter } from "./DashboardFooter";
|
||||
import {
|
||||
KPI_ITEMS,
|
||||
MENU_ITEMS,
|
||||
ACTIVITY_ITEMS,
|
||||
NOTICE_ITEMS,
|
||||
NOTICE_MARQUEE_TEXT,
|
||||
} from "./data";
|
||||
import "./dashboard.css";
|
||||
|
||||
export function PopDashboard() {
|
||||
const [theme, setTheme] = useState<"dark" | "light">("dark");
|
||||
|
||||
// 로컬 스토리지에서 테마 로드
|
||||
useEffect(() => {
|
||||
const savedTheme = localStorage.getItem("popTheme") as "dark" | "light" | null;
|
||||
if (savedTheme) {
|
||||
setTheme(savedTheme);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleThemeToggle = () => {
|
||||
const newTheme = theme === "dark" ? "light" : "dark";
|
||||
setTheme(newTheme);
|
||||
localStorage.setItem("popTheme", newTheme);
|
||||
};
|
||||
|
||||
const handleUserClick = () => {
|
||||
if (confirm("로그아웃 하시겠습니까?")) {
|
||||
alert("로그아웃되었습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const handleActivityMore = () => {
|
||||
alert("전체 활동 내역 화면으로 이동합니다.");
|
||||
};
|
||||
|
||||
const handleNoticeMore = () => {
|
||||
alert("전체 공지사항 화면으로 이동합니다.");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`pop-dashboard-container ${theme === "light" ? "light" : ""}`}>
|
||||
<div className="pop-dashboard">
|
||||
<DashboardHeader
|
||||
theme={theme}
|
||||
weather={{ temp: "18°C", description: "맑음" }}
|
||||
user={{ name: "김철수", role: "생산1팀", avatar: "김" }}
|
||||
company={{ name: "탑씰", subTitle: "현장 관리 시스템" }}
|
||||
onThemeToggle={handleThemeToggle}
|
||||
onUserClick={handleUserClick}
|
||||
/>
|
||||
|
||||
<NoticeBanner text={NOTICE_MARQUEE_TEXT} />
|
||||
|
||||
<KpiBar items={KPI_ITEMS} />
|
||||
|
||||
<MenuGrid items={MENU_ITEMS} />
|
||||
|
||||
<div className="pop-dashboard-bottom-section">
|
||||
<ActivityList items={ACTIVITY_ITEMS} onMoreClick={handleActivityMore} />
|
||||
<NoticeList items={NOTICE_ITEMS} onMoreClick={handleNoticeMore} />
|
||||
</div>
|
||||
|
||||
<DashboardFooter
|
||||
companyName="탑씰"
|
||||
version="1.0.0"
|
||||
emergencyContact="042-XXX-XXXX"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
906
frontend/components/pop/dashboard/dashboard.css
Normal file
906
frontend/components/pop/dashboard/dashboard.css
Normal file
@@ -0,0 +1,906 @@
|
||||
/* ============================================
|
||||
POP 대시보드 스타일시트
|
||||
다크 모드 (사이버펑크) + 라이트 모드 (소프트 그레이 민트)
|
||||
============================================ */
|
||||
|
||||
/* ========== 다크 모드 (기본) ========== */
|
||||
.pop-dashboard-container {
|
||||
--db-bg-page: #080c15;
|
||||
--db-bg-card: linear-gradient(145deg, rgba(25, 35, 60, 0.9) 0%, rgba(18, 26, 47, 0.95) 100%);
|
||||
--db-bg-card-solid: #121a2f;
|
||||
--db-bg-card-alt: rgba(0, 0, 0, 0.2);
|
||||
--db-bg-elevated: #202d4b;
|
||||
|
||||
--db-accent-primary: #00d4ff;
|
||||
--db-accent-primary-light: #00f0ff;
|
||||
--db-indigo: #4169e1;
|
||||
--db-violet: #8a2be2;
|
||||
--db-mint: #00d4ff;
|
||||
--db-emerald: #00ff88;
|
||||
--db-amber: #ffaa00;
|
||||
--db-rose: #ff3333;
|
||||
|
||||
--db-text-primary: #ffffff;
|
||||
--db-text-secondary: #b4c3dc;
|
||||
--db-text-muted: #64788c;
|
||||
|
||||
--db-border: rgba(40, 55, 85, 1);
|
||||
--db-border-light: rgba(55, 75, 110, 1);
|
||||
|
||||
--db-shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
--db-shadow-md: 0 4px 16px rgba(0, 0, 0, 0.4);
|
||||
--db-shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.5);
|
||||
--db-glow-accent: 0 0 20px rgba(0, 212, 255, 0.5), 0 0 40px rgba(0, 212, 255, 0.3);
|
||||
|
||||
--db-radius-sm: 6px;
|
||||
--db-radius-md: 10px;
|
||||
--db-radius-lg: 14px;
|
||||
|
||||
--db-card-border-production: rgba(65, 105, 225, 0.5);
|
||||
--db-card-border-material: rgba(138, 43, 226, 0.5);
|
||||
--db-card-border-quality: rgba(0, 212, 255, 0.5);
|
||||
--db-card-border-equipment: rgba(0, 255, 136, 0.5);
|
||||
--db-card-border-safety: rgba(255, 170, 0, 0.5);
|
||||
|
||||
--db-notice-bg: rgba(255, 170, 0, 0.1);
|
||||
--db-notice-border: rgba(255, 170, 0, 0.3);
|
||||
--db-notice-text: #ffaa00;
|
||||
|
||||
--db-weather-bg: rgba(0, 0, 0, 0.2);
|
||||
--db-weather-border: rgba(40, 55, 85, 1);
|
||||
|
||||
--db-user-badge-bg: rgba(0, 0, 0, 0.3);
|
||||
--db-user-badge-hover: rgba(0, 212, 255, 0.1);
|
||||
|
||||
--db-btn-more-bg: rgba(0, 212, 255, 0.08);
|
||||
--db-btn-more-border: rgba(0, 212, 255, 0.2);
|
||||
--db-btn-more-color: #00d4ff;
|
||||
|
||||
--db-status-bg: rgba(0, 212, 255, 0.1);
|
||||
--db-status-border: rgba(0, 212, 255, 0.2);
|
||||
--db-status-color: #00d4ff;
|
||||
}
|
||||
|
||||
/* ========== 라이트 모드 ========== */
|
||||
.pop-dashboard-container.light {
|
||||
--db-bg-page: #f8f9fb;
|
||||
--db-bg-card: #ffffff;
|
||||
--db-bg-card-solid: #ffffff;
|
||||
--db-bg-card-alt: #f3f5f7;
|
||||
--db-bg-elevated: #fafbfc;
|
||||
|
||||
--db-accent-primary: #14b8a6;
|
||||
--db-accent-primary-light: #2dd4bf;
|
||||
--db-indigo: #6366f1;
|
||||
--db-violet: #8b5cf6;
|
||||
--db-mint: #14b8a6;
|
||||
--db-emerald: #10b981;
|
||||
--db-amber: #f59e0b;
|
||||
--db-rose: #f43f5e;
|
||||
|
||||
--db-text-primary: #1e293b;
|
||||
--db-text-secondary: #475569;
|
||||
--db-text-muted: #94a3b8;
|
||||
|
||||
--db-border: #e2e8f0;
|
||||
--db-border-light: #f1f5f9;
|
||||
|
||||
--db-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06);
|
||||
--db-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.05);
|
||||
--db-shadow-lg: 0 10px 25px -5px rgba(0, 0, 0, 0.08), 0 8px 10px -6px rgba(0, 0, 0, 0.04);
|
||||
--db-glow-accent: none;
|
||||
|
||||
--db-card-border-production: rgba(99, 102, 241, 0.3);
|
||||
--db-card-border-material: rgba(139, 92, 246, 0.3);
|
||||
--db-card-border-quality: rgba(20, 184, 166, 0.3);
|
||||
--db-card-border-equipment: rgba(16, 185, 129, 0.3);
|
||||
--db-card-border-safety: rgba(245, 158, 11, 0.3);
|
||||
|
||||
--db-notice-bg: linear-gradient(90deg, rgba(245, 158, 11, 0.08), rgba(251, 191, 36, 0.05));
|
||||
--db-notice-border: rgba(245, 158, 11, 0.2);
|
||||
--db-notice-text: #475569;
|
||||
|
||||
--db-weather-bg: rgba(20, 184, 166, 0.08);
|
||||
--db-weather-border: rgba(20, 184, 166, 0.25);
|
||||
|
||||
--db-user-badge-bg: #f3f5f7;
|
||||
--db-user-badge-hover: #e2e8f0;
|
||||
|
||||
--db-btn-more-bg: rgba(20, 184, 166, 0.08);
|
||||
--db-btn-more-border: rgba(20, 184, 166, 0.25);
|
||||
--db-btn-more-color: #0d9488;
|
||||
|
||||
--db-status-bg: #f3f5f7;
|
||||
--db-status-border: transparent;
|
||||
--db-status-color: #475569;
|
||||
}
|
||||
|
||||
/* ========== 기본 컨테이너 ========== */
|
||||
.pop-dashboard-container {
|
||||
font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
||||
background: var(--db-bg-page);
|
||||
color: var(--db-text-primary);
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
transition: background 0.3s, color 0.3s;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 다크 모드 배경 그리드 */
|
||||
.pop-dashboard-container::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background:
|
||||
repeating-linear-gradient(90deg, rgba(0, 212, 255, 0.03) 0px, transparent 1px, transparent 60px),
|
||||
repeating-linear-gradient(0deg, rgba(0, 212, 255, 0.03) 0px, transparent 1px, transparent 60px),
|
||||
radial-gradient(ellipse 80% 50% at 50% 0%, rgba(0, 212, 255, 0.08) 0%, transparent 60%),
|
||||
linear-gradient(180deg, #080c15 0%, #0a0f1c 50%, #0d1323 100%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.pop-dashboard-container.light::before {
|
||||
background: linear-gradient(180deg, #f1f5f9 0%, #f8fafc 50%, #ffffff 100%);
|
||||
}
|
||||
|
||||
.pop-dashboard {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
}
|
||||
|
||||
/* ========== 헤더 ========== */
|
||||
.pop-dashboard-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding: 14px 24px;
|
||||
background: var(--db-bg-card);
|
||||
border: 1px solid var(--db-border);
|
||||
border-radius: var(--db-radius-lg);
|
||||
box-shadow: var(--db-shadow-sm);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pop-dashboard-container:not(.light) .pop-dashboard-header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 20%;
|
||||
right: 20%;
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg, transparent, var(--db-accent-primary), transparent);
|
||||
}
|
||||
|
||||
.pop-dashboard-header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.pop-dashboard-time-display {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.pop-dashboard-time-main {
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1px;
|
||||
color: var(--db-accent-primary);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.pop-dashboard-container:not(.light) .pop-dashboard-time-main {
|
||||
text-shadow: var(--db-glow-accent);
|
||||
animation: neonFlicker 3s infinite;
|
||||
}
|
||||
|
||||
.pop-dashboard-time-date {
|
||||
font-size: 13px;
|
||||
color: var(--db-text-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.pop-dashboard-header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
/* 테마 토글 */
|
||||
.pop-dashboard-theme-toggle {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--db-bg-card-alt);
|
||||
border: 1px solid var(--db-border);
|
||||
border-radius: var(--db-radius-md);
|
||||
color: var(--db-text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.pop-dashboard-theme-toggle:hover {
|
||||
border-color: var(--db-accent-primary);
|
||||
color: var(--db-accent-primary);
|
||||
}
|
||||
|
||||
/* 날씨 정보 */
|
||||
.pop-dashboard-weather {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
background: var(--db-weather-bg);
|
||||
border: 1px solid var(--db-weather-border);
|
||||
border-radius: var(--db-radius-md);
|
||||
}
|
||||
|
||||
.pop-dashboard-weather-temp {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: var(--db-amber);
|
||||
}
|
||||
|
||||
.pop-dashboard-container.light .pop-dashboard-weather-temp {
|
||||
color: var(--db-accent-primary);
|
||||
}
|
||||
|
||||
.pop-dashboard-weather-desc {
|
||||
font-size: 11px;
|
||||
color: var(--db-text-muted);
|
||||
}
|
||||
|
||||
/* 회사 정보 */
|
||||
.pop-dashboard-company {
|
||||
padding-right: 14px;
|
||||
border-right: 1px solid var(--db-border);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.pop-dashboard-company-name {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: var(--db-text-primary);
|
||||
}
|
||||
|
||||
.pop-dashboard-company-sub {
|
||||
font-size: 11px;
|
||||
color: var(--db-text-muted);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* 사용자 배지 */
|
||||
.pop-dashboard-user-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 14px;
|
||||
background: var(--db-user-badge-bg);
|
||||
border: 1px solid var(--db-border);
|
||||
border-radius: var(--db-radius-md);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.pop-dashboard-user-badge:hover {
|
||||
background: var(--db-user-badge-hover);
|
||||
}
|
||||
|
||||
.pop-dashboard-user-badge:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.pop-dashboard-user-avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: linear-gradient(135deg, var(--db-accent-primary), var(--db-emerald));
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.pop-dashboard-container:not(.light) .pop-dashboard-user-avatar {
|
||||
box-shadow: 0 0 10px rgba(0, 212, 255, 0.4);
|
||||
}
|
||||
|
||||
.pop-dashboard-container.light .pop-dashboard-user-avatar {
|
||||
box-shadow: 0 2px 8px rgba(20, 184, 166, 0.3);
|
||||
}
|
||||
|
||||
.pop-dashboard-user-name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--db-text-primary);
|
||||
}
|
||||
|
||||
.pop-dashboard-user-role {
|
||||
font-size: 11px;
|
||||
color: var(--db-text-muted);
|
||||
}
|
||||
|
||||
/* ========== 공지사항 배너 ========== */
|
||||
.pop-dashboard-notice-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
padding: 10px 16px;
|
||||
background: var(--db-notice-bg);
|
||||
border: 1px solid var(--db-notice-border);
|
||||
border-radius: var(--db-radius-md);
|
||||
}
|
||||
|
||||
.pop-dashboard-notice-label {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
color: var(--db-bg-page);
|
||||
background: var(--db-amber);
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
flex-shrink: 0;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.pop-dashboard-container.light .pop-dashboard-notice-label {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.pop-dashboard-notice-content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pop-dashboard-notice-marquee {
|
||||
display: flex;
|
||||
animation: dashboardMarquee 30s linear infinite;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pop-dashboard-notice-text {
|
||||
font-size: 12px;
|
||||
color: var(--db-notice-text);
|
||||
padding-right: 100px;
|
||||
}
|
||||
|
||||
@keyframes dashboardMarquee {
|
||||
0% { transform: translateX(0); }
|
||||
100% { transform: translateX(-50%); }
|
||||
}
|
||||
|
||||
.pop-dashboard-notice-banner:hover .pop-dashboard-notice-marquee {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
/* ========== KPI 바 ========== */
|
||||
.pop-dashboard-kpi-bar {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.pop-dashboard-kpi-item {
|
||||
background: var(--db-bg-card);
|
||||
border: 1px solid var(--db-border);
|
||||
border-radius: var(--db-radius-lg);
|
||||
padding: 16px 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
box-shadow: var(--db-shadow-sm);
|
||||
transition: all 0.2s;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pop-dashboard-container:not(.light) .pop-dashboard-kpi-item::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, rgba(0, 212, 255, 0.5), transparent);
|
||||
}
|
||||
|
||||
.pop-dashboard-kpi-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--db-shadow-md);
|
||||
}
|
||||
|
||||
.pop-dashboard-container:not(.light) .pop-dashboard-kpi-item:hover {
|
||||
border-color: rgba(0, 212, 255, 0.3);
|
||||
box-shadow: 0 0 30px rgba(0, 212, 255, 0.1), inset 0 0 30px rgba(0, 212, 255, 0.02);
|
||||
}
|
||||
|
||||
.pop-dashboard-kpi-gauge {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.pop-dashboard-kpi-gauge svg {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.pop-dashboard-kpi-gauge-bg {
|
||||
fill: none;
|
||||
stroke: var(--db-border);
|
||||
stroke-width: 5;
|
||||
}
|
||||
|
||||
.pop-dashboard-kpi-gauge-fill {
|
||||
fill: none;
|
||||
stroke-width: 5;
|
||||
stroke-linecap: round;
|
||||
transition: stroke-dashoffset 0.5s;
|
||||
}
|
||||
|
||||
.pop-dashboard-container:not(.light) .pop-dashboard-kpi-gauge-fill {
|
||||
filter: drop-shadow(0 0 6px currentColor);
|
||||
}
|
||||
|
||||
.pop-dashboard-container.light .pop-dashboard-kpi-gauge-fill {
|
||||
filter: drop-shadow(0 1px 2px rgba(0,0,0,0.1));
|
||||
}
|
||||
|
||||
.pop-dashboard-kpi-gauge-text {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.pop-dashboard-kpi-info { flex: 1; }
|
||||
|
||||
.pop-dashboard-kpi-label {
|
||||
font-size: 11px;
|
||||
color: var(--db-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.pop-dashboard-kpi-value {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.pop-dashboard-kpi-unit {
|
||||
font-size: 12px;
|
||||
color: var(--db-text-muted);
|
||||
margin-left: 3px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* KPI 색상 */
|
||||
.kpi-color-cyan { color: var(--db-mint); stroke: var(--db-mint); }
|
||||
.kpi-color-emerald { color: var(--db-emerald); stroke: var(--db-emerald); }
|
||||
.kpi-color-rose { color: var(--db-rose); stroke: var(--db-rose); }
|
||||
.kpi-color-amber { color: var(--db-amber); stroke: var(--db-amber); }
|
||||
|
||||
/* ========== 메뉴 그리드 ========== */
|
||||
.pop-dashboard-menu-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.pop-dashboard-menu-card {
|
||||
background: var(--db-bg-card);
|
||||
border-radius: var(--db-radius-lg);
|
||||
padding: 18px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
box-shadow: var(--db-shadow-sm);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pop-dashboard-menu-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--db-shadow-lg);
|
||||
}
|
||||
|
||||
.pop-dashboard-menu-card:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.pop-dashboard-menu-card.production { border: 2px solid var(--db-card-border-production); }
|
||||
.pop-dashboard-menu-card.material { border: 2px solid var(--db-card-border-material); }
|
||||
.pop-dashboard-menu-card.quality { border: 2px solid var(--db-card-border-quality); }
|
||||
.pop-dashboard-menu-card.equipment { border: 2px solid var(--db-card-border-equipment); }
|
||||
.pop-dashboard-menu-card.safety { border: 2px solid var(--db-card-border-safety); }
|
||||
|
||||
.pop-dashboard-container:not(.light) .pop-dashboard-menu-card.production:hover { box-shadow: 0 0 20px rgba(65, 105, 225, 0.3); }
|
||||
.pop-dashboard-container:not(.light) .pop-dashboard-menu-card.material:hover { box-shadow: 0 0 20px rgba(138, 43, 226, 0.3); }
|
||||
.pop-dashboard-container:not(.light) .pop-dashboard-menu-card.quality:hover { box-shadow: 0 0 20px rgba(0, 212, 255, 0.3); }
|
||||
.pop-dashboard-container:not(.light) .pop-dashboard-menu-card.equipment:hover { box-shadow: 0 0 20px rgba(0, 255, 136, 0.3); }
|
||||
.pop-dashboard-container:not(.light) .pop-dashboard-menu-card.safety:hover { box-shadow: 0 0 20px rgba(255, 170, 0, 0.3); }
|
||||
|
||||
.pop-dashboard-menu-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.pop-dashboard-menu-title {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: var(--db-text-primary);
|
||||
}
|
||||
|
||||
.pop-dashboard-menu-count {
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.pop-dashboard-container:not(.light) .pop-dashboard-menu-count {
|
||||
text-shadow: 0 0 20px currentColor;
|
||||
}
|
||||
|
||||
.pop-dashboard-menu-count.production { color: var(--db-indigo); }
|
||||
.pop-dashboard-menu-count.material { color: var(--db-violet); }
|
||||
.pop-dashboard-menu-count.quality { color: var(--db-mint); }
|
||||
.pop-dashboard-menu-count.equipment { color: var(--db-emerald); }
|
||||
.pop-dashboard-menu-count.safety { color: var(--db-amber); }
|
||||
|
||||
.pop-dashboard-menu-desc {
|
||||
font-size: 11px;
|
||||
color: var(--db-text-muted);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.pop-dashboard-menu-status {
|
||||
display: inline-block;
|
||||
margin-top: 10px;
|
||||
padding: 4px 10px;
|
||||
background: var(--db-status-bg);
|
||||
border: 1px solid var(--db-status-border);
|
||||
border-radius: 16px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: var(--db-status-color);
|
||||
}
|
||||
|
||||
/* ========== 하단 섹션 ========== */
|
||||
.pop-dashboard-bottom-section {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.pop-dashboard-card {
|
||||
background: var(--db-bg-card);
|
||||
border: 1px solid var(--db-border);
|
||||
border-radius: var(--db-radius-lg);
|
||||
padding: 18px;
|
||||
box-shadow: var(--db-shadow-sm);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pop-dashboard-container:not(.light) .pop-dashboard-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, rgba(0, 212, 255, 0.5), transparent);
|
||||
}
|
||||
|
||||
.pop-dashboard-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 14px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid var(--db-border);
|
||||
}
|
||||
|
||||
.pop-dashboard-card-title {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: var(--db-text-primary);
|
||||
}
|
||||
|
||||
.pop-dashboard-btn-more {
|
||||
padding: 6px 12px;
|
||||
background: var(--db-btn-more-bg);
|
||||
border: 1px solid var(--db-btn-more-border);
|
||||
color: var(--db-btn-more-color);
|
||||
border-radius: var(--db-radius-sm);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.pop-dashboard-btn-more:hover {
|
||||
background: var(--db-accent-primary);
|
||||
color: white;
|
||||
border-color: var(--db-accent-primary);
|
||||
}
|
||||
|
||||
/* 활동 리스트 */
|
||||
.pop-dashboard-activity-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.pop-dashboard-activity-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
background: var(--db-bg-card-alt);
|
||||
border-radius: var(--db-radius-md);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.pop-dashboard-container:not(.light) .pop-dashboard-activity-item {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.pop-dashboard-container:not(.light) .pop-dashboard-activity-item:hover {
|
||||
background: rgba(0, 212, 255, 0.05);
|
||||
border-color: rgba(0, 212, 255, 0.2);
|
||||
}
|
||||
|
||||
.pop-dashboard-container.light .pop-dashboard-activity-item:hover {
|
||||
background: var(--db-border-light);
|
||||
}
|
||||
|
||||
.pop-dashboard-activity-time {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: var(--db-accent-primary);
|
||||
font-variant-numeric: tabular-nums;
|
||||
min-width: 48px;
|
||||
}
|
||||
|
||||
.pop-dashboard-container:not(.light) .pop-dashboard-activity-time {
|
||||
text-shadow: 0 0 10px rgba(0, 212, 255, 0.5);
|
||||
}
|
||||
|
||||
.pop-dashboard-activity-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.pop-dashboard-container:not(.light) .pop-dashboard-activity-dot {
|
||||
box-shadow: 0 0 8px currentColor;
|
||||
}
|
||||
|
||||
.pop-dashboard-activity-dot.production { background: var(--db-indigo); color: var(--db-indigo); }
|
||||
.pop-dashboard-activity-dot.material { background: var(--db-violet); color: var(--db-violet); }
|
||||
.pop-dashboard-activity-dot.quality { background: var(--db-mint); color: var(--db-mint); }
|
||||
.pop-dashboard-activity-dot.equipment { background: var(--db-emerald); color: var(--db-emerald); }
|
||||
|
||||
.pop-dashboard-activity-content { flex: 1; }
|
||||
|
||||
.pop-dashboard-activity-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--db-text-primary);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.pop-dashboard-activity-desc {
|
||||
font-size: 11px;
|
||||
color: var(--db-text-muted);
|
||||
}
|
||||
|
||||
/* 공지사항 리스트 */
|
||||
.pop-dashboard-notice-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.pop-dashboard-notice-item {
|
||||
padding: 12px;
|
||||
background: var(--db-bg-card-alt);
|
||||
border-radius: var(--db-radius-md);
|
||||
transition: all 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pop-dashboard-container:not(.light) .pop-dashboard-notice-item:hover {
|
||||
background: rgba(255, 170, 0, 0.05);
|
||||
}
|
||||
|
||||
.pop-dashboard-container.light .pop-dashboard-notice-item:hover {
|
||||
background: var(--db-border-light);
|
||||
}
|
||||
|
||||
.pop-dashboard-notice-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--db-text-primary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.pop-dashboard-notice-date {
|
||||
font-size: 11px;
|
||||
color: var(--db-text-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* ========== 푸터 ========== */
|
||||
.pop-dashboard-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 16px;
|
||||
padding: 14px 18px;
|
||||
background: var(--db-bg-card);
|
||||
border: 1px solid var(--db-border);
|
||||
border-radius: var(--db-radius-md);
|
||||
font-size: 11px;
|
||||
color: var(--db-text-muted);
|
||||
}
|
||||
|
||||
/* ========== 반응형 ========== */
|
||||
|
||||
/* 가로 모드 */
|
||||
@media (orientation: landscape) {
|
||||
.pop-dashboard { padding: 16px 24px; }
|
||||
.pop-dashboard-kpi-bar { grid-template-columns: repeat(4, 1fr) !important; gap: 10px; }
|
||||
.pop-dashboard-kpi-item { padding: 12px 14px; }
|
||||
.pop-dashboard-kpi-gauge { width: 44px; height: 44px; }
|
||||
.pop-dashboard-kpi-gauge svg { width: 44px; height: 44px; }
|
||||
.pop-dashboard-kpi-value { font-size: 20px; }
|
||||
|
||||
.pop-dashboard-menu-grid { grid-template-columns: repeat(5, 1fr) !important; gap: 10px; }
|
||||
.pop-dashboard-menu-card { padding: 14px; display: block; }
|
||||
.pop-dashboard-menu-header { margin-bottom: 8px; display: block; }
|
||||
.pop-dashboard-menu-title { font-size: 13px; }
|
||||
.pop-dashboard-menu-count { font-size: 20px; }
|
||||
.pop-dashboard-menu-desc { display: block; font-size: 10px; }
|
||||
.pop-dashboard-menu-status { margin-top: 8px; }
|
||||
|
||||
.pop-dashboard-bottom-section { grid-template-columns: 2fr 1fr; }
|
||||
}
|
||||
|
||||
/* 세로 모드 */
|
||||
@media (orientation: portrait) {
|
||||
.pop-dashboard { padding: 16px; }
|
||||
.pop-dashboard-kpi-bar { grid-template-columns: repeat(2, 1fr) !important; gap: 10px; }
|
||||
|
||||
.pop-dashboard-menu-grid { grid-template-columns: 1fr !important; gap: 8px; }
|
||||
.pop-dashboard-menu-card {
|
||||
padding: 14px 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.pop-dashboard-menu-header { margin-bottom: 0; display: flex; align-items: center; gap: 12px; }
|
||||
.pop-dashboard-menu-title { font-size: 15px; }
|
||||
.pop-dashboard-menu-count { font-size: 20px; }
|
||||
.pop-dashboard-menu-desc { display: none; }
|
||||
.pop-dashboard-menu-status { margin-top: 0; padding: 5px 12px; font-size: 11px; }
|
||||
|
||||
.pop-dashboard-bottom-section { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
/* 작은 화면 세로 */
|
||||
@media (max-width: 600px) and (orientation: portrait) {
|
||||
.pop-dashboard { padding: 12px; }
|
||||
.pop-dashboard-header { padding: 10px 14px; }
|
||||
.pop-dashboard-time-main { font-size: 20px; }
|
||||
.pop-dashboard-time-date { display: none; }
|
||||
.pop-dashboard-weather { padding: 4px 8px; }
|
||||
.pop-dashboard-weather-temp { font-size: 11px; }
|
||||
.pop-dashboard-weather-desc { display: none; }
|
||||
.pop-dashboard-company { display: none; }
|
||||
.pop-dashboard-user-text { display: none; }
|
||||
.pop-dashboard-user-avatar { width: 30px; height: 30px; }
|
||||
|
||||
.pop-dashboard-notice-banner { padding: 8px 12px; }
|
||||
.pop-dashboard-notice-label { font-size: 9px; }
|
||||
.pop-dashboard-notice-text { font-size: 11px; }
|
||||
|
||||
.pop-dashboard-kpi-item { padding: 12px 14px; gap: 10px; }
|
||||
.pop-dashboard-kpi-gauge { width: 44px; height: 44px; }
|
||||
.pop-dashboard-kpi-gauge svg { width: 44px; height: 44px; }
|
||||
.pop-dashboard-kpi-gauge-text { font-size: 10px; }
|
||||
.pop-dashboard-kpi-label { font-size: 10px; }
|
||||
.pop-dashboard-kpi-value { font-size: 18px; }
|
||||
|
||||
.pop-dashboard-menu-card { padding: 12px 16px; }
|
||||
.pop-dashboard-menu-title { font-size: 14px; }
|
||||
.pop-dashboard-menu-count { font-size: 18px; }
|
||||
.pop-dashboard-menu-status { padding: 4px 10px; font-size: 10px; }
|
||||
}
|
||||
|
||||
/* 작은 화면 가로 */
|
||||
@media (max-width: 600px) and (orientation: landscape) {
|
||||
.pop-dashboard { padding: 10px 16px; }
|
||||
.pop-dashboard-header { padding: 8px 12px; }
|
||||
.pop-dashboard-time-main { font-size: 18px; }
|
||||
.pop-dashboard-time-date { font-size: 10px; }
|
||||
.pop-dashboard-weather { display: none; }
|
||||
.pop-dashboard-company { display: none; }
|
||||
.pop-dashboard-user-text { display: none; }
|
||||
|
||||
.pop-dashboard-notice-banner { padding: 6px 10px; margin-bottom: 10px; }
|
||||
|
||||
.pop-dashboard-kpi-item { padding: 8px 10px; gap: 8px; }
|
||||
.pop-dashboard-kpi-gauge { width: 36px; height: 36px; }
|
||||
.pop-dashboard-kpi-gauge svg { width: 36px; height: 36px; }
|
||||
.pop-dashboard-kpi-gauge-text { font-size: 9px; }
|
||||
.pop-dashboard-kpi-label { font-size: 9px; }
|
||||
.pop-dashboard-kpi-value { font-size: 16px; }
|
||||
|
||||
.pop-dashboard-menu-card { padding: 10px; }
|
||||
.pop-dashboard-menu-title { font-size: 11px; }
|
||||
.pop-dashboard-menu-count { font-size: 16px; }
|
||||
.pop-dashboard-menu-status { margin-top: 4px; padding: 2px 6px; font-size: 8px; }
|
||||
}
|
||||
|
||||
/* ========== 애니메이션 ========== */
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(8px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes neonFlicker {
|
||||
0%, 19%, 21%, 23%, 25%, 54%, 56%, 100% { opacity: 1; }
|
||||
20%, 24%, 55% { opacity: 0.85; }
|
||||
}
|
||||
|
||||
.pop-dashboard-kpi-item, .pop-dashboard-menu-card, .pop-dashboard-card {
|
||||
animation: fadeIn 0.35s ease-out backwards;
|
||||
}
|
||||
|
||||
.pop-dashboard-kpi-item:nth-child(1) { animation-delay: 0.05s; }
|
||||
.pop-dashboard-kpi-item:nth-child(2) { animation-delay: 0.1s; }
|
||||
.pop-dashboard-kpi-item:nth-child(3) { animation-delay: 0.15s; }
|
||||
.pop-dashboard-kpi-item:nth-child(4) { animation-delay: 0.2s; }
|
||||
|
||||
.pop-dashboard-menu-card:nth-child(1) { animation-delay: 0.1s; }
|
||||
.pop-dashboard-menu-card:nth-child(2) { animation-delay: 0.15s; }
|
||||
.pop-dashboard-menu-card:nth-child(3) { animation-delay: 0.2s; }
|
||||
.pop-dashboard-menu-card:nth-child(4) { animation-delay: 0.25s; }
|
||||
.pop-dashboard-menu-card:nth-child(5) { animation-delay: 0.3s; }
|
||||
|
||||
/* 스크롤바 */
|
||||
.pop-dashboard-container ::-webkit-scrollbar { width: 6px; height: 6px; }
|
||||
.pop-dashboard-container ::-webkit-scrollbar-track { background: transparent; }
|
||||
.pop-dashboard-container ::-webkit-scrollbar-thumb { background: var(--db-border); border-radius: 3px; }
|
||||
.pop-dashboard-container ::-webkit-scrollbar-thumb:hover { background: var(--db-accent-primary); }
|
||||
|
||||
139
frontend/components/pop/dashboard/data.ts
Normal file
139
frontend/components/pop/dashboard/data.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
// POP 대시보드 샘플 데이터
|
||||
|
||||
import { KpiItem, MenuItem, ActivityItem, NoticeItem } from "./types";
|
||||
|
||||
export const KPI_ITEMS: KpiItem[] = [
|
||||
{
|
||||
id: "achievement",
|
||||
label: "목표 달성률",
|
||||
value: 83.3,
|
||||
unit: "%",
|
||||
percentage: 83,
|
||||
color: "cyan",
|
||||
},
|
||||
{
|
||||
id: "production",
|
||||
label: "금일 생산실적",
|
||||
value: 1250,
|
||||
unit: "EA",
|
||||
percentage: 100,
|
||||
color: "emerald",
|
||||
},
|
||||
{
|
||||
id: "defect",
|
||||
label: "불량률",
|
||||
value: 0.8,
|
||||
unit: "%",
|
||||
percentage: 1,
|
||||
color: "rose",
|
||||
},
|
||||
{
|
||||
id: "equipment",
|
||||
label: "가동 설비",
|
||||
value: 8,
|
||||
unit: "/ 10",
|
||||
percentage: 80,
|
||||
color: "amber",
|
||||
},
|
||||
];
|
||||
|
||||
export const MENU_ITEMS: MenuItem[] = [
|
||||
{
|
||||
id: "production",
|
||||
title: "생산관리",
|
||||
count: 5,
|
||||
description: "작업지시 / 생산실적 / 공정관리",
|
||||
status: "진행중",
|
||||
category: "production",
|
||||
href: "/pop/work",
|
||||
},
|
||||
{
|
||||
id: "material",
|
||||
title: "자재관리",
|
||||
count: 12,
|
||||
description: "자재출고 / 재고확인 / 입고처리",
|
||||
status: "대기",
|
||||
category: "material",
|
||||
href: "#",
|
||||
},
|
||||
{
|
||||
id: "quality",
|
||||
title: "품질관리",
|
||||
count: 3,
|
||||
description: "품질검사 / 불량처리 / 검사기록",
|
||||
status: "검사대기",
|
||||
category: "quality",
|
||||
href: "#",
|
||||
},
|
||||
{
|
||||
id: "equipment",
|
||||
title: "설비관리",
|
||||
count: 2,
|
||||
description: "설비현황 / 점검관리 / 고장신고",
|
||||
status: "점검필요",
|
||||
category: "equipment",
|
||||
href: "#",
|
||||
},
|
||||
{
|
||||
id: "safety",
|
||||
title: "안전관리",
|
||||
count: 0,
|
||||
description: "안전점검 / 사고신고 / 안전교육",
|
||||
status: "이상무",
|
||||
category: "safety",
|
||||
href: "#",
|
||||
},
|
||||
];
|
||||
|
||||
export const ACTIVITY_ITEMS: ActivityItem[] = [
|
||||
{
|
||||
id: "1",
|
||||
time: "14:25",
|
||||
title: "생산실적 등록",
|
||||
description: "WO-2024-156 - 500EA 생산완료",
|
||||
category: "production",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
time: "13:50",
|
||||
title: "자재출고",
|
||||
description: "알루미늄 프로파일 A100 - 200EA",
|
||||
category: "material",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
time: "11:30",
|
||||
title: "품질검사 완료",
|
||||
description: "LOT-2024-156 합격 (불량 0건)",
|
||||
category: "quality",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
time: "09:15",
|
||||
title: "설비점검",
|
||||
description: "5호기 정기점검 완료",
|
||||
category: "equipment",
|
||||
},
|
||||
];
|
||||
|
||||
export const NOTICE_ITEMS: NoticeItem[] = [
|
||||
{
|
||||
id: "1",
|
||||
title: "금일 15:00 전체 안전교육",
|
||||
date: "2024-01-05",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
title: "3호기 정기점검 안내",
|
||||
date: "2024-01-04",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
title: "11월 우수팀 - 생산1팀",
|
||||
date: "2024-01-03",
|
||||
},
|
||||
];
|
||||
|
||||
export const NOTICE_MARQUEE_TEXT =
|
||||
"[공지] 금일 오후 3시 전체 안전교육 실시 예정입니다. 전 직원 필참 바랍니다. | [알림] 내일 설비 정기점검으로 인한 3호기 가동 중지 예정 | [안내] 11월 생산실적 우수팀 발표 - 생산1팀 축하드립니다!";
|
||||
|
||||
11
frontend/components/pop/dashboard/index.ts
Normal file
11
frontend/components/pop/dashboard/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export { PopDashboard } from "./PopDashboard";
|
||||
export { DashboardHeader } from "./DashboardHeader";
|
||||
export { NoticeBanner } from "./NoticeBanner";
|
||||
export { KpiBar } from "./KpiBar";
|
||||
export { MenuGrid } from "./MenuGrid";
|
||||
export { ActivityList } from "./ActivityList";
|
||||
export { NoticeList } from "./NoticeList";
|
||||
export { DashboardFooter } from "./DashboardFooter";
|
||||
export * from "./types";
|
||||
export * from "./data";
|
||||
|
||||
51
frontend/components/pop/dashboard/types.ts
Normal file
51
frontend/components/pop/dashboard/types.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
// POP 대시보드 타입 정의
|
||||
|
||||
export interface KpiItem {
|
||||
id: string;
|
||||
label: string;
|
||||
value: number;
|
||||
unit: string;
|
||||
percentage: number;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export interface MenuItem {
|
||||
id: string;
|
||||
title: string;
|
||||
count: number;
|
||||
description: string;
|
||||
status: string;
|
||||
category: "production" | "material" | "quality" | "equipment" | "safety";
|
||||
href: string;
|
||||
}
|
||||
|
||||
export interface ActivityItem {
|
||||
id: string;
|
||||
time: string;
|
||||
title: string;
|
||||
description: string;
|
||||
category: "production" | "material" | "quality" | "equipment";
|
||||
}
|
||||
|
||||
export interface NoticeItem {
|
||||
id: string;
|
||||
title: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
export interface WeatherInfo {
|
||||
temp: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface UserInfo {
|
||||
name: string;
|
||||
role: string;
|
||||
avatar: string;
|
||||
}
|
||||
|
||||
export interface CompanyInfo {
|
||||
name: string;
|
||||
subTitle: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user