feat: 프로필 드롭다운 메뉴 (PC모드/앱모드/홈/로그아웃)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect, ReactNode } from "react";
|
||||
import React, { useState, useEffect, useRef, ReactNode } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
interface PopShellProps {
|
||||
@@ -19,6 +19,8 @@ export function PopShell({ children, showBanner = true, title, showBack = false,
|
||||
const [seconds, setSeconds] = useState("00");
|
||||
const [dateStr, setDateStr] = useState("2026-01-01");
|
||||
const [colonVisible, setColonVisible] = useState(true);
|
||||
const [profileOpen, setProfileOpen] = useState(false);
|
||||
const profileRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
@@ -45,6 +47,52 @@ export function PopShell({ children, showBanner = true, title, showBack = false,
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Profile dropdown: close on outside click
|
||||
useEffect(() => {
|
||||
function handleClickOutside(e: MouseEvent) {
|
||||
if (profileRef.current && !profileRef.current.contains(e.target as Node)) {
|
||||
setProfileOpen(false);
|
||||
}
|
||||
}
|
||||
if (profileOpen) {
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
}
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, [profileOpen]);
|
||||
|
||||
const handlePcMode = () => {
|
||||
setProfileOpen(false);
|
||||
router.push("/");
|
||||
};
|
||||
|
||||
const handlePopHome = () => {
|
||||
setProfileOpen(false);
|
||||
router.push("/pop/home");
|
||||
};
|
||||
|
||||
const toggleFullscreen = async () => {
|
||||
setProfileOpen(false);
|
||||
try {
|
||||
if (document.fullscreenElement) {
|
||||
await document.exitFullscreen();
|
||||
} else {
|
||||
await document.documentElement.requestFullscreen();
|
||||
}
|
||||
} catch {
|
||||
// fullscreen not supported
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
setProfileOpen(false);
|
||||
localStorage.removeItem("token");
|
||||
localStorage.removeItem("accessToken");
|
||||
localStorage.removeItem("refreshToken");
|
||||
window.location.href = "/login";
|
||||
};
|
||||
|
||||
const marqueeText =
|
||||
"[공지] 금일 오후 3시 전체 안전교육 실시 예정입니다. 전 직원 필참 바랍니다. \u00a0\u00a0|\u00a0\u00a0 [알림] 내일 설비 정기점검으로 인한 3호기 가동 중지 예정 \u00a0\u00a0|\u00a0\u00a0 [안내] 4월 생산실적 우수팀 발표 - 생산1팀 축하드립니다!";
|
||||
|
||||
@@ -166,17 +214,77 @@ export function PopShell({ children, showBanner = true, title, showBack = false,
|
||||
|
||||
<div className="hidden sm:block h-5 w-px bg-white/20" />
|
||||
|
||||
{/* Profile */}
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="hidden sm:flex flex-col items-end">
|
||||
<span className="text-sm text-white/90 font-semibold leading-tight">김철수</span>
|
||||
<span className="text-xs text-white/40 font-medium leading-tight">생산1팀</span>
|
||||
</div>
|
||||
<div
|
||||
className="w-10 h-10 rounded-full bg-blue-500 flex items-center justify-center text-sm font-bold text-white shrink-0"
|
||||
style={{ boxShadow: "0 2px 8px rgba(59,130,246,.35)" }}
|
||||
{/* Profile with Dropdown */}
|
||||
<div className="relative" ref={profileRef}>
|
||||
<button
|
||||
onClick={() => setProfileOpen((v) => !v)}
|
||||
className="flex items-center gap-2.5 cursor-pointer"
|
||||
>
|
||||
김
|
||||
<div className="hidden sm:flex flex-col items-end">
|
||||
<span className="text-sm text-white/90 font-semibold leading-tight">김철수</span>
|
||||
<span className="text-xs text-white/40 font-medium leading-tight">생산1팀</span>
|
||||
</div>
|
||||
<div
|
||||
className="w-10 h-10 rounded-full bg-blue-500 flex items-center justify-center text-sm font-bold text-white shrink-0 transition-transform active:scale-95"
|
||||
style={{ boxShadow: "0 2px 8px rgba(59,130,246,.35)" }}
|
||||
>
|
||||
김
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Profile Dropdown */}
|
||||
<div
|
||||
className={`absolute right-0 top-full mt-2 w-56 bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden z-[60] transition-all duration-200 origin-top-right ${
|
||||
profileOpen
|
||||
? "opacity-100 scale-100 translate-y-0"
|
||||
: "opacity-0 scale-95 -translate-y-1 pointer-events-none"
|
||||
}`}
|
||||
>
|
||||
{/* User Info */}
|
||||
<div className="px-4 py-3 border-b border-gray-100">
|
||||
<p className="text-sm font-semibold text-gray-900">김철수</p>
|
||||
<p className="text-xs text-gray-400 mt-0.5">생산1팀</p>
|
||||
</div>
|
||||
|
||||
{/* Menu Items */}
|
||||
<div className="py-1">
|
||||
<button
|
||||
onClick={handlePcMode}
|
||||
className="flex items-center gap-3 w-full px-4 text-sm text-gray-700 hover:bg-gray-50 active:scale-95 transition-all"
|
||||
style={{ minHeight: 48 }}
|
||||
>
|
||||
<span className="text-base">🖥</span>
|
||||
<span>PC 모드 전환</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={toggleFullscreen}
|
||||
className="flex items-center gap-3 w-full px-4 text-sm text-gray-700 hover:bg-gray-50 active:scale-95 transition-all"
|
||||
style={{ minHeight: 48 }}
|
||||
>
|
||||
<span className="text-base">📱</span>
|
||||
<span>앱 모드 (전체화면)</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={handlePopHome}
|
||||
className="flex items-center gap-3 w-full px-4 text-sm text-gray-700 hover:bg-gray-50 active:scale-95 transition-all"
|
||||
style={{ minHeight: 48 }}
|
||||
>
|
||||
<span className="text-base">🏠</span>
|
||||
<span>POP 홈</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Logout */}
|
||||
<div className="border-t border-gray-100 py-1">
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="flex items-center gap-3 w-full px-4 text-sm text-red-500 hover:bg-red-50 active:scale-95 transition-all"
|
||||
style={{ minHeight: 48 }}
|
||||
>
|
||||
<span className="text-base">🚪</span>
|
||||
<span>로그아웃</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user