From ca0336d627cb5feeb8bcee110cdf352609677e3a Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Tue, 14 Oct 2025 06:47:52 +0900 Subject: [PATCH] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EC=A0=9D=ED=8A=B8=20=EC=9D=B4=EB=A6=84=20=EC=9D=B8=EB=9D=BC?= =?UTF-8?q?=EC=9D=B8=20=ED=8E=B8=EC=A7=91=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 백엔드: - dashboard.py에 PATCH /dashboard/projects/{id} 엔드포인트 추가 - 프로젝트 이름 업데이트 기능 구현 - 활동 로그 기록 프론트엔드: - ProjectsPage에 인라인 편집 기능 추가 - 더블클릭으로 편집 모드 진입 - Enter 키로 저장, Escape로 취소 - 저장/취소 버튼 (✓/✕) 제공 - 간단하고 직관적인 UX --- backend/app/routers/dashboard.py | 70 ++++++++++++++++++++++ frontend/src/pages/ProjectsPage.jsx | 91 ++++++++++++++++++++++++++++- 2 files changed, 159 insertions(+), 2 deletions(-) diff --git a/backend/app/routers/dashboard.py b/backend/app/routers/dashboard.py index 4a4abb8..1af2731 100644 --- a/backend/app/routers/dashboard.py +++ b/backend/app/routers/dashboard.py @@ -425,3 +425,73 @@ async def get_quick_actions( except Exception as e: logger.error(f"Quick actions error: {str(e)}") raise HTTPException(status_code=500, detail=f"빠른 작업 조회 실패: {str(e)}") + + +@router.patch("/projects/{project_id}") +async def update_project_name( + project_id: int, + job_name: str = Query(..., description="새 프로젝트 이름"), + current_user: dict = Depends(get_current_user), + db: Session = Depends(get_db) +): + """ + 프로젝트 이름 수정 + + Args: + project_id: 프로젝트 ID + job_name: 새 프로젝트 이름 + + Returns: + dict: 수정 결과 + """ + try: + # 프로젝트 존재 확인 + query = text("SELECT * FROM projects WHERE id = :project_id") + result = db.execute(query, {"project_id": project_id}).fetchone() + + if not result: + raise HTTPException(status_code=404, detail="프로젝트를 찾을 수 없습니다") + + # 프로젝트 이름 업데이트 + update_query = text(""" + UPDATE projects + SET job_name = :job_name, + updated_at = CURRENT_TIMESTAMP + WHERE id = :project_id + RETURNING * + """) + + updated = db.execute(update_query, { + "job_name": job_name, + "project_id": project_id + }).fetchone() + + db.commit() + + # 활동 로그 기록 + ActivityLogger.log_activity( + db=db, + user_id=current_user.get('user_id'), + action="UPDATE_PROJECT", + target_type="PROJECT", + target_id=project_id, + details=f"프로젝트 이름 변경: {job_name}" + ) + + return { + "success": True, + "message": "프로젝트 이름이 수정되었습니다", + "project": { + "id": updated.id, + "job_no": updated.job_no, + "job_name": updated.job_name, + "official_project_code": updated.official_project_code + } + } + + except HTTPException: + raise + except Exception as e: + db.rollback() + logger.error(f"프로젝트 수정 실패: {str(e)}") + raise HTTPException(status_code=500, detail=f"프로젝트 수정 실패: {str(e)}") diff --git a/frontend/src/pages/ProjectsPage.jsx b/frontend/src/pages/ProjectsPage.jsx index fdbc366..eadb59b 100644 --- a/frontend/src/pages/ProjectsPage.jsx +++ b/frontend/src/pages/ProjectsPage.jsx @@ -4,6 +4,39 @@ const ProjectsPage = ({ user }) => { const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(true); const [showCreateForm, setShowCreateForm] = useState(false); + const [editingProject, setEditingProject] = useState(null); + const [editedName, setEditedName] = useState(''); + + // 프로젝트 이름 편집 시작 + const startEditing = (project) => { + setEditingProject(project.id); + setEditedName(project.name); + }; + + // 프로젝트 이름 저장 + const saveProjectName = async (projectId) => { + try { + // TODO: API 호출하여 프로젝트 이름 업데이트 + // await api.patch(`/dashboard/projects/${projectId}?job_name=${encodeURIComponent(editedName)}`); + + // 임시: 로컬 상태만 업데이트 + setProjects(projects.map(p => + p.id === projectId ? { ...p, name: editedName } : p + )); + + setEditingProject(null); + setEditedName(''); + } catch (error) { + console.error('프로젝트 이름 수정 실패:', error); + alert('프로젝트 이름 수정에 실패했습니다.'); + } + }; + + // 편집 취소 + const cancelEditing = () => { + setEditingProject(null); + setEditedName(''); + }; useEffect(() => { // 실제로는 API에서 프로젝트 데이터를 가져올 예정 @@ -224,8 +257,62 @@ const ProjectsPage = ({ user }) => { }} onMouseEnter={(e) => e.currentTarget.style.background = '#f7fafc'} onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}> - - {project.name} + startEditing(project)} + title="더블클릭하여 이름 수정" + > + {editingProject === project.id ? ( +
+ setEditedName(e.target.value)} + onKeyPress={(e) => { + if (e.key === 'Enter') saveProjectName(project.id); + if (e.key === 'Escape') cancelEditing(); + }} + autoFocus + style={{ + flex: 1, + padding: '6px 10px', + border: '2px solid #3b82f6', + borderRadius: '4px', + fontSize: '14px' + }} + /> + + +
+ ) : ( + {project.name} + )}