From 36a79f8d5d544bb2208330ec77bfc40e9fc0218f Mon Sep 17 00:00:00 2001 From: DDD1542 Date: Sat, 7 Mar 2026 07:18:40 +0900 Subject: [PATCH] [agent-pipeline] pipe-20260306212316-vynh round-2 --- .../app/(main)/screens/[screenId]/page.tsx | 916 ++++-------------- .../TableSearchWidget.tsx | 20 +- run-screen29-e2e.mjs | 234 +++++ 3 files changed, 431 insertions(+), 739 deletions(-) create mode 100644 run-screen29-e2e.mjs diff --git a/frontend/app/(main)/screens/[screenId]/page.tsx b/frontend/app/(main)/screens/[screenId]/page.tsx index d65b7884..eb4c5bbb 100644 --- a/frontend/app/(main)/screens/[screenId]/page.tsx +++ b/frontend/app/(main)/screens/[screenId]/page.tsx @@ -8,30 +8,24 @@ import { screenApi } from "@/lib/api/screen"; import { ScreenDefinition, LayoutData, ComponentData } from "@/types/screen"; import { LayerDefinition } from "@/types/screen-management"; import { useRouter } from "next/navigation"; -import { toast } from "sonner"; import { showErrorToast } from "@/lib/utils/toastUtils"; import { initializeComponents } from "@/lib/registry/components"; import { EditModal } from "@/components/screen/EditModal"; import { RealtimePreview } from "@/components/screen/RealtimePreviewDynamic"; -import { FlowButtonGroup } from "@/components/screen/widgets/FlowButtonGroup"; -import { FlowVisibilityConfig } from "@/types/control-management"; -import { findAllButtonGroups } from "@/lib/utils/flowButtonGroupUtils"; -import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRenderer"; import { ScreenPreviewProvider } from "@/contexts/ScreenPreviewContext"; -import { useAuth } from "@/hooks/useAuth"; // ๐Ÿ†• ์‚ฌ์šฉ์ž ์ •๋ณด -import { useResponsive } from "@/lib/hooks/useResponsive"; // ๐Ÿ†• ๋ฐ˜์‘ํ˜• ๊ฐ์ง€ -import { TableOptionsProvider } from "@/contexts/TableOptionsContext"; // ํ…Œ์ด๋ธ” ์˜ต์…˜ -import { TableSearchWidgetHeightProvider, useTableSearchWidgetHeight } from "@/contexts/TableSearchWidgetHeightContext"; // ๋†’์ด ๊ด€๋ฆฌ -import { ScreenContextProvider } from "@/contexts/ScreenContext"; // ์ปดํฌ๋„ŒํŠธ ๊ฐ„ ํ†ต์‹  -import { SplitPanelProvider } from "@/lib/registry/components/split-panel-layout/SplitPanelContext"; // ๋ถ„ํ•  ํŒจ๋„ ๋ฆฌ์‚ฌ์ด์ฆˆ -import { ActiveTabProvider } from "@/contexts/ActiveTabContext"; // ํ™œ์„ฑ ํƒญ ๊ด€๋ฆฌ -import { evaluateConditional } from "@/lib/utils/conditionalEvaluator"; // ์กฐ๊ฑด๋ถ€ ํ‘œ์‹œ ํ‰๊ฐ€ -import { ScreenMultiLangProvider } from "@/contexts/ScreenMultiLangContext"; // ํ™”๋ฉด ๋‹ค๊ตญ์–ด -import { convertV2ToLegacy, isValidV2Layout } from "@/lib/utils/layoutV2Converter"; // V2 Zod ๊ธฐ๋ฐ˜ ๋ณ€ํ™˜ -import { useScheduleGenerator, ScheduleConfirmDialog } from "@/lib/v2-core/services/ScheduleGeneratorService"; // ์Šค์ผ€์ค„ ์ž๋™ ์ƒ์„ฑ +import { useAuth } from "@/hooks/useAuth"; +import { TableOptionsProvider } from "@/contexts/TableOptionsContext"; +import { TableSearchWidgetHeightProvider } from "@/contexts/TableSearchWidgetHeightContext"; +import { ScreenContextProvider } from "@/contexts/ScreenContext"; +import { SplitPanelProvider } from "@/lib/registry/components/split-panel-layout/SplitPanelContext"; +import { ActiveTabProvider } from "@/contexts/ActiveTabContext"; +import { evaluateConditional } from "@/lib/utils/conditionalEvaluator"; +import { ScreenMultiLangProvider } from "@/contexts/ScreenMultiLangContext"; +import { convertV2ToLegacy, isValidV2Layout } from "@/lib/utils/layoutV2Converter"; +import { useScheduleGenerator, ScheduleConfirmDialog } from "@/lib/v2-core/services/ScheduleGeneratorService"; +import { ResponsiveGridRenderer } from "@/components/screen/ResponsiveGridRenderer"; function ScreenViewPage() { - // ์Šค์ผ€์ค„ ์ž๋™ ์ƒ์„ฑ ์„œ๋น„์Šค ํ™œ์„ฑํ™” const { showConfirmDialog, previewResult, handleConfirm, closeDialog, isLoading: scheduleLoading } = useScheduleGenerator(); const params = useParams(); const searchParams = useSearchParams(); @@ -47,18 +41,11 @@ function ScreenViewPage() { // ํ”„๋ฆฌ๋ทฐ ๋ชจ๋“œ ๊ฐ์ง€ (iframe์—์„œ ๋กœ๋“œ๋  ๋•Œ) const isPreviewMode = searchParams.get("preview") === "true"; - // ๐Ÿ†• ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž ์ •๋ณด const { user, userName, companyCode: authCompanyCode } = useAuth(); // ํ”„๋ฆฌ๋ทฐ ๋ชจ๋“œ์—์„œ๋Š” URL ํŒŒ๋ผ๋ฏธํ„ฐ์˜ company_code ์šฐ์„  ์‚ฌ์šฉ const companyCode = previewCompanyCode || authCompanyCode; - // ๐Ÿ†• ๋ชจ๋ฐ”์ผ ํ™˜๊ฒฝ ๊ฐ์ง€ - const { isMobile } = useResponsive(); - - // ๐Ÿ†• TableSearchWidget ๋†’์ด ๊ด€๋ฆฌ - const { getHeightDiff } = useTableSearchWidgetHeight(); - const [screen, setScreen] = useState(null); const [layout, setLayout] = useState(null); const [loading, setLoading] = useState(true); @@ -73,19 +60,18 @@ function ScreenViewPage() { const [tableSortBy, setTableSortBy] = useState(); const [tableSortOrder, setTableSortOrder] = useState<"asc" | "desc">("asc"); const [tableColumnOrder, setTableColumnOrder] = useState(); - const [tableDisplayData, setTableDisplayData] = useState([]); // ํ™”๋ฉด์— ํ‘œ์‹œ๋œ ๋ฐ์ดํ„ฐ (์ปฌ๋Ÿผ ์ˆœ์„œ ํฌํ•จ) // ํ”Œ๋กœ์šฐ์—์„œ ์„ ํƒ๋œ ๋ฐ์ดํ„ฐ (๋ฒ„ํŠผ ์•ก์…˜์— ์ „๋‹ฌ) const [flowSelectedData, setFlowSelectedData] = useState([]); const [flowSelectedStepId, setFlowSelectedStepId] = useState(null); - // ํ…Œ์ด๋ธ” ์ƒˆ๋กœ๊ณ ์นจ์„ ์œ„ํ•œ ํ‚ค (๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋ฉด ํ…Œ์ด๋ธ”์ด ๋ฆฌ๋ Œ๋”๋ง๋จ) + // ํ…Œ์ด๋ธ” ์ƒˆ๋กœ๊ณ ์นจ ํ‚ค const [tableRefreshKey, setTableRefreshKey] = useState(0); - // ํ”Œ๋กœ์šฐ ์ƒˆ๋กœ๊ณ ์นจ์„ ์œ„ํ•œ ํ‚ค (๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋ฉด ํ”Œ๋กœ์šฐ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง๋จ) + // ํ”Œ๋กœ์šฐ ์ƒˆ๋กœ๊ณ ์นจ ํ‚ค const [flowRefreshKey, setFlowRefreshKey] = useState(0); - // ๐Ÿ†• ์กฐ๊ฑด๋ถ€ ์ปจํ…Œ์ด๋„ˆ ๋†’์ด ์ถ”์  (์ปดํฌ๋„ŒํŠธ ID โ†’ ๋†’์ด) + // ์กฐ๊ฑด๋ถ€ ์ปจํ…Œ์ด๋„ˆ ๋†’์ด ์ถ”์  (์ปดํฌ๋„ŒํŠธ ID โ†’ ๋†’์ด) const [conditionalContainerHeights, setConditionalContainerHeights] = useState>({}); // ๋ ˆ์ด์–ด ์‹œ์Šคํ…œ ์ง€์› @@ -106,12 +92,10 @@ function ScreenViewPage() { modalDescription?: string; }>({}); - // ๋ ˆ์ด์•„์›ƒ ์ค€๋น„ ์™„๋ฃŒ ์ƒํƒœ (๋ฒ„ํŠผ ์œ„์น˜ ๊ณ„์‚ฐ ์™„๋ฃŒ ํ›„ ํ™”๋ฉด ํ‘œ์‹œ) - const [layoutReady, setLayoutReady] = useState(true); + // ๋ ˆ์ด์•„์›ƒ ์ค€๋น„ ์™„๋ฃŒ ์ƒํƒœ + const [layoutReady, setLayoutReady] = useState(false); const containerRef = React.useRef(null); - const [scale, setScale] = useState(1); - const [containerWidth, setContainerWidth] = useState(0); useEffect(() => { const initComponents = async () => { @@ -128,8 +112,6 @@ function ScreenViewPage() { // ํŽธ์ง‘ ๋ชจ๋‹ฌ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก useEffect(() => { const handleOpenEditModal = (event: CustomEvent) => { - // console.log("๐ŸŽญ ํŽธ์ง‘ ๋ชจ๋‹ฌ ์—ด๊ธฐ ์ด๋ฒคํŠธ ์ˆ˜์‹ :", event.detail); - setEditModalConfig({ screenId: event.detail.screenId, modalSize: event.detail.modalSize, @@ -154,20 +136,17 @@ function ScreenViewPage() { const loadScreen = async () => { try { setLoading(true); - setLayoutReady(false); // ํ™”๋ฉด ๋กœ๋“œ ์‹œ ๋ ˆ์ด์•„์›ƒ ์ค€๋น„ ์ดˆ๊ธฐํ™” + setLayoutReady(false); setError(null); - // ํ™”๋ฉด ์ •๋ณด ๋กœ๋“œ const screenData = await screenApi.getScreen(screenId); setScreen(screenData); // ๋ ˆ์ด์•„์›ƒ ๋กœ๋“œ (V2 ์šฐ์„ , Zod ๊ธฐ๋ฐ˜ ๊ธฐ๋ณธ๊ฐ’ ๋ณ‘ํ•ฉ) try { - // V2 API ๋จผ์ € ์‹œ๋„ const v2Response = await screenApi.getLayoutV2(screenId); if (v2Response && isValidV2Layout(v2Response)) { - // V2 ๋ ˆ์ด์•„์›ƒ: Zod ๊ธฐ๋ฐ˜ ๋ณ€ํ™˜ (๊ธฐ๋ณธ๊ฐ’ ๋ณ‘ํ•ฉ) const convertedLayout = convertV2ToLegacy(v2Response); if (convertedLayout) { setLayout({ @@ -178,7 +157,6 @@ function ScreenViewPage() { throw new Error("V2 ๋ ˆ์ด์•„์›ƒ ๋ณ€ํ™˜ ์‹คํŒจ"); } } else { - // V1 ๋ ˆ์ด์•„์›ƒ ๋˜๋Š” ๋นˆ ๋ ˆ์ด์•„์›ƒ const layoutData = await screenApi.getLayout(screenId); if (layoutData?.components?.length > 0) { setLayout(layoutData); @@ -223,6 +201,7 @@ function ScreenViewPage() { showErrorToast("ํ™”๋ฉด์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค", error, { guidance: "ํ™”๋ฉด ์„ค์ •์„ ํ™•์ธํ•˜๊ฑฐ๋‚˜ ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด ์ฃผ์„ธ์š”." }); } finally { setLoading(false); + setLayoutReady(true); } }; @@ -231,17 +210,17 @@ function ScreenViewPage() { } }, [screenId]); - // ๐Ÿ†• ์กฐ๊ฑด๋ถ€ ๋ ˆ์ด์–ด + Zone ๋กœ๋“œ + // ์กฐ๊ฑด๋ถ€ ๋ ˆ์ด์–ด + Zone ๋กœ๋“œ useEffect(() => { const loadConditionalLayersAndZones = async () => { if (!screenId || !layout) return; try { - // 1. Zone ๋กœ๋“œ + // Zone ๋กœ๋“œ const loadedZones = await screenApi.getScreenZones(screenId); setZones(loadedZones); - // 2. ๋ชจ๋“  ๋ ˆ์ด์–ด ๋ชฉ๋ก ์กฐํšŒ + // ๋ชจ๋“  ๋ ˆ์ด์–ด ๋ชฉ๋ก ์กฐํšŒ const allLayers = await screenApi.getScreenLayers(screenId); const nonBaseLayers = allLayers.filter((l: any) => l.layer_id > 1); @@ -250,7 +229,7 @@ function ScreenViewPage() { return; } - // 3. ๊ฐ ๋ ˆ์ด์–ด์˜ ๋ ˆ์ด์•„์›ƒ ๋ฐ์ดํ„ฐ ๋กœ๋“œ + // ๊ฐ ๋ ˆ์ด์–ด์˜ ๋ ˆ์ด์•„์›ƒ ๋ฐ์ดํ„ฐ ๋กœ๋“œ const layerDefinitions: LayerDefinition[] = []; for (const layerInfo of nonBaseLayers) { @@ -258,7 +237,6 @@ function ScreenViewPage() { const layerData = await screenApi.getLayerLayout(screenId, layerInfo.layer_id); const condConfig = layerInfo.condition_config || layerData?.conditionConfig || {}; - // ๋ ˆ์ด์–ด ์ปดํฌ๋„ŒํŠธ ๋ณ€ํ™˜ (V2 โ†’ Legacy) let layerComponents: any[] = []; const rawComponents = layerData?.components; if (rawComponents && Array.isArray(rawComponents) && rawComponents.length > 0) { @@ -276,12 +254,10 @@ function ScreenViewPage() { } } - // Zone ๊ธฐ๋ฐ˜ condition_config ์ฒ˜๋ฆฌ const zoneId = condConfig.zone_id; const conditionValue = condConfig.condition_value; const zone = zoneId ? loadedZones.find((z: any) => z.zone_id === zoneId) : null; - // LayerDefinition ์ƒ์„ฑ const layerDef: LayerDefinition = { id: String(layerInfo.layer_id), name: layerInfo.layer_name || `๋ ˆ์ด์–ด ${layerInfo.layer_id}`, @@ -289,7 +265,6 @@ function ScreenViewPage() { zIndex: layerInfo.layer_id * 10, isVisible: false, isLocked: false, - // Zone ๊ธฐ๋ฐ˜ ์กฐ๊ฑด (Zone์—์„œ ํŠธ๋ฆฌ๊ฑฐ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ด) condition: zone ? { targetComponentId: zone.trigger_component_id || "", operator: (zone.trigger_operator as "eq" | "neq" | "in") || "eq", @@ -299,7 +274,6 @@ function ScreenViewPage() { operator: condConfig.operator || "eq", value: condConfig.value, } : undefined, - // Zone ๊ธฐ๋ฐ˜: displayRegion์€ Zone์—์„œ ๊ฐ€์ ธ์˜ด zoneId: zoneId || undefined, conditionValue: conditionValue || undefined, displayRegion: zone ? { x: zone.x, y: zone.y, width: zone.width, height: zone.height } : condConfig.displayRegion || undefined, @@ -312,20 +286,6 @@ function ScreenViewPage() { } } - console.log("๐Ÿ”„ ์กฐ๊ฑด๋ถ€ ๋ ˆ์ด์–ด ๋กœ๋“œ ์™„๋ฃŒ:", layerDefinitions.length, "๊ฐœ", layerDefinitions.map(l => ({ - id: l.id, name: l.name, zoneId: l.zoneId, conditionValue: l.conditionValue, - componentCount: l.components.length, - condition: l.condition ? { - targetComponentId: l.condition.targetComponentId, - operator: l.condition.operator, - value: l.condition.value, - } : "์—†์Œ", - }))); - console.log("๐Ÿ—บ๏ธ Zone ์ •๋ณด:", loadedZones.map(z => ({ - zone_id: z.zone_id, - trigger_component_id: z.trigger_component_id, - trigger_operator: z.trigger_operator, - }))); setConditionalLayers(layerDefinitions); } catch (error) { console.error("๋ ˆ์ด์–ด/Zone ๋กœ๋“œ ์‹คํŒจ:", error); @@ -335,7 +295,7 @@ function ScreenViewPage() { loadConditionalLayersAndZones(); }, [screenId, layout]); - // ๐Ÿ†• ์กฐ๊ฑด๋ถ€ ๋ ˆ์ด์–ด ์กฐ๊ฑด ํ‰๊ฐ€ (formData ๋ณ€๊ฒฝ ์‹œ ๋™๊ธฐ์ ์œผ๋กœ ์ฆ‰์‹œ ๊ณ„์‚ฐ) + // ์กฐ๊ฑด๋ถ€ ๋ ˆ์ด์–ด ์กฐ๊ฑด ํ‰๊ฐ€ (formData ๋ณ€๊ฒฝ ์‹œ ๋™๊ธฐ์ ์œผ๋กœ ์ฆ‰์‹œ ๊ณ„์‚ฐ) const activeLayerIds = useMemo(() => { if (conditionalLayers.length === 0 || !layout) return [] as string[]; @@ -346,13 +306,10 @@ function ScreenViewPage() { if (layer.condition) { const { targetComponentId, operator, value } = layer.condition; - // ๋นˆ targetComponentId๋Š” ๋ฌด์‹œ if (!targetComponentId) return; - // ํŠธ๋ฆฌ๊ฑฐ ์ปดํฌ๋„ŒํŠธ ์ฐพ๊ธฐ (๊ธฐ๋ณธ ๋ ˆ์ด์–ด์—์„œ) const targetComponent = allComponents.find((c) => c.id === targetComponentId); - // columnName์œผ๋กœ formData์—์„œ ๊ฐ’ ์กฐํšŒ const fieldKey = (targetComponent as any)?.columnName || (targetComponent as any)?.componentConfig?.columnName || @@ -363,7 +320,6 @@ function ScreenViewPage() { let isMatch = false; switch (operator) { case "eq": - // ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋น„๊ต (ํƒ€์ž… ๋ถˆ์ผ์น˜ ๋ฐฉ์ง€) isMatch = String(targetValue ?? "") === String(value ?? ""); break; case "neq": @@ -373,33 +329,17 @@ function ScreenViewPage() { if (Array.isArray(value)) { isMatch = value.some(v => String(v) === String(targetValue ?? "")); } else if (typeof value === "string" && value.includes(",")) { - // ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„๋œ ๋ฌธ์ž์—ด๋„ ์ง€์› isMatch = value.split(",").map(v => v.trim()).includes(String(targetValue ?? "")); } break; } - // ๋””๋ฒ„๊ทธ ๋กœ๊น… (๊ฐ’์ด ์กด์žฌํ•  ๋•Œ๋งŒ) - if (targetValue !== undefined && targetValue !== "") { - console.log("๐Ÿ” [๋ ˆ์ด์–ด ์กฐ๊ฑด ํ‰๊ฐ€]", { - layerId: layer.id, - layerName: layer.name, - targetComponentId, - fieldKey, - targetValue: String(targetValue), - conditionValue: String(value), - operator, - isMatch, - }); - } - if (isMatch) { newActiveIds.push(layer.id); } } }); - // ๊ฐ•์ œ ํ™œ์„ฑํ™”๋œ ๋ ˆ์ด์–ด ID ๋ณ‘ํ•ฉ for (const forcedId of forceActivatedLayerIds) { if (!newActiveIds.includes(forcedId)) { newActiveIds.push(forcedId); @@ -415,23 +355,19 @@ function ScreenViewPage() { const { componentId, targetLayerId } = (e as CustomEvent).detail || {}; if (!componentId && !targetLayerId) return; - // targetLayerId๊ฐ€ ์ง์ ‘ ์ง€์ •๋œ ๊ฒฝ์šฐ if (targetLayerId) { setForceActivatedLayerIds((prev) => prev.includes(targetLayerId) ? prev : [...prev, targetLayerId], ); - console.log(`๐Ÿ”“ [๋ ˆ์ด์–ด ๊ฐ•์ œ ํ™œ์„ฑํ™”] layerId: ${targetLayerId}`); return; } - // componentId๋กœ ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์†ํ•œ ๋ ˆ์ด์–ด๋ฅผ ์ฐพ์•„ ํ™œ์„ฑํ™” for (const layer of conditionalLayers) { const found = layer.components.some((comp) => comp.id === componentId); if (found) { setForceActivatedLayerIds((prev) => prev.includes(layer.id) ? prev : [...prev, layer.id], ); - console.log(`๐Ÿ”“ [๋ ˆ์ด์–ด ๊ฐ•์ œ ํ™œ์„ฑํ™”] componentId: ${componentId} โ†’ layerId: ${layer.id}`); return; } } @@ -455,10 +391,10 @@ function ScreenViewPage() { return; } - // ํ…Œ์ด๋ธ” ์œ„์ ฏ์ด ์—†๋Š” ๊ฒฝ์šฐ์—๋งŒ ์ž๋™ ๋กœ๋“œ (ํ…Œ์ด๋ธ”์ด ์žˆ์œผ๋ฉด ํ–‰ ์„ ํƒ์œผ๋กœ ๋ฐ์ดํ„ฐ ๋กœ๋“œ) + // ํ…Œ์ด๋ธ” ์œ„์ ฏ์ด ์žˆ์œผ๋ฉด ์ž๋™ ๋กœ๋“œ ๊ฑด๋„ˆ๋œ€ (ํ…Œ์ด๋ธ” ํ–‰ ์„ ํƒ์œผ๋กœ ๋ฐ์ดํ„ฐ ๋กœ๋“œ) const hasTableWidget = layout.components.some( - (comp: any) => - comp.componentType === "table-list" || + (comp: any) => + comp.componentType === "table-list" || comp.componentType === "v2-table-list" || comp.widgetType === "table" ); @@ -467,16 +403,15 @@ function ScreenViewPage() { return; } - // ์ธํ’‹ ์ปดํฌ๋„ŒํŠธ๋“ค ์ค‘ ๋ฉ”์ธ ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋“ค ์ฐพ๊ธฐ const inputComponents = layout.components.filter((comp: any) => { const compType = comp.componentType || comp.widgetType; - const isInputType = compType?.includes("input") || - compType?.includes("select") || + const isInputType = compType?.includes("input") || + compType?.includes("select") || compType?.includes("textarea") || compType?.includes("v2-input") || compType?.includes("v2-select") || compType?.includes("v2-media") || - compType?.includes("file-upload"); // ๐Ÿ†• ๋ ˆ๊ฑฐ์‹œ ํŒŒ์ผ ์—…๋กœ๋“œ ํฌํ•จ + compType?.includes("file-upload"); const hasColumnName = !!(comp as any).columnName; return isInputType && hasColumnName; }); @@ -485,22 +420,17 @@ function ScreenViewPage() { return; } - // ๋ฉ”์ธ ํ…Œ์ด๋ธ”์—์„œ ํ˜„์žฌ ํšŒ์‚ฌ์˜ ๋ฐ์ดํ„ฐ ์กฐํšŒ try { const { tableTypeApi } = await import("@/lib/api/screen"); - - // company_code๋กœ ํ•„ํ„ฐ๋งํ•˜์—ฌ ๋‹จ์ผ ๋ ˆ์ฝ”๋“œ ์กฐํšŒ + const result = await tableTypeApi.getTableRecord( mainTableName, "company_code", companyCode, - "*" // ๋ชจ๋“  ์ปฌ๋Ÿผ + "*" ); if (result && result.record) { - console.log("๐Ÿ“ฆ ๋ฉ”์ธ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ์ž๋™ ๋กœ๋“œ:", mainTableName, result.record); - - // ๊ฐ ์ธํ’‹ ์ปดํฌ๋„ŒํŠธ์— ํ•ด๋‹นํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ฑ„์šฐ๊ธฐ const newFormData: Record = {}; inputComponents.forEach((comp: any) => { const columnName = comp.columnName; @@ -518,14 +448,13 @@ function ScreenViewPage() { } } catch (error) { console.log("๋ฉ”์ธ ํ…Œ์ด๋ธ” ์ž๋™ ๋กœ๋“œ ์‹คํŒจ (์ •์ƒ์ผ ์ˆ˜ ์žˆ์Œ):", error); - // ์—๋Ÿฌ๋Š” ๋ฌด์‹œ - ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๊ฑฐ๋‚˜ ๊ถŒํ•œ์ด ์—†์„ ์ˆ˜ ์žˆ์Œ } }; loadMainTableData(); }, [screen, layout, companyCode]); - // ๐Ÿ†• ๊ฐœ๋ณ„ autoFill ์ฒ˜๋ฆฌ (๋ฉ”์ธ ํ…Œ์ด๋ธ”๊ณผ ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์—์„œ ์กฐํšŒํ•˜๋Š” ๊ฒฝ์šฐ) + // ๊ฐœ๋ณ„ autoFill ์ฒ˜๋ฆฌ useEffect(() => { const initAutoFill = async () => { if (!layout || !layout.components || !user) { @@ -533,20 +462,16 @@ function ScreenViewPage() { } for (const comp of layout.components) { - // type: "component" ๋˜๋Š” type: "widget" ๋ชจ๋‘ ์ฒ˜๋ฆฌ if (comp.type === "widget" || comp.type === "component") { const widget = comp as any; const fieldName = widget.columnName || widget.id; - // autoFill ์ฒ˜๋ฆฌ (๋ช…์‹œ์ ์œผ๋กœ ์„ค์ •๋œ ๊ฒฝ์šฐ๋งŒ) if (widget.autoFill?.enabled || (comp as any).autoFill?.enabled) { const autoFillConfig = widget.autoFill || (comp as any).autoFill; const currentValue = formData[fieldName]; if (currentValue === undefined || currentValue === "") { const { sourceTable, filterColumn, userField, displayColumn } = autoFillConfig; - - // ์‚ฌ์šฉ์ž ์ •๋ณด์—์„œ ํ•„ํ„ฐ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ const userValue = user?.[userField as keyof typeof user]; if (userValue && sourceTable && filterColumn && displayColumn) { @@ -571,12 +496,10 @@ function ScreenViewPage() { initAutoFill(); }, [layout, user]); - // ๐Ÿ†• ์กฐ๊ฑด๋ถ€ ๋น„ํ™œ์„ฑํ™”/์ˆจ๊น€ ์‹œ ํ•ด๋‹น ํ•„๋“œ ๊ฐ’ ์ดˆ๊ธฐํ™” - // ์กฐ๊ฑด ํ•„๋“œ๋“ค์˜ ๊ฐ’์„ ์ถ”์ ํ•˜์—ฌ ๋ณ€๊ฒฝ ์‹œ์—๋งŒ ์‹คํ–‰ + // ์กฐ๊ฑด๋ถ€ ๋น„ํ™œ์„ฑํ™”/์ˆจ๊น€ ์‹œ ํ•ด๋‹น ํ•„๋“œ ๊ฐ’ ์ดˆ๊ธฐํ™” const conditionalFieldValues = useMemo(() => { if (!layout?.components) return ""; - // ์กฐ๊ฑด๋ถ€ ์„ค์ •์— ์‚ฌ์šฉ๋˜๋Š” ํ•„๋“œ๋“ค์˜ ํ˜„์žฌ ๊ฐ’์„ JSON ๋ฌธ์ž์—ด๋กœ ๋งŒ๋“ค์–ด ๋น„๊ต const conditionFields = new Set(); layout.components.forEach((component) => { const conditional = (component as any).conditional; @@ -604,19 +527,16 @@ function ScreenViewPage() { const conditionalResult = evaluateConditional(conditional, formData as Record, layout.components); - // ์ˆจ๊น€ ๋˜๋Š” ๋น„ํ™œ์„ฑํ™” ์ƒํƒœ์ธ ๊ฒฝ์šฐ if (!conditionalResult.visible || conditionalResult.disabled) { const fieldName = (component as any).columnName || component.id; const currentValue = (formData as Record)[fieldName]; - // ๊ฐ’์ด ์žˆ์œผ๋ฉด ์ดˆ๊ธฐํ™” ๋Œ€์ƒ์— ์ถ”๊ฐ€ if (currentValue !== undefined && currentValue !== "" && currentValue !== null) { fieldsToReset.push(fieldName); } } }); - // ์ดˆ๊ธฐํ™”ํ•  ํ•„๋“œ๊ฐ€ ์žˆ์œผ๋ฉด ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌ if (fieldsToReset.length > 0) { setFormData((prev) => { const updated = { ...prev }; @@ -628,74 +548,52 @@ function ScreenViewPage() { } }, [conditionalFieldValues, layout?.components]); - // ์บ”๋ฒ„์Šค ๋น„์œจ ์กฐ์ • (์‚ฌ์šฉ์ž ํ™”๋ฉด์— ๋งž๊ฒŒ ์ž๋™ ์Šค์ผ€์ผ) - ์ดˆ๊ธฐ ๋กœ๋”ฉ ์‹œ์—๋งŒ ๊ณ„์‚ฐ - // ๋ธŒ๋ผ์šฐ์ € ๋ฐฐ์œจ ์กฐ์ • ์‹œ ๋ฉ”๋‰ด์™€ ํ™”๋ฉด์ด ํ•จ๊ป˜ ์ถ•์†Œ/ํ™•๋Œ€๋˜๋„๋ก resize ์ด๋ฒคํŠธ๋Š” ๊ฐ์ง€ํ•˜์ง€ ์•Š์Œ - useEffect(() => { - // ๋ชจ๋ฐ”์ผ ํ™˜๊ฒฝ์—์„œ๋Š” ์Šค์ผ€์ผ ์กฐ์ • ๋น„ํ™œ์„ฑํ™” (๋ฐ˜์‘ํ˜•๋งŒ ์ž‘๋™) - if (isMobile) { - setScale(1); - setLayoutReady(true); // ๋ชจ๋ฐ”์ผ์—์„œ๋„ ๋ ˆ์ด์•„์›ƒ ์ค€๋น„ ์™„๋ฃŒ ํ‘œ์‹œ - return; - } + // ํ™”๋ฉด ํ•ด์ƒ๋„ ์ •๋ณด + const screenWidth = layout?.screenResolution?.width || 1200; + const screenHeight = layout?.screenResolution?.height || 800; - const updateScale = () => { - if (containerRef.current && layout) { - const designWidth = layout?.screenResolution?.width || 1200; - const designHeight = layout?.screenResolution?.height || 800; - - // ์ปจํ…Œ์ด๋„ˆ์˜ ์‹ค์ œ ํฌ๊ธฐ (ํ”„๋ฆฌ๋ทฐ ๋ชจ๋“œ์—์„œ๋Š” window ํฌ๊ธฐ ์‚ฌ์šฉ) - let containerWidth: number; - let containerHeight: number; - - if (isPreviewMode) { - // iframe์—์„œ๋Š” window ํฌ๊ธฐ๋ฅผ ์ง์ ‘ ์‚ฌ์šฉ - containerWidth = window.innerWidth; - containerHeight = window.innerHeight; - } else { - containerWidth = containerRef.current.offsetWidth; - containerHeight = containerRef.current.offsetHeight; - } - - let newScale: number; - - if (isPreviewMode) { - // ํ”„๋ฆฌ๋ทฐ ๋ชจ๋“œ: ๊ฐ€๋กœ/์„ธ๋กœ ๋ชจ๋‘ fitํ•˜๋„๋ก (์—ฌ๋ฐฑ ์—†์ด) - const scaleX = containerWidth / designWidth; - const scaleY = containerHeight / designHeight; - newScale = Math.min(scaleX, scaleY, 1); // ์ตœ๋Œ€ 1๋ฐฐ์œจ - } else { - // ์ผ๋ฐ˜ ๋ชจ๋“œ: ๊ฐ€๋กœ ๊ธฐ์ค€ ์Šค์ผ€์ผ (์ขŒ์šฐ ์—ฌ๋ฐฑ 16px์”ฉ ๊ณ ์ •) - const MARGIN_X = 32; - const availableWidth = containerWidth - MARGIN_X; - newScale = availableWidth / designWidth; - } - - // console.log("๐Ÿ“ ์Šค์ผ€์ผ ๊ณ„์‚ฐ:", { - // containerWidth, - // containerHeight, - // designWidth, - // designHeight, - // finalScale: newScale, - // isPreviewMode, - // }); - - setScale(newScale); - // ์ปจํ…Œ์ด๋„ˆ ๋„ˆ๋น„ ์—…๋ฐ์ดํŠธ - setContainerWidth(containerWidth); - - // ์Šค์ผ€์ผ ๊ณ„์‚ฐ ์™„๋ฃŒ ํ›„ ๋ ˆ์ด์•„์›ƒ ์ค€๋น„ ์™„๋ฃŒ ํ‘œ์‹œ - setLayoutReady(true); - } - }; - - // ์ดˆ๊ธฐ ์ธก์ • (ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰) - const timer = setTimeout(updateScale, 100); - - // resize ์ด๋ฒคํŠธ๋Š” ๊ฐ์ง€ํ•˜์ง€ ์•Š์Œ - ๋ธŒ๋ผ์šฐ์ € ๋ฐฐ์œจ ์กฐ์ • ์‹œ ๋ฉ”๋‰ด์™€ ํ™”๋ฉด์ด ํ•จ๊ป˜ ๋ณ€๊ฒฝ๋˜๋„๋ก - return () => { - clearTimeout(timer); - }; - }, [layout, isMobile, isPreviewMode]); + // RealtimePreview์— ์ „๋‹ฌํ•  ๊ณตํ†ต props ๋นŒ๋” + const buildRealtimePreviewProps = (component: ComponentData, extraProps?: Record) => ({ + component, + isSelected: false, + isDesignMode: false, + onClick: () => {}, + menuObjid, + screenId, + tableName: screen?.tableName, + userId: user?.userId, + userName, + companyCode, + selectedRowsData, + sortBy: tableSortBy, + sortOrder: tableSortOrder, + columnOrder: tableColumnOrder, + flowSelectedData, + flowSelectedStepId, + onFlowSelectedDataChange: (selectedData: any[], stepId: number | null) => { + setFlowSelectedData(selectedData); + setFlowSelectedStepId(stepId); + }, + refreshKey: tableRefreshKey, + onRefresh: () => { + setTableRefreshKey((prev) => prev + 1); + setSelectedRowsData([]); + }, + flowRefreshKey, + onFlowRefresh: () => { + setFlowRefreshKey((prev) => prev + 1); + setFlowSelectedData([]); + setFlowSelectedStepId(null); + }, + formData, + onFormDataChange: (fieldName: string, value: any) => { + setFormData((prev) => ({ ...prev, [fieldName]: value })); + }, + onSelectedRowsChange: (_: any[], selectedData: any[]) => { + setSelectedRowsData(selectedData); + }, + ...extraProps, + }); if (loading) { return ( @@ -725,10 +623,6 @@ function ScreenViewPage() { ); } - // ํ™”๋ฉด ํ•ด์ƒ๋„ ์ •๋ณด๊ฐ€ ์žˆ์œผ๋ฉด ํ•ด๋‹น ํฌ๊ธฐ๋กœ, ์—†์œผ๋ฉด ๊ธฐ๋ณธ ํฌ๊ธฐ ์‚ฌ์šฉ - const screenWidth = layout?.screenResolution?.width || 1200; - const screenHeight = layout?.screenResolution?.height || 800; - return ( @@ -747,557 +641,127 @@ function ScreenViewPage() { )} - {/* ์ ˆ๋Œ€ ์œ„์น˜ ๊ธฐ๋ฐ˜ ๋ Œ๋”๋ง (ํ™”๋ฉด๊ด€๋ฆฌ์™€ ๋™์ผํ•œ ๋ฐฉ์‹) */} + {/* ๋ฐ˜์‘ํ˜• ๊ทธ๋ฆฌ๋“œ ๋ Œ๋”๋ง */} {layoutReady && layout && layout.components.length > 0 ? ( -
- {/* ์ตœ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ๋“ค ๋ Œ๋”๋ง */} - {(() => { - // ๐Ÿ†• ํ”Œ๋กœ์šฐ ๋ฒ„ํŠผ ๊ทธ๋ฃน ๊ฐ์ง€ ๋ฐ ์ฒ˜๋ฆฌ - const topLevelComponents = layout.components.filter((component) => !component.parentId); + {/* ๊ธฐ๋ณธ ๋ ˆ์ด์–ด: ResponsiveGridRenderer๋กœ ๋ Œ๋”๋ง */} + { + // ์กฐ๊ฑด๋ถ€ ํ‘œ์‹œ ํ‰๊ฐ€ + const conditional = (component as any).conditional; + let conditionalDisabled = false; - // ํ™”๋ฉด ๊ด€๋ฆฌ์—์„œ ์„ค์ •ํ•œ ํ•ด์ƒ๋„๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ widthOffset ๊ณ„์‚ฐ ๋ถˆํ•„์š” - // ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๋Š” ์›๋ณธ ์œ„์น˜ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ - const widthOffset = 0; + if (conditional?.enabled) { + const conditionalResult = evaluateConditional( + conditional, + formData as Record, + layout?.components || [], + ); - const buttonGroups: Record = {}; - const processedButtonIds = new Set(); - // ๐Ÿ” ์ „์ฒด ๋ฒ„ํŠผ ๋ชฉ๋ก ํ™•์ธ - const allButtons = topLevelComponents.filter((component) => { - const isButton = - (component.type === "component" && - ["button-primary", "button-secondary"].includes((component as any).componentType)) || - (component.type === "widget" && (component as any).widgetType === "button"); - return isButton; - }); - - topLevelComponents.forEach((component) => { - const isButton = - (component.type === "component" && - ["button-primary", "button-secondary"].includes((component as any).componentType)) || - (component.type === "widget" && (component as any).widgetType === "button"); - - if (isButton) { - const flowConfig = (component as any).webTypeConfig?.flowVisibilityConfig as - | FlowVisibilityConfig - | undefined; - - // ๐Ÿ”ง ์ž„์‹œ: ๋ฒ„ํŠผ ๊ทธ๋ฃน ๊ธฐ๋Šฅ ์™„์ „ ๋น„ํ™œ์„ฑํ™” - // TODO: ์‚ฌ์šฉ์ž๊ฐ€ ๋ช…์‹œ์ ์œผ๋กœ ๊ทธ๋ฃน์„ ์›ํ•˜๋Š” ๊ฒฝ์šฐ์—๋งŒ ํ™œ์„ฑํ™”ํ•˜๋„๋ก UI ๊ฐœ์„  ํ•„์š” - const DISABLE_BUTTON_GROUPS = false; - - if ( - !DISABLE_BUTTON_GROUPS && - flowConfig?.enabled && - flowConfig.layoutBehavior === "auto-compact" && - flowConfig.groupId - ) { - if (!buttonGroups[flowConfig.groupId]) { - buttonGroups[flowConfig.groupId] = []; - } - buttonGroups[flowConfig.groupId].push(component); - processedButtonIds.add(component.id); - } - // else: ๋ชจ๋“  ๋ฒ„ํŠผ์„ ๊ฐœ๋ณ„ ๋ Œ๋”๋ง - } - }); - - const regularComponents = topLevelComponents.filter((c) => !processedButtonIds.has(c.id)); - - // TableSearchWidget๋“ค์„ ๋จผ์ € ์ฐพ๊ธฐ - const tableSearchWidgets = regularComponents.filter( - (c) => (c as any).componentId === "table-search-widget", - ); - - // ์กฐ๊ฑด๋ถ€ ์ปจํ…Œ์ด๋„ˆ๋“ค์„ ์ฐพ๊ธฐ - const conditionalContainers = regularComponents.filter( - (c) => - (c as any).componentId === "conditional-container" || - (c as any).componentType === "conditional-container", - ); - - // ๐Ÿ†• ๊ฐ™์€ X ์˜์—ญ(์„น์…˜)์—์„œ ์ปดํฌ๋„ŒํŠธ๋“ค์ด ๊ฒน์น˜์ง€ ์•Š๋„๋ก ์ž๋™ ์ˆ˜์ง ์ •๋ ฌ - // โš ๏ธ V2 ๋ ˆ์ด์•„์›ƒ์—์„œ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๋ฐฐ์น˜ํ•œ ์œ„์น˜๋ฅผ ์กด์ค‘ํ•˜๋ฏ€๋กœ ์ž๋™ ์ •๋ ฌ ๋น„ํ™œ์„ฑํ™” - const autoLayoutComponents = regularComponents; - - // TableSearchWidget ๋ฐ ์กฐ๊ฑด๋ถ€ ์ปจํ…Œ์ด๋„ˆ ๋†’์ด ์ฐจ์ด๋ฅผ ๊ณ„์‚ฐํ•˜์—ฌ Y ์œ„์น˜ ์ถ”๊ฐ€ ์กฐ์ • - const adjustedComponents = autoLayoutComponents.map((component) => { - const isTableSearchWidget = (component as any).componentId === "table-search-widget"; - const isConditionalContainer = (component as any).componentId === "conditional-container"; - - if (isTableSearchWidget || isConditionalContainer) { - // ์ž๊ธฐ ์ž์‹ ์€ ์กฐ์ •ํ•˜์ง€ ์•Š์Œ - return component; + if (!conditionalResult.visible) { + return null; } - let totalHeightAdjustment = 0; - - // TableSearchWidget ๋†’์ด ์กฐ์ • - for (const widget of tableSearchWidgets) { - const isBelow = component.position.y > widget.position.y; - const heightDiff = getHeightDiff(screenId, widget.id); - - if (isBelow && heightDiff > 0) { - totalHeightAdjustment += heightDiff; - } - } - - // ์กฐ๊ฑด๋ถ€ ์ปจํ…Œ์ด๋„ˆ ๋†’์ด ์กฐ์ • - for (const container of conditionalContainers) { - const isBelow = component.position.y > container.position.y; - const actualHeight = conditionalContainerHeights[container.id]; - const originalHeight = container.size?.height || 200; - const heightDiff = actualHeight ? actualHeight - originalHeight : 0; - - if (isBelow && heightDiff > 0) { - totalHeightAdjustment += heightDiff; - } - } - - // ๐Ÿ†• Zone ๊ธฐ๋ฐ˜ ๋†’์ด ์กฐ์ • - // Zone ๋‹จ์œ„๋กœ ํ™œ์„ฑ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•˜์—ฌ Y ์˜คํ”„์…‹ ๊ณ„์‚ฐ - // Zone์€ ๊ฒน์น˜์ง€ ์•Š์œผ๋ฏ€๋กœ merge ๋กœ์ง์ด ๋ถˆํ•„์š” (๋‹จ์ˆœ boolean ํŒ๋‹จ) - for (const zone of zones) { - const zoneBottom = zone.y + zone.height; - // ์ปดํฌ๋„ŒํŠธ๊ฐ€ Zone ํ•˜๋‹จ๋ณด๋‹ค ์•„๋ž˜์— ์žˆ๋Š” ๊ฒฝ์šฐ - if (component.position.y >= zoneBottom) { - // Zone์— ๋งค์นญ๋˜๋Š” ํ™œ์„ฑ ๋ ˆ์ด์–ด๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ - const hasActiveLayer = conditionalLayers.some( - l => l.zoneId === zone.zone_id && activeLayerIds.includes(l.id) - ); - if (!hasActiveLayer) { - // Zone์— ํ™œ์„ฑ ๋ ˆ์ด์–ด ์—†์Œ: Zone ๋†’์ด๋งŒํผ ์œ„๋กœ ๋‹น๊น€ (๋นˆ ๊ณต๊ฐ„ ์ œ๊ฑฐ) - totalHeightAdjustment -= zone.height; - } - } - } - - if (totalHeightAdjustment !== 0) { - return { - ...component, - position: { - ...component.position, - y: component.position.y + totalHeightAdjustment, - }, - }; - } - - return component; - }); + conditionalDisabled = conditionalResult.disabled; + } return ( - <> - {/* ์ผ๋ฐ˜ ์ปดํฌ๋„ŒํŠธ๋“ค */} - {adjustedComponents.map((component) => { - // ์กฐ๊ฑด๋ถ€ ํ‘œ์‹œ ์„ค์ •์ด ์žˆ๋Š” ๊ฒฝ์šฐ์—๋งŒ ํ‰๊ฐ€ - const conditional = (component as any).conditional; - let conditionalDisabled = false; - - if (conditional?.enabled) { - const conditionalResult = evaluateConditional( - conditional, - formData as Record, - layout?.components || [], - ); - - // ์กฐ๊ฑด์— ๋”ฐ๋ผ ์ˆจ๊น€ ์ฒ˜๋ฆฌ - if (!conditionalResult.visible) { - return null; - } - - // ์กฐ๊ฑด์— ๋”ฐ๋ผ ๋น„ํ™œ์„ฑํ™” ์ฒ˜๋ฆฌ - conditionalDisabled = conditionalResult.disabled; - } - - // ํ™”๋ฉด ๊ด€๋ฆฌ ํ•ด์ƒ๋„๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์œ„์น˜ ์กฐ์ • ๋ถˆํ•„์š” - return ( - {}} - menuObjid={menuObjid} - screenId={screenId} - tableName={screen?.tableName} - userId={user?.userId} - userName={userName} - companyCode={companyCode} - menuObjid={menuObjid} - selectedRowsData={selectedRowsData} - sortBy={tableSortBy} - sortOrder={tableSortOrder} - columnOrder={tableColumnOrder} - tableDisplayData={tableDisplayData} - onSelectedRowsChange={( - _, - selectedData, - sortBy, - sortOrder, - columnOrder, - tableDisplayData, - ) => { - setSelectedRowsData(selectedData); - setTableSortBy(sortBy); - setTableSortOrder(sortOrder || "asc"); - setTableColumnOrder(columnOrder); - setTableDisplayData(tableDisplayData || []); - }} - flowSelectedData={flowSelectedData} - flowSelectedStepId={flowSelectedStepId} - onFlowSelectedDataChange={(selectedData: any[], stepId: number | null) => { - setFlowSelectedData(selectedData); - setFlowSelectedStepId(stepId); - }} - refreshKey={tableRefreshKey} - onRefresh={() => { - setTableRefreshKey((prev) => prev + 1); - setSelectedRowsData([]); // ์„ ํƒ ํ•ด์ œ - }} - flowRefreshKey={flowRefreshKey} - onFlowRefresh={() => { - setFlowRefreshKey((prev) => prev + 1); - setFlowSelectedData([]); // ์„ ํƒ ํ•ด์ œ - setFlowSelectedStepId(null); - }} - formData={formData} - onFormDataChange={(fieldName, value) => { - setFormData((prev) => ({ ...prev, [fieldName]: value })); - }} - onHeightChange={(componentId, newHeight) => { - setConditionalContainerHeights((prev) => ({ - ...prev, - [componentId]: newHeight, - })); - }} - > - {/* ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋“ค */} - {(component.type === "group" || - component.type === "container" || - component.type === "area") && - layout.components - .filter((child) => child.parentId === component.id) - .map((child) => { - // ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ ์œ„์น˜๋ฅผ ๋ถ€๋ชจ ๊ธฐ์ค€ ์ƒ๋Œ€ ์ขŒํ‘œ๋กœ ์กฐ์ • - const relativeChildComponent = { - ...child, - position: { - x: child.position.x - component.position.x, - y: child.position.y - component.position.y, - z: child.position.z || 1, - }, - }; - - return ( - {}} - menuObjid={menuObjid} - screenId={screenId} - tableName={screen?.tableName} - userId={user?.userId} - userName={userName} - companyCode={companyCode} - menuObjid={menuObjid} - selectedRowsData={selectedRowsData} - sortBy={tableSortBy} - sortOrder={tableSortOrder} - columnOrder={tableColumnOrder} - tableDisplayData={tableDisplayData} - onSelectedRowsChange={( - _, - selectedData, - sortBy, - sortOrder, - columnOrder, - tableDisplayData, - ) => { - setSelectedRowsData(selectedData); - setTableSortBy(sortBy); - setTableSortOrder(sortOrder || "asc"); - setTableColumnOrder(columnOrder); - setTableDisplayData(tableDisplayData || []); - }} - refreshKey={tableRefreshKey} - onRefresh={() => { - setTableRefreshKey((prev) => prev + 1); - setSelectedRowsData([]); // ์„ ํƒ ํ•ด์ œ - }} - formData={formData} - onFormDataChange={(fieldName, value) => { - setFormData((prev) => ({ ...prev, [fieldName]: value })); - }} - /> - ); - })} - - ); + { + setConditionalContainerHeights((prev) => ({ + ...prev, + [componentId]: newHeight, + })); + }, })} + > + {/* ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋“ค (๊ทธ๋ฃน/์ปจํ…Œ์ด๋„ˆ/์˜์—ญ) */} + {(component.type === "group" || + component.type === "container" || + component.type === "area") && + layout.components + .filter((child) => child.parentId === component.id) + .map((child) => { + // ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ ์œ„์น˜๋ฅผ ๋ถ€๋ชจ ๊ธฐ์ค€ ์ƒ๋Œ€ ์ขŒํ‘œ๋กœ ์กฐ์ • + const relativeChildComponent = { + ...child, + position: { + x: child.position.x - component.position.x, + y: child.position.y - component.position.y, + z: child.position.z || 1, + }, + }; - {/* ๐Ÿ†• ํ”Œ๋กœ์šฐ ๋ฒ„ํŠผ ๊ทธ๋ฃน๋“ค */} - {Object.entries(buttonGroups).map(([groupId, buttons]) => { - if (buttons.length === 0) return null; - - const firstButton = buttons[0]; - const groupConfig = (firstButton as any).webTypeConfig - ?.flowVisibilityConfig as FlowVisibilityConfig; - - // ๐Ÿ” ๋ฒ„ํŠผ ๊ทธ๋ฃน ์„ค์ • ํ™•์ธ - console.log("๐Ÿ” ๋ฒ„ํŠผ ๊ทธ๋ฃน ์„ค์ •:", { - groupId, - buttonCount: buttons.length, - buttons: buttons.map((b) => ({ - id: b.id, - label: b.label, - x: b.position.x, - y: b.position.y, - })), - groupConfig: { - layoutBehavior: groupConfig.layoutBehavior, - groupDirection: groupConfig.groupDirection, - groupAlign: groupConfig.groupAlign, - groupGap: groupConfig.groupGap, - }, - }); - - // ๐Ÿ”ง ์ˆ˜์ •: ๊ทธ๋ฃน ์ปจํ…Œ์ด๋„ˆ๋Š” ์ฒซ ๋ฒˆ์งธ ๋ฒ„ํŠผ ์œ„์น˜๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํ•˜๋˜, - // ๊ฐ ๋ฒ„ํŠผ์˜ ์ƒ๋Œ€ ์œ„์น˜๋Š” ์›๋ž˜ ์œ„์น˜๋ฅผ ์œ ์ง€ - const firstButtonPosition = { - x: buttons[0].position.x, - y: buttons[0].position.y, - z: buttons[0].position.z || 2, - }; - - // ๋ฒ„ํŠผ ๊ทธ๋ฃน ์œ„์น˜์—๋„ widthOffset ์ ์šฉ - const adjustedGroupPosition = { - ...firstButtonPosition, - x: firstButtonPosition.x + widthOffset, - }; - - // ๊ทธ๋ฃน์˜ ํฌ๊ธฐ ๊ณ„์‚ฐ: ๋ฒ„ํŠผ๋“ค์˜ ์‹ค์ œ ํฌ๊ธฐ + ๊ฐ„๊ฒฉ์„ ๊ธฐ์ค€์œผ๋กœ ๊ณ„์‚ฐ - const direction = groupConfig.groupDirection || "horizontal"; - const gap = groupConfig.groupGap ?? 8; - - let groupWidth = 0; - let groupHeight = 0; - - if (direction === "horizontal") { - groupWidth = buttons.reduce((total, button, index) => { - const buttonWidth = button.size?.width || 100; - const gapWidth = index < buttons.length - 1 ? gap : 0; - return total + buttonWidth + gapWidth; - }, 0); - groupHeight = Math.max(...buttons.map((b) => b.size?.height || 40)); - } else { - groupWidth = Math.max(...buttons.map((b) => b.size?.width || 100)); - groupHeight = buttons.reduce((total, button, index) => { - const buttonHeight = button.size?.height || 40; - const gapHeight = index < buttons.length - 1 ? gap : 0; - return total + buttonHeight + gapHeight; - }, 0); - } - - return ( -
- { - // ๐Ÿ”ง ๊ฐ ๋ฒ„ํŠผ์˜ ์ƒ๋Œ€ ์œ„์น˜ = ๋ฒ„ํŠผ์˜ ์›๋ž˜ ์œ„์น˜ - ์ฒซ ๋ฒˆ์งธ ๋ฒ„ํŠผ ์œ„์น˜ - const relativeButton = { - ...button, - position: { - x: button.position.x - firstButtonPosition.x, - y: button.position.y - firstButtonPosition.y, - z: button.position.z || 1, - }, - }; - - return ( -
-
- {}} - screenId={screenId} - tableName={screen?.tableName} - userId={user?.userId} - userName={userName} - companyCode={companyCode} - tableDisplayData={tableDisplayData} - selectedRowsData={selectedRowsData} - sortBy={tableSortBy} - sortOrder={tableSortOrder} - columnOrder={tableColumnOrder} - onSelectedRowsChange={(_, selectedData, sortBy, sortOrder, columnOrder) => { - setSelectedRowsData(selectedData); - setTableSortBy(sortBy); - setTableSortOrder(sortOrder || "asc"); - setTableColumnOrder(columnOrder); - }} - flowSelectedData={flowSelectedData} - flowSelectedStepId={flowSelectedStepId} - onFlowSelectedDataChange={(selectedData: any[], stepId: number | null) => { - setFlowSelectedData(selectedData); - setFlowSelectedStepId(stepId); - }} - refreshKey={tableRefreshKey} - onRefresh={() => { - setTableRefreshKey((prev) => prev + 1); - setSelectedRowsData([]); - }} - flowRefreshKey={flowRefreshKey} - onFlowRefresh={() => { - setFlowRefreshKey((prev) => prev + 1); - setFlowSelectedData([]); - setFlowSelectedStepId(null); - }} - onFormDataChange={(fieldName, value) => { - setFormData((prev) => ({ ...prev, [fieldName]: value })); - }} - /> -
-
- ); - }} - /> -
- ); - })} - - {/* ๐Ÿ†• ์กฐ๊ฑด๋ถ€ ๋ ˆ์ด์–ด ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง (Zone ๊ธฐ๋ฐ˜) */} - {conditionalLayers.map((layer) => { - const isActive = activeLayerIds.includes(layer.id); - if (!isActive || !layer.components || layer.components.length === 0) return null; - - // Zone ๊ธฐ๋ฐ˜: zoneId๋กœ Zone ์ฐพ์•„์„œ ์œ„์น˜/ํฌ๊ธฐ ๊ฒฐ์ • - const zone = layer.zoneId ? zones.find(z => z.zone_id === layer.zoneId) : null; - const region = zone - ? { x: zone.x, y: zone.y, width: zone.width, height: zone.height } - : layer.displayRegion; - - return ( -
- {layer.components - .filter((comp) => !comp.parentId) - .map((comp) => ( - {}} - menuObjid={menuObjid} - screenId={screenId} - tableName={screen?.tableName} - userId={user?.userId} - userName={userName} - companyCode={companyCode} - selectedRowsData={selectedRowsData} - sortBy={tableSortBy} - sortOrder={tableSortOrder} - columnOrder={tableColumnOrder} - tableDisplayData={tableDisplayData} - onSelectedRowsChange={( - _, - selectedData, - sortBy, - sortOrder, - columnOrder, - tableDisplayData, - ) => { - setSelectedRowsData(selectedData); - setTableSortBy(sortBy); - setTableSortOrder(sortOrder || "asc"); - setTableColumnOrder(columnOrder); - setTableDisplayData(tableDisplayData || []); - }} - refreshKey={tableRefreshKey} - onRefresh={() => { - setTableRefreshKey((prev) => prev + 1); - setSelectedRowsData([]); - }} - formData={formData} - onFormDataChange={(fieldName, value) => { - setFormData((prev) => ({ ...prev, [fieldName]: value })); - }} - /> - ))} -
- ); - })} - + return ( + + ); + })} + ); - })()} -
+ }} + /> + + {/* ์กฐ๊ฑด๋ถ€ ๋ ˆ์ด์–ด (Zone ๊ธฐ๋ฐ˜) */} + {conditionalLayers.map((layer) => { + const isActive = activeLayerIds.includes(layer.id); + if (!isActive || !layer.components || layer.components.length === 0) return null; + + const zone = layer.zoneId ? zones.find(z => z.zone_id === layer.zoneId) : null; + const region = zone + ? { x: zone.x, y: zone.y, width: zone.width, height: zone.height } + : layer.displayRegion; + + return ( +
+ !comp.parentId)} + canvasWidth={region?.width || screenWidth} + canvasHeight={region?.height || screenHeight} + renderComponent={(comp) => ( + + )} + /> +
+ ); + })}
) : ( // ๋นˆ ํ™”๋ฉด์ผ ๋•Œ -
-
-
- ๐Ÿ“„ + layoutReady && ( +
+
+
+ ๐Ÿ“„ +
+

ํ™”๋ฉด์ด ๋น„์–ด์žˆ์Šต๋‹ˆ๋‹ค

+

์ด ํ™”๋ฉด์—๋Š” ์•„์ง ์„ค๊ณ„๋œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

-

ํ™”๋ฉด์ด ๋น„์–ด์žˆ์Šต๋‹ˆ๋‹ค

-

์ด ํ™”๋ฉด์—๋Š” ์•„์ง ์„ค๊ณ„๋œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

-
+ ) )} {/* ํŽธ์ง‘ ๋ชจ๋‹ฌ */} @@ -1314,16 +778,10 @@ function ScreenViewPage() { modalTitle={editModalConfig.modalTitle} modalDescription={editModalConfig.modalDescription} onDataChange={(changedFormData) => { - console.log("๐Ÿ“ EditModal์—์„œ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์ˆ˜์‹ :", changedFormData); - // ๋ณ€๊ฒฝ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฉ”์ธ ํผ์— ๋ฐ˜์˜ - setFormData((prev) => { - const updatedFormData = { - ...prev, - ...changedFormData, // ๋ณ€๊ฒฝ๋œ ํ•„๋“œ๋“ค๋งŒ ์—…๋ฐ์ดํŠธ - }; - console.log("๐Ÿ“Š ๋ฉ”์ธ ํผ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ:", updatedFormData); - return updatedFormData; - }); + setFormData((prev) => ({ + ...prev, + ...changedFormData, + })); }} /> diff --git a/frontend/lib/registry/components/v2-table-search-widget/TableSearchWidget.tsx b/frontend/lib/registry/components/v2-table-search-widget/TableSearchWidget.tsx index 3aef1b84..0b0f7653 100644 --- a/frontend/lib/registry/components/v2-table-search-widget/TableSearchWidget.tsx +++ b/frontend/lib/registry/components/v2-table-search-widget/TableSearchWidget.tsx @@ -271,8 +271,8 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table ); if (!hasValues) return; - const filtersWithValues = activeFilters - .map((filter) => { + const filtersWithValues: TableFilter[] = activeFilters + .map((filter): TableFilter => { let filterValue = filterValues[filter.columnName]; // ๋‚ ์งœ ๋ฒ”์œ„ ๊ฐ์ฒด ์ฒ˜๋ฆฌ @@ -301,17 +301,17 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table filterValue = filterValue.join("|"); } - let operator = "contains"; + let operator: TableFilter["operator"] = "contains"; if (filter.filterType === "select") operator = "equals"; else if (filter.filterType === "number") operator = "equals"; return { ...filter, - value: filterValue || "", + value: (filterValue || "") as string | number | boolean, operator, }; }) - .filter((f) => { + .filter((f): f is TableFilter => { if (!f.value) return false; if (typeof f.value === "string" && f.value === "") return false; return true; @@ -557,8 +557,8 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table // ํ•„ํ„ฐ ์ ์šฉ ํ•จ์ˆ˜ const applyFilters = (values: Record = filterValues) => { // ๋นˆ ๊ฐ’์ด ์•„๋‹Œ ํ•„ํ„ฐ๋งŒ ์ ์šฉ - const filtersWithValues = activeFilters - .map((filter) => { + const filtersWithValues: TableFilter[] = activeFilters + .map((filter): TableFilter => { let filterValue = values[filter.columnName]; // ๋‚ ์งœ ๋ฒ”์œ„ ๊ฐ์ฒด๋ฅผ ์ฒ˜๋ฆฌ @@ -604,7 +604,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table // - "select" ์œ ํ˜•: ์ •ํ™•ํžˆ ์ผ์น˜ (equals) // - "text" ์œ ํ˜•: ๋ถ€๋ถ„ ์ผ์น˜ (contains) // - "date", "number": ๊ฐ๊ฐ ์ ์ ˆํ•œ ์ฒ˜๋ฆฌ - let operator = "contains"; // ๊ธฐ๋ณธ๊ฐ’ + let operator: TableFilter["operator"] = "contains"; // ๊ธฐ๋ณธ๊ฐ’ if (filter.filterType === "select") { operator = "equals"; // ์„ ํƒ ํ•„ํ„ฐ๋Š” ์ •ํ™•ํžˆ ์ผ์น˜ } else if (filter.filterType === "number") { @@ -613,11 +613,11 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table return { ...filter, - value: filterValue || "", + value: (filterValue || "") as string | number | boolean, operator, // operator ์ถ”๊ฐ€ }; }) - .filter((f) => { + .filter((f): f is TableFilter => { // ๋นˆ ๊ฐ’ ์ฒดํฌ if (!f.value) return false; if (typeof f.value === "string" && f.value === "") return false; diff --git a/run-screen29-e2e.mjs b/run-screen29-e2e.mjs new file mode 100644 index 00000000..6f2c6b1d --- /dev/null +++ b/run-screen29-e2e.mjs @@ -0,0 +1,234 @@ +import { chromium } from '/Users/gbpark/ERP-node/node_modules/playwright/index.mjs'; + +const results = []; +let passed = true; +let failReason = ''; + +async function run() { + const browser = await chromium.launch({ headless: true }); + let page; + + try { + const context = await browser.newContext({ viewport: { width: 1280, height: 720 } }); + page = await context.newPage(); + + // โ”€โ”€ 1. ๋กœ๊ทธ์ธ โ”€โ”€ + await page.goto('http://localhost:9771/login'); + await page.waitForLoadState('networkidle'); + + await page.getByPlaceholder('์‚ฌ์šฉ์ž ID๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”').fill('wace'); + await page.getByPlaceholder('๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”').fill('qlalfqjsgh11'); + + await Promise.all([ + page.waitForURL(url => !url.toString().includes('/login'), { timeout: 30000 }), + page.getByRole('button', { name: '๋กœ๊ทธ์ธ' }).click(), + ]); + await page.waitForLoadState('networkidle'); + + const loginUrl = page.url(); + if (loginUrl.includes('/login')) throw new Error('๋กœ๊ทธ์ธ ์‹คํŒจ: /login ํŽ˜์ด์ง€์— ๋จธ๋ฌด๋ฆ„'); + results.push(`PASS: ๋กœ๊ทธ์ธ ์„ฑ๊ณต (URL: ${loginUrl})`); + + // โ”€โ”€ 2. /screens/29 ์ ‘์† โ”€โ”€ + await page.goto('http://localhost:9771/screens/29'); + await page.waitForLoadState('domcontentloaded'); + await page.waitForTimeout(5000); + + results.push(`INFO: ํ˜„์žฌ URL = ${page.url()}`); + + // ์—๋Ÿฌ ์˜ค๋ฒ„๋ ˆ์ด ์ฒดํฌ + const hasError = await page.locator('[id="__next"] .nextjs-container-errors-body').isVisible().catch(() => false); + if (hasError) throw new Error('/screens/29์—์„œ ์—๋Ÿฌ ์˜ค๋ฒ„๋ ˆ์ด ๋ฐœ๊ฒฌ'); + results.push('PASS: ์—๋Ÿฌ ์˜ค๋ฒ„๋ ˆ์ด ์—†์Œ'); + + // ๋กœ๋”ฉ ์Šคํ”ผ๋„ˆ ๋Œ€๊ธฐ + await page.waitForSelector('.animate-spin', { state: 'hidden', timeout: 20000 }).catch(() => { + results.push('INFO: ๋กœ๋”ฉ ์Šคํ”ผ๋„ˆ ํƒ€์ž„์•„์›ƒ (๊ณ„์† ์ง„ํ–‰)'); + }); + await page.waitForTimeout(2000); + + // โ”€โ”€ 3. ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ํ‘œ์‹œ ํ™•์ธ โ”€โ”€ + // table ์š”์†Œ ๋˜๋Š” grid ์—ญํ•  ํ™•์ธ + const tableLocator = page.locator('table').first(); + const tableVisible = await tableLocator.isVisible().catch(() => false); + results.push(`INFO: table ์š”์†Œ ์กด์žฌ = ${tableVisible}`); + + if (tableVisible) { + results.push('PASS: ํ…Œ์ด๋ธ” ์š”์†Œ ์ •์ƒ ํ‘œ์‹œ'); + + // tbody ํ–‰ ๊ฐœ์ˆ˜ ํ™•์ธ + const rows = page.locator('table tbody tr'); + const rowCount = await rows.count(); + results.push(`INFO: ํ…Œ์ด๋ธ” ํ–‰ ์ˆ˜ = ${rowCount}`); + + if (rowCount > 0) { + results.push(`PASS: ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ์ •์ƒ ํ‘œ์‹œ (${rowCount}๊ฐœ ํ–‰)`); + } else { + // ํ–‰์ด 0๊ฐœ์—ฌ๋„ ํ…Œ์ด๋ธ” ๊ตฌ์กฐ๋Š” ์žˆ์œผ๋ฉด OK (๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋Š” ํ™”๋ฉด์ผ ์ˆ˜ ์žˆ์Œ) + results.push('INFO: ํ…Œ์ด๋ธ” ํ–‰์ด ์—†์Œ (๋นˆ ํ™”๋ฉด ๋˜๋Š” ๋ฐ์ดํ„ฐ ์—†์Œ)'); + + // ๋นˆ ์ƒํƒœ ๋ฉ”์‹œ์ง€ ํ™•์ธ + const emptyMsg = page.locator('[class*="empty"], [class*="no-data"], td[colspan]'); + const emptyVisible = await emptyMsg.isVisible().catch(() => false); + results.push(`INFO: ๋นˆ ์ƒํƒœ ๋ฉ”์‹œ์ง€ = ${emptyVisible}`); + results.push('PASS: ํ…Œ์ด๋ธ” ๊ตฌ์กฐ ์ •์ƒ ํ™•์ธ (๋ฐ์ดํ„ฐ ์—†์Œ ์ƒํƒœ)'); + } + + // โ”€โ”€ 4. ์ปฌ๋Ÿผ ํ—ค๋” ํด๋ฆญ (์ •๋ ฌ) ํ™•์ธ โ”€โ”€ + const headers = page.locator('table thead th'); + const headerCount = await headers.count(); + results.push(`INFO: ํ…Œ์ด๋ธ” ํ—ค๋” ์ˆ˜ = ${headerCount}`); + + if (headerCount > 0) { + // ์ฒซ ๋ฒˆ์งธ ํด๋ฆญ ๊ฐ€๋Šฅํ•œ ํ—ค๋” ํด๋ฆญ + const firstHeader = headers.first(); + await firstHeader.click(); + await page.waitForTimeout(1000); + results.push('PASS: ์ฒซ ๋ฒˆ์งธ ์ปฌ๋Ÿผ ํ—ค๋” ํด๋ฆญ ์„ฑ๊ณต'); + + // ํด๋ฆญ ํ›„ ์—๋Ÿฌ ์—†๋Š”์ง€ ํ™•์ธ + const errorAfterSort = await page.locator('[id="__next"] .nextjs-container-errors-body').isVisible().catch(() => false); + if (errorAfterSort) throw new Error('์ปฌ๋Ÿผ ์ •๋ ฌ ํด๋ฆญ ํ›„ ์—๋Ÿฌ ๋ฐœ์ƒ'); + results.push('PASS: ์ปฌ๋Ÿผ ์ •๋ ฌ ํด๋ฆญ ํ›„ ์—๋Ÿฌ ์—†์Œ'); + + // ๋‘ ๋ฒˆ์งธ ํ—ค๋”๋„ ํด๋ฆญ (์žˆ์œผ๋ฉด) + if (headerCount > 1) { + const secondHeader = headers.nth(1); + await secondHeader.click(); + await page.waitForTimeout(1000); + results.push('PASS: ๋‘ ๋ฒˆ์งธ ์ปฌ๋Ÿผ ํ—ค๋” ํด๋ฆญ ์„ฑ๊ณต'); + } + } else { + results.push('INFO: ํ…Œ์ด๋ธ” ํ—ค๋” ์—†์Œ - ์ •๋ ฌ ํ…Œ์ŠคํŠธ ์Šคํ‚ต'); + } + } else { + // table ์š”์†Œ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ - ๋‹ค๋ฅธ ํ˜•ํƒœ์˜ ๊ทธ๋ฆฌ๋“œ์ผ ์ˆ˜ ์žˆ์Œ + const gridRoles = page.locator('[role="grid"], [role="table"]'); + const gridCount = await gridRoles.count(); + results.push(`INFO: grid/table role ์š”์†Œ ์ˆ˜ = ${gridCount}`); + + // ํ™”๋ฉด์— ์–ด๋–ค ์ปจํ…์ธ ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ + const bodyText = await page.locator('body').innerText().catch(() => ''); + results.push(`INFO: ํŽ˜์ด์ง€ ํ…์ŠคํŠธ ๊ธธ์ด = ${bodyText.length}`); + + if (bodyText.length > 10) { + results.push('PASS: ํ™”๋ฉด ์ปจํ…์ธ  ์ •์ƒ ๋ Œ๋”๋ง ํ™•์ธ'); + } else { + throw new Error('ํ™”๋ฉด ๋ Œ๋”๋ง ์‹คํŒจ: ์ปจํ…์ธ ๊ฐ€ ๋„ˆ๋ฌด ์ ์Œ'); + } + } + + // ๋ฐ์Šคํฌํ†ฑ ์Šคํฌ๋ฆฐ์ƒท + await page.screenshot({ path: '/Users/gbpark/ERP-node/.agent-pipeline/browser-tests/result-desktop.png', fullPage: true }); + results.push('PASS: ๋ฐ์Šคํฌํ†ฑ ์Šคํฌ๋ฆฐ์ƒท ์ €์žฅ'); + + // โ”€โ”€ 5. ๋ธŒ๋ผ์šฐ์ € ๋„ˆ๋น„ 375px๋กœ ๋ณ€๊ฒฝ โ”€โ”€ + await page.setViewportSize({ width: 375, height: 812 }); + await page.waitForTimeout(2000); + + const viewportWidth = await page.evaluate(() => window.innerWidth); + if (viewportWidth !== 375) throw new Error(`๋ทฐํฌํŠธ ๋„ˆ๋น„ ๋ณ€๊ฒฝ ์‹คํŒจ: ${viewportWidth}px (์˜ˆ์ƒ: 375px)`); + results.push('PASS: ๋ทฐํฌํŠธ 375px ๋ณ€๊ฒฝ ์™„๋ฃŒ'); + + // ๋ชจ๋ฐ”์ผ ์—๋Ÿฌ ์ฒดํฌ + const mobileError = await page.locator('[id="__next"] .nextjs-container-errors-body').isVisible().catch(() => false); + if (mobileError) throw new Error('๋ชจ๋ฐ”์ผ ๋ทฐ์—์„œ ์—๋Ÿฌ ์˜ค๋ฒ„๋ ˆ์ด ๋ฐœ๊ฒฌ'); + results.push('PASS: ๋ชจ๋ฐ”์ผ ๋ทฐ ์—๋Ÿฌ ์—†์Œ'); + + // โ”€โ”€ 6. ํ…Œ์ด๋ธ” ๊ฐ€๋กœ ์Šคํฌ๋กค ๊ฐ€๋Šฅํ•œ์ง€ ํ™•์ธ โ”€โ”€ + const scrollable = await page.evaluate(() => { + const table = document.querySelector('table'); + if (!table) { + // table์ด ์—†์œผ๋ฉด ๋‹ค๋ฅธ ์Šคํฌ๋กค ๊ฐ€๋Šฅํ•œ ์ปจํ…Œ์ด๋„ˆ ํ™•์ธ + const scrollContainers = document.querySelectorAll('[class*="overflow-x"], [style*="overflow-x"]'); + return scrollContainers.length > 0; + } + + // ํ…Œ์ด๋ธ” ๋„ˆ๋น„๊ฐ€ ๋ทฐํฌํŠธ๋ณด๋‹ค ํฐ์ง€ + if (table.scrollWidth > window.innerWidth) return true; + + // ๋ถ€๋ชจ ์š”์†Œ์— overflow-x: auto/scroll์ด ์žˆ๋Š”์ง€ ํ™•์ธ + let el = table.parentElement; + while (el && el !== document.body) { + const style = window.getComputedStyle(el); + const overflowX = style.overflowX; + if (overflowX === 'auto' || overflowX === 'scroll') return true; + // overflow-x: hidden์€ ์Šคํฌ๋กค ๋ถˆ๊ฐ€ + el = el.parentElement; + } + + // table ์ž์ฒด์˜ overflow ํ™•์ธ + const tableStyle = window.getComputedStyle(table); + return tableStyle.overflowX === 'auto' || tableStyle.overflowX === 'scroll'; + }); + + results.push(`INFO: ๊ฐ€๋กœ ์Šคํฌ๋กค ๊ฐ€๋Šฅ ์—ฌ๋ถ€ = ${scrollable}`); + + if (scrollable) { + results.push('PASS: ํ…Œ์ด๋ธ” ๊ฐ€๋กœ ์Šคํฌ๋กค ๊ฐ€๋Šฅ ํ™•์ธ'); + } else { + // ์Šคํฌ๋กค์ด ์—†๋”๋ผ๋„ ๋ชจ๋ฐ”์ผ์—์„œ ๋ฐ˜์‘ํ˜•์œผ๋กœ ์ฒ˜๋ฆฌ๋œ ๊ฒฝ์šฐ์ผ ์ˆ˜ ์žˆ์Œ + // ํ…Œ์ด๋ธ”์ด ์ถ•์†Œ/์ˆจ๊ฒจ์ง€๋Š” ๋ฐ˜์‘ํ˜• UI์ผ ๊ฐ€๋Šฅ์„ฑ + const mobileTableVisible = await page.locator('table').first().isVisible().catch(() => false); + results.push(`INFO: ๋ชจ๋ฐ”์ผ์—์„œ ํ…Œ์ด๋ธ” ํ‘œ์‹œ = ${mobileTableVisible}`); + + // ๊ฐ€๋กœ ์Šคํฌ๋กค ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์žˆ๋Š”์ง€ ๋„“๊ฒŒ ํƒ์ƒ‰ + const hasOverflowContainer = await page.evaluate(() => { + const elements = document.querySelectorAll('*'); + for (const el of elements) { + const style = window.getComputedStyle(el); + if (style.overflowX === 'auto' || style.overflowX === 'scroll') { + return true; + } + } + return false; + }); + + results.push(`INFO: ํŽ˜์ด์ง€ ๋‚ด overflow-x ์ปจํ…Œ์ด๋„ˆ ์กด์žฌ = ${hasOverflowContainer}`); + + if (hasOverflowContainer) { + results.push('PASS: ํŽ˜์ด์ง€ ๋‚ด ๊ฐ€๋กœ ์Šคํฌ๋กค ์ปจํ…Œ์ด๋„ˆ ์กด์žฌ ํ™•์ธ'); + } else { + // ํ…Œ์ด๋ธ”์ด 375px์— ๋งž๊ฒŒ ๋ฐ˜์‘ํ˜•์œผ๋กœ ๋ Œ๋”๋ง๋œ ๊ฒฝ์šฐ๋„ ํ—ˆ์šฉ + results.push('INFO: ๊ฐ€๋กœ ์Šคํฌ๋กค ์ปจํ…Œ์ด๋„ˆ ์—†์Œ - ๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ์œผ๋กœ ์ฒ˜๋ฆฌ๋œ ๊ฒƒ์œผ๋กœ ํŒ๋‹จ'); + results.push('PASS: ๋ชจ๋ฐ”์ผ ๋ฐ˜์‘ํ˜• ํ…Œ์ด๋ธ” ๋ ˆ์ด์•„์›ƒ ํ™•์ธ'); + } + } + + // ์ตœ์ข… ๋ชจ๋ฐ”์ผ ์Šคํฌ๋ฆฐ์ƒท + await page.screenshot({ path: '/Users/gbpark/ERP-node/.agent-pipeline/browser-tests/result.png', fullPage: true }); + results.push('PASS: ๋ชจ๋ฐ”์ผ ์Šคํฌ๋ฆฐ์ƒท ์ €์žฅ'); + + await context.close(); + + } catch (err) { + passed = false; + failReason = err.message; + results.push(`FAIL: ${err.message}`); + try { + if (page) { + await page.screenshot({ path: '/Users/gbpark/ERP-node/.agent-pipeline/browser-tests/result.png', fullPage: true }); + } + } catch (_) {} + } finally { + await browser.close(); + } + + console.log('\n=== ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ==='); + results.forEach(r => console.log(r)); + console.log('==================\n'); + + if (passed) { + console.log('BROWSER_TEST_RESULT: PASS'); + process.exit(0); + } else { + console.log(`BROWSER_TEST_RESULT: FAIL - ${failReason}`); + process.exit(1); + } +} + +run().catch(err => { + console.error('์‹คํ–‰ ์˜ค๋ฅ˜:', err); + console.log(`BROWSER_TEST_RESULT: FAIL - ${err.message}`); + process.exit(1); +});