프로젝트 생성 및 관리 기능 완성
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled

백엔드:
- POST /dashboard/projects 엔드포인트 추가
- 프로젝트 생성 기능 구현
- 중복 코드 검사
- GET /dashboard/projects 컬럼명 수정 (실제 DB 스키마에 맞춤)
- PATCH /dashboard/projects/{id} 컬럼명 수정

프론트엔드:
- 메인 대시보드에 프로젝트 관리 섹션 추가
- ' 새 프로젝트' 버튼으로 생성 폼 표시/숨김
- '✏️ 이름 수정' 버튼으로 프로젝트 이름 수정
- 프로젝트 생성 폼:
  - 프로젝트 코드 (필수)
  - 프로젝트 이름 (필수)
  - 고객사명 (선택)
- 실시간 프로젝트 목록 갱신
- API 연동 완료
This commit is contained in:
Hyungi Ahn
2025-10-14 07:14:55 +09:00
parent 003983872c
commit 6d8bb468c3
2 changed files with 267 additions and 24 deletions

View File

@@ -23,16 +23,50 @@ function App() {
const [projects, setProjects] = useState([]);
const [editingProject, setEditingProject] = useState(null);
const [editedProjectName, setEditedProjectName] = useState('');
const [showCreateProject, setShowCreateProject] = useState(false);
const [newProjectCode, setNewProjectCode] = useState('');
const [newProjectName, setNewProjectName] = useState('');
const [newClientName, setNewClientName] = useState('');
// 프로젝트 목록 로드
const loadProjects = async () => {
try {
const response = await api.get('/projects');
const response = await api.get('/dashboard/projects');
if (response.data && response.data.projects) {
setProjects(response.data.projects);
}
} catch (error) {
console.error('프로젝트 목록 로드 실패:', error);
// API 실패 시 에러를 무시하고 더미 데이터 사용
}
};
// 프로젝트 생성
const createProject = async () => {
if (!newProjectCode || !newProjectName) {
alert('프로젝트 코드와 이름을 입력해주세요.');
return;
}
try {
const response = await api.post(`/dashboard/projects?official_project_code=${encodeURIComponent(newProjectCode)}&project_name=${encodeURIComponent(newProjectName)}&client_name=${encodeURIComponent(newClientName)}`);
if (response.data.success) {
// 프로젝트 목록 갱신
await loadProjects();
// 폼 초기화
setShowCreateProject(false);
setNewProjectCode('');
setNewProjectName('');
setNewClientName('');
alert('프로젝트가 생성되었습니다.');
}
} catch (error) {
console.error('프로젝트 생성 실패:', error);
const errorMsg = error.response?.data?.detail || '프로젝트 생성에 실패했습니다.';
alert(errorMsg);
}
};
@@ -355,21 +389,18 @@ function App() {
{/* 메인 콘텐츠 */}
<div style={{ padding: '32px', maxWidth: '800px', margin: '0 auto' }}>
{/* 프로젝트 선택 */}
{/* 프로젝트 관리 */}
<div style={{ marginBottom: '32px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '12px' }}>
<h2 style={{ fontSize: '18px', fontWeight: '600', color: '#2d3748', margin: 0 }}>
📁 프로젝트 선택
📁 프로젝트 관리
</h2>
{selectedProject && (
<div style={{ display: 'flex', gap: '8px' }}>
<button
onClick={() => {
setEditingProject(selectedProject);
setEditedProjectName(selectedProject.project_name || '');
}}
onClick={() => setShowCreateProject(!showCreateProject)}
style={{
padding: '6px 12px',
background: '#10b981',
background: showCreateProject ? '#ef4444' : '#3b82f6',
color: 'white',
border: 'none',
borderRadius: '6px',
@@ -380,13 +411,149 @@ function App() {
alignItems: 'center',
gap: '4px'
}}
title="프로젝트 이름 수정"
>
이름 수정
{showCreateProject ? '✕ 닫기' : ' 새 프로젝트'}
</button>
)}
{selectedProject && (
<button
onClick={() => {
setEditingProject(selectedProject);
setEditedProjectName(selectedProject.project_name || '');
}}
style={{
padding: '6px 12px',
background: '#10b981',
color: 'white',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
fontSize: '13px',
fontWeight: '600',
display: 'flex',
alignItems: 'center',
gap: '4px'
}}
title="프로젝트 이름 수정"
>
이름 수정
</button>
)}
</div>
</div>
{/* 프로젝트 생성 폼 */}
{showCreateProject && (
<div style={{
background: '#f0fdf4',
border: '2px solid #10b981',
borderRadius: '8px',
padding: '16px',
marginBottom: '16px'
}}>
<div style={{ marginBottom: '12px', fontWeight: '600', color: '#065f46', fontSize: '14px' }}>
프로젝트 생성
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
<div>
<label style={{ display: 'block', marginBottom: '4px', fontSize: '13px', fontWeight: '500', color: '#374151' }}>
프로젝트 코드 <span style={{ color: '#ef4444' }}>*</span>
</label>
<input
type="text"
value={newProjectCode}
onChange={(e) => setNewProjectCode(e.target.value)}
placeholder="예: J24-004"
style={{
width: '100%',
padding: '10px 12px',
border: '1px solid #d1d5db',
borderRadius: '6px',
fontSize: '14px'
}}
/>
</div>
<div>
<label style={{ display: 'block', marginBottom: '4px', fontSize: '13px', fontWeight: '500', color: '#374151' }}>
프로젝트 이름 <span style={{ color: '#ef4444' }}>*</span>
</label>
<input
type="text"
value={newProjectName}
onChange={(e) => setNewProjectName(e.target.value)}
placeholder="예: 새로운 프로젝트"
style={{
width: '100%',
padding: '10px 12px',
border: '1px solid #d1d5db',
borderRadius: '6px',
fontSize: '14px'
}}
/>
</div>
<div>
<label style={{ display: 'block', marginBottom: '4px', fontSize: '13px', fontWeight: '500', color: '#374151' }}>
고객사명 (선택)
</label>
<input
type="text"
value={newClientName}
onChange={(e) => setNewClientName(e.target.value)}
placeholder="예: ABC 주식회사"
style={{
width: '100%',
padding: '10px 12px',
border: '1px solid #d1d5db',
borderRadius: '6px',
fontSize: '14px'
}}
/>
</div>
<div style={{ display: 'flex', gap: '8px', marginTop: '8px' }}>
<button
onClick={createProject}
style={{
flex: 1,
padding: '12px',
background: '#10b981',
color: 'white',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
fontSize: '14px',
fontWeight: '600'
}}
>
프로젝트 생성
</button>
<button
onClick={() => {
setShowCreateProject(false);
setNewProjectCode('');
setNewProjectName('');
setNewClientName('');
}}
style={{
padding: '12px 24px',
background: '#6b7280',
color: 'white',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
fontSize: '14px',
fontWeight: '600'
}}
>
취소
</button>
</div>
</div>
</div>
)}
<label style={{ display: 'block', marginBottom: '8px', fontSize: '14px', fontWeight: '500', color: '#374151' }}>
프로젝트 선택
</label>
<select
value={selectedProject?.official_project_code || ''}
onChange={(e) => {