- @push('scripts') 대신 인라인 x-data로 변경 (HTMX 호환)
- x-collapse 플러그인 의존성 제거, x-transition 사용
- $parent 참조 대신 window 이벤트(CustomEvent) 사용
- 체크리스트 토글, 진행률 업데이트, 단계 이동 정상화
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
231 lines
12 KiB
PHP
231 lines
12 KiB
PHP
{{-- 시나리오 단계별 체크리스트 --}}
|
|
@php
|
|
use App\Models\Sales\SalesScenarioChecklist;
|
|
|
|
$step = $step ?? collect($steps)->firstWhere('id', $currentStep ?? 1);
|
|
// DB에서 체크된 항목 조회
|
|
$checklist = SalesScenarioChecklist::getChecklist($tenant->id, $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' }},
|
|
async toggle() {
|
|
this.checked = !this.checked;
|
|
try {
|
|
const response = await fetch('/sales/scenarios/checklist/toggle', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content,
|
|
'Accept': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
tenant_id: {{ $tenant->id }},
|
|
scenario_type: '{{ $scenarioType }}',
|
|
step_id: {{ $step['id'] }},
|
|
checkpoint_id: '{{ $checkpoint['id'] }}',
|
|
checked: this.checked,
|
|
}),
|
|
});
|
|
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 hover:border-green-400'">
|
|
<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>
|
|
|
|
{{-- 하단: 상담 기록 및 파일 영역 (마지막 단계에서만) --}}
|
|
@if($step['id'] === 6)
|
|
<div class="border-t border-gray-200 pt-6 space-y-4">
|
|
<h3 class="text-lg font-semibold text-gray-900">상담 기록 및 첨부파일</h3>
|
|
|
|
{{-- 상담 기록 --}}
|
|
<div id="consultation-log-container"
|
|
hx-get="{{ route('sales.consultations.index', $tenant->id) }}?scenario_type={{ $scenarioType }}&step_id={{ $step['id'] }}"
|
|
hx-trigger="load"
|
|
hx-swap="innerHTML">
|
|
<div class="animate-pulse flex space-x-4">
|
|
<div class="flex-1 space-y-4 py-1">
|
|
<div class="h-4 bg-gray-200 rounded w-3/4"></div>
|
|
<div class="space-y-2">
|
|
<div class="h-4 bg-gray-200 rounded"></div>
|
|
<div class="h-4 bg-gray-200 rounded w-5/6"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- 음성 녹음 --}}
|
|
<div class="mt-4">
|
|
@include('sales.modals.voice-recorder', [
|
|
'tenant' => $tenant,
|
|
'scenarioType' => $scenarioType,
|
|
'stepId' => $step['id'],
|
|
])
|
|
</div>
|
|
|
|
{{-- 첨부파일 업로드 --}}
|
|
<div class="mt-4">
|
|
@include('sales.modals.file-uploader', [
|
|
'tenant' => $tenant,
|
|
'scenarioType' => $scenarioType,
|
|
'stepId' => $step['id'],
|
|
])
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- 단계 이동 버튼 --}}
|
|
<div class="flex items-center justify-between pt-4 border-t border-gray-200">
|
|
@if($step['id'] > 1)
|
|
<button type="button"
|
|
hx-get="{{ route('sales.scenarios.' . $scenarioType, $tenant->id) }}?step={{ $step['id'] - 1 }}"
|
|
hx-target="#scenario-step-content"
|
|
hx-swap="innerHTML"
|
|
@click="window.dispatchEvent(new CustomEvent('step-changed', { detail: {{ $step['id'] - 1 }} }))"
|
|
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($step['id'] < count($steps ?? []))
|
|
<button type="button"
|
|
hx-get="{{ route('sales.scenarios.' . $scenarioType, $tenant->id) }}?step={{ $step['id'] + 1 }}"
|
|
hx-target="#scenario-step-content"
|
|
hx-swap="innerHTML"
|
|
@click="window.dispatchEvent(new CustomEvent('step-changed', { detail: {{ $step['id'] + 1 }} }))"
|
|
class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-{{ $step['color'] }}-600 rounded-lg hover:bg-{{ $step['color'] }}-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>
|
|
@else
|
|
<button type="button"
|
|
@click="window.dispatchEvent(new CustomEvent('scenario-modal-closed'))"
|
|
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>
|
|
완료
|
|
</button>
|
|
@endif
|
|
</div>
|
|
</div>
|