From aab1f91d3d57aecabc0cc2ac8c53ae8b86578e76 Mon Sep 17 00:00:00 2001 From: Taehoon Date: Thu, 18 Jun 2026 19:49:15 +0900 Subject: [PATCH] feat(map): implement robust ID-based asset mapping and fix UI rendering inconsistencies - Migrated map mapping from fuzzy coordinates to precise asset_id tracking - Updated MapEditor to allow explicit asset assignment via dropdown - Fixed LocationView rendering logic to search across all hardware categories - Standardized map indicators to always render as areas (boxes) with minimum size - Restored stable CSS max-height for detail modal photos to prevent clipping - Synced MapEditor saves directly to database via asset_id --- map_config.json | 580 +++++++++++++++++--------------------- server.js | 13 +- src/core/state.ts | 4 +- src/views/LocationView.ts | 53 ++-- src/views/MapEditor.ts | 55 +++- 5 files changed, 350 insertions(+), 355 deletions(-) diff --git a/map_config.json b/map_config.json index b56bc15..064aab7 100644 --- a/map_config.json +++ b/map_config.json @@ -4,99 +4,115 @@ "x": "50.78", "y": "1.53", "w": "46.10", - "h": "6.27" + "h": "6.27", + "asset_id": null }, { "x": "50.78", "y": "10.35", "w": "46.10", - "h": "6.27" + "h": "6.27", + "asset_id": null }, { "x": "50.78", "y": "19.06", "w": "46.10", - "h": "6.50" + "h": "6.50", + "asset_id": null }, { "x": "50.78", "y": "27.89", "w": "46.10", - "h": "6.50" + "h": "6.50", + "asset_id": "server_1779761946023_14" }, { "x": "50.78", "y": "36.71", "w": "46.10", - "h": "6.50" + "h": "6.50", + "asset_id": "server_1779761946023_18" }, { "x": "50.78", "y": "45.64", "w": "46.10", - "h": "6.32" + "h": "6.32", + "asset_id": "server_1779761946023_23" }, { "x": "50.78", "y": "54.25", "w": "46.10", - "h": "6.54" + "h": "6.54", + "asset_id": "server_1779761946023_24" }, { "x": "50.78", "y": "63.29", "w": "46.10", - "h": "6.50" + "h": "6.50", + "asset_id": "server_1779761946023_1" }, { "x": "50.78", "y": "72.00", "w": "46.10", - "h": "6.32" + "h": "6.32", + "asset_id": "server_1779761946023_21" }, { "x": "50.78", "y": "81.92", "w": "18.40", - "h": "15.58" + "h": "15.58", + "asset_id": "server_1779761946023_17" }, { "x": "78.62", "y": "81.92", "w": "18.31", - "h": "15.58" + "h": "15.58", + "asset_id": "server_1779761946023_20" } ], "img/location_photo/IDC/서관202.png": [ { - "x": "58.35", + "x": "56.35", "y": "64.02", "w": "40.87", - "h": "6.24" + "h": "6.24", + "asset_id": "server_1779761946023_9" }, { "x": "56.35", "y": "71.57", "w": "40.87", - "h": "6.24" + "h": "6.24", + "asset_id": "server_1779761946023_10" }, { "x": "56.35", "y": "79.17", "w": "40.87", - "h": "6.24" + "h": "6.24", + "asset_id": "server_1779761946023_26" }, { "x": "56.35", "y": "86.66", "w": "40.87", - "h": "6.24" + "h": "6.24", + "asset_id": "server_1779761946023_8" }, { "x": "56.35", "y": "32.01", "w": "40.87", - "h": "6.24" + "h": "6.24", + "asset_id": null } ], "img/location_photo/IDC/서관203.png": [ @@ -104,37 +120,43 @@ "x": "56.07", "y": "2.54", "w": "41.11", - "h": "6.52" + "h": "6.52", + "asset_id": null }, { "x": "56.07", "y": "10.12", "w": "41.11", - "h": "6.52" + "h": "6.52", + "asset_id": null }, { "x": "56.07", "y": "17.80", "w": "41.11", - "h": "6.52" + "h": "6.52", + "asset_id": null }, { "x": "56.07", "y": "63.51", "w": "41.11", - "h": "6.52" + "h": "6.52", + "asset_id": null }, { "x": "56.07", "y": "71.19", "w": "41.11", - "h": "6.52" + "h": "6.52", + "asset_id": null }, { "x": "56.07", "y": "87.70", "w": "41.11", - "h": "6.52" + "h": "6.52", + "asset_id": "server_1779761946023_25" } ], "img/location_photo/IDC/서관204.png": [ @@ -142,73 +164,85 @@ "x": "48.87", "y": "2.73", "w": "47.80", - "h": "6.27" + "h": "6.27", + "asset_id": null }, { "x": "48.87", "y": "10.38", "w": "47.80", - "h": "6.27" + "h": "6.27", + "asset_id": "server_1779761946023_3" }, { "x": "48.87", "y": "17.93", "w": "47.80", - "h": "6.50" + "h": "6.50", + "asset_id": "server_1779761946023_6" }, { "x": "48.87", "y": "25.49", "w": "47.80", - "h": "6.50" + "h": "6.50", + "asset_id": null }, { "x": "48.87", "y": "33.17", "w": "47.80", - "h": "6.50" + "h": "6.50", + "asset_id": "server_1779761946023_5" }, { "x": "48.87", "y": "40.59", "w": "47.80", - "h": "6.50" + "h": "6.50", + "asset_id": "server_1779761946023_4" }, { "x": "48.87", "y": "48.40", "w": "47.80", - "h": "6.50" + "h": "6.50", + "asset_id": null }, { "x": "48.87", "y": "55.95", "w": "47.80", - "h": "6.50" + "h": "6.50", + "asset_id": "server_1779761946023_19" }, { "x": "48.87", "y": "63.63", "w": "47.80", - "h": "6.50" + "h": "6.50", + "asset_id": "server_1779761946023_2" }, { "x": "48.87", "y": "71.06", "w": "47.80", - "h": "6.50" + "h": "6.50", + "asset_id": "server_1779761946023_0" }, { "x": "48.87", "y": "78.74", "w": "47.80", - "h": "6.50" + "h": "6.50", + "asset_id": "server_1779761946023_7" }, { "x": "48.87", "y": "86.68", "w": "18.99", - "h": "12.62" + "h": "12.62", + "asset_id": "server_1779761946023_29" } ], "img/location_photo/IDC/동관53.png": [ @@ -216,19 +250,22 @@ "x": "61.62", "y": "3.08", "w": "35.96", - "h": "7.90" + "h": "7.90", + "asset_id": "server_1779761946023_13" }, { "x": "61.62", "y": "12.68", "w": "35.96", - "h": "7.90" + "h": "7.90", + "asset_id": "server_1779761946023_15" }, { "x": "61.62", "y": "21.75", "w": "35.96", - "h": "7.90" + "h": "7.90", + "asset_id": "server_1779761946023_22" } ], "img/location_photo/IDC/동관54.png": [ @@ -236,191 +273,50 @@ "x": "54.71", "y": "2.57", "w": "42.42", - "h": "6.50" + "h": "6.50", + "asset_id": null }, { "x": "54.71", "y": "10.38", "w": "42.42", - "h": "6.50" + "h": "6.50", + "asset_id": null }, { "x": "54.71", "y": "27.15", "w": "42.42", - "h": "6.62" + "h": "6.62", + "asset_id": "server_1779761946023_12" }, { "x": "54.71", "y": "43.54", "w": "42.42", - "h": "6.50" + "h": "6.50", + "asset_id": "server_1779761946023_11" }, { "x": "54.71", "y": "54.93", "w": "42.42", - "h": "6.50" + "h": "6.50", + "asset_id": null }, { "x": "54.71", "y": "70.16", "w": "42.42", - "h": "6.50" + "h": "6.50", + "asset_id": "server_1779761946023_27" }, { "x": "54.71", "y": "79.51", "w": "42.42", - "h": "6.50" - } - ], - "img/location_photo/기술개발센터/서버실_1.png": [ - { - "x": "69.45", - "y": "1.10", - "w": "8.58", - "h": "11.45" - }, - { - "x": "79.21", - "y": "1.10", - "w": "11.65", - "h": "11.45" - }, - { - "x": "90.16", - "y": "23.23", - "w": "8.43", - "h": "21.11" - }, - { - "x": "52.91", - "y": "53.35", - "w": "8.66", - "h": "21.11" - }, - { - "x": "62.36", - "y": "53.47", - "w": "8.43", - "h": "21.11" - }, - { - "x": "71.65", - "y": "53.47", - "w": "8.50", - "h": "20.98" - }, - { - "x": "80.87", - "y": "53.35", - "w": "8.35", - "h": "21.23" - }, - { - "x": "90.08", - "y": "53.35", - "w": "8.58", - "h": "21.11" - }, - { - "x": "43.78", - "y": "76.38", - "w": "8.50", - "h": "21.11" - }, - { - "x": "53.15", - "y": "76.38", - "w": "8.43", - "h": "21.23" - }, - { - "x": "62.44", - "y": "76.51", - "w": "8.35", - "h": "20.98" - }, - { - "x": "71.57", - "y": "76.25", - "w": "8.43", - "h": "21.11" - }, - { - "x": "81.02", - "y": "76.64", - "w": "8.27", - "h": "20.85" - }, - { - "x": "90.24", - "y": "76.64", - "w": "8.50", - "h": "20.98" - } - ], - "img/location_photo/기술개발센터/서버실_2.png": [ - { - "x": "49.60", - "y": "1.93", - "w": "46.96", - "h": "6.53" - }, - { - "x": "49.34", - "y": "11.92", - "w": "47.09", - "h": "6.66" - }, - { - "x": "49.34", - "y": "21.39", - "w": "47.35", - "h": "6.40" - }, - { - "x": "49.47", - "y": "30.73", - "w": "47.22", - "h": "6.40" - }, - { - "x": "49.34", - "y": "39.82", - "w": "47.22", - "h": "6.53" - }, - { - "x": "49.47", - "y": "49.68", - "w": "47.09", - "h": "6.91" - }, - { - "x": "49.60", - "y": "59.28", - "w": "46.82", - "h": "6.27" - }, - { - "x": "49.34", - "y": "68.63", - "w": "47.35", - "h": "6.40" - }, - { - "x": "49.47", - "y": "77.84", - "w": "46.82", - "h": "6.40" - }, - { - "x": "49.60", - "y": "86.93", - "w": "46.82", - "h": "6.53" + "h": "6.50", + "asset_id": "server_1779761946023_28" } ], "img/location_photo/한맥빌딩/MDF실/MDF_1.png": [ @@ -428,127 +324,148 @@ "x": "49.33", "y": "14.99", "w": "7.35", - "h": "11.22" + "h": "11.22", + "asset_id": "cdp0e0c" }, { "x": "59.23", "y": "14.99", "w": "7.35", - "h": "11.22" + "h": "11.22", + "asset_id": "emys9gb" }, { "x": "69.22", "y": "14.99", "w": "7.35", - "h": "11.22" + "h": "11.22", + "asset_id": "vmbv3pj" }, { - "x": "78.96", + "x": "79.12", "y": "14.99", "w": "7.35", - "h": "11.22" + "h": "11.22", + "asset_id": "4fysk40" }, { - "x": "89.03", + "x": "88.97", "y": "14.99", "w": "7.35", - "h": "11.22" + "h": "11.22", + "asset_id": "x6jaehn" }, { "x": "48.57", "y": "34.11", "w": "7.52", - "h": "11.44" + "h": "11.44", + "asset_id": "t87p0l0" }, { "x": "56.80", "y": "34.11", "w": "7.52", - "h": "11.44" + "h": "11.44", + "asset_id": "ywosxiv" }, { "x": "64.94", "y": "34.11", "w": "7.52", - "h": "11.44" + "h": "11.44", + "asset_id": null }, { "x": "72.89", "y": "34.11", "w": "7.56", - "h": "11.44" + "h": "11.44", + "asset_id": null }, { "x": "81.22", "y": "34.06", "w": "7.52", - "h": "11.44" + "h": "11.44", + "asset_id": null }, { "x": "89.36", "y": "34.06", "w": "7.52", - "h": "11.44" + "h": "11.44", + "asset_id": "tormk2l" }, { "x": "48.57", "y": "53.06", "w": "9.06", - "h": "20.99" + "h": "20.99", + "asset_id": null }, { "x": "58.48", "y": "53.06", "w": "9.06", - "h": "20.99" + "h": "20.99", + "asset_id": "server_1779761946023_30" }, { "x": "68.55", "y": "53.06", "w": "9.06", - "h": "20.99" + "h": "20.99", + "asset_id": "server_1779761946023_31" }, { "x": "78.54", "y": "53.06", "w": "9.01", - "h": "20.99" + "h": "20.99", + "asset_id": "server_1779761946023_32" }, { "x": "89.36", "y": "53.22", "w": "7.45", - "h": "10.11" + "h": "10.11", + "asset_id": "TEMP-03g59cx" }, { "x": "89.36", "y": "64.92", "w": "7.45", - "h": "9.81" + "h": "9.81", + "asset_id": "TEMP-06l8zjx" }, { "x": "48.57", "y": "77.41", "w": "9.18", - "h": "21.45" + "h": "21.45", + "asset_id": "server_1779761946023_34" }, { "x": "58.56", "y": "77.41", "w": "9.23", - "h": "21.45" + "h": "21.45", + "asset_id": "server_1779761946023_35" }, { "x": "68.63", "y": "77.41", "w": "9.06", - "h": "21.45" + "h": "21.45", + "asset_id": "server_1779761946023_36" }, { "x": "78.71", "y": "77.41", "w": "8.98", - "h": "21.45" + "h": "21.45", + "asset_id": "server_1779761946023_37" } ], "img/location_photo/한맥빌딩/MDF실/MDF_2.png": [ @@ -556,19 +473,22 @@ "x": "56.59", "y": "44.53", "w": "40.65", - "h": "6.90" + "h": "6.90", + "asset_id": "1vbkbzr" }, { "x": "56.59", "y": "54.80", "w": "40.65", - "h": "6.90" + "h": "6.90", + "asset_id": "0ru63ay" }, { "x": "56.59", "y": "65.94", "w": "40.65", - "h": "6.90" + "h": "6.90", + "asset_id": "server_1779761946023_40" } ], "img/location_photo/한맥빌딩/MDF실/MDF_3.png": [ @@ -576,43 +496,50 @@ "x": "56.71", "y": "13.20", "w": "40.58", - "h": "6.90" + "h": "6.90", + "asset_id": null }, { "x": "56.71", "y": "23.57", "w": "40.58", - "h": "6.90" + "h": "6.90", + "asset_id": "8aeog58" }, { "x": "56.71", "y": "34.57", "w": "40.58", - "h": "6.90" + "h": "6.90", + "asset_id": "ywosxiv" }, { "x": "56.71", "y": "44.69", "w": "40.58", - "h": "6.90" + "h": "6.90", + "asset_id": "1vbkbzr" }, { "x": "56.71", "y": "54.80", "w": "40.58", - "h": "6.90" + "h": "6.90", + "asset_id": "0ru63ay" }, { "x": "56.71", "y": "65.81", "w": "40.58", - "h": "6.90" + "h": "6.90", + "asset_id": "server_1779761946023_40" }, { "x": "56.71", "y": "76.05", "w": "40.58", - "h": "6.90" + "h": "6.90", + "asset_id": null } ], "img/location_photo/한맥빌딩/MDF실/MDF_4.png": [ @@ -620,155 +547,180 @@ "x": "52.36", "y": "64.02", "w": "44.60", - "h": "6.73" + "h": "6.73", + "asset_id": "5tbpuy4" } ], "img/location_photo/기술개발센터/서버실/서버실_1.png": [ { - "x": "69.40", - "y": "3.06", - "w": "8.61", - "h": "11.57" + "x": "69.45", + "y": "3.30", + "w": "8.58", + "h": "11.45", + "asset_id": "server_1779761946023_41" }, { - "x": "79.00", - "y": "3.06", - "w": "12.05", - "h": "11.57" + "x": "79.05", + "y": "3.30", + "w": "12.02", + "h": "11.45", + "asset_id": "server_1779761946023_42" }, { - "x": "90.19", - "y": "25.69", - "w": "8.50", - "h": "21.48" + "x": "90.16", + "y": "26.04", + "w": "8.43", + "h": "21.11", + "asset_id": "server_1779761946023_43" }, { - "x": "53.07", - "y": "52.94", - "w": "8.50", - "h": "21.25" + "x": "53.04", + "y": "52.91", + "w": "8.43", + "h": "21.11", + "asset_id": "server_1779761946023_44" }, { - "x": "62.28", - "y": "52.94", - "w": "8.50", - "h": "21.25" + "x": "62.36", + "y": "52.91", + "w": "8.43", + "h": "21.11", + "asset_id": "server_1779761946023_45" }, { - "x": "71.50", - "y": "52.94", - "w": "8.50", - "h": "21.25" + "x": "71.65", + "y": "52.91", + "w": "8.43", + "h": "21.11", + "asset_id": "server_1779761946023_46" }, { "x": "80.87", - "y": "52.94", - "w": "8.50", - "h": "21.25" + "y": "52.91", + "w": "8.43", + "h": "21.11", + "asset_id": "server_1779761946023_47" }, { "x": "90.08", - "y": "52.94", - "w": "8.50", - "h": "21.25" + "y": "52.91", + "w": "8.43", + "h": "21.11", + "asset_id": "server_1779761946023_48" }, { - "x": "43.86", - "y": "78.30", + "x": "43.78", + "y": "78.00", "w": "8.50", - "h": "21.20" + "h": "21.23", + "asset_id": "19kai41" }, { "x": "53.15", - "y": "78.30", - "w": "8.50", - "h": "21.20" + "y": "78.00", + "w": "8.43", + "h": "21.23", + "asset_id": "server_1779761946023_50" }, { - "x": "62.32", - "y": "78.30", - "w": "8.50", - "h": "21.20" + "x": "62.36", + "y": "78.00", + "w": "8.43", + "h": "21.23", + "asset_id": "server_1779761946023_51" }, { - "x": "71.35", - "y": "78.30", - "w": "8.50", - "h": "21.20" + "x": "71.36", + "y": "78.00", + "w": "8.43", + "h": "21.23", + "asset_id": "server_1779761946023_52" }, { - "x": "80.58", - "y": "78.30", - "w": "8.50", - "h": "21.20" + "x": "80.53", + "y": "78.00", + "w": "8.43", + "h": "21.23", + "asset_id": "srlmyar" }, { - "x": "89.79", - "y": "78.30", + "x": "89.77", + "y": "78.00", "w": "8.50", - "h": "21.20" + "h": "21.23", + "asset_id": "server_1779761946023_54" } ], "img/location_photo/기술개발센터/서버실/서버실_2.png": [ { - "x": "49.47", - "y": "1.80", - "w": "47.49", - "h": "7.04" + "x": "49.60", + "y": "1.93", + "w": "47.19", + "h": "6.75", + "asset_id": "server_1779761946023_55" }, { - "x": "49.47", + "x": "49.60", "y": "12.04", - "w": "47.49", - "h": "7.04" + "w": "47.19", + "h": "6.75", + "asset_id": "server_1779761946023_56" }, { - "x": "49.47", - "y": "21.52", - "w": "47.49", - "h": "6.97" + "x": "49.60", + "y": "21.39", + "w": "47.19", + "h": "6.75", + "asset_id": "server_1779761946023_57" }, { - "x": "49.47", - "y": "30.48", - "w": "47.49", - "h": "7.04" + "x": "49.60", + "y": "30.73", + "w": "47.19", + "h": "6.75", + "asset_id": "server_1779761946023_58" }, { - "x": "49.47", + "x": "49.60", "y": "39.82", - "w": "47.49", - "h": "7.04" + "w": "47.19", + "h": "6.75", + "asset_id": "server_1779761946023_59" }, { - "x": "49.47", - "y": "50.06", - "w": "47.49", - "h": "7.04" + "x": "49.60", + "y": "50.13", + "w": "47.19", + "h": "6.75", + "asset_id": "server_1779761946023_60" }, { - "x": "49.47", + "x": "49.60", "y": "59.28", - "w": "47.49", - "h": "7.04" + "w": "47.19", + "h": "6.75", + "asset_id": "server_1779761946023_53" }, { - "x": "49.47", - "y": "68.37", - "w": "47.49", - "h": "7.04" + "x": "49.60", + "y": "68.63", + "w": "47.19", + "h": "6.75", + "asset_id": "server_1779761946023_62" }, { - "x": "49.47", - "y": "77.97", - "w": "47.49", - "h": "6.97" + "x": "49.60", + "y": "77.84", + "w": "47.19", + "h": "6.75", + "asset_id": "server_1779761946023_63" }, { - "x": "49.47", + "x": "49.60", "y": "86.93", - "w": "47.49", - "h": "7.04" + "w": "47.19", + "h": "6.75", + "asset_id": "server_1779761946023_64" } ] } \ No newline at end of file diff --git a/server.js b/server.js index 5585f16..9251ed3 100644 --- a/server.js +++ b/server.js @@ -694,15 +694,12 @@ app.post('/api/maps/save', async (req, res) => { // 3. Sync Database Assets (asset_location table) connection = await pool.getConnection(); - for (let i = 0; i < boxes.length; i++) { - const newBox = boxes[i]; - const oldBox = oldBoxes[i]; - - if (oldBox && (String(oldBox.x) !== String(newBox.x) || String(oldBox.y) !== String(newBox.y))) { - console.log(`Syncing moved box #${i+1} on ${path}: [${oldBox.x}, ${oldBox.y}] -> [${newBox.x}, ${newBox.y}]`); + for (const box of boxes) { + if (box.asset_id) { + console.log(`Syncing asset ${box.asset_id} to new position: [${box.x}, ${box.y}]`); await connection.query( - 'UPDATE asset_location SET loc_x = ?, loc_y = ? WHERE loc_x = ? AND loc_y = ? AND is_active = 1', - [newBox.x, newBox.y, oldBox.x, oldBox.y] + 'UPDATE asset_location SET loc_x = ?, loc_y = ? WHERE asset_id = ? AND is_active = 1', + [box.x, box.y, box.asset_id] ); } } diff --git a/src/core/state.ts b/src/core/state.ts index 0e2b391..ae63496 100644 --- a/src/core/state.ts +++ b/src/core/state.ts @@ -24,7 +24,7 @@ export const state: AppState = { masterData: { users: [], pc: [], server: [], storage: [], network: [], - survey: [], pcParts: [], partsMaster: [], equipment: [], officeSupplies: [], + survey: [], pcParts: [], partsMaster: [], equipment: [], officeSupplies: [], swInternal: [], swExternal: [], cloud: [], domain: [], cost: [], vip: [], hw: [], sw: [], @@ -34,6 +34,8 @@ export const state: AppState = { } }; +(window as any).__itam_state = state; + /** * 통합 V2 스키마에 맞춘 데이터 로드 */ diff --git a/src/views/LocationView.ts b/src/views/LocationView.ts index 5554972..9c98842 100644 --- a/src/views/LocationView.ts +++ b/src/views/LocationView.ts @@ -25,16 +25,21 @@ export async function renderLocationView(container: HTMLElement) { : []; const mapPath = locImages[currentPage] || ''; - // 조회 모드: 자산이 등록된 구역만 필터링하여 노출 + // 조회 모드: 설정 파일에 정의된 asset_id를 기준으로 자산 데이터 매핑 const allBoxes = mapConfig[mapPath] || []; - const boxes = allBoxes.filter((box: any) => - state.masterData.hw.some(a => - a.location === currentLoc && - a.location_detail === currentDetail && - String(a.loc_x) === String(box.x) && - String(a.loc_y) === String(box.y) - ) - ); + const boxes = allBoxes.filter((box: any) => box.asset_id != null); + + // 모든 하드웨어 카테고리에서 자산 검색 + const allHwAssets = [ + ...state.masterData.pc, + ...state.masterData.server, + ...state.masterData.storage, + ...state.masterData.network, + ...state.masterData.equipment, + ...state.masterData.survey, + ...state.masterData.officeSupplies, + ...state.masterData.pcParts + ]; container.innerHTML = `
@@ -81,14 +86,17 @@ export async function renderLocationView(container: HTMLElement) {
${boxes.map((box: any, idx: number) => { - const name = box.name || `#${idx+1}`; + const asset = allHwAssets.find(a => a.id === box.asset_id); + const name = asset ? (asset.asset_purpose || asset.asset_code) : (box.name || `#${idx+1}`); + // w, h가 없거나 너무 작으면 최소 크기(3%) 보장하여 영역으로 표시 + const width = Math.max(parseFloat(box.w || '3'), 3); + const height = Math.max(parseFloat(box.h || '3'), 3); return ` -
+ style="left:${box.x}%; top:${box.y}%; width:${width}%; height:${height}%; + border: 2px solid var(--primary-color); background: rgba(30, 81, 73, 0.1); cursor:pointer; pointer-events: auto; position: absolute;">
`}).join('')}
@@ -170,20 +178,15 @@ export async function renderLocationView(container: HTMLElement) { chkBox.addEventListener('change', handleToggle); } - container.querySelectorAll('.location-box-point').forEach(box => { + container.querySelectorAll('.location-box-area').forEach(box => { box.addEventListener('click', () => { - const x = box.getAttribute('data-x'); - const y = box.getAttribute('data-y'); + const assetId = box.getAttribute('data-asset-id'); + if (!assetId) return; - const targetAsset = state.masterData.hw.find(a => - a.location === currentLoc && - a.location_detail === currentDetail && - String(a.loc_x) === String(x) && - String(a.loc_y) === String(y) - ); + const targetAsset = allHwAssets.find(a => a.id === assetId); if (targetAsset) renderAssetDetail(targetAsset); - container.querySelectorAll('.location-box-point').forEach(b => (b as HTMLElement).style.background = 'rgba(30, 81, 73, 0.1)'); + container.querySelectorAll('.location-box-area').forEach(b => (b as HTMLElement).style.background = 'rgba(30, 81, 73, 0.1)'); (box as HTMLElement).style.background = 'rgba(30, 81, 73, 0.4)'; }); }); diff --git a/src/views/MapEditor.ts b/src/views/MapEditor.ts index 78eeb1d..b050779 100644 --- a/src/views/MapEditor.ts +++ b/src/views/MapEditor.ts @@ -18,6 +18,7 @@ export class MapEditor { private startY: number = 0; private currentBox: HTMLElement | null = null; private currentPath: string = ''; + private assetOptions: {id: string, name: string}[] = []; constructor() { this.container = document.getElementById('container')!; @@ -33,11 +34,35 @@ export class MapEditor { public async init() { this.renderFileSidebar(); await this.loadConfig(); + await this.loadAssets(); this.bindEvents(); this.selectFirstFile(); createIcons({ icons: { X, Save, Trash2, ChevronLeft, ChevronRight } }); } + private async loadAssets() { + try { + const res = await fetch(`http://${location.hostname}:3000/api/assets/master`); + const masterData = await res.json(); + const allHw = [ + ...(masterData.pc || []), + ...(masterData.server || []), + ...(masterData.storage || []), + ...(masterData.network || []), + ...(masterData.equipment || []), + ...(masterData.survey || []), + ...(masterData.officeSupplies || []), + ...(masterData.pcParts || []) + ]; + this.assetOptions = allHw.map(a => ({ + id: a.id, + name: `[${a.asset_code}] ${a.asset_purpose || a.model_name || a.category}` + })); + } catch (err) { + console.error('Failed to load assets for mapping', err); + } + } + private renderFileSidebar() { let html = ''; Object.entries(IMAGE_LOCATIONS).forEach(([bldg, details]) => { @@ -137,7 +162,8 @@ export class MapEditor { x: (parseFloat(this.currentBox.style.left) / rect.width * 100).toFixed(2), y: (parseFloat(this.currentBox.style.top) / rect.height * 100).toFixed(2), w: (width / rect.width * 100).toFixed(2), - h: (height / rect.height * 100).toFixed(2) + h: (height / rect.height * 100).toFixed(2), + asset_id: null }; this.boxes.push(boxData); this.render(); @@ -210,6 +236,13 @@ export class MapEditor { this.wrapper.appendChild(div); + // Create asset options dropdown + let optionsHtml = ''; + this.assetOptions.forEach(opt => { + const selected = box.asset_id === opt.id ? 'selected' : ''; + optionsHtml += ``; + }); + const item = document.createElement('div'); item.className = 'box-item'; item.innerHTML = ` @@ -217,6 +250,11 @@ export class MapEditor { #${i+1}
+
+ +
@@ -239,17 +277,20 @@ export class MapEditor { this.boxListEl.appendChild(item); }); - // Add events to new inputs - this.boxListEl.querySelectorAll('input').forEach(input => { + // Add events to new inputs and selects + this.boxListEl.querySelectorAll('input, select').forEach(input => { input.addEventListener('change', (e) => { - const target = e.target as HTMLInputElement; + const target = e.target as HTMLInputElement | HTMLSelectElement; const index = parseInt(target.dataset.index!); const prop = target.dataset.prop!; - const val = parseFloat(target.value).toFixed(2); if (this.boxes[index]) { - this.boxes[index][prop] = val; - this.render(); // Re-render to update the map and sync other inputs + if (prop === 'asset_id') { + this.boxes[index][prop] = target.value || null; + } else { + this.boxes[index][prop] = parseFloat(target.value).toFixed(2); + this.render(); // Re-render to update the map visual size + } } }); });