대시보드 구현 완료 세세한 오류 수정 진행중
Build & Deploy to K8s / build-and-deploy (push) Successful in 5m4s

This commit is contained in:
2026-04-19 21:15:25 +09:00
parent 937505ab28
commit b3ad787179
70 changed files with 4366 additions and 1368 deletions
+84 -9
View File
@@ -20,7 +20,12 @@ import {
Building2,
FileCheck,
Monitor,
Plus,
Edit3,
} from "lucide-react";
import { useDashboardStore } from "@/stores/dashboardStore";
import { insertDashboard } from "@/lib/api/dashMenu";
import { CreateDashboardModal } from "@/components/dash/CreateDashboardModal";
import { useMenu } from "@/contexts/MenuContext";
import { useAuth } from "@/hooks/useAuth";
import { useProfile } from "@/hooks/useProfile";
@@ -29,13 +34,13 @@ import { menuScreenApi } from "@/lib/api/screen";
import { apiClient } from "@/lib/api/client";
import { toast } from "sonner";
import { ProfileModal } from "./ProfileModal";
import { SettingsModal } from "./SettingsModal";
import { Logo } from "./Logo";
import { SideMenu } from "./SideMenu";
import { TabBar } from "./TabBar";
import { TabContent } from "./TabContent";
import { useTabStore } from "@/stores/tabStore";
import { ThemeToggle } from "./ThemeToggle";
import { CosmicBackground } from "./CosmicBackground";
import { useTheme } from "next-themes";
import {
DropdownMenu,
@@ -246,8 +251,35 @@ function AppLayoutInner({ children }: AppLayoutProps) {
const [isMobile, setIsMobile] = useState(false);
const [showCompanySwitcher, setShowCompanySwitcher] = useState(false);
const [currentCompanyName, setCurrentCompanyName] = useState<string>("");
const [settingsOpen, setSettingsOpen] = useState(false);
const { theme, setTheme: rawSetTheme } = useTheme();
// 대시보드 생성/편집 모드 / 템플릿 추가 (전역 헤더 버튼)
const dashEditMode = useDashboardStore((s) => s.editMode);
const toggleDashEditMode = useDashboardStore((s) => s.toggleEditMode);
const dashCreateOpen = useDashboardStore((s) => s.createOpen);
const openDashCreate = useDashboardStore((s) => s.openCreate);
const closeDashCreate = useDashboardStore((s) => s.closeCreate);
const dashActiveId = useDashboardStore((s) => s.activeDashboardId);
const openDashLib = useDashboardStore((s) => s.openLib);
const [dashCreateSubmitting, setDashCreateSubmitting] = useState(false);
const handleDashCreateSubmit = useCallback(async (payload: { name: string; icon: string; is_personal: boolean }) => {
setDashCreateSubmitting(true);
try {
const result = await insertDashboard(payload);
try { await refreshMenus(); } catch { /* ignore */ }
const newUrl = result?.menu_url ?? result?.MENU_URL;
closeDashCreate();
toast.success(`"${payload.name}" 대시보드를 만들었습니다`);
if (newUrl) router.push(newUrl);
} catch (err) {
toast.error("대시보드 생성 실패");
} finally {
setDashCreateSubmitting(false);
}
}, [refreshMenus, closeDashCreate, router]);
// 테마 전환 — 클릭 위치에서 원형으로 새 테마가 reveal (View Transitions API)
const setNextTheme = useCallback((t: "light" | "dark", e?: React.MouseEvent) => {
if (theme === t) return;
@@ -435,10 +467,11 @@ function AppLayoutInner({ children }: AppLayoutProps) {
}
}
// 3) 대시보드 할당 (/dashboard/xxx) → admin 탭으로 렌더링 (AdminPageRenderer가 처리)
if (menu.url && menu.url.startsWith("/dashboard/")) {
console.log("[handleMenuClick] → 대시보드 탭:", menu.url);
openTab({ type: "admin", title: menuName, admin_url: menu.url });
// 3) 대시보드 (사용자 메뉴) → 자동 부여된 /숫자 URL 로 이동
// - 회사별 시퀀스가 URL 그 자체. "/{seq}" 형태만 대시보드로 인식
if (menu.url && /^\/\d+$/.test(menu.url)) {
console.log("[handleMenuClick] → 대시보드 페이지:", menu.url);
router.push(menu.url);
if (isMobile) setSidebarOpen(false);
return;
}
@@ -777,9 +810,6 @@ function AppLayoutInner({ children }: AppLayoutProps) {
return (
<>
{/* Cosmic background */}
<CosmicBackground />
{/* Theme fade overlay */}
<div className="v5-theme-fade" id="v5-theme-fade" />
@@ -804,6 +834,34 @@ function AppLayoutInner({ children }: AppLayoutProps) {
{/* mode transition 헤더 glow 라인 — 평소엔 opacity 0, mode change 시에만 flash */}
<div className="v5-hdr-glow" />
<div className="v5-hdr-r">
{/* 대시보드 생성 + 템플릿 추가 + 편집 모드 (Light/Dark 토글 왼쪽) */}
<button
className="v5-dash-btn"
onClick={openDashCreate}
title="새 대시보드 만들기"
>
<Plus size={13} />
<span></span>
</button>
<button
className="v5-dash-btn"
onClick={openDashLib}
disabled={!dashActiveId}
title={dashActiveId ? "템플릿 라이브러리에서 카드 추가" : "대시보드를 먼저 선택하세요"}
>
<Plus size={13} />
<span>릿 </span>
</button>
<button
className={`v5-dash-btn${dashEditMode ? " on" : ""}`}
onClick={toggleDashEditMode}
disabled={!dashActiveId}
title={dashActiveId ? (dashEditMode ? "편집 모드 끄기" : "편집 모드 켜기") : "대시보드 화면에서만 사용할 수 있습니다"}
>
<Edit3 size={13} />
<span>{dashEditMode ? "편집 중" : "편집"}</span>
</button>
{/* Theme pill */}
<div className="v5-pill">
<button className={theme !== "dark" ? "on" : ""} onClick={(e) => setNextTheme("light", e)}>Light</button>
@@ -862,6 +920,10 @@ function AppLayoutInner({ children }: AppLayoutProps) {
<User className="mr-2 h-4 w-4" />
<span> </span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setSettingsOpen(true)}>
<Settings className="mr-2 h-4 w-4" />
<span></span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => router.push("/admin/approvalBox")}>
<FileCheck className="mr-2 h-4 w-4" />
<span></span>
@@ -938,9 +1000,11 @@ function AppLayoutInner({ children }: AppLayoutProps) {
{/* ★ INVYONE 신규 페이지는 children 직접 렌더, 기존 VEX 페이지는 탭 시스템 */}
<main className="v5-content flex min-w-0 flex-1 flex-col overflow-hidden">
{pathname && (
pathname.startsWith('/dashboard') ||
pathname.startsWith('/dash') ||
pathname.startsWith('/admin/builder') ||
pathname.startsWith('/test-fc')
pathname.startsWith('/test-fc') ||
/^\/\d+$/.test(pathname) // /숫자 = 신규 대시보드(메뉴 시퀀스) 페이지
) ? (
// ★ flex 컨테이너로 만들어서 안쪽 dash-shell이 height:100% 잘 먹도록
<div className="relative flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
@@ -996,6 +1060,17 @@ function AppLayoutInner({ children }: AppLayoutProps) {
</div>
</DialogContent>
</Dialog>
<SettingsModal open={settingsOpen} onOpenChange={setSettingsOpen} />
{/* 전역 대시보드 생성 모달 — 헤더 "대시보드" 버튼에서 열림 */}
<CreateDashboardModal
open={dashCreateOpen}
onClose={closeDashCreate}
onSubmit={handleDashCreateSubmit}
defaultName="새 대시보드"
submitting={dashCreateSubmitting}
/>
</>
);
}