feat: POP 설정 저장소를 screen_layouts_pop으로 전환

- usePopSettings: pop_settings 테이블 대신 screen_layouts_pop.popConfig에서 읽기
- 화면별 독립 설정 (URL→screen_id 자동 매핑)
- PC 설정 페이지: layout-pop API로 저장/조회
- "같은 유형의 모든 화면에 적용" 동기화 체크박스
- pop_settings 400 에러 완전 제거
- 신규 화면 등록: 판매출고(5), 출고유형(6), 공정실행(7), 생산관리(8)
This commit is contained in:
SeongHyun Kim
2026-04-06 10:46:37 +09:00
parent b844eb8eb8
commit ac913990a3
2 changed files with 207 additions and 97 deletions
+95 -37
View File
@@ -1,6 +1,7 @@
"use client";
import { useState, useEffect, useCallback } from "react";
import { usePathname } from "next/navigation";
import { apiClient } from "@/lib/api/client";
export interface PopSettings {
@@ -109,53 +110,110 @@ const DEFAULT_SETTINGS: PopSettings = {
},
};
let cachedSettings: PopSettings | null = null;
// URL -> screen_id mapping
const POP_SCREEN_MAP: Record<string, number> = {
"/pop/home": 6526,
"/pop/inbound": 6529,
"/pop/inbound/purchase": 6528,
"/pop/inbound/cart": 6527,
"/pop/outbound": 6,
"/pop/outbound/sales": 5,
"/pop/production": 8,
"/pop/production/process": 7,
};
export function usePopSettings() {
const [settings, setSettings] = useState<PopSettings>(cachedSettings || DEFAULT_SETTINGS);
const [loading, setLoading] = useState(!cachedSettings);
// URL -> settingsKey mapping
const PATH_TO_SETTINGS_KEY: Record<string, keyof PopSettings["screens"]> = {
"/pop/home": "home",
"/pop/inbound": "inbound",
"/pop/inbound/purchase": "inbound",
"/pop/inbound/cart": "inbound",
"/pop/outbound": "outbound",
"/pop/outbound/sales": "outbound",
"/pop/production": "processExecution",
"/pop/production/process": "processExecution",
};
function getScreenIdFromPath(pathname: string): number | null {
// Exact match first
if (POP_SCREEN_MAP[pathname]) return POP_SCREEN_MAP[pathname];
// Longest-prefix match (e.g. /pop/production/process/xxx -> 7)
const sorted = Object.keys(POP_SCREEN_MAP).sort((a, b) => b.length - a.length);
for (const path of sorted) {
if (pathname.startsWith(path)) return POP_SCREEN_MAP[path];
}
return null;
}
function getSettingsKeyFromPath(pathname: string): keyof PopSettings["screens"] | null {
// Exact match first
if (PATH_TO_SETTINGS_KEY[pathname]) return PATH_TO_SETTINGS_KEY[pathname];
// Longest-prefix match
const sorted = Object.keys(PATH_TO_SETTINGS_KEY).sort((a, b) => b.length - a.length);
for (const path of sorted) {
if (pathname.startsWith(path)) return PATH_TO_SETTINGS_KEY[path];
}
return null;
}
// Per-screenId cache to avoid redundant fetches
const screenCache: Record<number, Record<string, unknown>> = {};
export function usePopSettings(screenPath?: string) {
const autoPathname = usePathname();
const pathname = screenPath || autoPathname || "";
const [settings, setSettings] = useState<PopSettings>(DEFAULT_SETTINGS);
const [loading, setLoading] = useState(true);
const fetchSettings = useCallback(async () => {
if (cachedSettings) { setSettings(cachedSettings); setLoading(false); return; }
const screenId = getScreenIdFromPath(pathname);
const settingsKey = getSettingsKeyFromPath(pathname);
if (!screenId || !settingsKey) {
setLoading(false);
return;
}
// Use cache if available
if (screenCache[screenId]) {
const popConfig = screenCache[screenId];
const merged = { ...DEFAULT_SETTINGS };
merged.screens = {
...merged.screens,
[settingsKey]: { ...merged.screens[settingsKey], ...popConfig },
};
setSettings(merged);
setLoading(false);
return;
}
try {
// pop_settings 테이블이 없을 수 있으므로 에러 시 기본값 사용
const res = await apiClient.get("/data/pop_settings", { params: { pageSize: 1 } }).catch(() => null);
if (!res) { setLoading(false); return; }
const rows = res.data?.data?.data || res.data?.data || [];
if (rows.length > 0 && rows[0].settings_data) {
const parsed = typeof rows[0].settings_data === "string"
? JSON.parse(rows[0].settings_data) : rows[0].settings_data;
const merged = {
...DEFAULT_SETTINGS,
...parsed,
screens: {
...DEFAULT_SETTINGS.screens,
...parsed.screens,
processExecution: { ...DEFAULT_SETTINGS.screens.processExecution, ...parsed.screens?.processExecution },
inbound: { ...DEFAULT_SETTINGS.screens.inbound, ...parsed.screens?.inbound },
outbound: { ...DEFAULT_SETTINGS.screens.outbound, ...parsed.screens?.outbound },
home: { ...DEFAULT_SETTINGS.screens.home, ...parsed.screens?.home },
plc: { ...DEFAULT_SETTINGS.screens.plc, ...parsed.screens?.plc },
},
const res = await apiClient
.get(`/screen-management/screens/${screenId}/layout-pop`)
.catch(() => null);
if (res?.data?.data?.settings?.popConfig) {
const popConfig = res.data.data.settings.popConfig;
screenCache[screenId] = popConfig;
const merged = { ...DEFAULT_SETTINGS };
merged.screens = {
...merged.screens,
[settingsKey]: { ...merged.screens[settingsKey], ...popConfig },
};
cachedSettings = merged;
setSettings(merged);
}
} catch {
// localStorage fallback
const local = localStorage.getItem("pop_settings");
if (local) {
try {
const parsed = JSON.parse(local);
cachedSettings = { ...DEFAULT_SETTINGS, ...parsed };
setSettings(cachedSettings);
} catch { /* use default */ }
}
// Use default settings on failure
}
setLoading(false);
}, []);
useEffect(() => { fetchSettings(); }, [fetchSettings]);
setLoading(false);
}, [pathname]);
useEffect(() => {
fetchSettings();
}, [fetchSettings]);
return { settings, loading };
}