feat: 기존 작업에 이슈 추가 Import 기능
- ImportService에 importIssuesToTask 메서드 추가
- ImportController에 importIssues 액션 추가
- ImportIssuesRequest FormRequest 생성
- POST /api/admin/pm/import/task/{taskId}/issues 라우트 추가
- import.blade.php UI에 '기존 작업에 이슈 추가' 모드 추가
- ImportProjectRequest에 tasks 레벨 검증 규칙 보완
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Http\Controllers\Api\Admin\ProjectManagement;
|
namespace App\Http\Controllers\Api\Admin\ProjectManagement;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\ProjectManagement\ImportIssuesRequest;
|
||||||
use App\Http\Requests\ProjectManagement\ImportProjectRequest;
|
use App\Http\Requests\ProjectManagement\ImportProjectRequest;
|
||||||
use App\Services\ProjectManagement\ImportService;
|
use App\Services\ProjectManagement\ImportService;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
@@ -75,6 +76,27 @@ public function importTasks(Request $request, int $projectId): JsonResponse
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 기존 작업에 이슈만 추가
|
||||||
|
*/
|
||||||
|
public function importIssues(ImportIssuesRequest $request, int $taskId): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$result = $this->importService->importIssuesToTask($taskId, $request->validated()['issues']);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => "이슈가 추가되었습니다. ({$result['issues_count']}개)",
|
||||||
|
'data' => $result,
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => '가져오기 실패: '.$e->getMessage(),
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSON 구조 사전 검증
|
* JSON 구조 사전 검증
|
||||||
*/
|
*/
|
||||||
|
|||||||
44
app/Http/Requests/ProjectManagement/ImportIssuesRequest.php
Normal file
44
app/Http/Requests/ProjectManagement/ImportIssuesRequest.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\ProjectManagement;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class ImportIssuesRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'issues' => 'required|array|min:1',
|
||||||
|
'issues.*.title' => 'required|string|max:255',
|
||||||
|
'issues.*.description' => 'nullable|string',
|
||||||
|
'issues.*.type' => 'nullable|in:bug,feature,improvement',
|
||||||
|
'issues.*.status' => 'nullable|in:open,in_progress,resolved,closed',
|
||||||
|
// 일정 관련
|
||||||
|
'issues.*.start_date' => 'nullable|date',
|
||||||
|
'issues.*.due_date' => 'nullable|date|after_or_equal:issues.*.start_date',
|
||||||
|
'issues.*.estimated_hours' => 'nullable|integer|min:0',
|
||||||
|
'issues.*.is_urgent' => 'nullable|boolean',
|
||||||
|
// 팀/담당자/고객사 (하이브리드)
|
||||||
|
'issues.*.department_id' => 'nullable|integer|exists:departments,id',
|
||||||
|
'issues.*.team' => 'nullable|string|max:100',
|
||||||
|
'issues.*.assignee_id' => 'nullable|integer|exists:users,id',
|
||||||
|
'issues.*.assignee_name' => 'nullable|string|max:100',
|
||||||
|
'issues.*.client' => 'nullable|string|max:100',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'issues.required' => 'issues 배열은 필수입니다.',
|
||||||
|
'issues.min' => '최소 1개 이상의 이슈가 필요합니다.',
|
||||||
|
'issues.*.title.required' => '각 이슈의 title은 필수입니다.',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,9 @@ public function rules(): array
|
|||||||
'tasks.*.status' => 'nullable|in:todo,in_progress,done',
|
'tasks.*.status' => 'nullable|in:todo,in_progress,done',
|
||||||
'tasks.*.priority' => 'nullable|in:low,medium,high',
|
'tasks.*.priority' => 'nullable|in:low,medium,high',
|
||||||
'tasks.*.due_date' => 'nullable|date',
|
'tasks.*.due_date' => 'nullable|date',
|
||||||
|
'tasks.*.is_urgent' => 'nullable|boolean',
|
||||||
|
'tasks.*.assignee_id' => 'nullable|integer|exists:users,id',
|
||||||
|
'tasks.*.assignee_name' => 'nullable|string|max:100',
|
||||||
|
|
||||||
// 작업별 이슈 목록
|
// 작업별 이슈 목록
|
||||||
'tasks.*.issues' => 'nullable|array',
|
'tasks.*.issues' => 'nullable|array',
|
||||||
|
|||||||
@@ -139,6 +139,48 @@ public function importTasksToProject(int $projectId, array $tasks): array
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 기존 작업에 이슈만 추가
|
||||||
|
*/
|
||||||
|
public function importIssuesToTask(int $taskId, array $issues): array
|
||||||
|
{
|
||||||
|
return DB::transaction(function () use ($taskId, $issues) {
|
||||||
|
$task = AdminPmTask::findOrFail($taskId);
|
||||||
|
|
||||||
|
$result = [
|
||||||
|
'task_id' => $taskId,
|
||||||
|
'task_title' => $task->title,
|
||||||
|
'project_id' => $task->project_id,
|
||||||
|
'issues_count' => 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($issues as $issueData) {
|
||||||
|
AdminPmIssue::create([
|
||||||
|
'project_id' => $task->project_id,
|
||||||
|
'task_id' => $task->id,
|
||||||
|
'title' => $issueData['title'],
|
||||||
|
'description' => $issueData['description'] ?? null,
|
||||||
|
'type' => $issueData['type'] ?? AdminPmIssue::TYPE_FEATURE,
|
||||||
|
'status' => $issueData['status'] ?? AdminPmIssue::STATUS_OPEN,
|
||||||
|
'start_date' => $issueData['start_date'] ?? null,
|
||||||
|
'due_date' => $issueData['due_date'] ?? null,
|
||||||
|
'estimated_hours' => $issueData['estimated_hours'] ?? null,
|
||||||
|
'is_urgent' => $issueData['is_urgent'] ?? false,
|
||||||
|
// 팀/담당자/고객사 (하이브리드)
|
||||||
|
'department_id' => $issueData['department_id'] ?? null,
|
||||||
|
'team' => $issueData['team'] ?? null,
|
||||||
|
'assignee_id' => $issueData['assignee_id'] ?? null,
|
||||||
|
'assignee_name' => $issueData['assignee_name'] ?? null,
|
||||||
|
'client' => $issueData['client'] ?? null,
|
||||||
|
'created_by' => auth()->id(),
|
||||||
|
]);
|
||||||
|
$result['issues_count']++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSON 샘플 템플릿 반환
|
* JSON 샘플 템플릿 반환
|
||||||
*/
|
*/
|
||||||
|
|||||||
15
ms1-issues.json
Normal file
15
ms1-issues.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"issues": [
|
||||||
|
{"title": "견적관리", "description": "공수: 1.75일", "type": "feature", "start_date": "2025-11-27", "due_date": "2025-11-28", "team": "디자인팀"},
|
||||||
|
{"title": "기준정보-견적수식", "description": "공수: 0.3일", "type": "feature", "start_date": "2025-11-28", "due_date": "2025-12-01", "team": "디자인팀"},
|
||||||
|
{"title": "수주관리", "description": "공수: 3.25일", "type": "feature", "start_date": "2025-12-01", "due_date": "2025-12-04", "team": "디자인팀"},
|
||||||
|
{"title": "생산관리", "description": "공수: 1.95일", "type": "feature", "start_date": "2025-12-04", "due_date": "2025-12-08", "team": "디자인팀"},
|
||||||
|
{"title": "기준정보-공정", "description": "공수: 1.45일", "type": "feature", "start_date": "2025-12-08", "due_date": "2025-12-10", "team": "디자인팀"},
|
||||||
|
{"title": "출하관리", "description": "공수: 2.25일", "type": "feature", "start_date": "2025-12-10", "due_date": "2025-12-12", "team": "디자인팀"},
|
||||||
|
{"title": "거래처관리", "description": "공수: 1.1일", "type": "feature", "start_date": "2025-12-15", "due_date": "2025-12-16", "team": "디자인팀"},
|
||||||
|
{"title": "품질관리", "description": "공수: 3.5일", "type": "feature", "start_date": "2025-12-16", "due_date": "2025-12-19", "team": "디자인팀"},
|
||||||
|
{"title": "자재관리", "description": "공수: 3.3일", "type": "feature", "start_date": "2025-12-19", "due_date": "2025-12-24", "team": "디자인팀"},
|
||||||
|
{"title": "단가관리", "description": "공수: 1일", "type": "feature", "start_date": "2025-12-24", "due_date": "2025-12-24", "team": "디자인팀"},
|
||||||
|
{"title": "회계관리", "description": "공수: 1일", "type": "feature", "start_date": "2025-12-26", "due_date": "2025-12-26", "team": "디자인팀"}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
{{-- Import 모드 선택 --}}
|
{{-- Import 모드 선택 --}}
|
||||||
<div class="p-6 bg-white border border-gray-200 rounded-lg shadow-sm">
|
<div class="p-6 bg-white border border-gray-200 rounded-lg shadow-sm">
|
||||||
<h2 class="mb-4 text-lg font-medium text-gray-900">Import 모드</h2>
|
<h2 class="mb-4 text-lg font-medium text-gray-900">Import 모드</h2>
|
||||||
<div class="flex space-x-4">
|
<div class="flex flex-wrap gap-4">
|
||||||
<label class="flex items-center">
|
<label class="flex items-center">
|
||||||
<input type="radio" name="import_mode" value="new" checked
|
<input type="radio" name="import_mode" value="new" checked
|
||||||
class="w-4 h-4 text-indigo-600 border-gray-300 focus:ring-indigo-500"
|
class="w-4 h-4 text-indigo-600 border-gray-300 focus:ring-indigo-500"
|
||||||
@@ -35,20 +35,35 @@ class="w-4 h-4 text-indigo-600 border-gray-300 focus:ring-indigo-500"
|
|||||||
<input type="radio" name="import_mode" value="existing"
|
<input type="radio" name="import_mode" value="existing"
|
||||||
class="w-4 h-4 text-indigo-600 border-gray-300 focus:ring-indigo-500"
|
class="w-4 h-4 text-indigo-600 border-gray-300 focus:ring-indigo-500"
|
||||||
onchange="toggleImportMode()">
|
onchange="toggleImportMode()">
|
||||||
<span class="ml-2 text-sm text-gray-700">기존 프로젝트에 추가</span>
|
<span class="ml-2 text-sm text-gray-700">기존 프로젝트에 작업 추가</span>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center">
|
||||||
|
<input type="radio" name="import_mode" value="task_issues"
|
||||||
|
class="w-4 h-4 text-indigo-600 border-gray-300 focus:ring-indigo-500"
|
||||||
|
onchange="toggleImportMode()">
|
||||||
|
<span class="ml-2 text-sm text-gray-700">기존 작업에 이슈 추가</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- 기존 프로젝트 선택 (existing 모드) --}}
|
{{-- 기존 프로젝트 선택 (existing, task_issues 모드) --}}
|
||||||
<div id="existingProjectSelect" class="hidden mt-4">
|
<div id="existingProjectSelect" class="hidden mt-4">
|
||||||
<label class="block text-sm font-medium text-gray-700">대상 프로젝트</label>
|
<label class="block text-sm font-medium text-gray-700">대상 프로젝트</label>
|
||||||
<select id="targetProject" class="block w-full px-3 py-2 mt-1 border border-gray-300 rounded-lg focus:ring-indigo-500 focus:border-indigo-500">
|
<select id="targetProject" class="block w-full px-3 py-2 mt-1 border border-gray-300 rounded-lg focus:ring-indigo-500 focus:border-indigo-500"
|
||||||
|
onchange="loadTasks()">
|
||||||
<option value="">프로젝트 선택...</option>
|
<option value="">프로젝트 선택...</option>
|
||||||
@foreach($projects as $project)
|
@foreach($projects as $project)
|
||||||
<option value="{{ $project->id }}">{{ $project->name }}</option>
|
<option value="{{ $project->id }}">{{ $project->name }}</option>
|
||||||
@endforeach
|
@endforeach
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- 작업 선택 (task_issues 모드) --}}
|
||||||
|
<div id="existingTaskSelect" class="hidden mt-4">
|
||||||
|
<label class="block text-sm font-medium text-gray-700">대상 작업</label>
|
||||||
|
<select id="targetTask" class="block w-full px-3 py-2 mt-1 border border-gray-300 rounded-lg focus:ring-indigo-500 focus:border-indigo-500">
|
||||||
|
<option value="">작업 선택...</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- JSON 입력 --}}
|
{{-- JSON 입력 --}}
|
||||||
@@ -116,27 +131,28 @@ class="flex-1 px-4 py-2.5 text-sm font-medium text-white bg-indigo-600 rounded-l
|
|||||||
<h4 class="text-sm font-medium text-gray-900">새 프로젝트 생성</h4>
|
<h4 class="text-sm font-medium text-gray-900">새 프로젝트 생성</h4>
|
||||||
<pre class="p-3 mt-2 overflow-x-auto text-xs bg-gray-50 rounded-lg"><code>{
|
<pre class="p-3 mt-2 overflow-x-auto text-xs bg-gray-50 rounded-lg"><code>{
|
||||||
"project": {
|
"project": {
|
||||||
"name": "프로젝트명 (필수)",
|
"name": "SAM 시스템 개발",
|
||||||
"description": "설명",
|
"description": "프로젝트 설명입니다",
|
||||||
"status": "active|completed|on_hold",
|
"status": "active",
|
||||||
"start_date": "2025-01-01",
|
"start_date": "2025-01-01",
|
||||||
"end_date": "2025-03-31"
|
"end_date": "2025-03-31"
|
||||||
},
|
},
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
{
|
||||||
"title": "작업 제목 (필수)",
|
"title": "API 개발",
|
||||||
"description": "작업 설명",
|
"description": "REST API 엔드포인트 구현",
|
||||||
"status": "todo|in_progress|done",
|
"status": "in_progress",
|
||||||
"priority": "low|medium|high",
|
"priority": "high",
|
||||||
"is_urgent": false,
|
"is_urgent": false,
|
||||||
"due_date": "2025-01-15",
|
"due_date": "2025-01-15",
|
||||||
"assignee_id": null,
|
"assignee_id": null,
|
||||||
|
"assignee_name": "김개발",
|
||||||
"issues": [
|
"issues": [
|
||||||
{
|
{
|
||||||
"title": "이슈 제목 (필수)",
|
"title": "사용자 인증 구현",
|
||||||
"description": "이슈 설명",
|
"description": "JWT 기반 인증 시스템",
|
||||||
"type": "bug|feature|improvement",
|
"type": "feature",
|
||||||
"status": "open|in_progress|resolved|closed",
|
"status": "open",
|
||||||
"start_date": "2025-01-01",
|
"start_date": "2025-01-01",
|
||||||
"due_date": "2025-01-15",
|
"due_date": "2025-01-15",
|
||||||
"estimated_hours": 8,
|
"estimated_hours": 8,
|
||||||
@@ -145,7 +161,7 @@ class="flex-1 px-4 py-2.5 text-sm font-medium text-white bg-indigo-600 rounded-l
|
|||||||
"team": "개발팀",
|
"team": "개발팀",
|
||||||
"assignee_id": null,
|
"assignee_id": null,
|
||||||
"assignee_name": "홍길동",
|
"assignee_name": "홍길동",
|
||||||
"client": "고객사명"
|
"client": "내부"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -163,6 +179,30 @@ class="flex-1 px-4 py-2.5 text-sm font-medium text-white bg-indigo-600 rounded-l
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}</code></pre>
|
}</code></pre>
|
||||||
|
|
||||||
|
<h4 class="mt-6 text-sm font-medium text-gray-900">기존 작업에 이슈 추가</h4>
|
||||||
|
<pre class="p-3 mt-2 overflow-x-auto text-xs bg-gray-50 rounded-lg"><code>{
|
||||||
|
"issues": [
|
||||||
|
{
|
||||||
|
"title": "Phase 1: 견적관리",
|
||||||
|
"description": "공수: 1.75일",
|
||||||
|
"type": "feature",
|
||||||
|
"status": "open",
|
||||||
|
"start_date": "2024-11-27",
|
||||||
|
"due_date": "2024-11-28",
|
||||||
|
"estimated_hours": 14,
|
||||||
|
"team": "개발팀"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Phase 2: 기준정보-견적수식",
|
||||||
|
"description": "공수: 0.3일",
|
||||||
|
"type": "feature",
|
||||||
|
"status": "open",
|
||||||
|
"start_date": "2024-11-28",
|
||||||
|
"due_date": "2024-12-01"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}</code></pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -249,12 +289,58 @@ class="flex-1 px-4 py-2.5 text-sm font-medium text-white bg-indigo-600 rounded-l
|
|||||||
function toggleImportMode() {
|
function toggleImportMode() {
|
||||||
const mode = document.querySelector('input[name="import_mode"]:checked').value;
|
const mode = document.querySelector('input[name="import_mode"]:checked').value;
|
||||||
const existingSelect = document.getElementById('existingProjectSelect');
|
const existingSelect = document.getElementById('existingProjectSelect');
|
||||||
|
const taskSelect = document.getElementById('existingTaskSelect');
|
||||||
|
|
||||||
if (mode === 'existing') {
|
if (mode === 'existing' || mode === 'task_issues') {
|
||||||
existingSelect.classList.remove('hidden');
|
existingSelect.classList.remove('hidden');
|
||||||
} else {
|
} else {
|
||||||
existingSelect.classList.add('hidden');
|
existingSelect.classList.add('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mode === 'task_issues') {
|
||||||
|
taskSelect.classList.remove('hidden');
|
||||||
|
loadTasks(); // 작업 목록 로드
|
||||||
|
} else {
|
||||||
|
taskSelect.classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 프로젝트별 작업 목록 로드
|
||||||
|
async function loadTasks() {
|
||||||
|
const mode = document.querySelector('input[name="import_mode"]:checked').value;
|
||||||
|
if (mode !== 'task_issues') return;
|
||||||
|
|
||||||
|
const projectId = document.getElementById('targetProject').value;
|
||||||
|
const taskSelect = document.getElementById('targetTask');
|
||||||
|
|
||||||
|
if (!projectId) {
|
||||||
|
taskSelect.innerHTML = '<option value="">작업 선택...</option>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/admin/pm/tasks/project/${projectId}`, {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error('작업 목록 로드 실패');
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
const tasks = result.data || [];
|
||||||
|
|
||||||
|
taskSelect.innerHTML = '<option value="">작업 선택...</option>';
|
||||||
|
tasks.forEach(task => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = task.id;
|
||||||
|
option.textContent = task.title;
|
||||||
|
taskSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
showError('작업 목록 로드 실패: ' + e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 샘플 템플릿 불러오기
|
// 샘플 템플릿 불러오기
|
||||||
@@ -265,6 +351,22 @@ function loadTemplate() {
|
|||||||
if (mode === 'existing') {
|
if (mode === 'existing') {
|
||||||
// 기존 프로젝트 모드에서는 tasks만 표시
|
// 기존 프로젝트 모드에서는 tasks만 표시
|
||||||
template = { tasks: sampleTemplate.tasks };
|
template = { tasks: sampleTemplate.tasks };
|
||||||
|
} else if (mode === 'task_issues') {
|
||||||
|
// 기존 작업에 이슈 추가 모드에서는 issues만 표시
|
||||||
|
template = {
|
||||||
|
issues: [
|
||||||
|
{
|
||||||
|
title: "이슈 제목",
|
||||||
|
description: "이슈 설명",
|
||||||
|
type: "feature",
|
||||||
|
status: "open",
|
||||||
|
start_date: new Date().toISOString().split('T')[0],
|
||||||
|
due_date: new Date(Date.now() + 7*24*60*60*1000).toISOString().split('T')[0],
|
||||||
|
estimated_hours: 8,
|
||||||
|
team: "개발팀"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('jsonInput').value = JSON.stringify(template, null, 2);
|
document.getElementById('jsonInput').value = JSON.stringify(template, null, 2);
|
||||||
@@ -387,6 +489,13 @@ function loadFile(event) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
url = `/api/admin/pm/import/project/${projectId}/tasks`;
|
url = `/api/admin/pm/import/project/${projectId}/tasks`;
|
||||||
|
} else if (mode === 'task_issues') {
|
||||||
|
const taskId = document.getElementById('targetTask').value;
|
||||||
|
if (!taskId) {
|
||||||
|
showError('대상 작업을 선택해주세요.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
url = `/api/admin/pm/import/task/${taskId}/issues`;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -415,17 +524,30 @@ function loadFile(event) {
|
|||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
showResult('success', result.message, [
|
let resultItems = [];
|
||||||
`프로젝트: ${result.data.project_name || result.data.project_id}`,
|
if (mode === 'task_issues') {
|
||||||
`작업: ${result.data.tasks_count}개`,
|
resultItems = [
|
||||||
`이슈: ${result.data.issues_count}개`,
|
`작업: ${result.data.task_title}`,
|
||||||
]);
|
`이슈: ${result.data.issues_count}개 추가`,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
resultItems = [
|
||||||
|
`프로젝트: ${result.data.project_name || result.data.project_id}`,
|
||||||
|
`작업: ${result.data.tasks_count}개`,
|
||||||
|
`이슈: ${result.data.issues_count}개`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
showResult('success', result.message, resultItems);
|
||||||
showToast('Import 완료!', 'success');
|
showToast('Import 완료!', 'success');
|
||||||
|
|
||||||
// 새 프로젝트면 상세 페이지로 이동 버튼 표시
|
// 새 프로젝트면 상세 페이지로 이동 버튼 표시
|
||||||
if (mode === 'new' && result.data.project_id) {
|
if (mode === 'new' && result.data.project_id) {
|
||||||
appendViewButton(result.data.project_id);
|
appendViewButton(result.data.project_id);
|
||||||
}
|
}
|
||||||
|
// 작업 이슈 추가 모드면 프로젝트 상세 페이지로 이동 버튼 표시
|
||||||
|
if (mode === 'task_issues' && result.data.project_id) {
|
||||||
|
appendViewButton(result.data.project_id);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
showResult('error', 'Import 실패', [result.message]);
|
showResult('error', 'Import 실패', [result.message]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -351,6 +351,7 @@
|
|||||||
Route::post('/validate', [PmImportController::class, 'validate'])->name('validate');
|
Route::post('/validate', [PmImportController::class, 'validate'])->name('validate');
|
||||||
Route::post('/', [PmImportController::class, 'import'])->name('import');
|
Route::post('/', [PmImportController::class, 'import'])->name('import');
|
||||||
Route::post('/project/{projectId}/tasks', [PmImportController::class, 'importTasks'])->name('importTasks');
|
Route::post('/project/{projectId}/tasks', [PmImportController::class, 'importTasks'])->name('importTasks');
|
||||||
|
Route::post('/task/{taskId}/issues', [PmImportController::class, 'importIssues'])->name('importIssues');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user