diff --git a/backend/app/routers/dashboard.py b/backend/app/routers/dashboard.py index 1af2731..8eafd86 100644 --- a/backend/app/routers/dashboard.py +++ b/backend/app/routers/dashboard.py @@ -427,6 +427,57 @@ async def get_quick_actions( raise HTTPException(status_code=500, detail=f"빠른 작업 조회 실패: {str(e)}") +@router.get("/projects") +async def get_projects( + current_user: dict = Depends(get_current_user), + db: Session = Depends(get_db) +): + """ + 프로젝트 목록 조회 + + Returns: + dict: 프로젝트 목록 + """ + try: + query = text(""" + SELECT + id, + job_no, + official_project_code, + job_name, + project_type, + created_at, + updated_at + FROM projects + ORDER BY created_at DESC + """) + + results = db.execute(query).fetchall() + + projects = [] + for row in results: + projects.append({ + "id": row.id, + "job_no": row.job_no, + "official_project_code": row.official_project_code, + "job_name": row.job_name, + "project_name": row.job_name, # 호환성을 위해 추가 + "project_type": row.project_type, + "created_at": row.created_at.isoformat() if row.created_at else None, + "updated_at": row.updated_at.isoformat() if row.updated_at else None + }) + + return { + "success": True, + "projects": projects, + "count": len(projects) + } + + except Exception as e: + logger.error(f"프로젝트 목록 조회 실패: {str(e)}") + raise HTTPException(status_code=500, detail=f"프로젝트 목록 조회 실패: {str(e)}") + + @router.patch("/projects/{project_id}") async def update_project_name( project_id: int, diff --git a/frontend/src/pages/NewMaterialsPage.jsx b/frontend/src/pages/NewMaterialsPage.jsx index 695680c..4203966 100644 --- a/frontend/src/pages/NewMaterialsPage.jsx +++ b/frontend/src/pages/NewMaterialsPage.jsx @@ -777,6 +777,20 @@ const NewMaterialsPage = ({ setSortConfig({ key: null, direction: 'asc' }); }; + // 자재 정보를 미리 계산하여 캐싱 (성능 최적화) + const parsedMaterialsMap = React.useMemo(() => { + const map = new Map(); + materials.forEach(material => { + map.set(material.id, parseMaterialInfo(material)); + }); + return map; + }, [materials]); + + // parseMaterialInfo를 캐시된 버전으로 교체 + const getParsedInfo = (material) => { + return parsedMaterialsMap.get(material.id) || parseMaterialInfo(material); + }; + // 필터링된 자재 목록 const filteredMaterials = materials .filter(material => { @@ -789,7 +803,7 @@ const NewMaterialsPage = ({ for (const [column, filterValue] of Object.entries(columnFilters)) { if (!filterValue) continue; - const info = parseMaterialInfo(material); + const info = getParsedInfo(material); let materialValue = ''; switch (column) { @@ -903,7 +917,7 @@ const NewMaterialsPage = ({ .slice(0, 200); categoryMaterials.forEach(material => { - const info = parseMaterialInfo(material); + const info = getParsedInfo(material); let value = ''; switch (filterKey) { @@ -1327,7 +1341,7 @@ const NewMaterialsPage = ({ )} {filteredMaterials.map((material) => { - const info = parseMaterialInfo(material); + const info = getParsedInfo(material); if (material.classified_category === 'SPECIAL') { // SPECIAL 카테고리 (10개 컬럼)