diff --git a/backend-node/src/app.ts b/backend-node/src/app.ts index 37965d00..0e41697f 100644 --- a/backend-node/src/app.ts +++ b/backend-node/src/app.ts @@ -56,7 +56,6 @@ import todoRoutes from "./routes/todoRoutes"; // To-Do 관리 import bookingRoutes from "./routes/bookingRoutes"; // 예약 요청 관리 import mapDataRoutes from "./routes/mapDataRoutes"; // 지도 데이터 관리 import yardLayoutRoutes from "./routes/yardLayoutRoutes"; // 야드 관리 3D -import materialRoutes from "./routes/materialRoutes"; // 자재 관리 import { BatchSchedulerService } from "./services/batchSchedulerService"; // import collectionRoutes from "./routes/collectionRoutes"; // 임시 주석 // import batchRoutes from "./routes/batchRoutes"; // 임시 주석 @@ -207,7 +206,6 @@ app.use("/api/todos", todoRoutes); // To-Do 관리 app.use("/api/bookings", bookingRoutes); // 예약 요청 관리 app.use("/api/map-data", mapDataRoutes); // 지도 데이터 조회 app.use("/api/yard-layouts", yardLayoutRoutes); // 야드 관리 3D -app.use("/api/materials", materialRoutes); // 자재 관리 // app.use("/api/collections", collectionRoutes); // 임시 주석 // app.use("/api/batch", batchRoutes); // 임시 주석 // app.use('/api/users', userRoutes); diff --git a/backend-node/src/controllers/MaterialController.ts b/backend-node/src/controllers/MaterialController.ts deleted file mode 100644 index bcac72d4..00000000 --- a/backend-node/src/controllers/MaterialController.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Request, Response } from "express"; -import MaterialService from "../services/MaterialService"; - -export class MaterialController { - // 임시 자재 마스터 목록 조회 - async getTempMaterials(req: Request, res: Response) { - try { - const { search, category, page, limit } = req.query; - - const result = await MaterialService.getTempMaterials({ - search: search as string, - category: category as string, - page: page ? parseInt(page as string) : 1, - limit: limit ? parseInt(limit as string) : 20, - }); - - return res.json({ success: true, ...result }); - } catch (error: any) { - console.error("Error fetching temp materials:", error); - return res.status(500).json({ - success: false, - message: "자재 목록 조회 중 오류가 발생했습니다.", - error: error.message, - }); - } - } - - // 특정 자재 상세 조회 - async getTempMaterialByCode(req: Request, res: Response) { - try { - const { code } = req.params; - const material = await MaterialService.getTempMaterialByCode(code); - - if (!material) { - return res.status(404).json({ - success: false, - message: "자재를 찾을 수 없습니다.", - }); - } - - return res.json({ success: true, data: material }); - } catch (error: any) { - console.error("Error fetching temp material:", error); - return res.status(500).json({ - success: false, - message: "자재 조회 중 오류가 발생했습니다.", - error: error.message, - }); - } - } - - // 카테고리 목록 조회 - async getCategories(req: Request, res: Response) { - try { - const categories = await MaterialService.getCategories(); - return res.json({ success: true, data: categories }); - } catch (error: any) { - console.error("Error fetching categories:", error); - return res.status(500).json({ - success: false, - message: "카테고리 목록 조회 중 오류가 발생했습니다.", - error: error.message, - }); - } - } -} - -export default new MaterialController(); diff --git a/backend-node/src/routes/materialRoutes.ts b/backend-node/src/routes/materialRoutes.ts deleted file mode 100644 index a85e10f6..00000000 --- a/backend-node/src/routes/materialRoutes.ts +++ /dev/null @@ -1,15 +0,0 @@ -import express from "express"; -import MaterialController from "../controllers/MaterialController"; -import { authenticateToken } from "../middleware/authMiddleware"; - -const router = express.Router(); - -// 모든 라우트에 인증 미들웨어 적용 -router.use(authenticateToken); - -// 임시 자재 마스터 관리 -router.get("/temp", MaterialController.getTempMaterials); -router.get("/temp/categories", MaterialController.getCategories); -router.get("/temp/:code", MaterialController.getTempMaterialByCode); - -export default router; diff --git a/backend-node/src/services/MaterialService.ts b/backend-node/src/services/MaterialService.ts deleted file mode 100644 index 0f316cdc..00000000 --- a/backend-node/src/services/MaterialService.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { getPool } from "../database/db"; - -export class MaterialService { - // 임시 자재 마스터 목록 조회 - async getTempMaterials(params: { - search?: string; - category?: string; - page?: number; - limit?: number; - }) { - const { search, category, page = 1, limit = 20 } = params; - const offset = (page - 1) * limit; - - let whereConditions: string[] = ["is_active = true"]; - const queryParams: any[] = []; - let paramIndex = 1; - - if (search) { - whereConditions.push( - `(material_code ILIKE $${paramIndex} OR material_name ILIKE $${paramIndex})` - ); - queryParams.push(`%${search}%`); - paramIndex++; - } - - if (category) { - whereConditions.push(`category = $${paramIndex}`); - queryParams.push(category); - paramIndex++; - } - - const whereClause = - whereConditions.length > 0 - ? `WHERE ${whereConditions.join(" AND ")}` - : ""; - - const pool = getPool(); - - // 전체 개수 조회 - const countQuery = `SELECT COUNT(*) as total FROM temp_material_master ${whereClause}`; - const countResult = await pool.query(countQuery, queryParams); - const total = parseInt(countResult.rows[0].total); - - // 데이터 조회 - const dataQuery = ` - SELECT - id, - material_code, - material_name, - category, - unit, - default_color, - description, - created_at - FROM temp_material_master - ${whereClause} - ORDER BY material_code ASC - LIMIT $${paramIndex} OFFSET $${paramIndex + 1} - `; - - queryParams.push(limit, offset); - const dataResult = await pool.query(dataQuery, queryParams); - - return { - data: dataResult.rows, - pagination: { - page, - limit, - total, - totalPages: Math.ceil(total / limit), - }, - }; - } - - // 특정 자재 상세 조회 - async getTempMaterialByCode(materialCode: string) { - const query = ` - SELECT - id, - material_code, - material_name, - category, - unit, - default_color, - description, - created_at - FROM temp_material_master - WHERE material_code = $1 AND is_active = true - `; - - const pool = getPool(); - const result = await pool.query(query, [materialCode]); - return result.rows[0] || null; - } - - // 카테고리 목록 조회 - async getCategories() { - const query = ` - SELECT DISTINCT category - FROM temp_material_master - WHERE is_active = true AND category IS NOT NULL - ORDER BY category ASC - `; - - const pool = getPool(); - const result = await pool.query(query); - return result.rows.map((row) => row.category); - } -} - -export default new MaterialService(); diff --git a/backend-node/src/services/YardLayoutService.ts b/backend-node/src/services/YardLayoutService.ts index 6d077915..7a814333 100644 --- a/backend-node/src/services/YardLayoutService.ts +++ b/backend-node/src/services/YardLayoutService.ts @@ -101,7 +101,6 @@ export class YardLayoutService { SELECT id, yard_layout_id, - external_material_id, material_code, material_name, quantity, @@ -113,6 +112,9 @@ export class YardLayoutService { size_y, size_z, color, + data_source_type, + data_source_config, + data_binding, memo, created_at, updated_at @@ -126,12 +128,11 @@ export class YardLayoutService { return result.rows; } - // 야드에 자재 배치 추가 + // 야드에 자재 배치 추가 (빈 요소 또는 설정된 요소) async addMaterialPlacement(layoutId: number, data: any) { const query = ` INSERT INTO yard_material_placement ( yard_layout_id, - external_material_id, material_code, material_name, quantity, @@ -143,52 +144,68 @@ export class YardLayoutService { size_y, size_z, color, + data_source_type, + data_source_config, + data_binding, memo - ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) RETURNING * `; const pool = getPool(); const result = await pool.query(query, [ layoutId, - data.external_material_id, - data.material_code, - data.material_name, - data.quantity, - data.unit, + data.material_code || null, + data.material_name || null, + data.quantity || null, + data.unit || null, data.position_x || 0, data.position_y || 0, data.position_z || 0, data.size_x || 5, data.size_y || 5, data.size_z || 5, - data.color || "#3b82f6", + data.color || "#9ca3af", // 미설정 시 회색 + data.data_source_type || null, + data.data_source_config ? JSON.stringify(data.data_source_config) : null, + data.data_binding ? JSON.stringify(data.data_binding) : null, data.memo || null, ]); return result.rows[0]; } - // 배치 정보 수정 (위치, 크기, 색상, 메모만) + // 배치 정보 수정 (위치, 크기, 색상, 데이터 바인딩 등) async updatePlacement(placementId: number, data: any) { const query = ` UPDATE yard_material_placement SET - position_x = COALESCE($1, position_x), - position_y = COALESCE($2, position_y), - position_z = COALESCE($3, position_z), - size_x = COALESCE($4, size_x), - size_y = COALESCE($5, size_y), - size_z = COALESCE($6, size_z), - color = COALESCE($7, color), - memo = COALESCE($8, memo), + material_code = COALESCE($1, material_code), + material_name = COALESCE($2, material_name), + quantity = COALESCE($3, quantity), + unit = COALESCE($4, unit), + position_x = COALESCE($5, position_x), + position_y = COALESCE($6, position_y), + position_z = COALESCE($7, position_z), + size_x = COALESCE($8, size_x), + size_y = COALESCE($9, size_y), + size_z = COALESCE($10, size_z), + color = COALESCE($11, color), + data_source_type = COALESCE($12, data_source_type), + data_source_config = COALESCE($13, data_source_config), + data_binding = COALESCE($14, data_binding), + memo = COALESCE($15, memo), updated_at = CURRENT_TIMESTAMP - WHERE id = $9 + WHERE id = $16 RETURNING * `; const pool = getPool(); const result = await pool.query(query, [ + data.material_code, + data.material_name, + data.quantity, + data.unit, data.position_x, data.position_y, data.position_z, @@ -196,6 +213,9 @@ export class YardLayoutService { data.size_y, data.size_z, data.color, + data.data_source_type, + data.data_source_config ? JSON.stringify(data.data_source_config) : null, + data.data_binding ? JSON.stringify(data.data_binding) : null, data.memo, placementId, ]); @@ -230,8 +250,9 @@ export class YardLayoutService { size_x = $4, size_y = $5, size_z = $6, + color = $7, updated_at = CURRENT_TIMESTAMP - WHERE id = $7 AND yard_layout_id = $8 + WHERE id = $8 AND yard_layout_id = $9 RETURNING * `; @@ -242,6 +263,7 @@ export class YardLayoutService { placement.size_x, placement.size_y, placement.size_z, + placement.color, placement.id, layoutId, ]); @@ -299,14 +321,14 @@ export class YardLayoutService { await client.query( ` INSERT INTO yard_material_placement ( - yard_layout_id, external_material_id, material_code, material_name, + yard_layout_id, material_code, material_name, quantity, unit, position_x, position_y, position_z, - size_x, size_y, size_z, color, memo - ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) + size_x, size_y, size_z, color, + data_source_type, data_source_config, data_binding, memo + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) `, [ newLayout.id, - placement.external_material_id, placement.material_code, placement.material_name, placement.quantity, @@ -318,6 +340,9 @@ export class YardLayoutService { placement.size_y, placement.size_z, placement.color, + placement.data_source_type, + placement.data_source_config, + placement.data_binding, placement.memo, ] ); diff --git a/frontend/components/admin/dashboard/widgets/yard-3d/YardEditor.tsx b/frontend/components/admin/dashboard/widgets/yard-3d/YardEditor.tsx index 8dd82e5d..3a620841 100644 --- a/frontend/components/admin/dashboard/widgets/yard-3d/YardEditor.tsx +++ b/frontend/components/admin/dashboard/widgets/yard-3d/YardEditor.tsx @@ -2,11 +2,12 @@ import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; -import { ArrowLeft, Save, Loader2, X } from "lucide-react"; -import { yardLayoutApi, materialApi } from "@/lib/api/yardLayoutApi"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +import { ArrowLeft, Save, Loader2, Plus, Settings, Trash2 } from "lucide-react"; +import { yardLayoutApi } from "@/lib/api/yardLayoutApi"; import dynamic from "next/dynamic"; +import { YardLayout, YardPlacement } from "./types"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { AlertCircle } from "lucide-react"; const Yard3DCanvas = dynamic(() => import("./Yard3DCanvas"), { ssr: false, @@ -17,41 +18,11 @@ const Yard3DCanvas = dynamic(() => import("./Yard3DCanvas"), { ), }); -interface TempMaterial { - id: number; - material_code: string; - material_name: string; - category: string; - unit: string; - default_color: string; - description: string; -} - -interface YardLayout { - id: number; - name: string; - description: string; - placement_count?: number; - updated_at: string; -} - -interface YardPlacement { - id: number; - yard_layout_id: number; - external_material_id: string; - material_code: string; - material_name: string; - quantity: number; - unit: string; - position_x: number; - position_y: number; - position_z: number; - size_x: number; - size_y: number; - size_z: number; - color: string; - memo?: string; -} +// 나중에 구현할 데이터 바인딩 패널 +const YardElementConfigPanel = dynamic(() => import("./YardElementConfigPanel"), { + ssr: false, + loading: () =>
총 {placements.length}개