오류난것들 해결 완료

This commit is contained in:
leeheejin 2025-10-20 11:52:23 +09:00
parent 66a8196411
commit ff58c84ac0
6 changed files with 91 additions and 31 deletions

View File

@ -4,7 +4,7 @@ import React, { useState, useCallback, useRef, useEffect } from "react";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { DashboardElement, QueryResult } from "./types"; import { DashboardElement, QueryResult } from "./types";
import { ChartRenderer } from "./charts/ChartRenderer"; import { ChartRenderer } from "./charts/ChartRenderer";
import { snapToGrid, snapSizeToGrid, GRID_CONFIG } from "./gridUtils"; import { GRID_CONFIG } from "./gridUtils";
// 위젯 동적 임포트 // 위젯 동적 임포트
const WeatherWidget = dynamic(() => import("@/components/dashboard/widgets/WeatherWidget"), { const WeatherWidget = dynamic(() => import("@/components/dashboard/widgets/WeatherWidget"), {
@ -116,6 +116,7 @@ interface CanvasElementProps {
element: DashboardElement; element: DashboardElement;
isSelected: boolean; isSelected: boolean;
cellSize: number; cellSize: number;
subGridSize: number;
canvasWidth?: number; canvasWidth?: number;
onUpdate: (id: string, updates: Partial<DashboardElement>) => void; onUpdate: (id: string, updates: Partial<DashboardElement>) => void;
onRemove: (id: string) => void; onRemove: (id: string) => void;
@ -133,6 +134,7 @@ export function CanvasElement({
element, element,
isSelected, isSelected,
cellSize, cellSize,
subGridSize,
canvasWidth = 1560, canvasWidth = 1560,
onUpdate, onUpdate,
onRemove, onRemove,
@ -233,7 +235,6 @@ export function CanvasElement({
rawX = Math.min(rawX, maxX); rawX = Math.min(rawX, maxX);
// 드래그 중 실시간 스냅 (마그네틱 스냅) // 드래그 중 실시간 스냅 (마그네틱 스냅)
const subGridSize = Math.floor(cellSize / 3);
const gridSize = cellSize + 5; // GAP 포함한 실제 그리드 크기 const gridSize = cellSize + 5; // GAP 포함한 실제 그리드 크기
const magneticThreshold = 15; // 큰 그리드에 끌리는 거리 (px) const magneticThreshold = 15; // 큰 그리드에 끌리는 거리 (px)
@ -291,7 +292,6 @@ export function CanvasElement({
newWidth = Math.min(newWidth, maxWidth); newWidth = Math.min(newWidth, maxWidth);
// 리사이즈 중 실시간 스냅 (마그네틱 스냅) // 리사이즈 중 실시간 스냅 (마그네틱 스냅)
const subGridSize = Math.floor(cellSize / 3);
const gridSize = cellSize + 5; // GAP 포함한 실제 그리드 크기 const gridSize = cellSize + 5; // GAP 포함한 실제 그리드 크기
const magneticThreshold = 15; const magneticThreshold = 15;
@ -336,6 +336,7 @@ export function CanvasElement({
element.subtype, element.subtype,
canvasWidth, canvasWidth,
cellSize, cellSize,
subGridSize,
], ],
); );
@ -726,6 +727,7 @@ export function CanvasElement({
isEditMode={true} isEditMode={true}
config={element.yardConfig} config={element.yardConfig}
onConfigChange={(newConfig) => { onConfigChange={(newConfig) => {
console.log("🏗️ 야드 설정 업데이트:", { elementId: element.id, newConfig });
onUpdate(element.id, { yardConfig: newConfig }); onUpdate(element.id, { yardConfig: newConfig });
}} }}
/> />

View File

@ -156,8 +156,7 @@ export const DashboardCanvas = forwardRef<HTMLDivElement, DashboardCanvasProps>(
const rawY = e.clientY - rect.top + (ref.current?.scrollTop || 0); const rawY = e.clientY - rect.top + (ref.current?.scrollTop || 0);
// 마그네틱 스냅 (큰 그리드 우선, 없으면 서브그리드) // 마그네틱 스냅 (큰 그리드 우선, 없으면 서브그리드)
const subGridSize = Math.floor(cellSize / 3); const gridSize = cellSize + GRID_CONFIG.GAP; // GAP 포함한 실제 그리드 크기
const gridSize = cellSize + 5; // GAP 포함한 실제 그리드 크기
const magneticThreshold = 15; const magneticThreshold = 15;
// X 좌표 스냅 // X 좌표 스냅
@ -196,6 +195,9 @@ export const DashboardCanvas = forwardRef<HTMLDivElement, DashboardCanvasProps>(
// 동적 그리드 크기 계산 // 동적 그리드 크기 계산
const cellWithGap = cellSize + GRID_CONFIG.GAP; const cellWithGap = cellSize + GRID_CONFIG.GAP;
const gridSize = `${cellWithGap}px ${cellWithGap}px`; const gridSize = `${cellWithGap}px ${cellWithGap}px`;
// 서브그리드 크기 계산 (gridConfig에서 정확하게 계산된 값 사용)
const subGridSize = gridConfig.SUB_GRID_SIZE;
// 12개 컬럼 구분선 위치 계산 // 12개 컬럼 구분선 위치 계산
const columnLines = Array.from({ length: GRID_CONFIG.COLUMNS + 1 }, (_, i) => i * cellWithGap); const columnLines = Array.from({ length: GRID_CONFIG.COLUMNS + 1 }, (_, i) => i * cellWithGap);
@ -208,12 +210,12 @@ export const DashboardCanvas = forwardRef<HTMLDivElement, DashboardCanvasProps>(
backgroundColor, backgroundColor,
height: `${canvasHeight}px`, height: `${canvasHeight}px`,
minHeight: `${canvasHeight}px`, minHeight: `${canvasHeight}px`,
// 세밀한 그리드 배경 // 서브그리드 배경 (세밀한 점선)
backgroundImage: ` backgroundImage: `
linear-gradient(rgba(59, 130, 246, 0.08) 1px, transparent 1px), linear-gradient(rgba(59, 130, 246, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(59, 130, 246, 0.08) 1px, transparent 1px) linear-gradient(90deg, rgba(59, 130, 246, 0.05) 1px, transparent 1px)
`, `,
backgroundSize: gridSize, backgroundSize: `${subGridSize}px ${subGridSize}px`,
backgroundPosition: "0 0", backgroundPosition: "0 0",
backgroundRepeat: "repeat", backgroundRepeat: "repeat",
}} }}
@ -229,8 +231,9 @@ export const DashboardCanvas = forwardRef<HTMLDivElement, DashboardCanvasProps>(
className="pointer-events-none absolute top-0 h-full" className="pointer-events-none absolute top-0 h-full"
style={{ style={{
left: `${x}px`, left: `${x}px`,
width: "2px", width: "1px",
zIndex: 1, backgroundColor: i === 0 || i === GRID_CONFIG.COLUMNS ? "rgba(59, 130, 246, 0.3)" : "rgba(59, 130, 246, 0.15)",
zIndex: 0,
}} }}
/> />
))} ))}
@ -248,6 +251,7 @@ export const DashboardCanvas = forwardRef<HTMLDivElement, DashboardCanvasProps>(
element={element} element={element}
isSelected={selectedElement === element.id} isSelected={selectedElement === element.id}
cellSize={cellSize} cellSize={cellSize}
subGridSize={subGridSize}
canvasWidth={canvasWidth} canvasWidth={canvasWidth}
onUpdate={handleUpdateWithCollisionDetection} onUpdate={handleUpdateWithCollisionDetection}
onRemove={onRemoveElement} onRemove={onRemoveElement}

View File

@ -332,21 +332,31 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D
try { try {
const { dashboardApi } = await import("@/lib/api/dashboard"); const { dashboardApi } = await import("@/lib/api/dashboard");
const elementsData = elements.map((el) => ({ const elementsData = elements.map((el) => {
id: el.id, // 야드 위젯인 경우 설정 로그 출력
type: el.type, if (el.subtype === "yard-management-3d") {
subtype: el.subtype, console.log("💾 야드 위젯 저장:", {
position: el.position, id: el.id,
size: el.size, yardConfig: el.yardConfig,
title: el.title, hasLayoutId: !!el.yardConfig?.layoutId,
customTitle: el.customTitle, });
showHeader: el.showHeader, }
content: el.content, return {
dataSource: el.dataSource, id: el.id,
chartConfig: el.chartConfig, type: el.type,
listConfig: el.listConfig, subtype: el.subtype,
yardConfig: el.yardConfig, position: el.position,
})); size: el.size,
title: el.title,
customTitle: el.customTitle,
showHeader: el.showHeader,
content: el.content,
dataSource: el.dataSource,
chartConfig: el.chartConfig,
listConfig: el.listConfig,
yardConfig: el.yardConfig,
};
});
let savedDashboard; let savedDashboard;

View File

@ -36,6 +36,11 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
// 차트 설정이 필요 없는 위젯 (쿼리/API만 필요) // 차트 설정이 필요 없는 위젯 (쿼리/API만 필요)
const isSimpleWidget = const isSimpleWidget =
element.subtype === "todo" || // To-Do 위젯
element.subtype === "booking-alert" || // 예약 알림 위젯
element.subtype === "maintenance" || // 정비 일정 위젯
element.subtype === "document" || // 문서 위젯
element.subtype === "risk-alert" || // 리스크 알림 위젯
element.subtype === "vehicle-status" || element.subtype === "vehicle-status" ||
element.subtype === "vehicle-list" || element.subtype === "vehicle-list" ||
element.subtype === "status-summary" || // 커스텀 상태 카드 element.subtype === "status-summary" || // 커스텀 상태 카드
@ -46,6 +51,12 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
element.subtype === "cargo-list" || element.subtype === "cargo-list" ||
element.subtype === "customer-issues" || element.subtype === "customer-issues" ||
element.subtype === "driver-management"; element.subtype === "driver-management";
// 자체 기능 위젯 (DB 연결 불필요, 헤더 설정만 가능)
const isSelfContainedWidget =
element.subtype === "weather" || // 날씨 위젯 (외부 API)
element.subtype === "exchange" || // 환율 위젯 (외부 API)
element.subtype === "calculator"; // 계산기 위젯 (자체 기능)
// 지도 위젯 (위도/경도 매핑 필요) // 지도 위젯 (위도/경도 매핑 필요)
const isMapWidget = element.subtype === "vehicle-map" || element.subtype === "map-summary"; const isMapWidget = element.subtype === "vehicle-map" || element.subtype === "map-summary";
@ -59,6 +70,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
setQueryResult(null); setQueryResult(null);
setCurrentStep(1); setCurrentStep(1);
setCustomTitle(element.customTitle || ""); setCustomTitle(element.customTitle || "");
setShowHeader(element.showHeader !== false); // showHeader 초기화
} }
}, [isOpen, element]); }, [isOpen, element]);
@ -135,8 +147,12 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
// 모달이 열려있지 않으면 렌더링하지 않음 // 모달이 열려있지 않으면 렌더링하지 않음
if (!isOpen) return null; if (!isOpen) return null;
// 시계, 달력, To-Do 위젯은 헤더 설정만 가능 // 시계, 달력, 날씨, 환율, 계산기 위젯은 헤더 설정만 가능
const isHeaderOnlyWidget = element.type === "widget" && (element.subtype === "clock" || element.subtype === "calendar" || element.subtype === "todo"); const isHeaderOnlyWidget =
element.type === "widget" &&
(element.subtype === "clock" ||
element.subtype === "calendar" ||
isSelfContainedWidget);
// 기사관리 위젯은 자체 설정 UI를 가지고 있으므로 모달 표시하지 않음 // 기사관리 위젯은 자체 설정 UI를 가지고 있으므로 모달 표시하지 않음
if (element.type === "widget" && element.subtype === "driver-management") { if (element.type === "widget" && element.subtype === "driver-management") {
@ -154,11 +170,15 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
// customTitle이 변경되었는지 확인 // customTitle이 변경되었는지 확인
const isTitleChanged = customTitle.trim() !== (element.customTitle || ""); const isTitleChanged = customTitle.trim() !== (element.customTitle || "");
// showHeader가 변경되었는지 확인
const isHeaderChanged = showHeader !== (element.showHeader !== false);
const canSave = const canSave =
isTitleChanged || // 제목만 변경해도 저장 가능 isTitleChanged || // 제목만 변경해도 저장 가능
isHeaderChanged || // 헤더 표시 여부만 변경해도 저장 가능
(isSimpleWidget (isSimpleWidget
? // 간단한 위젯: 2단계에서 쿼리 테스트 후 저장 가능 ? // 간단한 위젯: 2단계에서 쿼리 테스트 후 저장 가능 (차트 설정 불필요)
currentStep === 2 && queryResult && queryResult.rows.length > 0 currentStep === 2 && queryResult && queryResult.rows.length > 0
: isMapWidget : isMapWidget
? // 지도 위젯: 위도/경도 매핑 필요 ? // 지도 위젯: 위도/경도 매핑 필요
@ -184,7 +204,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/50 backdrop-blur-sm"> <div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/50 backdrop-blur-sm">
<div <div
className={`flex flex-col rounded-xl border bg-white shadow-2xl ${ className={`flex flex-col rounded-xl border bg-white shadow-2xl ${
currentStep === 1 ? "h-auto max-h-[70vh] w-full max-w-3xl" : "h-[85vh] w-full max-w-5xl" currentStep === 1 && !isSimpleWidget ? "h-auto max-h-[70vh] w-full max-w-3xl" : "h-[85vh] w-full max-w-5xl"
}`} }`}
> >
{/* 모달 헤더 */} {/* 모달 헤더 */}
@ -336,7 +356,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
</Button> </Button>
) : currentStep === 1 ? ( ) : currentStep === 1 ? (
// 1단계: 다음 버튼 // 1단계: 다음 버튼 (차트 위젯, 간단한 위젯 모두)
<Button onClick={handleNext}> <Button onClick={handleNext}>
<ChevronRight className="ml-2 h-4 w-4" /> <ChevronRight className="ml-2 h-4 w-4" />
@ -354,3 +374,4 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
</div> </div>
); );
} }

View File

@ -57,6 +57,17 @@ export default function YardManagement3DWidget({
} }
}, [isEditMode]); }, [isEditMode]);
// 레이아웃 목록이 로드되었고, 설정이 없으면 첫 번째 레이아웃 자동 선택
useEffect(() => {
if (isEditMode && layouts.length > 0 && !config?.layoutId && onConfigChange) {
console.log("🔧 첫 번째 야드 레이아웃 자동 선택:", layouts[0]);
onConfigChange({
layoutId: layouts[0].id,
layoutName: layouts[0].name,
});
}
}, [isEditMode, layouts, config?.layoutId, onConfigChange]);
// 레이아웃 선택 (편집 모드에서만) // 레이아웃 선택 (편집 모드에서만)
const handleSelectLayout = (layout: YardLayout) => { const handleSelectLayout = (layout: YardLayout) => {
if (onConfigChange) { if (onConfigChange) {
@ -243,12 +254,16 @@ export default function YardManagement3DWidget({
// 뷰 모드: 선택된 레이아웃의 3D 뷰어 표시 // 뷰 모드: 선택된 레이아웃의 3D 뷰어 표시
if (!config?.layoutId) { if (!config?.layoutId) {
console.warn("⚠️ 야드관리 위젯: layoutId가 설정되지 않음", { config, isEditMode });
return ( return (
<div className="flex h-full w-full items-center justify-center bg-gray-50"> <div className="flex h-full w-full items-center justify-center bg-gray-50">
<div className="text-center"> <div className="text-center">
<div className="mb-2 text-4xl">🏗</div> <div className="mb-2 text-4xl">🏗</div>
<div className="text-sm font-medium text-gray-600"> </div> <div className="text-sm font-medium text-gray-600"> </div>
<div className="mt-1 text-xs text-gray-400"> </div> <div className="mt-1 text-xs text-gray-400"> </div>
<div className="mt-2 text-xs text-red-500">
디버그: config={JSON.stringify(config)}
</div>
</div> </div>
</div> </div>
); );

View File

@ -82,6 +82,14 @@ function renderWidget(element: DashboardElement) {
return <ListWidget element={element} />; return <ListWidget element={element} />;
case "yard-management-3d": case "yard-management-3d":
console.log("🏗️ 야드관리 위젯 렌더링:", {
elementId: element.id,
yardConfig: element.yardConfig,
yardConfigType: typeof element.yardConfig,
hasLayoutId: !!element.yardConfig?.layoutId,
layoutId: element.yardConfig?.layoutId,
layoutName: element.yardConfig?.layoutName,
});
return <YardManagement3DWidget isEditMode={false} config={element.yardConfig} />; return <YardManagement3DWidget isEditMode={false} config={element.yardConfig} />;
// === 차량 관련 (추가 위젯) === // === 차량 관련 (추가 위젯) ===