diff --git a/app/Http/Requests/ProjectManagement/StoreIssueRequest.php b/app/Http/Requests/ProjectManagement/StoreIssueRequest.php index 68b33de2..55fe325a 100644 --- a/app/Http/Requests/ProjectManagement/StoreIssueRequest.php +++ b/app/Http/Requests/ProjectManagement/StoreIssueRequest.php @@ -27,6 +27,9 @@ public function rules(): array 'description' => 'nullable|string|max:5000', 'type' => 'nullable|in:'.implode(',', array_keys(AdminPmIssue::getTypes())), 'status' => 'nullable|in:'.implode(',', array_keys(AdminPmIssue::getStatuses())), + 'start_date' => 'nullable|date', + 'due_date' => 'nullable|date|after_or_equal:start_date', + 'estimated_hours' => 'nullable|integer|min:0|max:9999', ]; } @@ -42,6 +45,9 @@ public function attributes(): array 'description' => '이슈 설명', 'type' => '타입', 'status' => '상태', + 'start_date' => '시작일', + 'due_date' => '마감일', + 'estimated_hours' => '예상 시간', ]; } diff --git a/app/Http/Requests/ProjectManagement/UpdateIssueRequest.php b/app/Http/Requests/ProjectManagement/UpdateIssueRequest.php index 80fbe0f4..b5f77801 100644 --- a/app/Http/Requests/ProjectManagement/UpdateIssueRequest.php +++ b/app/Http/Requests/ProjectManagement/UpdateIssueRequest.php @@ -27,6 +27,9 @@ public function rules(): array 'description' => 'nullable|string|max:5000', 'type' => 'sometimes|in:'.implode(',', array_keys(AdminPmIssue::getTypes())), 'status' => 'sometimes|in:'.implode(',', array_keys(AdminPmIssue::getStatuses())), + 'start_date' => 'nullable|date', + 'due_date' => 'nullable|date|after_or_equal:start_date', + 'estimated_hours' => 'nullable|integer|min:0|max:9999', ]; } @@ -42,6 +45,9 @@ public function attributes(): array 'description' => '이슈 설명', 'type' => '타입', 'status' => '상태', + 'start_date' => '시작일', + 'due_date' => '마감일', + 'estimated_hours' => '예상 시간', ]; } diff --git a/app/Models/Admin/AdminPmIssue.php b/app/Models/Admin/AdminPmIssue.php index 569d7f60..366ef800 100644 --- a/app/Models/Admin/AdminPmIssue.php +++ b/app/Models/Admin/AdminPmIssue.php @@ -37,6 +37,9 @@ class AdminPmIssue extends Model 'description', 'type', 'status', + 'start_date', + 'due_date', + 'estimated_hours', 'is_urgent', 'created_by', 'updated_by', @@ -46,6 +49,9 @@ class AdminPmIssue extends Model protected $casts = [ 'project_id' => 'integer', 'task_id' => 'integer', + 'start_date' => 'date', + 'due_date' => 'date', + 'estimated_hours' => 'integer', 'is_urgent' => 'boolean', 'created_by' => 'integer', 'updated_by' => 'integer', diff --git a/resources/views/project-management/projects/show.blade.php b/resources/views/project-management/projects/show.blade.php index f0ba941b..2b4346fb 100644 --- a/resources/views/project-management/projects/show.blade.php +++ b/resources/views/project-management/projects/show.blade.php @@ -283,6 +283,25 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:rin +
+
+ + +
+
+ + +
+
+ + +
+
+
@@ -486,7 +505,7 @@ function renderTasks(container, tasks) { const issueResolved = task.resolved_issues_count || 0; const issueProgress = issueTotal > 0 ? Math.round((issueResolved / issueTotal) * 100) : 0; const isOverdue = task.due_date && new Date(task.due_date) < new Date() && task.status !== 'done'; - const issues = task.issues || []; + const issues = sortIssues(task.issues || []); const hasIssues = issues.length > 0; // 작업 Row @@ -553,7 +572,9 @@ function renderTasks(container, tasks) { ${issue.title}
- - + + ${issue.due_date ? formatDate(issue.due_date) : '-'} + - ${issueStatusLabels[issue.status]} @@ -598,6 +619,24 @@ function renderTasks(container, tasks) { }); } + // 이슈 정렬 함수 (마감일 → 상태) + function sortIssues(issues) { + const statusPriority = { open: 0, in_progress: 1, resolved: 2, closed: 3 }; + return [...issues].sort((a, b) => { + // 1. 마감일 기준 정렬 (null은 맨 뒤) + if (a.due_date && b.due_date) { + const diff = new Date(a.due_date) - new Date(b.due_date); + if (diff !== 0) return diff; + } else if (a.due_date && !b.due_date) { + return -1; + } else if (!a.due_date && b.due_date) { + return 1; + } + // 2. 마감일이 같거나 없으면 상태로 정렬 + return (statusPriority[a.status] || 99) - (statusPriority[b.status] || 99); + }); + } + // 이슈 목록 렌더링 (테이블 형식) function renderIssues(container, issues) { if (!issues || issues.length === 0) { @@ -605,6 +644,9 @@ function renderIssues(container, issues) { return; } + // 정렬 적용 + issues = sortIssues(issues); + const typeLabels = { bug: '버그', feature: '기능', improvement: '개선' }; const statusColors = { open: 'bg-red-100 text-red-600', @@ -624,6 +666,8 @@ function renderIssues(container, issues) { 타입 이슈명 연결 작업 + 시작일 + 마감일 상태 변경 @@ -651,6 +695,12 @@ function renderIssues(container, issues) { ${issue.task ? issue.task.title : '-'} + + ${issue.start_date ? formatDate(issue.start_date) : '-'} + + + ${issue.due_date ? formatDate(issue.due_date) : '-'} + ${statusLabels[issue.status]} @@ -843,6 +893,9 @@ function closeIssueModal() { document.getElementById('issueType').value = issue.type; document.getElementById('issueStatus').value = issue.status; document.getElementById('issueTaskId').value = issue.task_id || ''; + document.getElementById('issueStartDate').value = issue.start_date ? formatDate(issue.start_date) : ''; + document.getElementById('issueDueDate').value = issue.due_date ? formatDate(issue.due_date) : ''; + document.getElementById('issueEstimatedHours').value = issue.estimated_hours || ''; document.getElementById('issueModal').classList.remove('hidden'); } }