Merge branch 'mhkim-node' of https://g.wace.me/jskim/vexplor_dev into jskim-node
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -5936,65 +5936,12 @@ export class ScreenManagementService {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 일반 사용자: 회사별 우선, 없으면 템플릿에서 자동 복제
|
||||
// 일반 사용자: 회사별 레이아웃만 조회 (fallback/자동 복제 없음)
|
||||
layout = await queryOne<{ layout_data: any }>(
|
||||
`SELECT layout_data FROM screen_layouts_pop
|
||||
WHERE screen_id = $1 AND company_code = $2`,
|
||||
[screenId, companyCode],
|
||||
);
|
||||
|
||||
// 회사별 레이아웃이 없으면 템플릿에서 자동 복제
|
||||
if (!layout && companyCode !== "*") {
|
||||
// 1. 공통(*) 템플릿 조회
|
||||
let templateLayout = await queryOne<{ layout_data: any }>(
|
||||
`SELECT layout_data FROM screen_layouts_pop
|
||||
WHERE screen_id = $1 AND company_code = '*'`,
|
||||
[screenId],
|
||||
);
|
||||
|
||||
// 2. 공통 없으면 COMPANY_7(탑씰) 폴백
|
||||
if (!templateLayout) {
|
||||
templateLayout = await queryOne<{ layout_data: any }>(
|
||||
`SELECT layout_data FROM screen_layouts_pop
|
||||
WHERE screen_id = $1 AND company_code = 'COMPANY_7'`,
|
||||
[screenId],
|
||||
);
|
||||
}
|
||||
|
||||
// 3. 템플릿이 있으면 해당 회사용으로 복제
|
||||
if (templateLayout) {
|
||||
console.log(`POP 레이아웃 자동 복제: screen_id=${screenId}, 대상 회사=${companyCode}`);
|
||||
|
||||
// 회사명 조회 (레이아웃 내 회사명 치환용)
|
||||
const companyInfo = await queryOne<{ company_name: string }>(
|
||||
`SELECT company_name FROM company_mng WHERE company_code = $1`,
|
||||
[companyCode],
|
||||
);
|
||||
const companyName = companyInfo?.company_name || companyCode;
|
||||
|
||||
let clonedData = JSON.parse(JSON.stringify(templateLayout.layout_data));
|
||||
|
||||
// layout_data 내 회사명 텍스트 치환 (탑씰 관련 문자열 → 대상 회사명)
|
||||
const layoutStr = JSON.stringify(clonedData);
|
||||
const replacedStr = layoutStr
|
||||
.replace(/\(주\)탑씰/g, companyName)
|
||||
.replace(/탑씰/g, companyName)
|
||||
.replace(/TOPSEAL/gi, companyName);
|
||||
clonedData = JSON.parse(replacedStr);
|
||||
|
||||
// 해당 회사 코드로 INSERT (UPSERT)
|
||||
await query(
|
||||
`INSERT INTO screen_layouts_pop (screen_id, company_code, layout_data, created_at, updated_at, created_by, updated_by)
|
||||
VALUES ($1, $2, $3, NOW(), NOW(), 'SYSTEM', 'SYSTEM')
|
||||
ON CONFLICT (screen_id, company_code)
|
||||
DO UPDATE SET layout_data = $3, updated_at = NOW(), updated_by = 'SYSTEM'`,
|
||||
[screenId, companyCode, JSON.stringify(clonedData)],
|
||||
);
|
||||
|
||||
console.log(`POP 레이아웃 자동 복제 완료: screen_id=${screenId}, company=${companyCode}`);
|
||||
layout = { layout_data: clonedData };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!layout) {
|
||||
@@ -6133,11 +6080,10 @@ export class ScreenManagementService {
|
||||
[],
|
||||
);
|
||||
} else {
|
||||
// 일반 회사: 해당 회사 레이아웃 + 공통(*)/COMPANY_7 템플릿도 포함
|
||||
// (getLayoutPop에서 자동 복제하므로 템플릿이 있으면 해당 회사도 사용 가능)
|
||||
// 일반 회사: 해당 회사 레이아웃만 조회 (자동 복제/fallback 없음)
|
||||
result = await query<{ screen_id: number }>(
|
||||
`SELECT DISTINCT screen_id FROM screen_layouts_pop
|
||||
WHERE company_code IN ($1, '*', 'COMPANY_7')`,
|
||||
WHERE company_code = $1`,
|
||||
[companyCode],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -143,10 +143,10 @@ export function PopShell({ children, showBanner = true, title, showBack = false,
|
||||
|
||||
// POP 설정에서 배너 텍스트 로드 (POP화면설정에서 관리)
|
||||
const { settings: popSettings } = usePopSettings();
|
||||
const homeConfig = (popSettings as any)?.screens?.home;
|
||||
const bannerEnabled = homeConfig?.bannerEnabled ?? true;
|
||||
const bannerText = homeConfig?.bannerText;
|
||||
const marqueeText = bannerText || "[공지] 금일 오후 3시 전체 안전교육 실시 예정입니다. 전 직원 필참 바랍니다. | [알림] 내일 설비 정기점검으로 인한 3호기 가동 중지 예정 | [안내] 4월 생산실적 우수팀 발표 - 생산1팀 축하드립니다!";
|
||||
const mainConfig = (popSettings as any)?.screens?.main;
|
||||
const bannerEnabled = mainConfig?.bannerEnabled ?? true;
|
||||
const bannerText = mainConfig?.bannerText;
|
||||
const marqueeText = bannerText || "";
|
||||
|
||||
return (
|
||||
<div className="min-h-screen min-h-dvh flex flex-col" style={{ background: "#F5F5F5" }}>
|
||||
@@ -402,7 +402,7 @@ export function PopShell({ children, showBanner = true, title, showBack = false,
|
||||
)}
|
||||
|
||||
{/* ===== NOTICE BANNER (Marquee) ===== */}
|
||||
{showBanner && bannerEnabled && <div className="bg-amber-50 border-b border-amber-200 px-4 py-2 flex items-center gap-3">
|
||||
{showBanner && bannerEnabled && bannerText && <div className="bg-amber-50 border-b border-amber-200 px-4 py-2 flex items-center gap-3">
|
||||
<div className="flex items-center gap-1.5 shrink-0">
|
||||
<span className="text-amber-600 text-sm">📢</span>
|
||||
<span className="text-xs font-bold text-amber-700">공지</span>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import type React from "react";
|
||||
import { KpiCarousel, RecentActivity } from "@/components/pop/hardcoded";
|
||||
import { usePopCompanyPath } from "@/hooks/usePopCompanyPath";
|
||||
|
||||
interface MenuIconItem {
|
||||
@@ -142,9 +141,7 @@ function LocalMenuIcons() {
|
||||
export default function PopMainPage() {
|
||||
return (
|
||||
<>
|
||||
<KpiCarousel />
|
||||
<LocalMenuIcons />
|
||||
<RecentActivity />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -143,10 +143,10 @@ export function PopShell({ children, showBanner = true, title, showBack = false,
|
||||
|
||||
// POP 설정에서 배너 텍스트 로드 (POP화면설정에서 관리)
|
||||
const { settings: popSettings } = usePopSettings();
|
||||
const homeConfig = (popSettings as any)?.screens?.home;
|
||||
const bannerEnabled = homeConfig?.bannerEnabled ?? true;
|
||||
const bannerText = homeConfig?.bannerText;
|
||||
const marqueeText = bannerText || "[공지] 금일 오후 3시 전체 안전교육 실시 예정입니다. 전 직원 필참 바랍니다. | [알림] 내일 설비 정기점검으로 인한 3호기 가동 중지 예정 | [안내] 4월 생산실적 우수팀 발표 - 생산1팀 축하드립니다!";
|
||||
const mainConfig = (popSettings as any)?.screens?.main;
|
||||
const bannerEnabled = mainConfig?.bannerEnabled ?? true;
|
||||
const bannerText = mainConfig?.bannerText;
|
||||
const marqueeText = bannerText || "";
|
||||
|
||||
return (
|
||||
<div className="min-h-screen min-h-dvh flex flex-col" style={{ background: "#F5F5F5" }}>
|
||||
@@ -402,7 +402,7 @@ export function PopShell({ children, showBanner = true, title, showBack = false,
|
||||
)}
|
||||
|
||||
{/* ===== NOTICE BANNER (Marquee) ===== */}
|
||||
{showBanner && bannerEnabled && <div className="bg-amber-50 border-b border-amber-200 px-4 py-2 flex items-center gap-3">
|
||||
{showBanner && bannerEnabled && bannerText && <div className="bg-amber-50 border-b border-amber-200 px-4 py-2 flex items-center gap-3">
|
||||
<div className="flex items-center gap-1.5 shrink-0">
|
||||
<span className="text-amber-600 text-sm">📢</span>
|
||||
<span className="text-xs font-bold text-amber-700">공지</span>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import type React from "react";
|
||||
import { KpiCarousel, RecentActivity } from "@/components/pop/hardcoded";
|
||||
import { usePopCompanyPath } from "@/hooks/usePopCompanyPath";
|
||||
|
||||
interface MenuIconItem {
|
||||
@@ -142,9 +141,7 @@ function LocalMenuIcons() {
|
||||
export default function PopMainPage() {
|
||||
return (
|
||||
<>
|
||||
<KpiCarousel />
|
||||
<LocalMenuIcons />
|
||||
<RecentActivity />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -143,10 +143,10 @@ export function PopShell({ children, showBanner = true, title, showBack = false,
|
||||
|
||||
// POP 설정에서 배너 텍스트 로드 (POP화면설정에서 관리)
|
||||
const { settings: popSettings } = usePopSettings();
|
||||
const homeConfig = (popSettings as any)?.screens?.home;
|
||||
const bannerEnabled = homeConfig?.bannerEnabled ?? true;
|
||||
const bannerText = homeConfig?.bannerText;
|
||||
const marqueeText = bannerText || "[공지] 금일 오후 3시 전체 안전교육 실시 예정입니다. 전 직원 필참 바랍니다. | [알림] 내일 설비 정기점검으로 인한 3호기 가동 중지 예정 | [안내] 4월 생산실적 우수팀 발표 - 생산1팀 축하드립니다!";
|
||||
const mainConfig = (popSettings as any)?.screens?.main;
|
||||
const bannerEnabled = mainConfig?.bannerEnabled ?? true;
|
||||
const bannerText = mainConfig?.bannerText;
|
||||
const marqueeText = bannerText || "";
|
||||
|
||||
return (
|
||||
<div className="min-h-screen min-h-dvh flex flex-col" style={{ background: "#F5F5F5" }}>
|
||||
@@ -402,7 +402,7 @@ export function PopShell({ children, showBanner = true, title, showBack = false,
|
||||
)}
|
||||
|
||||
{/* ===== NOTICE BANNER (Marquee) ===== */}
|
||||
{showBanner && bannerEnabled && <div className="bg-amber-50 border-b border-amber-200 px-4 py-2 flex items-center gap-3">
|
||||
{showBanner && bannerEnabled && bannerText && <div className="bg-amber-50 border-b border-amber-200 px-4 py-2 flex items-center gap-3">
|
||||
<div className="flex items-center gap-1.5 shrink-0">
|
||||
<span className="text-amber-600 text-sm">📢</span>
|
||||
<span className="text-xs font-bold text-amber-700">공지</span>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import type React from "react";
|
||||
import { KpiCarousel, RecentActivity } from "@/components/pop/hardcoded";
|
||||
import { usePopCompanyPath } from "@/hooks/usePopCompanyPath";
|
||||
|
||||
interface MenuIconItem {
|
||||
@@ -142,9 +141,7 @@ function LocalMenuIcons() {
|
||||
export default function PopMainPage() {
|
||||
return (
|
||||
<>
|
||||
<KpiCarousel />
|
||||
<LocalMenuIcons />
|
||||
<RecentActivity />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -143,10 +143,10 @@ export function PopShell({ children, showBanner = true, title, showBack = false,
|
||||
|
||||
// POP 설정에서 배너 텍스트 로드 (POP화면설정에서 관리)
|
||||
const { settings: popSettings } = usePopSettings();
|
||||
const homeConfig = (popSettings as any)?.screens?.home;
|
||||
const bannerEnabled = homeConfig?.bannerEnabled ?? true;
|
||||
const bannerText = homeConfig?.bannerText;
|
||||
const marqueeText = bannerText || "[공지] 금일 오후 3시 전체 안전교육 실시 예정입니다. 전 직원 필참 바랍니다. | [알림] 내일 설비 정기점검으로 인한 3호기 가동 중지 예정 | [안내] 4월 생산실적 우수팀 발표 - 생산1팀 축하드립니다!";
|
||||
const mainConfig = (popSettings as any)?.screens?.main;
|
||||
const bannerEnabled = mainConfig?.bannerEnabled ?? true;
|
||||
const bannerText = mainConfig?.bannerText;
|
||||
const marqueeText = bannerText || "";
|
||||
|
||||
return (
|
||||
<div className="min-h-screen min-h-dvh flex flex-col" style={{ background: "#F5F5F5" }}>
|
||||
@@ -402,7 +402,7 @@ export function PopShell({ children, showBanner = true, title, showBack = false,
|
||||
)}
|
||||
|
||||
{/* ===== NOTICE BANNER (Marquee) ===== */}
|
||||
{showBanner && bannerEnabled && <div className="bg-amber-50 border-b border-amber-200 px-4 py-2 flex items-center gap-3">
|
||||
{showBanner && bannerEnabled && bannerText && <div className="bg-amber-50 border-b border-amber-200 px-4 py-2 flex items-center gap-3">
|
||||
<div className="flex items-center gap-1.5 shrink-0">
|
||||
<span className="text-amber-600 text-sm">📢</span>
|
||||
<span className="text-xs font-bold text-amber-700">공지</span>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import type React from "react";
|
||||
import { KpiCarousel, RecentActivity } from "@/components/pop/hardcoded";
|
||||
import { usePopCompanyPath } from "@/hooks/usePopCompanyPath";
|
||||
|
||||
interface MenuIconItem {
|
||||
@@ -142,9 +141,7 @@ function LocalMenuIcons() {
|
||||
export default function PopMainPage() {
|
||||
return (
|
||||
<>
|
||||
<KpiCarousel />
|
||||
<LocalMenuIcons />
|
||||
<RecentActivity />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -143,10 +143,10 @@ export function PopShell({ children, showBanner = true, title, showBack = false,
|
||||
|
||||
// POP 설정에서 배너 텍스트 로드 (POP화면설정에서 관리)
|
||||
const { settings: popSettings } = usePopSettings();
|
||||
const homeConfig = (popSettings as any)?.screens?.home;
|
||||
const bannerEnabled = homeConfig?.bannerEnabled ?? true;
|
||||
const bannerText = homeConfig?.bannerText;
|
||||
const marqueeText = bannerText || "[공지] 금일 오후 3시 전체 안전교육 실시 예정입니다. 전 직원 필참 바랍니다. | [알림] 내일 설비 정기점검으로 인한 3호기 가동 중지 예정 | [안내] 4월 생산실적 우수팀 발표 - 생산1팀 축하드립니다!";
|
||||
const mainConfig = (popSettings as any)?.screens?.main;
|
||||
const bannerEnabled = mainConfig?.bannerEnabled ?? true;
|
||||
const bannerText = mainConfig?.bannerText;
|
||||
const marqueeText = bannerText || "";
|
||||
|
||||
return (
|
||||
<div className="min-h-screen min-h-dvh flex flex-col" style={{ background: "#F5F5F5" }}>
|
||||
@@ -402,7 +402,7 @@ export function PopShell({ children, showBanner = true, title, showBack = false,
|
||||
)}
|
||||
|
||||
{/* ===== NOTICE BANNER (Marquee) ===== */}
|
||||
{showBanner && bannerEnabled && <div className="bg-amber-50 border-b border-amber-200 px-4 py-2 flex items-center gap-3">
|
||||
{showBanner && bannerEnabled && bannerText && <div className="bg-amber-50 border-b border-amber-200 px-4 py-2 flex items-center gap-3">
|
||||
<div className="flex items-center gap-1.5 shrink-0">
|
||||
<span className="text-amber-600 text-sm">📢</span>
|
||||
<span className="text-xs font-bold text-amber-700">공지</span>
|
||||
|
||||
@@ -831,7 +831,7 @@ export function WorkOrderList(props: WorkOrderListProps) {
|
||||
const masterProcesses = useMemo(() => {
|
||||
// 마스터 행 + 분할 행(진행중/완료/리워크) — 중복 제거
|
||||
const seen = new Set<string>();
|
||||
return allProcesses.filter((p) => {
|
||||
const result = allProcesses.filter((p) => {
|
||||
if (seen.has(p.id)) return false;
|
||||
const include =
|
||||
!p.parent_process_id || // 마스터 행
|
||||
@@ -841,6 +841,32 @@ export function WorkOrderList(props: WorkOrderListProps) {
|
||||
if (include) seen.add(p.id);
|
||||
return include;
|
||||
});
|
||||
const __tag = `[POP-DEBUG][WorkOrderList][${new Date().toISOString()}]`;
|
||||
const dist: Record<string, number> = {};
|
||||
for (const p of result) {
|
||||
const kind = p.parent_process_id ? "split" : "master";
|
||||
const rw = isReworkProcess(p) ? "(R)" : "";
|
||||
const k = `${p.process_code}:${kind}${rw}:${p.status}`;
|
||||
dist[k] = (dist[k] || 0) + 1;
|
||||
}
|
||||
console.log(`${__tag} 3) masterProcesses 생성:`, {
|
||||
input_allProcesses: allProcesses.length,
|
||||
output_masterProcesses: result.length,
|
||||
excluded: allProcesses.length - result.length,
|
||||
distribution: dist,
|
||||
});
|
||||
console.log(`${__tag} 3-1) masterProcesses — P001 + 모든 리워크 상세:`);
|
||||
console.table(result
|
||||
.filter((p) => p.process_code === "P001" || isReworkProcess(p))
|
||||
.map((p) => ({
|
||||
id: p.id,
|
||||
wo_id: p.wo_id,
|
||||
process_code: p.process_code,
|
||||
kind: p.parent_process_id ? "split" : "master",
|
||||
status: p.status,
|
||||
is_rework: p.is_rework,
|
||||
})));
|
||||
return result;
|
||||
}, [allProcesses]);
|
||||
|
||||
const equipmentMap = useMemo(() => {
|
||||
@@ -857,36 +883,56 @@ export function WorkOrderList(props: WorkOrderListProps) {
|
||||
|
||||
/* ---- Filtered processes ---- */
|
||||
const filteredProcesses = useMemo(() => {
|
||||
if (selectedProcess === "__all__") return []; // 공정 미선택 시 빈 목록
|
||||
return masterProcesses.filter((proc) => {
|
||||
const __tag = `[POP-DEBUG][WorkOrderList][${new Date().toISOString()}]`;
|
||||
if (selectedProcess === "__all__") {
|
||||
console.log(`${__tag} 4) filteredProcesses: 공정 미선택 — 빈 배열`);
|
||||
return [];
|
||||
}
|
||||
const __excluded: Array<{ id: string; reason: string; status: string; process_code: string }> = [];
|
||||
const result = masterProcesses.filter((proc) => {
|
||||
const isRework = isReworkProcess(proc);
|
||||
const isMaster = !proc.parent_process_id;
|
||||
// 완료/진행중 탭에서는 SPLIT만 표시 (마스터 제외)
|
||||
if (
|
||||
isMaster &&
|
||||
!isRework &&
|
||||
(activeTab === "completed" || activeTab === "in_progress")
|
||||
)
|
||||
if (isMaster && !isRework && (activeTab === "completed" || activeTab === "in_progress")) {
|
||||
__excluded.push({ id: proc.id, reason: "master_in_completed_or_in_progress_tab", status: proc.status, process_code: proc.process_code });
|
||||
return false;
|
||||
// 리워크 마스터가 in_progress/completed면 SPLIT이 생성된 것 → 리워크 마스터 숨김 (SPLIT은 표시)
|
||||
if (
|
||||
isRework &&
|
||||
!proc.parent_process_id &&
|
||||
(proc.status === "in_progress" || proc.status === "completed")
|
||||
)
|
||||
}
|
||||
if (isRework && !proc.parent_process_id && (proc.status === "in_progress" || proc.status === "completed")) {
|
||||
__excluded.push({ id: proc.id, reason: "rework_master_with_split", status: proc.status, process_code: proc.process_code });
|
||||
return false;
|
||||
// 재작업 카드는 공정 필터 무시 (모든 공정에서 표시)
|
||||
if (!isRework && proc.process_code !== selectedProcess) return false;
|
||||
}
|
||||
if (!isRework && proc.process_code !== selectedProcess) {
|
||||
__excluded.push({ id: proc.id, reason: `process_code_mismatch(${proc.process_code} vs ${selectedProcess})`, status: proc.status, process_code: proc.process_code });
|
||||
return false;
|
||||
}
|
||||
if (selectedEquipment !== "__all__") {
|
||||
const wi = instructionMap[proc.wo_id];
|
||||
if (!wi) return false;
|
||||
if (!wi) {
|
||||
__excluded.push({ id: proc.id, reason: "no_wi_for_equipment_filter", status: proc.status, process_code: proc.process_code });
|
||||
return false;
|
||||
}
|
||||
const eqId = wi.equipment_id;
|
||||
const eq = equipmentMap[eqId];
|
||||
if (!eq || eq.equipment_code !== selectedEquipment) return false;
|
||||
if (!eq || eq.equipment_code !== selectedEquipment) {
|
||||
__excluded.push({ id: proc.id, reason: `equipment_mismatch(${eq?.equipment_code} vs ${selectedEquipment})`, status: proc.status, process_code: proc.process_code });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (activeTab !== "all" && proc.status !== activeTab) {
|
||||
__excluded.push({ id: proc.id, reason: `tab_mismatch(${proc.status} vs ${activeTab})`, status: proc.status, process_code: proc.process_code });
|
||||
return false;
|
||||
}
|
||||
if (activeTab !== "all" && proc.status !== activeTab) return false;
|
||||
return true;
|
||||
});
|
||||
console.log(`${__tag} 4) filteredProcesses 계산:`, {
|
||||
activeTab,
|
||||
selectedProcess,
|
||||
selectedEquipment,
|
||||
input: masterProcesses.length,
|
||||
output: result.length,
|
||||
excluded_count: __excluded.length,
|
||||
excluded_by_reason: __excluded.reduce((m: Record<string, number>, e) => { m[e.reason] = (m[e.reason] || 0) + 1; return m; }, {}),
|
||||
});
|
||||
return result;
|
||||
}, [
|
||||
masterProcesses,
|
||||
selectedProcess,
|
||||
@@ -923,23 +969,36 @@ export function WorkOrderList(props: WorkOrderListProps) {
|
||||
waiting: 0,
|
||||
completed: 0,
|
||||
};
|
||||
const __tag = `[POP-DEBUG][WorkOrderList][${new Date().toISOString()}]`;
|
||||
const breakdown: Array<{ id: string; wo_id: string; process_code: string; status: string; kind: string; is_rework: string; bucket: string }> = [];
|
||||
for (const proc of preFiltered) {
|
||||
const isMaster = !proc.parent_process_id;
|
||||
const isRw = isReworkProcess(proc);
|
||||
// 리워크 마스터가 in_progress/completed면 SPLIT이 있으므로 카운트 제외
|
||||
const kind = isMaster ? "master" : "split";
|
||||
if (
|
||||
isRw &&
|
||||
!proc.parent_process_id &&
|
||||
(proc.status === "in_progress" || proc.status === "completed")
|
||||
)
|
||||
) {
|
||||
breakdown.push({ id: proc.id, wo_id: proc.wo_id, process_code: proc.process_code, status: proc.status, kind, is_rework: proc.is_rework || "", bucket: "★SKIPPED" });
|
||||
continue;
|
||||
if (proc.status === "acceptable") counts.acceptable++;
|
||||
else if (proc.status === "in_progress" && (!isMaster || isRw))
|
||||
counts.in_progress++;
|
||||
else if (proc.status === "completed" && (!isMaster || isRw))
|
||||
counts.completed++;
|
||||
else counts.waiting++;
|
||||
}
|
||||
let bucket: string;
|
||||
if (proc.status === "acceptable") { counts.acceptable++; bucket = "acceptable"; }
|
||||
else if (proc.status === "in_progress" && (!isMaster || isRw)) { counts.in_progress++; bucket = "in_progress"; }
|
||||
else if (proc.status === "completed" && (!isMaster || isRw)) { counts.completed++; bucket = "completed"; }
|
||||
else { counts.waiting++; bucket = "waiting"; }
|
||||
breakdown.push({ id: proc.id, wo_id: proc.wo_id, process_code: proc.process_code, status: proc.status, kind, is_rework: proc.is_rework || "", bucket });
|
||||
}
|
||||
console.log(`${__tag} 5) tabCounts 계산:`, {
|
||||
selectedProcess,
|
||||
selectedEquipment,
|
||||
masterProcesses_total: masterProcesses.length,
|
||||
preFiltered_total: preFiltered.length,
|
||||
counts,
|
||||
});
|
||||
console.log(`${__tag} 5-1) 카드별 분류 breakdown (${breakdown.length}개):`);
|
||||
console.table(breakdown);
|
||||
return counts;
|
||||
}, [
|
||||
masterProcesses,
|
||||
|
||||
@@ -77,25 +77,53 @@ export function useProcessData() {
|
||||
if (inFlight.current) return inFlight.current;
|
||||
|
||||
const task = (async () => {
|
||||
const __tag = `[POP-DEBUG][useProcessData][${new Date().toISOString()}]`;
|
||||
console.log(`${__tag} ▶ fetchData 시작`, { withSync: !!opts?.withSync });
|
||||
setLoading(true);
|
||||
if (opts?.withSync) setSyncing(true);
|
||||
try {
|
||||
if (opts?.withSync) {
|
||||
try {
|
||||
await apiClient.post("/pop/production/sync-work-instructions");
|
||||
} catch {
|
||||
const __t0 = Date.now();
|
||||
const syncRes = await apiClient.post("/pop/production/sync-work-instructions");
|
||||
console.log(`${__tag} 0) POST /sync-work-instructions (${Date.now() - __t0}ms)`, syncRes.data);
|
||||
} catch (e) {
|
||||
console.warn(`${__tag} 0) sync 실패`, e);
|
||||
toast.warning(
|
||||
"동기화 실패 — 최신 작업지시가 반영되지 않을 수 있습니다",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const __t1 = Date.now();
|
||||
const [wiRes, procRes, pmRes, eqRes] = await Promise.all([
|
||||
apiClient.get("/work-instruction/list"),
|
||||
apiClient.get("/pop/production/processes"),
|
||||
dataApi.getTableData("process_mng", { size: 500 }),
|
||||
dataApi.getTableData("equipment_mng", { size: 500 }),
|
||||
]);
|
||||
console.log(`${__tag} 1) 4개 API 병렬 호출 완료 (${Date.now() - __t1}ms)`);
|
||||
const __wiArr = (Array.isArray(wiRes.data?.data) ? wiRes.data.data : wiRes.data?.data?.rows ?? []) as any[];
|
||||
console.log(`${__tag} 1-1) /work-instruction/list 응답:`, {
|
||||
raw_length: __wiArr.length,
|
||||
unique_wi_ids: new Set(__wiArr.map((r: any) => r.wi_id || r.id)).size,
|
||||
sample: __wiArr.slice(0, 3),
|
||||
});
|
||||
console.log(`${__tag} 1-2) /pop/production/processes 응답:`, {
|
||||
raw_length: procRes.data?.data?.length ?? 0,
|
||||
by_process_code: (procRes.data?.data ?? []).reduce((m: Record<string, number>, d: any) => { m[d.process_code] = (m[d.process_code] || 0) + 1; return m; }, {}),
|
||||
});
|
||||
console.log(`${__tag} 1-3) /pop/production/processes — P001 카드 RAW:`);
|
||||
console.table((procRes.data?.data ?? [])
|
||||
.filter((d: any) => d.process_code === "P001")
|
||||
.map((d: any) => ({
|
||||
id: d.id,
|
||||
seq_no: d.seq_no,
|
||||
parent: d.parent_process_id || "",
|
||||
master_status: d.status,
|
||||
wopr_count: d.accepted_results?.length ?? 0,
|
||||
wopr_statuses: (d.accepted_results || []).map((r: any) => `${r.seq}:${r.status}${r.is_rework === "Y" ? "(R)" : ""}`).join("|"),
|
||||
})));
|
||||
|
||||
// work-instruction: header+detail 조인이라 id 중복 → 첫 행만 취함
|
||||
let wiRaw: Record<string, unknown>[] = [];
|
||||
@@ -175,6 +203,55 @@ export function useProcessData() {
|
||||
setAllProcesses(flat);
|
||||
setProcessList((pmRes.data ?? []) as ProcessMng[]);
|
||||
setEquipmentList((eqRes.data ?? []) as EquipmentMng[]);
|
||||
|
||||
// 2) flat 펼친 후 분포 — useProcessData가 master + 모든 wopr를 split row로 펼친 결과
|
||||
const allDist: Record<string, number> = {};
|
||||
const p001Dist: Record<string, number> = {};
|
||||
const reworkDist: Record<string, number> = {};
|
||||
for (const p of flat) {
|
||||
const kind = p.parent_process_id ? "split" : "master";
|
||||
const key = `${kind}:${p.status}`;
|
||||
allDist[key] = (allDist[key] || 0) + 1;
|
||||
if (p.process_code === "P001") {
|
||||
p001Dist[key] = (p001Dist[key] || 0) + 1;
|
||||
}
|
||||
if (p.is_rework === "Y") {
|
||||
reworkDist[`${p.process_code}:${kind}:${p.status}`] =
|
||||
(reworkDist[`${p.process_code}:${kind}:${p.status}`] || 0) + 1;
|
||||
}
|
||||
}
|
||||
console.log(`${__tag} 2) flat 펼침 완료:`, {
|
||||
raw_wop_count: rawRows.length,
|
||||
flat_count: flat.length,
|
||||
expand_factor: flat.length / Math.max(1, rawRows.length),
|
||||
all_kind_status_dist: allDist,
|
||||
p001_kind_status_dist: p001Dist,
|
||||
rework_dist: reworkDist,
|
||||
});
|
||||
console.log(`${__tag} 2-1) flat — P001 row 상세:`);
|
||||
console.table(flat
|
||||
.filter((p) => p.process_code === "P001")
|
||||
.map((p) => ({
|
||||
id: p.id,
|
||||
wo_id: p.wo_id,
|
||||
seq_no: p.seq_no,
|
||||
kind: p.parent_process_id ? "split" : "master",
|
||||
status: p.status,
|
||||
is_rework: p.is_rework,
|
||||
split_no: p.split_no ?? "",
|
||||
})));
|
||||
console.log(`${__tag} 2-2) flat — 모든 리워크 row:`);
|
||||
console.table(flat
|
||||
.filter((p) => p.is_rework === "Y")
|
||||
.map((p) => ({
|
||||
id: p.id,
|
||||
wo_id: p.wo_id,
|
||||
process_code: p.process_code,
|
||||
seq_no: p.seq_no,
|
||||
kind: p.parent_process_id ? "split" : "master",
|
||||
status: p.status,
|
||||
})));
|
||||
console.log(`${__tag} ◀ fetchData 종료`);
|
||||
} catch {
|
||||
toast.error("데이터 조회 실패");
|
||||
} finally {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import type React from "react";
|
||||
import { KpiCarousel, RecentActivity } from "@/components/pop/hardcoded";
|
||||
import { usePopCompanyPath } from "@/hooks/usePopCompanyPath";
|
||||
|
||||
interface MenuIconItem {
|
||||
@@ -142,9 +141,7 @@ function LocalMenuIcons() {
|
||||
export default function PopMainPage() {
|
||||
return (
|
||||
<>
|
||||
<KpiCarousel />
|
||||
<LocalMenuIcons />
|
||||
<RecentActivity />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -143,10 +143,10 @@ export function PopShell({ children, showBanner = true, title, showBack = false,
|
||||
|
||||
// POP 설정에서 배너 텍스트 로드 (POP화면설정에서 관리)
|
||||
const { settings: popSettings } = usePopSettings();
|
||||
const homeConfig = (popSettings as any)?.screens?.home;
|
||||
const bannerEnabled = homeConfig?.bannerEnabled ?? true;
|
||||
const bannerText = homeConfig?.bannerText;
|
||||
const marqueeText = bannerText || "[공지] 금일 오후 3시 전체 안전교육 실시 예정입니다. 전 직원 필참 바랍니다. | [알림] 내일 설비 정기점검으로 인한 3호기 가동 중지 예정 | [안내] 4월 생산실적 우수팀 발표 - 생산1팀 축하드립니다!";
|
||||
const mainConfig = (popSettings as any)?.screens?.main;
|
||||
const bannerEnabled = mainConfig?.bannerEnabled ?? true;
|
||||
const bannerText = mainConfig?.bannerText;
|
||||
const marqueeText = bannerText || "";
|
||||
|
||||
return (
|
||||
<div className="min-h-screen min-h-dvh flex flex-col" style={{ background: "#F5F5F5" }}>
|
||||
@@ -402,7 +402,7 @@ export function PopShell({ children, showBanner = true, title, showBack = false,
|
||||
)}
|
||||
|
||||
{/* ===== NOTICE BANNER (Marquee) ===== */}
|
||||
{showBanner && bannerEnabled && <div className="bg-amber-50 border-b border-amber-200 px-4 py-2 flex items-center gap-3">
|
||||
{showBanner && bannerEnabled && bannerText && <div className="bg-amber-50 border-b border-amber-200 px-4 py-2 flex items-center gap-3">
|
||||
<div className="flex items-center gap-1.5 shrink-0">
|
||||
<span className="text-amber-600 text-sm">📢</span>
|
||||
<span className="text-xs font-bold text-amber-700">공지</span>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import type React from "react";
|
||||
import { KpiCarousel, RecentActivity } from "@/components/pop/hardcoded";
|
||||
import { usePopCompanyPath } from "@/hooks/usePopCompanyPath";
|
||||
|
||||
interface MenuIconItem {
|
||||
@@ -142,9 +141,7 @@ function LocalMenuIcons() {
|
||||
export default function PopMainPage() {
|
||||
return (
|
||||
<>
|
||||
<KpiCarousel />
|
||||
<LocalMenuIcons />
|
||||
<RecentActivity />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -143,10 +143,10 @@ export function PopShell({ children, showBanner = true, title, showBack = false,
|
||||
|
||||
// POP 설정에서 배너 텍스트 로드 (POP화면설정에서 관리)
|
||||
const { settings: popSettings } = usePopSettings();
|
||||
const homeConfig = (popSettings as any)?.screens?.home;
|
||||
const bannerEnabled = homeConfig?.bannerEnabled ?? true;
|
||||
const bannerText = homeConfig?.bannerText;
|
||||
const marqueeText = bannerText || "[공지] 금일 오후 3시 전체 안전교육 실시 예정입니다. 전 직원 필참 바랍니다. | [알림] 내일 설비 정기점검으로 인한 3호기 가동 중지 예정 | [안내] 4월 생산실적 우수팀 발표 - 생산1팀 축하드립니다!";
|
||||
const mainConfig = (popSettings as any)?.screens?.main;
|
||||
const bannerEnabled = mainConfig?.bannerEnabled ?? true;
|
||||
const bannerText = mainConfig?.bannerText;
|
||||
const marqueeText = bannerText || "";
|
||||
|
||||
return (
|
||||
<div className="min-h-screen min-h-dvh flex flex-col" style={{ background: "#F5F5F5" }}>
|
||||
@@ -402,7 +402,7 @@ export function PopShell({ children, showBanner = true, title, showBack = false,
|
||||
)}
|
||||
|
||||
{/* ===== NOTICE BANNER (Marquee) ===== */}
|
||||
{showBanner && bannerEnabled && <div className="bg-amber-50 border-b border-amber-200 px-4 py-2 flex items-center gap-3">
|
||||
{showBanner && bannerEnabled && bannerText && <div className="bg-amber-50 border-b border-amber-200 px-4 py-2 flex items-center gap-3">
|
||||
<div className="flex items-center gap-1.5 shrink-0">
|
||||
<span className="text-amber-600 text-sm">📢</span>
|
||||
<span className="text-xs font-bold text-amber-700">공지</span>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import type React from "react";
|
||||
import { KpiCarousel, RecentActivity } from "@/components/pop/hardcoded";
|
||||
import { usePopCompanyPath } from "@/hooks/usePopCompanyPath";
|
||||
|
||||
interface MenuIconItem {
|
||||
@@ -142,9 +141,7 @@ function LocalMenuIcons() {
|
||||
export default function PopMainPage() {
|
||||
return (
|
||||
<>
|
||||
<KpiCarousel />
|
||||
<LocalMenuIcons />
|
||||
<RecentActivity />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { MessengerModal } from "@/components/messenger/MessengerModal";
|
||||
|
||||
export default function MainLayout({ children }: { children: React.ReactNode }) {
|
||||
const pathname = usePathname();
|
||||
const isPop = pathname.includes("/pop/") || pathname.endsWith("/pop");
|
||||
const isPop = pathname.includes("/pop/");
|
||||
|
||||
if (isPop) {
|
||||
return <>{children}</>;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// DEPRECATED: 구 POP 라우트 그룹. 신 POP 는 frontend/app/(main)/COMPANY_X/pop/ 사용
|
||||
|
||||
import "@/app/globals.css";
|
||||
|
||||
export const metadata = {
|
||||
|
||||
@@ -525,11 +525,27 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
||||
|
||||
// POP 모드 진입 핸들러
|
||||
const handlePopModeClick = async () => {
|
||||
if (isSuperAdmin) {
|
||||
const userCompanyCode = (user as ExtendedUserInfo)?.companyCode;
|
||||
|
||||
// SUPER_ADMIN: 회사 미선택(*) 상태 → 회사 선택 모달
|
||||
if (isSuperAdmin && userCompanyCode === "*") {
|
||||
setPopCompanySelectOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// SUPER_ADMIN: 특정 회사 선택 상태 → 해당 회사 POP 으로 직행
|
||||
if (isSuperAdmin && userCompanyCode && userCompanyCode !== "*") {
|
||||
try {
|
||||
if (!document.fullscreenElement) {
|
||||
await document.documentElement.requestFullscreen();
|
||||
}
|
||||
} catch {
|
||||
// 전체화면 미지원 또는 거부 시 무시
|
||||
}
|
||||
router.push(`/${userCompanyCode}/pop/main`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// PC → POP 전환 시 전체화면 적용
|
||||
try {
|
||||
@@ -551,7 +567,6 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
||||
} else if (childMenus.length === 1) {
|
||||
router.push(childMenus[0].menu_url);
|
||||
} else {
|
||||
const userCompanyCode = (user as ExtendedUserInfo)?.companyCode;
|
||||
if (userCompanyCode && userCompanyCode !== "*") {
|
||||
router.push(`/${userCompanyCode}/pop/main`);
|
||||
} else {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// DEPRECATED: 구 POP 컴포넌트 묶음. 신 POP 는 frontend/app/(main)/COMPANY_X/pop/_components/ 사용
|
||||
|
||||
export {
|
||||
InboundCart,
|
||||
InboundTypeSelect,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { apiClient } from "@/lib/api/client";
|
||||
import { useState } from "react";
|
||||
|
||||
export interface PopSettings {
|
||||
version: string;
|
||||
@@ -34,7 +32,7 @@ export interface PopSettings {
|
||||
photoUpload: boolean;
|
||||
barcodeEnabled: boolean;
|
||||
};
|
||||
home: {
|
||||
main: {
|
||||
kpiCarousel: boolean;
|
||||
recentActivity: boolean;
|
||||
bannerEnabled: boolean;
|
||||
@@ -66,13 +64,13 @@ const DEFAULT_SETTINGS: PopSettings = {
|
||||
version: "hardcoded-1.0",
|
||||
screens: {
|
||||
processExecution: {
|
||||
materialInput: true,
|
||||
photoUpload: true,
|
||||
materialInput: false,
|
||||
photoUpload: false,
|
||||
plcEnabled: false,
|
||||
bomFlexible: true,
|
||||
packagingOptions: ["낱개", "박스", "파렛트"],
|
||||
defectTypes: ["스크래치", "치수불량", "변색", "크랙", "기포"],
|
||||
reworkTargetSelection: true,
|
||||
bomFlexible: false,
|
||||
packagingOptions: [],
|
||||
defectTypes: [],
|
||||
reworkTargetSelection: false,
|
||||
groupPhotoEnabled: false,
|
||||
dateFilter: false,
|
||||
lastProcessInventory: "manual",
|
||||
@@ -84,20 +82,20 @@ const DEFAULT_SETTINGS: PopSettings = {
|
||||
inbound: {
|
||||
inspectionRequired: false,
|
||||
photoUpload: false,
|
||||
barcodeEnabled: true,
|
||||
barcodeEnabled: false,
|
||||
packagingRecord: false,
|
||||
defectSeparation: false,
|
||||
},
|
||||
outbound: {
|
||||
photoUpload: false,
|
||||
barcodeEnabled: true,
|
||||
barcodeEnabled: false,
|
||||
},
|
||||
home: {
|
||||
kpiCarousel: true,
|
||||
recentActivity: true,
|
||||
main: {
|
||||
kpiCarousel: false,
|
||||
recentActivity: false,
|
||||
bannerEnabled: false,
|
||||
bannerText: "",
|
||||
iconThemeColor: "#2563eb",
|
||||
iconThemeColor: "",
|
||||
iconCustomImages: false,
|
||||
dashboardLayout: "default",
|
||||
},
|
||||
@@ -110,142 +108,8 @@ const DEFAULT_SETTINGS: PopSettings = {
|
||||
},
|
||||
};
|
||||
|
||||
// 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,
|
||||
"/COMPANY_7/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",
|
||||
"/COMPANY_7/pop/production/process": "processExecution",
|
||||
};
|
||||
|
||||
// 신 URL `/COMPANY_X/pop/<tail>` 에서 화면 키 추출 (main → home 정규화)
|
||||
function extractScreenKey(pathname: string): string | null {
|
||||
const match = pathname.match(/^\/COMPANY_\d+\/pop\/(.+)$/);
|
||||
if (!match) return null;
|
||||
const tail = match[1];
|
||||
return tail === "main" ? "home" : tail;
|
||||
}
|
||||
|
||||
function getScreenIdFromPath(pathname: string): number | null {
|
||||
// 신 URL 우선 처리 (회사 prefix 제거 후 화면 키 매핑)
|
||||
const screenKey = extractScreenKey(pathname);
|
||||
if (screenKey) {
|
||||
const lookupPath = `/pop/${screenKey}`;
|
||||
if (POP_SCREEN_MAP[lookupPath]) return POP_SCREEN_MAP[lookupPath];
|
||||
const sortedNew = Object.keys(POP_SCREEN_MAP).sort((a, b) => b.length - a.length);
|
||||
for (const path of sortedNew) {
|
||||
if (lookupPath.startsWith(path)) return POP_SCREEN_MAP[path];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 구 (pop)/ 라우트 호환 fallback
|
||||
if (POP_SCREEN_MAP[pathname]) return POP_SCREEN_MAP[pathname];
|
||||
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 {
|
||||
// 신 URL 우선 처리 (회사 prefix 제거 후 화면 키 매핑)
|
||||
const screenKey = extractScreenKey(pathname);
|
||||
if (screenKey) {
|
||||
const lookupPath = `/pop/${screenKey}`;
|
||||
if (PATH_TO_SETTINGS_KEY[lookupPath]) return PATH_TO_SETTINGS_KEY[lookupPath];
|
||||
const sortedNew = Object.keys(PATH_TO_SETTINGS_KEY).sort((a, b) => b.length - a.length);
|
||||
for (const path of sortedNew) {
|
||||
if (lookupPath.startsWith(path)) return PATH_TO_SETTINGS_KEY[path];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 구 (pop)/ 라우트 호환 fallback
|
||||
if (PATH_TO_SETTINGS_KEY[pathname]) return PATH_TO_SETTINGS_KEY[pathname];
|
||||
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]);
|
||||
|
||||
export function usePopSettings(_screenPath?: string) {
|
||||
const [settings] = useState<PopSettings>(DEFAULT_SETTINGS);
|
||||
const [loading] = useState(false);
|
||||
return { settings, loading };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user