feat: [approval] 결재서명란 테이블 추가 (전통 결재 양식)

- 문서 상세 우측 상단에 결재서명란 테이블 배치
- 작성자 + 결재자 컬럼, 직급/이름/서명/날짜 표시
- 승인/반려/보류/전결 상태별 도장 아이콘
- 기존 원형 타임라인 결재 진행 제거, 결재 의견만 유지
This commit is contained in:
김보곤
2026-03-05 11:23:32 +09:00
parent 76aabebc6e
commit daa7d40f4e
2 changed files with 163 additions and 82 deletions

View File

@@ -0,0 +1,83 @@
{{--
결재서명란 (전통 테이블 형식)
Props:
$approval (Approval model) - 결재 문서
--}}
@php
$drafter = $approval->drafter;
$approverSteps = $approval->steps->whereIn('step_type', ['approval', 'agreement'])->sortBy('step_order')->values();
@endphp
<table style="border-collapse: collapse; border: 2px solid #4338ca; font-size: 12px; min-width: 120px;">
<tbody>
{{-- 1: 직급/역할 --}}
<tr>
<td rowspan="3" style="border: 1px solid #a5b4fc; padding: 4px 6px; text-align: center; font-weight: 700; color: #4338ca; background: #eef2ff; writing-mode: vertical-rl; letter-spacing: 6px; width: 28px; font-size: 13px;">
결재
</td>
<td style="border: 1px solid #c7d2fe; padding: 4px 10px; text-align: center; font-weight: 600; color: #374151; background: #f9fafb; min-width: 64px; white-space: nowrap;">
작성자
</td>
@foreach($approverSteps as $step)
<td style="border: 1px solid #c7d2fe; padding: 4px 10px; text-align: center; font-weight: 600; color: #374151; background: #f9fafb; min-width: 64px; white-space: nowrap;">
{{ $step->approver_position ?: ($step->step_type === 'agreement' ? '합의' : '결재') }}
</td>
@endforeach
</tr>
{{-- 2: 서명/도장 영역 --}}
<tr>
<td style="border: 1px solid #c7d2fe; padding: 6px 10px; text-align: center; height: 40px; vertical-align: middle;">
@if(in_array($approval->status, ['pending', 'approved', 'rejected', 'cancelled', 'on_hold']))
<span style="color: #2563eb; font-size: 11px; font-weight: 500;">{{ $drafter?->name ?? '-' }}</span>
@endif
</td>
@foreach($approverSteps as $step)
<td style="border: 1px solid #c7d2fe; padding: 6px 10px; text-align: center; height: 40px; vertical-align: middle;">
@if($step->status === 'approved')
@if(($step->approval_type ?? 'normal') === 'pre_decided')
<div style="display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: 2px solid #6366f1; border-radius: 50%; color: #6366f1; font-size: 9px; font-weight: 700; line-height: 1;">
전결
</div>
@else
<div style="display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: 2px solid #dc2626; border-radius: 50%; color: #dc2626; font-size: 9px; font-weight: 700; line-height: 1;">
승인
</div>
@endif
@elseif($step->status === 'rejected')
<div style="display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: 2px solid #dc2626; border-radius: 50%; color: #dc2626; font-size: 9px; font-weight: 700; line-height: 1;">
반려
</div>
@elseif($step->status === 'on_hold')
<div style="display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: 2px solid #d97706; border-radius: 50%; color: #d97706; font-size: 9px; font-weight: 700; line-height: 1;">
보류
</div>
@elseif($step->status === 'skipped')
<span style="color: #9ca3af; font-size: 10px;">-</span>
@endif
</td>
@endforeach
</tr>
{{-- 3: 이름 + 처리일시 --}}
<tr>
<td style="border: 1px solid #c7d2fe; padding: 3px 10px; text-align: center; white-space: nowrap;">
@if(in_array($approval->status, ['pending', 'approved', 'rejected', 'cancelled', 'on_hold']))
<div style="font-size: 10px; color: #6b7280;">
{{ $approval->drafted_at?->format('m/d') ?? '' }}
</div>
@endif
</td>
@foreach($approverSteps as $step)
<td style="border: 1px solid #c7d2fe; padding: 3px 10px; text-align: center; white-space: nowrap;">
<div style="font-size: 11px; color: #374151; font-weight: 500;">
{{ $step->approver_name ?? ($step->approver?->name ?? '') }}
</div>
@if($step->acted_at)
<div style="font-size: 10px; color: #6b7280;">
{{ $step->acted_at->format('m/d') }}
</div>
@endif
</td>
@endforeach
</tr>
</tbody>
</table>

