diff --git a/backend/main.py b/backend/main.py index 3f8cbbd..9cf11d2 100644 --- a/backend/main.py +++ b/backend/main.py @@ -543,77 +543,22 @@ def stats_top_chargers(limit: int = 10): @app.get("/api/stats/charger-error-codes") -def stats_charger_error_codes(code_limit: int = 10, charger_limit: int = 5): - """에러코드별 누적 건수 Top N (어느 충전기에서 발생했는지 함께 반환).""" +def stats_charger_error_codes(code_limit: int = 10): + """에러코드별 누적 건수 Top N (단순 순위).""" from database import SessionLocal from sqlalchemy import text - from collections import defaultdict db = SessionLocal() try: rows = db.execute(text(""" - SELECT TRIM(rep.error_code) AS error_code, - rep.charger_id, - COALESCE(c.station_name, rep.charger_id) AS station_name, - COALESCE(c.name, '') AS charger_name, - COUNT(*) AS cnt - FROM reports rep - LEFT JOIN chargers c ON c.id = rep.charger_id - WHERE rep.error_code IS NOT NULL - AND TRIM(rep.error_code) != '' - GROUP BY TRIM(rep.error_code), rep.charger_id, c.station_name, c.name - """)).fetchall() - - if not rows: - return {"error_codes": [], "charger_labels": {}} - - # error_code → {total, chargers: {cid: cnt}} - code_info = {} - # charger_id → label, total across all codes - charger_info = {} - - for ecode, cid, sname, cname, cnt in rows: - cnt = int(cnt) - if ecode not in code_info: - code_info[ecode] = {"total": 0, "chargers": {}} - code_info[ecode]["total"] += cnt - code_info[ecode]["chargers"][cid] = cnt - - if cid not in charger_info: - label = sname + (f" ({cname})" if cname else "") - if len(label) > 20: label = label[:18] + "…" - charger_info[cid] = {"label": label, "total": 0} - charger_info[cid]["total"] += cnt - - # Top N error codes by total - top_codes = sorted(code_info.items(), key=lambda x: -x[1]["total"])[:code_limit] - - # Top M chargers across all top codes - top_cids = [cid for cid, _ in - sorted(charger_info.items(), key=lambda x: -x[1]["total"])[:charger_limit]] - - result = [] - for ecode, info in reversed(top_codes): # 역순: 차트에서 1위가 위에 - entry = {"error_code": ecode, "total": info["total"]} - other = 0 - for cid, cnt in info["chargers"].items(): - if cid in top_cids: - entry[cid] = cnt - else: - other += cnt - if other: - entry["__other__"] = other - result.append(entry) - - has_other = any(r.get("__other__", 0) > 0 for r in result) - charger_labels = {cid: charger_info[cid]["label"] for cid in top_cids} - if has_other: - charger_labels["__other__"] = "기타" - - dataset_keys = top_cids + (["__other__"] if has_other else []) - return { - "error_codes": result, # 에러코드별 집계 (역순) - "charger_labels": charger_labels, # charger_id → 표시명 - "dataset_keys": dataset_keys, # 차트 dataset 순서 - } + SELECT TRIM(error_code) AS error_code, COUNT(*) AS cnt + FROM reports + WHERE error_code IS NOT NULL AND TRIM(error_code) != '' + GROUP BY TRIM(error_code) + ORDER BY cnt DESC + LIMIT :limit + """), {"limit": code_limit}).fetchall() + # 역순: 차트 Y축에서 1위가 맨 위 + result = [{"error_code": r[0], "total": int(r[1])} for r in reversed(rows)] + return {"error_codes": result} finally: db.close() diff --git a/frontend/static/js/imageCompress.js b/frontend/static/js/imageCompress.js index 870539e..afe5c30 100755 --- a/frontend/static/js/imageCompress.js +++ b/frontend/static/js/imageCompress.js @@ -170,5 +170,26 @@ const ImageCompressor = (() => { : (bytes / (1024 * 1024)).toFixed(1) + ' MB'; } - return { compressAll, setupPreview, loadConfig }; + /** + * 카메라 input(capture)이 찍은 사진을 갤러리 input에 병합 후 change 이벤트 발생 + * → setupPreview는 갤러리 input 하나만 바라보면 됨 + * @param {string} cameraId - capture="environment" input id + * @param {string} galleryId - 기존 multiple input id (setupPreview 대상) + */ + function setupCameraAppend(cameraId, galleryId) { + const cam = document.getElementById(cameraId); + const main = document.getElementById(galleryId); + if (!cam || !main) return; + cam.addEventListener('change', function () { + if (!this.files.length) return; + const dt = new DataTransfer(); + Array.from(main.files).forEach(f => dt.items.add(f)); // 기존 파일 유지 + Array.from(this.files).forEach(f => dt.items.add(f)); // 새 사진 추가 + main.files = dt.files; + main.dispatchEvent(new Event('change')); // setupPreview 재실행 + this.value = ''; + }); + } + + return { compressAll, setupPreview, loadConfig, setupCameraAppend }; })(); diff --git a/frontend/static/pages/admin/dashboard.html b/frontend/static/pages/admin/dashboard.html index 9574bd1..6a25a1e 100644 --- a/frontend/static/pages/admin/dashboard.html +++ b/frontend/static/pages/admin/dashboard.html @@ -231,7 +231,7 @@