- usePopSettings: pop_settings 테이블 대신 screen_layouts_pop.popConfig에서 읽기 - 화면별 독립 설정 (URL→screen_id 자동 매핑) - PC 설정 페이지: layout-pop API로 저장/조회 - "같은 유형의 모든 화면에 적용" 동기화 체크박스 - pop_settings 400 에러 완전 제거 - 신규 화면 등록: 판매출고(5), 출고유형(6), 공정실행(7), 생산관리(8)
220 lines
6.0 KiB
TypeScript
220 lines
6.0 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect, useCallback } from "react";
|
|
import { usePathname } from "next/navigation";
|
|
import { apiClient } from "@/lib/api/client";
|
|
|
|
export interface PopSettings {
|
|
version: string;
|
|
screens: {
|
|
processExecution: {
|
|
materialInput: boolean;
|
|
photoUpload: boolean;
|
|
plcEnabled: boolean;
|
|
bomFlexible: boolean;
|
|
packagingOptions: string[];
|
|
defectTypes: string[];
|
|
reworkTargetSelection: boolean;
|
|
groupPhotoEnabled: boolean;
|
|
dateFilter: boolean;
|
|
lastProcessInventory: "auto" | "manual" | "button";
|
|
defaultWarehouse: boolean;
|
|
inspectionAutoJudge: "off" | "warn" | "fail";
|
|
standardTimeDisplay: boolean;
|
|
progressDisplay: boolean;
|
|
};
|
|
inbound: {
|
|
inspectionRequired: boolean;
|
|
photoUpload: boolean;
|
|
barcodeEnabled: boolean;
|
|
packagingRecord: boolean;
|
|
defectSeparation: boolean;
|
|
};
|
|
outbound: {
|
|
photoUpload: boolean;
|
|
barcodeEnabled: boolean;
|
|
};
|
|
home: {
|
|
kpiCarousel: boolean;
|
|
recentActivity: boolean;
|
|
bannerEnabled: boolean;
|
|
bannerText: string;
|
|
iconThemeColor: string;
|
|
iconCustomImages: boolean;
|
|
dashboardLayout: "default" | "compact" | "detailed";
|
|
};
|
|
plc: {
|
|
connectionType: "db" | "opcua" | "rest";
|
|
refreshInterval: number;
|
|
tagMappings: Array<{
|
|
tagName: string;
|
|
processCode: string;
|
|
checklistItemId: string;
|
|
unit: string;
|
|
}>;
|
|
alarmThresholds: Array<{
|
|
tagName: string;
|
|
lowerLimit: number;
|
|
upperLimit: number;
|
|
action: "warn" | "stop";
|
|
}>;
|
|
};
|
|
};
|
|
}
|
|
|
|
const DEFAULT_SETTINGS: PopSettings = {
|
|
version: "hardcoded-1.0",
|
|
screens: {
|
|
processExecution: {
|
|
materialInput: true,
|
|
photoUpload: true,
|
|
plcEnabled: false,
|
|
bomFlexible: true,
|
|
packagingOptions: ["낱개", "박스", "파렛트"],
|
|
defectTypes: ["스크래치", "치수불량", "변색", "크랙", "기포"],
|
|
reworkTargetSelection: true,
|
|
groupPhotoEnabled: false,
|
|
dateFilter: false,
|
|
lastProcessInventory: "manual",
|
|
defaultWarehouse: false,
|
|
inspectionAutoJudge: "off",
|
|
standardTimeDisplay: false,
|
|
progressDisplay: false,
|
|
},
|
|
inbound: {
|
|
inspectionRequired: false,
|
|
photoUpload: false,
|
|
barcodeEnabled: true,
|
|
packagingRecord: false,
|
|
defectSeparation: false,
|
|
},
|
|
outbound: {
|
|
photoUpload: false,
|
|
barcodeEnabled: true,
|
|
},
|
|
home: {
|
|
kpiCarousel: true,
|
|
recentActivity: true,
|
|
bannerEnabled: false,
|
|
bannerText: "",
|
|
iconThemeColor: "#2563eb",
|
|
iconCustomImages: false,
|
|
dashboardLayout: "default",
|
|
},
|
|
plc: {
|
|
connectionType: "db",
|
|
refreshInterval: 5,
|
|
tagMappings: [],
|
|
alarmThresholds: [],
|
|
},
|
|
},
|
|
};
|
|
|
|
// 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,
|
|
};
|
|
|
|
// 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 () => {
|
|
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 {
|
|
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 },
|
|
};
|
|
setSettings(merged);
|
|
}
|
|
} catch {
|
|
// Use default settings on failure
|
|
}
|
|
|
|
setLoading(false);
|
|
}, [pathname]);
|
|
|
|
useEffect(() => {
|
|
fetchSettings();
|
|
}, [fetchSettings]);
|
|
|
|
return { settings, loading };
|
|
}
|