View File

@@ -23,44 +23,51 @@ class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg transition
{{-- 문서 정보 --}} {{-- 문서 정보 --}}
<div class="bg-white rounded-lg shadow-sm p-6 mb-6"> <div class="bg-white rounded-lg shadow-sm p-6 mb-6">
<div class="flex flex-wrap gap-y-3 mb-4" style="gap-column: 0;"> <div class="flex justify-between items-start gap-4 mb-4">
<div class="pr-6 border-r border-gray-200 mr-6"> <div class="flex flex-wrap gap-y-3" style="gap-column: 0;">
<span class="text-xs text-gray-500">상태</span> <div class="pr-6 border-r border-gray-200 mr-6">
<div class="mt-1"> <span class="text-xs text-gray-500">상태</span>
@include('approvals.partials._status-badge', ['status' => $approval->status]) <div class="mt-1">
@if($approval->is_urgent) @include('approvals.partials._status-badge', ['status' => $approval->status])
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-700 ml-1">긴급</span> @if($approval->is_urgent)
@endif <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-700 ml-1">긴급</span>
</div> @endif
</div>
<div class="pr-6 border-r border-gray-200 mr-6">
<span class="text-xs text-gray-500">양식</span>
<div class="mt-1 text-sm font-medium">{{ $approval->form?->name ?? '-' }}</div>
</div>
<div class="pr-6 border-r border-gray-200 mr-6">
<span class="text-xs text-gray-500">기안자</span>
<div class="mt-1 text-sm font-medium">{{ $approval->drafter?->name ?? '-' }}</div>
</div>
<div class="pr-6{{ $approval->completed_at || $approval->parent_doc_id ? ' border-r border-gray-200 mr-6' : '' }}">
<span class="text-xs text-gray-500">기안일</span>
<div class="mt-1 text-sm">{{ $approval->drafted_at?->format('Y-m-d H:i') ?? '-' }}</div>
</div>
@if($approval->completed_at)
<div class="pr-6{{ $approval->parent_doc_id ? ' border-r border-gray-200 mr-6' : '' }}">
<span class="text-xs text-gray-500">완료일</span>
<div class="mt-1 text-sm">{{ $approval->completed_at->format('Y-m-d H:i') }}</div>
</div>
@endif
@if($approval->parent_doc_id)
<div class="pr-6">
<span class="text-xs text-gray-500">원본 문서</span>
<div class="mt-1 text-sm">
<a href="{{ route('approvals.show', $approval->parent_doc_id) }}" class="text-blue-600 hover:underline">
{{ $approval->parentDocument?->document_number ?? '원본 보기' }}
</a>
</div> </div>
</div> </div>
@endif <div class="pr-6 border-r border-gray-200 mr-6">
<span class="text-xs text-gray-500">양식</span>
<div class="mt-1 text-sm font-medium">{{ $approval->form?->name ?? '-' }}</div>
</div>
<div class="pr-6 border-r border-gray-200 mr-6">
<span class="text-xs text-gray-500">기안자</span>
<div class="mt-1 text-sm font-medium">{{ $approval->drafter?->name ?? '-' }}</div>
</div>
<div class="pr-6{{ $approval->completed_at || $approval->parent_doc_id ? ' border-r border-gray-200 mr-6' : '' }}">
<span class="text-xs text-gray-500">기안일</span>
<div class="mt-1 text-sm">{{ $approval->drafted_at?->format('Y-m-d H:i') ?? '-' }}</div>
</div>
@if($approval->completed_at)
<div class="pr-6{{ $approval->parent_doc_id ? ' border-r border-gray-200 mr-6' : '' }}">
<span class="text-xs text-gray-500">완료일</span>
<div class="mt-1 text-sm">{{ $approval->completed_at->format('Y-m-d H:i') }}</div>
</div>
@endif
@if($approval->parent_doc_id)
<div class="pr-6">
<span class="text-xs text-gray-500">원본 문서</span>
<div class="mt-1 text-sm">
<a href="{{ route('approvals.show', $approval->parent_doc_id) }}" class="text-blue-600 hover:underline">
{{ $approval->parentDocument?->document_number ?? '원본 보기' }}
</a>
</div>
</div>
@endif
</div>
{{-- 결재서명란 --}}
<div class="shrink-0">
@include('approvals.partials._approval-stamp-table', ['approval' => $approval])
</div>
</div> </div>
{{-- 회수 사유 표시 --}} {{-- 회수 사유 표시 --}}
@@ -85,56 +92,47 @@ class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg transition
</div> </div>
</div> </div>
{{-- 결재 진행 단계 --}} {{-- 결재 의견 --}}
@php
$stepsWithComments = $approval->steps->filter(fn($s) => $s->comment);
@endphp
@if($stepsWithComments->isNotEmpty())
<div class="bg-white rounded-lg shadow-sm p-6 mb-6"> <div class="bg-white rounded-lg shadow-sm p-6 mb-6">
<h3 class="text-lg font-semibold text-gray-800 mb-2">결재 진행</h3> <h3 class="text-lg font-semibold text-gray-800 mb-2">결재 의견</h3>
@include('approvals.partials._step-progress', [ <div class="space-y-2">
'steps' => $approval->steps->toArray(), @foreach($stepsWithComments as $step)
'currentStep' => $approval->current_step, <div class="flex gap-3 p-3 bg-gray-50 rounded-lg">
]) <div class="shrink-0">
@if($step->status === 'approved')
{{-- 결재 의견 목록 --}} @if(($step->approval_type ?? 'normal') === 'pre_decided')
@php <span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-indigo-100 text-indigo-600 text-xs">&#9889;</span>
$stepsWithComments = $approval->steps->filter(fn($s) => $s->comment); @else
@endphp <span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-green-100 text-green-600 text-xs">&#10003;</span>
@if($stepsWithComments->isNotEmpty()) @endif
<div class="mt-4 border-t pt-4"> @elseif($step->status === 'on_hold')
<h4 class="text-sm font-medium text-gray-700 mb-2">결재 의견</h4> <span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-amber-100 text-amber-600 text-xs">&#9208;</span>
<div class="space-y-2"> @else
@foreach($stepsWithComments as $step) <span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-red-100 text-red-600 text-xs">&#10007;</span>
<div class="flex gap-3 p-3 bg-gray-50 rounded-lg"> @endif
<div class="shrink-0"> </div>
@if($step->status === 'approved') <div>
@if(($step->approval_type ?? 'normal') === 'pre_decided') <div class="text-sm font-medium">
<span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-indigo-100 text-indigo-600 text-xs">&#9889;</span> {{ $step->approver_name ?? ($step->approver?->name ?? '') }}
@else @if(($step->approval_type ?? 'normal') === 'pre_decided')
<span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-green-100 text-green-600 text-xs">&#10003;</span> <span class="text-xs text-indigo-500 font-normal">(전결)</span>
@endif @endif
@elseif($step->status === 'on_hold') @if($step->status === 'on_hold')
<span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-amber-100 text-amber-600 text-xs">&#9208;</span> <span class="text-xs text-amber-500 font-normal">(보류)</span>
@else @endif
<span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-red-100 text-red-600 text-xs">&#10007;</span> <span class="text-gray-400 font-normal text-xs">{{ $step->acted_at?->format('Y-m-d H:i') }}</span>
@endif
</div>
<div>
<div class="text-sm font-medium">
{{ $step->approver_name ?? ($step->approver?->name ?? '') }}
@if(($step->approval_type ?? 'normal') === 'pre_decided')
<span class="text-xs text-indigo-500 font-normal">(전결)</span>
@endif
@if($step->status === 'on_hold')
<span class="text-xs text-amber-500 font-normal">(보류)</span>
@endif
<span class="text-gray-400 font-normal text-xs">{{ $step->acted_at?->format('Y-m-d H:i') }}</span>
</div>
<p class="text-sm text-gray-600 mt-1">{{ $step->comment }}</p>
</div>
</div> </div>
@endforeach <p class="text-sm text-gray-600 mt-1">{{ $step->comment }}</p>
</div>
</div> </div>
</div> @endforeach
@endif </div>
</div> </div>
@endif
{{-- 결재 처리 (승인/반려/보류/전결) --}} {{-- 결재 처리 (승인/반려/보류/전결) --}}
@if($approval->isActionable() && $approval->isCurrentApprover(auth()->id())) @if($approval->isActionable() && $approval->isCurrentApprover(auth()->id()))