|
|
|
|
@@ -187,6 +187,204 @@
|
|
|
|
|
<span class="px-1.5 py-0.5 bg-green-100 text-green-700 rounded">{{ $issueStats['resolved'] ?? 0 }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@php
|
|
|
|
|
$todayScrum = $project->dailyLogs->first();
|
|
|
|
|
$scrumEntries = $todayScrum?->entries ?? collect([]);
|
|
|
|
|
@endphp
|
|
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
|
<span class="text-gray-500">스크럼:</span>
|
|
|
|
|
<span class="font-medium {{ $scrumEntries->count() > 0 ? 'text-indigo-600' : 'text-gray-400' }}">
|
|
|
|
|
{{ $scrumEntries->count() }}개
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 오늘의 스크럼 (칸반 스타일) -->
|
|
|
|
|
@php
|
|
|
|
|
$todoEntries = $scrumEntries->where('status', 'todo');
|
|
|
|
|
$inProgressEntries = $scrumEntries->where('status', 'in_progress');
|
|
|
|
|
$doneEntries = $scrumEntries->where('status', 'done');
|
|
|
|
|
@endphp
|
|
|
|
|
<div class="mt-4 pt-4 border-t border-gray-100">
|
|
|
|
|
<div class="flex items-center justify-between mb-3">
|
|
|
|
|
<h4 class="text-sm font-medium text-gray-700 flex items-center gap-1">
|
|
|
|
|
<svg class="w-4 h-4 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
|
|
|
|
</svg>
|
|
|
|
|
오늘의 활동
|
|
|
|
|
@if($scrumEntries->count() > 0)
|
|
|
|
|
<span class="text-xs text-gray-400 ml-1">({{ $scrumEntries->count() }})</span>
|
|
|
|
|
@endif
|
|
|
|
|
</h4>
|
|
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
|
<button type="button"
|
|
|
|
|
onclick="openQuickScrumModal({{ $project->id }}, '{{ $project->name }}', {{ $todayScrum?->id ?? 'null' }})"
|
|
|
|
|
class="text-xs text-indigo-600 hover:text-indigo-800 flex items-center gap-1">
|
|
|
|
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
|
|
|
|
</svg>
|
|
|
|
|
추가
|
|
|
|
|
</button>
|
|
|
|
|
@if($todayScrum)
|
|
|
|
|
<a href="{{ route('daily-logs.index', ['highlight' => $todayScrum->id]) }}"
|
|
|
|
|
class="text-xs text-gray-500 hover:text-gray-700">
|
|
|
|
|
더보기 →
|
|
|
|
|
</a>
|
|
|
|
|
@endif
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
@if($scrumEntries->count() > 0)
|
|
|
|
|
<!-- 칸반 3컬럼 레이아웃 (담당자별 그룹핑) -->
|
|
|
|
|
<div class="grid grid-cols-3 gap-2">
|
|
|
|
|
<!-- 예정 컬럼 -->
|
|
|
|
|
<div class="bg-gray-50 rounded-lg p-2">
|
|
|
|
|
<div class="flex items-center gap-1 mb-2 pb-1 border-b border-gray-200">
|
|
|
|
|
<span class="w-2 h-2 bg-gray-400 rounded-full"></span>
|
|
|
|
|
<span class="text-xs font-medium text-gray-600">예정</span>
|
|
|
|
|
<span class="text-xs text-gray-400">({{ $todoEntries->count() }})</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="space-y-2 max-h-40 overflow-y-auto">
|
|
|
|
|
@forelse($todoEntries->groupBy('assignee_name') as $assigneeName => $groupedEntries)
|
|
|
|
|
@php
|
|
|
|
|
$entriesJson = $groupedEntries->map(fn($e) => [
|
|
|
|
|
'id' => $e->id,
|
|
|
|
|
'daily_log_id' => $e->daily_log_id,
|
|
|
|
|
'content' => $e->content,
|
|
|
|
|
'status' => $e->status
|
|
|
|
|
])->values()->toJson();
|
|
|
|
|
@endphp
|
|
|
|
|
<div class="bg-white rounded p-2 shadow-sm border border-gray-100">
|
|
|
|
|
<p class="text-xs font-semibold text-gray-700 mb-1.5 pb-1 border-b border-gray-100 cursor-pointer hover:text-indigo-600"
|
|
|
|
|
onclick='openEditEntryModal(@json($entriesJson), "{{ addslashes($assigneeName) }}", {{ $project->id }}, "{{ addslashes($project->name) }}")'>{{ $assigneeName }}</p>
|
|
|
|
|
<div class="space-y-1">
|
|
|
|
|
@foreach($groupedEntries as $entry)
|
|
|
|
|
<div class="group flex items-start justify-between gap-1 text-xs py-0.5 hover:bg-gray-50 rounded px-1 -mx-1">
|
|
|
|
|
<span class="text-gray-600 truncate flex-1 cursor-pointer" onclick='openEditEntryModal(@json($entriesJson), "{{ addslashes($assigneeName) }}", {{ $project->id }}, "{{ addslashes($project->name) }}")'>{{ Str::limit($entry->content, 25) }}</span>
|
|
|
|
|
<div class="flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
|
|
|
|
|
<button onclick="changeEntryStatus({{ $entry->id }}, 'in_progress')" class="p-0.5 text-blue-400 hover:text-blue-600" title="진행중으로">
|
|
|
|
|
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20"><circle cx="10" cy="10" r="8"/></svg>
|
|
|
|
|
</button>
|
|
|
|
|
<button onclick="changeEntryStatus({{ $entry->id }}, 'done')" class="p-0.5 text-green-400 hover:text-green-600" title="완료로">
|
|
|
|
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
|
|
|
|
</button>
|
|
|
|
|
<button onclick="quickDeleteEntry({{ $entry->id }})" class="p-0.5 text-red-400 hover:text-red-600" title="삭제">
|
|
|
|
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@endforeach
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@empty
|
|
|
|
|
<p class="text-xs text-gray-400 text-center py-2">-</p>
|
|
|
|
|
@endforelse
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 진행중 컬럼 -->
|
|
|
|
|
<div class="bg-blue-50 rounded-lg p-2">
|
|
|
|
|
<div class="flex items-center gap-1 mb-2 pb-1 border-b border-blue-200">
|
|
|
|
|
<span class="w-2 h-2 bg-blue-500 rounded-full animate-pulse"></span>
|
|
|
|
|
<span class="text-xs font-medium text-blue-700">진행중</span>
|
|
|
|
|
<span class="text-xs text-blue-400">({{ $inProgressEntries->count() }})</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="space-y-2 max-h-40 overflow-y-auto">
|
|
|
|
|
@forelse($inProgressEntries->groupBy('assignee_name') as $assigneeName => $groupedEntries)
|
|
|
|
|
@php
|
|
|
|
|
$entriesJson = $groupedEntries->map(fn($e) => [
|
|
|
|
|
'id' => $e->id,
|
|
|
|
|
'daily_log_id' => $e->daily_log_id,
|
|
|
|
|
'content' => $e->content,
|
|
|
|
|
'status' => $e->status
|
|
|
|
|
])->values()->toJson();
|
|
|
|
|
@endphp
|
|
|
|
|
<div class="bg-white rounded p-2 shadow-sm border border-blue-100">
|
|
|
|
|
<p class="text-xs font-semibold text-gray-700 mb-1.5 pb-1 border-b border-blue-100 cursor-pointer hover:text-indigo-600"
|
|
|
|
|
onclick='openEditEntryModal(@json($entriesJson), "{{ addslashes($assigneeName) }}", {{ $project->id }}, "{{ addslashes($project->name) }}")'>{{ $assigneeName }}</p>
|
|
|
|
|
<div class="space-y-1">
|
|
|
|
|
@foreach($groupedEntries as $entry)
|
|
|
|
|
<div class="group flex items-start justify-between gap-1 text-xs py-0.5 hover:bg-blue-50 rounded px-1 -mx-1">
|
|
|
|
|
<span class="text-gray-600 truncate flex-1 cursor-pointer" onclick='openEditEntryModal(@json($entriesJson), "{{ addslashes($assigneeName) }}", {{ $project->id }}, "{{ addslashes($project->name) }}")'>{{ Str::limit($entry->content, 25) }}</span>
|
|
|
|
|
<div class="flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
|
|
|
|
|
<button onclick="changeEntryStatus({{ $entry->id }}, 'todo')" class="p-0.5 text-gray-400 hover:text-gray-600" title="예정으로">
|
|
|
|
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" stroke-width="2"/></svg>
|
|
|
|
|
</button>
|
|
|
|
|
<button onclick="changeEntryStatus({{ $entry->id }}, 'done')" class="p-0.5 text-green-400 hover:text-green-600" title="완료로">
|
|
|
|
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
|
|
|
|
</button>
|
|
|
|
|
<button onclick="quickDeleteEntry({{ $entry->id }})" class="p-0.5 text-red-400 hover:text-red-600" title="삭제">
|
|
|
|
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@endforeach
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@empty
|
|
|
|
|
<p class="text-xs text-blue-400 text-center py-2">-</p>
|
|
|
|
|
@endforelse
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 완료 컬럼 -->
|
|
|
|
|
<div class="bg-green-50 rounded-lg p-2">
|
|
|
|
|
<div class="flex items-center gap-1 mb-2 pb-1 border-b border-green-200">
|
|
|
|
|
<svg class="w-3 h-3 text-green-600" fill="currentColor" viewBox="0 0 20 20">
|
|
|
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
|
|
|
|
|
</svg>
|
|
|
|
|
<span class="text-xs font-medium text-green-700">완료</span>
|
|
|
|
|
<span class="text-xs text-green-400">({{ $doneEntries->count() }})</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="space-y-2 max-h-40 overflow-y-auto">
|
|
|
|
|
@forelse($doneEntries->groupBy('assignee_name') as $assigneeName => $groupedEntries)
|
|
|
|
|
@php
|
|
|
|
|
$entriesJson = $groupedEntries->map(fn($e) => [
|
|
|
|
|
'id' => $e->id,
|
|
|
|
|
'daily_log_id' => $e->daily_log_id,
|
|
|
|
|
'content' => $e->content,
|
|
|
|
|
'status' => $e->status
|
|
|
|
|
])->values()->toJson();
|
|
|
|
|
@endphp
|
|
|
|
|
<div class="bg-white rounded p-2 shadow-sm border border-green-100">
|
|
|
|
|
<p class="text-xs font-semibold text-gray-700 mb-1.5 pb-1 border-b border-green-100 cursor-pointer hover:text-indigo-600"
|
|
|
|
|
onclick='openEditEntryModal(@json($entriesJson), "{{ addslashes($assigneeName) }}", {{ $project->id }}, "{{ addslashes($project->name) }}")'>{{ $assigneeName }}</p>
|
|
|
|
|
<div class="space-y-1">
|
|
|
|
|
@foreach($groupedEntries as $entry)
|
|
|
|
|
<div class="group flex items-start justify-between gap-1 text-xs py-0.5 hover:bg-green-50 rounded px-1 -mx-1">
|
|
|
|
|
<span class="text-gray-400 truncate flex-1 line-through cursor-pointer" onclick='openEditEntryModal(@json($entriesJson), "{{ addslashes($assigneeName) }}", {{ $project->id }}, "{{ addslashes($project->name) }}")'>{{ Str::limit($entry->content, 25) }}</span>
|
|
|
|
|
<div class="flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
|
|
|
|
|
<button onclick="changeEntryStatus({{ $entry->id }}, 'todo')" class="p-0.5 text-gray-400 hover:text-gray-600" title="예정으로">
|
|
|
|
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" stroke-width="2"/></svg>
|
|
|
|
|
</button>
|
|
|
|
|
<button onclick="changeEntryStatus({{ $entry->id }}, 'in_progress')" class="p-0.5 text-blue-400 hover:text-blue-600" title="진행중으로">
|
|
|
|
|
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20"><circle cx="10" cy="10" r="8"/></svg>
|
|
|
|
|
</button>
|
|
|
|
|
<button onclick="quickDeleteEntry({{ $entry->id }})" class="p-0.5 text-red-400 hover:text-red-600" title="삭제">
|
|
|
|
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@endforeach
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@empty
|
|
|
|
|
<p class="text-xs text-green-400 text-center py-2">-</p>
|
|
|
|
|
@endforelse
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@else
|
|
|
|
|
<!-- 활동 없음 -->
|
|
|
|
|
<div class="text-center py-4 bg-gray-50 rounded-lg">
|
|
|
|
|
<p class="text-sm text-gray-400">오늘의 활동이 없습니다</p>
|
|
|
|
|
<button type="button"
|
|
|
|
|
onclick="openQuickScrumModal({{ $project->id }}, '{{ $project->name }}', null)"
|
|
|
|
|
class="mt-2 text-xs text-indigo-600 hover:text-indigo-800">
|
|
|
|
|
+ 첫 활동 추가하기
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
@endif
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@empty
|
|
|
|
|
@@ -244,6 +442,180 @@ class="p-6">
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 스크럼 항목 수정 모달 (담당자별 그룹 편집) -->
|
|
|
|
|
<div id="editEntryModal" class="fixed inset-0 z-50 hidden overflow-y-auto" aria-labelledby="edit-modal-title" role="dialog" aria-modal="true">
|
|
|
|
|
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
|
|
|
|
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" onclick="closeEditEntryModal()"></div>
|
|
|
|
|
<span class="hidden sm:inline-block sm:align-middle sm:h-screen">​</span>
|
|
|
|
|
<div class="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-2xl sm:w-full sm:p-6">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="flex items-center justify-between mb-4">
|
|
|
|
|
<h3 class="text-lg font-medium text-gray-900 flex items-center gap-2" id="edit-modal-title">
|
|
|
|
|
<svg class="w-5 h-5 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
|
|
|
|
</svg>
|
|
|
|
|
스크럼 항목 수정
|
|
|
|
|
</h3>
|
|
|
|
|
<button type="button" onclick="closeEditEntryModal()" class="text-gray-400 hover:text-gray-500">
|
|
|
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="mb-4 p-3 bg-gray-50 rounded-lg">
|
|
|
|
|
<span class="text-xs text-gray-500">프로젝트</span>
|
|
|
|
|
<p id="editEntryProjectName" class="font-medium text-gray-900"></p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<form id="editEntryForm" onsubmit="submitEditEntries(event)">
|
|
|
|
|
<input type="hidden" id="editEntryProjectId" name="project_id">
|
|
|
|
|
|
|
|
|
|
<!-- 담당자 (공통) -->
|
|
|
|
|
<div class="mb-4">
|
|
|
|
|
<label for="editEntryAssignee" class="block text-sm font-medium text-gray-700 mb-1">담당자</label>
|
|
|
|
|
<input type="text" id="editEntryAssignee" name="assignee_name" required
|
|
|
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 업무 항목 목록 -->
|
|
|
|
|
<div class="mb-4">
|
|
|
|
|
<div class="flex items-center justify-between mb-2">
|
|
|
|
|
<label class="block text-sm font-medium text-gray-700">업무 내용</label>
|
|
|
|
|
<button type="button" onclick="addNewEntryRow()" class="text-xs text-indigo-600 hover:text-indigo-800 flex items-center gap-1">
|
|
|
|
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
|
|
|
|
</svg>
|
|
|
|
|
항목 추가
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div id="editEntriesContainer" class="space-y-2">
|
|
|
|
|
<!-- 동적으로 항목 행들이 추가됨 -->
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="flex gap-3 pt-4 border-t border-gray-200">
|
|
|
|
|
<button type="button" onclick="closeEditEntryModal()"
|
|
|
|
|
class="flex-1 px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50">
|
|
|
|
|
취소
|
|
|
|
|
</button>
|
|
|
|
|
<button type="submit" id="editEntrySubmitBtn"
|
|
|
|
|
class="flex-1 px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 disabled:opacity-50">
|
|
|
|
|
저장
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 빠른 스크럼 추가 모달 -->
|
|
|
|
|
<div id="quickScrumModal" class="fixed inset-0 z-50 hidden overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
|
|
|
|
|
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
|
|
|
|
<!-- 배경 오버레이 -->
|
|
|
|
|
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" onclick="closeQuickScrumModal()"></div>
|
|
|
|
|
|
|
|
|
|
<span class="hidden sm:inline-block sm:align-middle sm:h-screen">​</span>
|
|
|
|
|
|
|
|
|
|
<!-- 모달 패널 -->
|
|
|
|
|
<div class="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="flex items-center justify-between mb-4">
|
|
|
|
|
<h3 class="text-lg font-medium text-gray-900 flex items-center gap-2" id="modal-title">
|
|
|
|
|
<svg class="w-5 h-5 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
|
|
|
|
</svg>
|
|
|
|
|
스크럼 항목 추가
|
|
|
|
|
</h3>
|
|
|
|
|
<button type="button" onclick="closeQuickScrumModal()" class="text-gray-400 hover:text-gray-500">
|
|
|
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 프로젝트 정보 -->
|
|
|
|
|
<div class="mb-4 p-3 bg-gray-50 rounded-lg">
|
|
|
|
|
<span class="text-xs text-gray-500">프로젝트</span>
|
|
|
|
|
<p id="quickScrumProjectName" class="font-medium text-gray-900"></p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<form id="quickScrumForm" onsubmit="submitQuickScrum(event)">
|
|
|
|
|
<input type="hidden" id="quickScrumProjectId" name="project_id">
|
|
|
|
|
<input type="hidden" id="quickScrumLogId" name="log_id">
|
|
|
|
|
|
|
|
|
|
<!-- 담당자 -->
|
|
|
|
|
<div class="mb-4">
|
|
|
|
|
<label for="quickScrumAssignee" class="block text-sm font-medium text-gray-700 mb-1">담당자</label>
|
|
|
|
|
<input type="text"
|
|
|
|
|
id="quickScrumAssignee"
|
|
|
|
|
name="assignee_name"
|
|
|
|
|
required
|
|
|
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
|
|
|
|
|
placeholder="담당자 이름">
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 업무 내용 -->
|
|
|
|
|
<div class="mb-4">
|
|
|
|
|
<label for="quickScrumContent" class="block text-sm font-medium text-gray-700 mb-1">업무 내용</label>
|
|
|
|
|
<textarea id="quickScrumContent"
|
|
|
|
|
name="content"
|
|
|
|
|
required
|
|
|
|
|
rows="3"
|
|
|
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
|
|
|
|
|
placeholder="오늘 진행한/진행할 업무 내용"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 상태 -->
|
|
|
|
|
<div class="mb-6">
|
|
|
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">상태</label>
|
|
|
|
|
<div class="flex gap-3">
|
|
|
|
|
<label class="flex-1 cursor-pointer">
|
|
|
|
|
<input type="radio" name="status" value="todo" class="sr-only peer" checked>
|
|
|
|
|
<div class="flex items-center justify-center gap-1 py-2 px-3 border rounded-lg peer-checked:border-gray-500 peer-checked:bg-gray-50 text-sm">
|
|
|
|
|
<span class="w-2 h-2 bg-gray-400 rounded-full"></span>
|
|
|
|
|
예정
|
|
|
|
|
</div>
|
|
|
|
|
</label>
|
|
|
|
|
<label class="flex-1 cursor-pointer">
|
|
|
|
|
<input type="radio" name="status" value="in_progress" class="sr-only peer">
|
|
|
|
|
<div class="flex items-center justify-center gap-1 py-2 px-3 border rounded-lg peer-checked:border-blue-500 peer-checked:bg-blue-50 text-sm">
|
|
|
|
|
<span class="w-2 h-2 bg-blue-500 rounded-full"></span>
|
|
|
|
|
진행중
|
|
|
|
|
</div>
|
|
|
|
|
</label>
|
|
|
|
|
<label class="flex-1 cursor-pointer">
|
|
|
|
|
<input type="radio" name="status" value="done" class="sr-only peer">
|
|
|
|
|
<div class="flex items-center justify-center gap-1 py-2 px-3 border rounded-lg peer-checked:border-green-500 peer-checked:bg-green-50 text-sm">
|
|
|
|
|
<svg class="w-3.5 h-3.5 text-green-500" fill="currentColor" viewBox="0 0 20 20">
|
|
|
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
|
|
|
|
|
</svg>
|
|
|
|
|
완료
|
|
|
|
|
</div>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 버튼 -->
|
|
|
|
|
<div class="flex gap-3">
|
|
|
|
|
<button type="button"
|
|
|
|
|
onclick="closeQuickScrumModal()"
|
|
|
|
|
class="flex-1 px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50">
|
|
|
|
|
취소
|
|
|
|
|
</button>
|
|
|
|
|
<button type="submit"
|
|
|
|
|
id="quickScrumSubmitBtn"
|
|
|
|
|
class="flex-1 px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 disabled:opacity-50 disabled:cursor-not-allowed">
|
|
|
|
|
추가
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@endsection
|
|
|
|
|
|
|
|
|
|
@push('scripts')
|
|
|
|
|
@@ -358,5 +730,375 @@ function renderOpenIssues(container, issues) {
|
|
|
|
|
|
|
|
|
|
container.innerHTML = html;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 빠른 스크럼 모달 관련 함수
|
|
|
|
|
function openQuickScrumModal(projectId, projectName, logId) {
|
|
|
|
|
document.getElementById('quickScrumProjectId').value = projectId;
|
|
|
|
|
document.getElementById('quickScrumProjectName').textContent = projectName;
|
|
|
|
|
document.getElementById('quickScrumLogId').value = logId || '';
|
|
|
|
|
document.getElementById('quickScrumModal').classList.remove('hidden');
|
|
|
|
|
|
|
|
|
|
// 폼 초기화
|
|
|
|
|
document.getElementById('quickScrumAssignee').value = '';
|
|
|
|
|
document.getElementById('quickScrumContent').value = '';
|
|
|
|
|
document.querySelector('input[name="status"][value="todo"]').checked = true;
|
|
|
|
|
|
|
|
|
|
// 담당자 입력창에 포커스
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
document.getElementById('quickScrumAssignee').focus();
|
|
|
|
|
}, 100);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function closeQuickScrumModal() {
|
|
|
|
|
document.getElementById('quickScrumModal').classList.add('hidden');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function submitQuickScrum(event) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
|
|
const form = document.getElementById('quickScrumForm');
|
|
|
|
|
const submitBtn = document.getElementById('quickScrumSubmitBtn');
|
|
|
|
|
const projectId = document.getElementById('quickScrumProjectId').value;
|
|
|
|
|
const logId = document.getElementById('quickScrumLogId').value;
|
|
|
|
|
|
|
|
|
|
const formData = {
|
|
|
|
|
project_id: parseInt(projectId),
|
|
|
|
|
log_date: new Date().toISOString().split('T')[0],
|
|
|
|
|
assignee_name: document.getElementById('quickScrumAssignee').value,
|
|
|
|
|
content: document.getElementById('quickScrumContent').value,
|
|
|
|
|
status: document.querySelector('input[name="status"]:checked').value,
|
|
|
|
|
assignee_type: 'user'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
submitBtn.disabled = true;
|
|
|
|
|
submitBtn.textContent = '추가 중...';
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
let response;
|
|
|
|
|
|
|
|
|
|
if (logId) {
|
|
|
|
|
// 기존 로그에 항목 추가
|
|
|
|
|
response = await fetch(`/api/admin/pm/daily-logs/${logId}/entries`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
'Accept': 'application/json',
|
|
|
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify(formData)
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
// 새 로그 생성과 함께 항목 추가
|
|
|
|
|
response = await fetch('/api/admin/pm/daily-logs', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
'Accept': 'application/json',
|
|
|
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
project_id: formData.project_id,
|
|
|
|
|
log_date: formData.log_date,
|
|
|
|
|
entries: [{
|
|
|
|
|
assignee_type: formData.assignee_type,
|
|
|
|
|
assignee_name: formData.assignee_name,
|
|
|
|
|
content: formData.content,
|
|
|
|
|
status: formData.status
|
|
|
|
|
}]
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
if (response.ok && data.success) {
|
|
|
|
|
closeQuickScrumModal();
|
|
|
|
|
// 페이지 새로고침으로 데이터 반영
|
|
|
|
|
window.location.reload();
|
|
|
|
|
} else {
|
|
|
|
|
alert(data.message || '스크럼 항목 추가에 실패했습니다.');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error:', error);
|
|
|
|
|
alert('오류가 발생했습니다. 다시 시도해주세요.');
|
|
|
|
|
} finally {
|
|
|
|
|
submitBtn.disabled = false;
|
|
|
|
|
submitBtn.textContent = '추가';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ESC 키로 모달 닫기
|
|
|
|
|
document.addEventListener('keydown', function(e) {
|
|
|
|
|
if (e.key === 'Escape') {
|
|
|
|
|
if (!document.getElementById('quickScrumModal').classList.contains('hidden')) {
|
|
|
|
|
closeQuickScrumModal();
|
|
|
|
|
}
|
|
|
|
|
if (!document.getElementById('editEntryModal').classList.contains('hidden')) {
|
|
|
|
|
closeEditEntryModal();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 스크럼 항목 수정 모달 관련 함수 (담당자별 그룹 편집)
|
|
|
|
|
let currentEditEntries = []; // 현재 편집 중인 항목들
|
|
|
|
|
let entriesToDelete = []; // 삭제할 항목 ID 목록
|
|
|
|
|
|
|
|
|
|
function openEditEntryModal(entriesJson, assigneeName, projectId, projectName) {
|
|
|
|
|
const entries = JSON.parse(entriesJson);
|
|
|
|
|
currentEditEntries = entries;
|
|
|
|
|
entriesToDelete = [];
|
|
|
|
|
|
|
|
|
|
document.getElementById('editEntryProjectId').value = projectId;
|
|
|
|
|
document.getElementById('editEntryProjectName').textContent = projectName;
|
|
|
|
|
document.getElementById('editEntryAssignee').value = assigneeName;
|
|
|
|
|
|
|
|
|
|
// 항목 컨테이너 초기화 및 렌더링
|
|
|
|
|
renderEntryRows(entries);
|
|
|
|
|
|
|
|
|
|
document.getElementById('editEntryModal').classList.remove('hidden');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderEntryRows(entries) {
|
|
|
|
|
const container = document.getElementById('editEntriesContainer');
|
|
|
|
|
container.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
entries.forEach((entry, index) => {
|
|
|
|
|
container.appendChild(createEntryRow(entry, index));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createEntryRow(entry, index) {
|
|
|
|
|
const row = document.createElement('div');
|
|
|
|
|
row.className = 'entry-row flex items-start gap-2 p-2 bg-gray-50 rounded-lg';
|
|
|
|
|
row.dataset.entryId = entry.id || '';
|
|
|
|
|
row.dataset.index = index;
|
|
|
|
|
|
|
|
|
|
row.innerHTML = `
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
<textarea name="content_${index}" rows="1" required
|
|
|
|
|
class="w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 resize-none"
|
|
|
|
|
placeholder="업무 내용">${entry.content || ''}</textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex items-center gap-1 shrink-0">
|
|
|
|
|
<button type="button" onclick="setEntryStatus(this, 'todo')"
|
|
|
|
|
class="status-btn p-1.5 rounded ${entry.status === 'todo' ? 'bg-gray-200' : 'hover:bg-gray-100'}"
|
|
|
|
|
data-status="todo" title="예정">
|
|
|
|
|
<span class="w-2.5 h-2.5 bg-gray-400 rounded-full block"></span>
|
|
|
|
|
</button>
|
|
|
|
|
<button type="button" onclick="setEntryStatus(this, 'in_progress')"
|
|
|
|
|
class="status-btn p-1.5 rounded ${entry.status === 'in_progress' ? 'bg-blue-100' : 'hover:bg-gray-100'}"
|
|
|
|
|
data-status="in_progress" title="진행중">
|
|
|
|
|
<span class="w-2.5 h-2.5 bg-blue-500 rounded-full block"></span>
|
|
|
|
|
</button>
|
|
|
|
|
<button type="button" onclick="setEntryStatus(this, 'done')"
|
|
|
|
|
class="status-btn p-1.5 rounded ${entry.status === 'done' ? 'bg-green-100' : 'hover:bg-gray-100'}"
|
|
|
|
|
data-status="done" title="완료">
|
|
|
|
|
<svg class="w-3 h-3 text-green-600" fill="currentColor" viewBox="0 0 20 20">
|
|
|
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
<button type="button" onclick="removeEntryRow(this)"
|
|
|
|
|
class="p-1.5 text-red-400 hover:text-red-600 hover:bg-red-50 rounded" title="삭제">
|
|
|
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<input type="hidden" name="status_${index}" value="${entry.status || 'todo'}">
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
return row;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setEntryStatus(btn, status) {
|
|
|
|
|
const row = btn.closest('.entry-row');
|
|
|
|
|
const statusInput = row.querySelector('input[type="hidden"]');
|
|
|
|
|
statusInput.value = status;
|
|
|
|
|
|
|
|
|
|
// 버튼 스타일 업데이트
|
|
|
|
|
row.querySelectorAll('.status-btn').forEach(b => {
|
|
|
|
|
b.classList.remove('bg-gray-200', 'bg-blue-100', 'bg-green-100');
|
|
|
|
|
b.classList.add('hover:bg-gray-100');
|
|
|
|
|
});
|
|
|
|
|
btn.classList.remove('hover:bg-gray-100');
|
|
|
|
|
if (status === 'todo') btn.classList.add('bg-gray-200');
|
|
|
|
|
else if (status === 'in_progress') btn.classList.add('bg-blue-100');
|
|
|
|
|
else if (status === 'done') btn.classList.add('bg-green-100');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function addNewEntryRow() {
|
|
|
|
|
const container = document.getElementById('editEntriesContainer');
|
|
|
|
|
const index = container.children.length;
|
|
|
|
|
container.appendChild(createEntryRow({ id: '', content: '', status: 'todo' }, index));
|
|
|
|
|
|
|
|
|
|
// 새로 추가된 행의 textarea에 포커스
|
|
|
|
|
const newRow = container.lastChild;
|
|
|
|
|
newRow.querySelector('textarea').focus();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function removeEntryRow(btn) {
|
|
|
|
|
const row = btn.closest('.entry-row');
|
|
|
|
|
const entryId = row.dataset.entryId;
|
|
|
|
|
|
|
|
|
|
// 기존 항목이면 삭제 목록에 추가
|
|
|
|
|
if (entryId) {
|
|
|
|
|
entriesToDelete.push(parseInt(entryId));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
row.remove();
|
|
|
|
|
|
|
|
|
|
// 마지막 항목은 삭제 불가 (최소 1개 유지)
|
|
|
|
|
const container = document.getElementById('editEntriesContainer');
|
|
|
|
|
if (container.children.length === 0) {
|
|
|
|
|
addNewEntryRow();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function closeEditEntryModal() {
|
|
|
|
|
document.getElementById('editEntryModal').classList.add('hidden');
|
|
|
|
|
currentEditEntries = [];
|
|
|
|
|
entriesToDelete = [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function submitEditEntries(event) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
|
|
const submitBtn = document.getElementById('editEntrySubmitBtn');
|
|
|
|
|
const assigneeName = document.getElementById('editEntryAssignee').value;
|
|
|
|
|
const container = document.getElementById('editEntriesContainer');
|
|
|
|
|
const rows = container.querySelectorAll('.entry-row');
|
|
|
|
|
|
|
|
|
|
submitBtn.disabled = true;
|
|
|
|
|
submitBtn.textContent = '저장 중...';
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const promises = [];
|
|
|
|
|
|
|
|
|
|
// 삭제 처리
|
|
|
|
|
for (const entryId of entriesToDelete) {
|
|
|
|
|
promises.push(
|
|
|
|
|
fetch(`/api/admin/daily-logs/entries/${entryId}`, {
|
|
|
|
|
method: 'DELETE',
|
|
|
|
|
headers: {
|
|
|
|
|
'Accept': 'application/json',
|
|
|
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 수정/생성 처리
|
|
|
|
|
rows.forEach((row, index) => {
|
|
|
|
|
const entryId = row.dataset.entryId;
|
|
|
|
|
const content = row.querySelector('textarea').value.trim();
|
|
|
|
|
const status = row.querySelector('input[type="hidden"]').value;
|
|
|
|
|
|
|
|
|
|
if (!content) return; // 빈 내용은 스킵
|
|
|
|
|
|
|
|
|
|
const data = {
|
|
|
|
|
assignee_name: assigneeName,
|
|
|
|
|
assignee_type: 'user',
|
|
|
|
|
content: content,
|
|
|
|
|
status: status
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (entryId) {
|
|
|
|
|
// 기존 항목 수정
|
|
|
|
|
promises.push(
|
|
|
|
|
fetch(`/api/admin/daily-logs/entries/${entryId}`, {
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
'Accept': 'application/json',
|
|
|
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify(data)
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
// 새 항목 - 기존 항목의 daily_log_id를 사용
|
|
|
|
|
const existingEntry = currentEditEntries.find(e => e.id);
|
|
|
|
|
if (existingEntry && existingEntry.daily_log_id) {
|
|
|
|
|
promises.push(
|
|
|
|
|
fetch(`/api/admin/pm/daily-logs/${existingEntry.daily_log_id}/entries`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
'Accept': 'application/json',
|
|
|
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify(data)
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await Promise.all(promises);
|
|
|
|
|
closeEditEntryModal();
|
|
|
|
|
window.location.reload();
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error:', error);
|
|
|
|
|
alert('오류가 발생했습니다. 다시 시도해주세요.');
|
|
|
|
|
} finally {
|
|
|
|
|
submitBtn.disabled = false;
|
|
|
|
|
submitBtn.textContent = '저장';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 인라인 상태 변경 (카드에서 바로 상태 변경)
|
|
|
|
|
async function changeEntryStatus(entryId, status) {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`/api/admin/daily-logs/entries/${entryId}`, {
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
'Accept': 'application/json',
|
|
|
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify({ status })
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
if (response.ok && data.success) {
|
|
|
|
|
window.location.reload();
|
|
|
|
|
} else {
|
|
|
|
|
alert(data.message || '상태 변경에 실패했습니다.');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error:', error);
|
|
|
|
|
alert('오류가 발생했습니다.');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 빠른 삭제 (모달 없이 바로 삭제)
|
|
|
|
|
async function quickDeleteEntry(entryId) {
|
|
|
|
|
if (!confirm('이 항목을 삭제하시겠습니까?')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`/api/admin/daily-logs/entries/${entryId}`, {
|
|
|
|
|
method: 'DELETE',
|
|
|
|
|
headers: {
|
|
|
|
|
'Accept': 'application/json',
|
|
|
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
if (response.ok && data.success) {
|
|
|
|
|
window.location.reload();
|
|
|
|
|
} else {
|
|
|
|
|
alert(data.message || '항목 삭제에 실패했습니다.');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error:', error);
|
|
|
|
|
alert('오류가 발생했습니다.');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
@endpush
|