feat: [daily-logs] 일일 스크럼 기능 구현

주요 기능:
- 일일 로그 CRUD (생성, 조회, 수정, 삭제, 복원, 영구삭제)
- 로그 항목(Entry) 관리 (추가, 상태변경, 삭제, 순서변경)
- 주간 타임라인 (최근 7일 진행률 표시)
- 테이블 리스트 아코디언 상세보기
- 담당자 자동완성 (일반 사용자는 슈퍼관리자 목록 제외)
- HTMX 기반 동적 테이블 로딩 및 필터링
- Soft Delete 지원

파일 추가:
- Models: AdminPmDailyLog, AdminPmDailyLogEntry
- Controllers: DailyLogController (Web, API)
- Service: DailyLogService
- Requests: StoreDailyLogRequest, UpdateDailyLogRequest
- Views: index, show, table partial, modal-form partial

라우트 추가:
- Web: /daily-logs, /daily-logs/today, /daily-logs/{id}
- API: /api/admin/daily-logs/* (CRUD + 항목관리)
This commit is contained in:
2025-12-01 14:07:55 +09:00
parent 189376ffad
commit a2477837d0
14 changed files with 2474 additions and 0 deletions

View File

@@ -0,0 +1,129 @@
<!-- 일일 로그 테이블 -->
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">날짜</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">프로젝트</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">요약</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">항목</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">작성자</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">상태</th>
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">액션</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@forelse($logs as $log)
<!-- 메인 (클릭 가능) -->
<tr class="log-row cursor-pointer hover:bg-gray-50 {{ $log->trashed() ? 'bg-red-50' : '' }}"
data-log-id="{{ $log->id }}"
onclick="toggleTableAccordion({{ $log->id }}, event)">
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<svg class="accordion-chevron w-4 h-4 mr-2 text-gray-400 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
</svg>
<div>
<div class="text-sm font-medium text-gray-900">
{{ $log->log_date->format('Y-m-d') }}
</div>
<div class="text-xs text-gray-500">
{{ $log->log_date->format('l') }}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
@if($log->project)
<span class="px-2 py-1 text-xs font-medium rounded-full bg-blue-100 text-blue-800">
{{ $log->project->name }}
</span>
@else
<span class="text-gray-400">전체</span>
@endif
</td>
<td class="px-6 py-4">
@if($log->summary)
<div class="text-sm text-gray-900 truncate max-w-xs" title="{{ $log->summary }}">
{{ Str::limit($log->summary, 50) }}
</div>
@else
<span class="text-gray-400">-</span>
@endif
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center space-x-2">
<span class="text-sm text-gray-900">{{ $log->entries_count }}</span>
@if($log->entries->count() > 0)
<div class="flex space-x-1">
@php
$stats = $log->entry_stats;
@endphp
@if($stats['todo'] > 0)
<span class="w-2 h-2 rounded-full bg-gray-400" title="예정: {{ $stats['todo'] }}"></span>
@endif
@if($stats['in_progress'] > 0)
<span class="w-2 h-2 rounded-full bg-yellow-400" title="진행중: {{ $stats['in_progress'] }}"></span>
@endif
@if($stats['done'] > 0)
<span class="w-2 h-2 rounded-full bg-green-400" title="완료: {{ $stats['done'] }}"></span>
@endif
</div>
@endif
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ $log->creator?->name ?? '-' }}
</td>
<td class="px-6 py-4 whitespace-nowrap">
@if($log->trashed())
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">
삭제됨
</span>
@else
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
활성
</span>
@endif
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2" onclick="event.stopPropagation()">
@if($log->trashed())
<!-- 삭제된 항목 -->
<button onclick="confirmRestore({{ $log->id }}, '{{ $log->log_date->format('Y-m-d') }}')"
class="text-green-600 hover:text-green-900">복원</button>
@if(auth()->user()?->is_super_admin)
<button onclick="confirmForceDelete({{ $log->id }}, '{{ $log->log_date->format('Y-m-d') }}')"
class="text-red-600 hover:text-red-900">영구삭제</button>
@endif
@else
<!-- 일반 항목 액션 -->
<button onclick="editLog({{ $log->id }})"
class="text-indigo-600 hover:text-indigo-900">수정</button>
<button onclick="confirmDelete({{ $log->id }}, '{{ $log->log_date->format('Y-m-d') }}')"
class="text-red-600 hover:text-red-900">삭제</button>
@endif
</td>
</tr>
<!-- 아코디언 상세 (숨겨진 상태) -->
<tr class="accordion-row hidden" data-accordion-for="{{ $log->id }}">
<td colspan="7" class="px-6 py-4 bg-gray-50">
<div class="accordion-content" id="accordion-content-{{ $log->id }}">
<div class="text-center py-4 text-gray-500">로딩 ...</div>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-6 py-12 text-center text-gray-500">
일일 로그가 없습니다.
</td>
</tr>
@endforelse
</tbody>
</table>
<!-- 페이지네이션 -->
@if($logs->hasPages())
<div class="px-6 py-4 border-t border-gray-200">
{{ $logs->withQueryString()->links() }}
</div>
@endif