37cac72085
- Docker/K8s 배포 설정을 pipeline-backend/pipeline-front로 통일 - 네임스페이스, 서비스, PVC 등 k8s 리소스명 pipeline-* 로 변경 - AI 에이전트 관리 기능 추가 (에이전트, 그룹, 프로바이더, 대화, API 키, 지식베이스) - 장비 연결 관리 기능 추가 (PLC/Modbus/OPC-UA/MQTT) - 배치 스케줄러에 AI agent/device collection/crawling 타입 추가 - 배치 편집 UI 개선 (6가지 실행 방식 지원) - 회사별 페이지(COMPANY_*) 제거 및 AdminPageRenderer 최적화 - 메뉴 재구성: 장비 연결 관리 시스템관리로 이동, 에이전트 오케스트레이션으로 개명 - ai-assistant 디렉토리 제거 (backend-node로 통합) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
101 lines
2.9 KiB
TypeScript
101 lines
2.9 KiB
TypeScript
"use client";
|
|
|
|
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
|
import { createContext, useCallback, useContext, useEffect, useState } from "react";
|
|
|
|
export type ColorTheme = "blue" | "teal" | "green" | "purple" | "red" | "dark";
|
|
|
|
interface ColorThemeContextType {
|
|
colorTheme: ColorTheme;
|
|
setColorTheme: (theme: ColorTheme, event?: React.MouseEvent) => void;
|
|
}
|
|
|
|
const ColorThemeContext = createContext<ColorThemeContextType>({
|
|
colorTheme: "blue",
|
|
setColorTheme: () => {},
|
|
});
|
|
|
|
export const useColorTheme = () => useContext(ColorThemeContext);
|
|
|
|
const applyTheme = (theme: ColorTheme) => {
|
|
const html = document.documentElement;
|
|
html.classList.remove("theme-teal", "theme-green", "theme-purple", "theme-red", "dark");
|
|
|
|
if (theme === "dark") {
|
|
html.classList.add("dark");
|
|
} else if (theme !== "blue") {
|
|
html.classList.add(`theme-${theme}`);
|
|
}
|
|
};
|
|
|
|
const THEME_COLORS: Record<ColorTheme, string> = {
|
|
blue: "#3b82f6",
|
|
teal: "#14b8a6",
|
|
green: "#22c55e",
|
|
purple: "#8b5cf6",
|
|
red: "#ef4444",
|
|
dark: "#0f172a",
|
|
};
|
|
|
|
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
|
const [colorTheme, setColorThemeState] = useState<ColorTheme>("blue");
|
|
|
|
useEffect(() => {
|
|
const saved = localStorage.getItem("colorTheme") as ColorTheme | null;
|
|
if (saved) {
|
|
setColorThemeState(saved);
|
|
applyTheme(saved);
|
|
}
|
|
}, []);
|
|
|
|
const setColorTheme = useCallback((theme: ColorTheme) => {
|
|
// 커튼 오버레이 생성 (왼쪽에서 오른쪽으로 밀면서 덮기)
|
|
const curtain = document.createElement("div");
|
|
curtain.style.cssText = `
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 99999;
|
|
pointer-events: none;
|
|
background: ${THEME_COLORS[theme]};
|
|
transform: translateX(-100%);
|
|
will-change: transform;
|
|
`;
|
|
document.body.appendChild(curtain);
|
|
|
|
// 1단계: 커튼이 왼→오 밀면서 화면을 덮음
|
|
requestAnimationFrame(() => {
|
|
curtain.style.transition = "transform 0.35s cubic-bezier(0.4, 0, 0.2, 1)";
|
|
curtain.style.transform = "translateX(0)";
|
|
});
|
|
|
|
// 2단계: 커튼이 화면을 완전히 덮었을 때 테마 적용
|
|
setTimeout(() => {
|
|
applyTheme(theme);
|
|
setColorThemeState(theme);
|
|
localStorage.setItem("colorTheme", theme);
|
|
|
|
// 3단계: 커튼이 오른쪽으로 빠져나감
|
|
requestAnimationFrame(() => {
|
|
curtain.style.transition = "transform 0.35s cubic-bezier(0.4, 0, 0.2, 1)";
|
|
curtain.style.transform = "translateX(100%)";
|
|
});
|
|
|
|
// 정리
|
|
setTimeout(() => curtain.remove(), 400);
|
|
}, 370);
|
|
}, []);
|
|
|
|
return (
|
|
<ColorThemeContext.Provider value={{ colorTheme, setColorTheme }}>
|
|
<NextThemesProvider
|
|
attribute="class"
|
|
defaultTheme="light"
|
|
enableSystem={false}
|
|
disableTransitionOnChange
|
|
>
|
|
{children}
|
|
</NextThemesProvider>
|
|
</ColorThemeContext.Provider>
|
|
);
|
|
}
|