refactor(pop): trim main page widgets, drop layout auto-clone
- Remove KpiCarousel/RecentActivity from pop main pages (7 companies) - Empty banner default; rename settings key home -> main - Strip API fetch/cache from usePopSettings, return hardcoded defaults - Drop screen_layouts_pop auto-clone/fallback for regular users - Add SUPER_ADMIN direct-entry branch in AppLayout pop handler Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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