Files
sam-manage/resources/views/sales/modals/scenario-step.blade.php
김보곤 646cd7d6ef feat:인계 완료 항목 영업/매니저 기록 조회 기능 추가
- 인계 완료 섹션에 영업/매니저 기록 조회 버튼 추가
- readonly 모드로 열어 수정 불가, 조회만 가능
- prospectManagerScenario에 readonly 파라미터 지원 추가
- 단계 이동 시 readonly 파라미터 유지
- 마지막 단계 버튼 텍스트 조건부 표시 (완료/닫기)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 17:15:19 +09:00

252 lines
13 KiB
PHP

{{-- 시나리오 단계별 체크리스트 --}}
@php
use App\Models\Sales\SalesScenarioChecklist;
// $steps가 없거나 비어있으면 config에서 가져오기 (안전장치)
if (empty($steps)) {
$steps = config($scenarioType === 'sales' ? 'sales_scenario.sales_steps' : 'sales_scenario.manager_steps', []);
}
$step = $step ?? collect($steps)->firstWhere('id', $currentStep ?? 1);
// prospect 모드 확인
$isProspectMode = isset($isProspect) && $isProspect;
$entity = $isProspectMode ? $prospect : $tenant;
$entityId = $entity->id;
// 읽기 전용 모드 확인
$isReadonly = isset($readonly) && $readonly;
// DB에서 체크된 항목 조회
$checklist = $isProspectMode
? SalesScenarioChecklist::getChecklistByProspect($entityId, $scenarioType)
: SalesScenarioChecklist::getChecklist($entityId, $scenarioType);
@endphp
<div class="space-y-6">
{{-- 단계 헤더 --}}
<div class="flex items-start gap-4">
<div class="{{ $step['bg_class'] }} {{ $step['text_class'] }} p-3 rounded-xl">
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{!! $icons[$step['icon']] ?? '' !!}
</svg>
</div>
<div class="flex-1">
<div class="flex items-center gap-2">
<span class="text-sm font-medium text-gray-500">STEP {{ $step['id'] }}</span>
<span class="text-sm text-gray-400">{{ $step['subtitle'] }}</span>
</div>
<h2 class="text-2xl font-bold text-gray-900">{{ $step['title'] }}</h2>
<p class="mt-1 text-gray-600">{{ $step['description'] }}</p>
</div>
</div>
{{-- 매니저용 (있는 경우) --}}
@if(isset($step['tips']))
<div class="bg-amber-50 border border-amber-200 rounded-lg p-4">
<div class="flex items-start gap-3">
<svg class="w-5 h-5 text-amber-600 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</svg>
<div>
<p class="text-sm font-medium text-amber-800">매니저 TIP</p>
<p class="text-sm text-amber-700">{{ $step['tips'] }}</p>
</div>
</div>
</div>
@endif
{{-- 체크포인트 목록 --}}
<div class="space-y-4">
@foreach($step['checkpoints'] as $checkpoint)
@php
$checkKey = "{$step['id']}_{$checkpoint['id']}";
$isChecked = isset($checklist[$checkKey]);
@endphp
<div x-data="{
expanded: false,
checked: {{ $isChecked ? 'true' : 'false' }},
isProspect: {{ $isProspectMode ? 'true' : 'false' }},
entityId: {{ $entityId }},
readonly: {{ $isReadonly ? 'true' : 'false' }},
async toggle() {
if (this.readonly) return;
this.checked = !this.checked;
try {
const url = this.isProspect
? '/sales/scenarios/prospect/checklist/toggle'
: '/sales/scenarios/checklist/toggle';
const bodyData = this.isProspect
? {
prospect_id: this.entityId,
scenario_type: '{{ $scenarioType }}',
step_id: {{ $step['id'] }},
checkpoint_id: '{{ $checkpoint['id'] }}',
checked: this.checked,
}
: {
tenant_id: this.entityId,
scenario_type: '{{ $scenarioType }}',
step_id: {{ $step['id'] }},
checkpoint_id: '{{ $checkpoint['id'] }}',
checked: this.checked,
};
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content,
'Accept': 'application/json',
},
body: JSON.stringify(bodyData),
});
const result = await response.json();
if (result.success) {
window.dispatchEvent(new CustomEvent('progress-updated', { detail: result.progress }));
}
} catch (error) {
console.error('체크리스트 토글 실패:', error);
this.checked = !this.checked;
}
}
}"
class="bg-white border rounded-xl overflow-hidden transition-all duration-200"
:class="checked ? 'border-green-300 bg-green-50/50' : 'border-gray-200 hover:border-gray-300'">
{{-- 체크포인트 헤더 --}}
<div class="flex items-center gap-4 p-4 cursor-pointer" @click="expanded = !expanded">
{{-- 체크박스 --}}
<button type="button"
@click.stop="toggle()"
class="flex-shrink-0 w-6 h-6 rounded-full border-2 flex items-center justify-center transition-all"
:class="[
checked ? 'bg-green-500 border-green-500' : 'border-gray-300',
readonly ? 'cursor-not-allowed opacity-60' : 'hover:border-green-400'
]"
:disabled="readonly">
<svg x-show="checked" class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7" />
</svg>
</button>
{{-- 제목 설명 --}}
<div class="flex-1 min-w-0">
<h4 class="font-semibold text-gray-900" :class="checked && 'line-through text-gray-500'">
{{ $checkpoint['title'] }}
</h4>
<p class="text-sm text-gray-600 truncate">{{ $checkpoint['detail'] }}</p>
</div>
{{-- 확장 아이콘 --}}
<svg class="w-5 h-5 text-gray-400 transition-transform duration-200"
:class="expanded && 'rotate-180'"
fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
{{-- 확장 콘텐츠 --}}
<div x-show="expanded"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 -translate-y-2"
x-transition:enter-end="opacity-100 translate-y-0"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100 translate-y-0"
x-transition:leave-end="opacity-0 -translate-y-2"
class="border-t border-gray-100">
<div class="p-4 space-y-4">
{{-- 상세 설명 --}}
<div>
<h5 class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">상세 설명</h5>
<p class="text-sm text-gray-700">{{ $checkpoint['detail'] }}</p>
</div>
{{-- PRO TIP --}}
<div class="bg-gradient-to-r from-indigo-50 to-purple-50 rounded-lg p-4">
<div class="flex items-start gap-3">
<div class="p-1.5 bg-indigo-100 rounded-lg">
<svg class="w-4 h-4 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<div>
<p class="text-xs font-semibold text-indigo-800 uppercase">PRO TIP</p>
<p class="text-sm text-indigo-700 mt-1">{{ $checkpoint['pro_tip'] }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
@endforeach
</div>
{{-- 계약 체결 단계 (Step 6)에서만 상품 선택 컴포넌트 표시 --}}
@if($step['id'] === 6 && $scenarioType === 'sales')
@include('sales.modals.partials.product-selection', [
'entity' => $entity,
'isProspect' => $isProspectMode,
])
@endif
{{-- 단계 이동 버튼 --}}
@php
$currentStepId = (int) $step['id'];
$totalSteps = count($steps);
$isLastStep = ($currentStepId >= $totalSteps);
$nextStepId = $currentStepId + 1;
$prevStepId = $currentStepId - 1;
$stepColor = $step['color'] ?? 'blue';
// 라우트 결정
$routeName = $isProspectMode
? 'sales.scenarios.prospect.' . $scenarioType
: 'sales.scenarios.' . $scenarioType;
// readonly 파라미터
$readonlyParam = $isReadonly ? '&readonly=1' : '';
@endphp
<div class="flex items-center justify-between pt-4 border-t border-gray-200">
{{-- 이전 단계 버튼 --}}
@if($currentStepId > 1)
<button type="button"
hx-get="{{ route($routeName, $entityId) }}?step={{ $prevStepId }}{{ $readonlyParam }}"
hx-target="#scenario-step-content"
hx-swap="innerHTML"
x-on:click="window.dispatchEvent(new CustomEvent('step-changed', { detail: {{ $prevStepId }} }))"
class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors">
<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="M15 19l-7-7 7-7" />
</svg>
이전 단계
</button>
@else
<div></div>
@endif
{{-- 다음 단계 / 완료 버튼 --}}
@if($isLastStep)
<button type="button"
x-on:click="window.dispatchEvent(new CustomEvent('scenario-completed', { detail: { {{ $isProspectMode ? 'prospectId' : 'tenantId' }}: {{ $entityId }}, scenarioType: '{{ $scenarioType }}' } }))"
class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-green-600 rounded-lg hover:bg-green-700 transition-colors">
<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="M5 13l4 4L19 7" />
</svg>
{{ $isReadonly ? '닫기' : '완료' }}
</button>
@else
<button type="button"
hx-get="{{ route($routeName, $entityId) }}?step={{ $nextStepId }}{{ $readonlyParam }}"
hx-target="#scenario-step-content"
hx-swap="innerHTML"
x-on:click="window.dispatchEvent(new CustomEvent('step-changed', { detail: {{ $nextStepId }} }))"
class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-indigo-600 rounded-lg hover:bg-indigo-700 transition-colors">
다음 단계
<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="M9 5l7 7-7 7" />
</svg>
</button>
@endif
</div>
</div>