- 6단계 영업 프로세스 체크리스트 UI 구현 - 사용자별 체크포인트 저장/조회 API 추가 - 레거시 스타일 가로 아코디언 UI 적용 - 단계별 진행률 표시 및 꿀팁 모달 추가
610 lines
24 KiB
PHP
610 lines
24 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', 'SAM 영업 시나리오')
|
|
|
|
@push('styles')
|
|
<style>
|
|
/* Custom Scrollbar */
|
|
::-webkit-scrollbar { width: 8px; height: 8px; }
|
|
::-webkit-scrollbar-track { background: #f1f5f9; }
|
|
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
|
|
::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
|
|
|
|
/* Color Palette */
|
|
.step-blue { --step-bg: #dbeafe; --step-text: #2563eb; --step-accent: #3b82f6; }
|
|
.step-indigo { --step-bg: #e0e7ff; --step-text: #4f46e5; --step-accent: #6366f1; }
|
|
.step-purple { --step-bg: #f3e8ff; --step-text: #9333ea; --step-accent: #a855f7; }
|
|
.step-pink { --step-bg: #fce7f3; --step-text: #db2777; --step-accent: #ec4899; }
|
|
.step-orange { --step-bg: #ffedd5; --step-text: #ea580c; --step-accent: #f97316; }
|
|
.step-green { --step-bg: #dcfce7; --step-text: #16a34a; --step-accent: #22c55e; }
|
|
|
|
/* Step Cards Container */
|
|
.steps-container {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 1rem;
|
|
overflow-x: auto;
|
|
padding: 1rem 2rem 2rem;
|
|
scroll-behavior: smooth;
|
|
margin: 0 -1rem;
|
|
}
|
|
.steps-container::before {
|
|
content: '';
|
|
flex-shrink: 0;
|
|
width: 1rem;
|
|
}
|
|
.steps-container::after {
|
|
content: '';
|
|
flex-shrink: 0;
|
|
width: 1rem;
|
|
}
|
|
|
|
/* Step Card Wrapper */
|
|
.step-wrapper {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* Step Cards - Accordion Style */
|
|
.step-card {
|
|
position: relative;
|
|
padding: 1.5rem;
|
|
border-radius: 12px;
|
|
border: 1px solid #e2e8f0;
|
|
background: white;
|
|
cursor: pointer;
|
|
transition: all 0.5s cubic-bezier(0.25, 1, 0.5, 1);
|
|
width: 8rem;
|
|
opacity: 0.7;
|
|
}
|
|
.step-card:hover {
|
|
border-color: #cbd5e1;
|
|
box-shadow: 0 4px 12px -2px rgba(0,0,0,0.1);
|
|
opacity: 1;
|
|
}
|
|
.step-card.active {
|
|
width: 20rem;
|
|
opacity: 1;
|
|
border-color: var(--step-accent);
|
|
box-shadow: 0 20px 40px -10px rgba(59, 130, 246, 0.25);
|
|
z-index: 10;
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
/* Step Number Badge */
|
|
.step-number {
|
|
position: absolute;
|
|
top: 1rem;
|
|
right: 1rem;
|
|
width: 2rem;
|
|
height: 2rem;
|
|
border-radius: 9999px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 0.875rem;
|
|
font-weight: 700;
|
|
border: 2px solid white;
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
transition: all 0.3s;
|
|
background: #f1f5f9;
|
|
color: #94a3b8;
|
|
}
|
|
.step-card.active .step-number {
|
|
background: var(--step-accent);
|
|
color: white;
|
|
}
|
|
|
|
/* Step Icon */
|
|
.step-icon {
|
|
width: 3rem;
|
|
height: 3rem;
|
|
border-radius: 1rem;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: #f8fafc;
|
|
color: #94a3b8;
|
|
margin: 0 auto 1rem;
|
|
transition: all 0.3s;
|
|
}
|
|
.step-card.active .step-icon {
|
|
width: 4rem;
|
|
height: 4rem;
|
|
background: var(--step-bg);
|
|
color: var(--step-text);
|
|
}
|
|
|
|
/* Step Title */
|
|
.step-title {
|
|
font-weight: 700;
|
|
text-align: center;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
transition: all 0.3s;
|
|
font-size: 0.75rem;
|
|
color: #64748b;
|
|
}
|
|
.step-card.active .step-title {
|
|
font-size: 1.125rem;
|
|
color: #1e293b;
|
|
}
|
|
|
|
/* Step Subtitle & Description - Hidden when inactive */
|
|
.step-subtitle,
|
|
.step-description {
|
|
display: none;
|
|
}
|
|
.step-card.active .step-subtitle {
|
|
display: block;
|
|
font-size: 0.75rem;
|
|
color: #94a3b8;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
text-align: center;
|
|
margin-top: 0.25rem;
|
|
animation: fadeIn 0.3s ease-out;
|
|
}
|
|
.step-card.active .step-description {
|
|
display: block;
|
|
font-size: 0.875rem;
|
|
color: #64748b;
|
|
text-align: left;
|
|
margin-top: 1rem;
|
|
padding-top: 1rem;
|
|
border-top: 1px solid #f1f5f9;
|
|
line-height: 1.6;
|
|
animation: fadeIn 0.3s ease-out;
|
|
}
|
|
|
|
/* Progress Bar below card */
|
|
.step-progress-wrapper {
|
|
width: 6rem;
|
|
transition: width 0.5s cubic-bezier(0.25, 1, 0.5, 1);
|
|
}
|
|
.step-wrapper.active .step-progress-wrapper {
|
|
width: 16rem;
|
|
}
|
|
.step-progress-label {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
font-size: 0.75rem;
|
|
color: #64748b;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
.step-progress-bar {
|
|
height: 0.5rem;
|
|
background: #e2e8f0;
|
|
border-radius: 9999px;
|
|
overflow: hidden;
|
|
}
|
|
.step-progress-fill {
|
|
height: 100%;
|
|
border-radius: 9999px;
|
|
transition: width 1s ease-out;
|
|
}
|
|
|
|
/* Animations */
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; }
|
|
to { opacity: 1; }
|
|
}
|
|
@keyframes slideUp {
|
|
from { transform: translateY(20px); opacity: 0; }
|
|
to { transform: translateY(0); opacity: 1; }
|
|
}
|
|
.animate-slide-up {
|
|
animation: slideUp 0.4s ease-out;
|
|
}
|
|
|
|
/* Checkbox Styling */
|
|
.checkpoint-checkbox {
|
|
appearance: none;
|
|
width: 20px;
|
|
height: 20px;
|
|
border: 2px solid #d1d5db;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
position: relative;
|
|
flex-shrink: 0;
|
|
}
|
|
.checkpoint-checkbox:checked {
|
|
background: #3b82f6;
|
|
border-color: #3b82f6;
|
|
}
|
|
.checkpoint-checkbox:checked::after {
|
|
content: '';
|
|
position: absolute;
|
|
left: 6px;
|
|
top: 2px;
|
|
width: 5px;
|
|
height: 10px;
|
|
border: solid white;
|
|
border-width: 0 2px 2px 0;
|
|
transform: rotate(45deg);
|
|
}
|
|
.checkpoint-item.checked .checkpoint-title { text-decoration: line-through; color: #9ca3af; }
|
|
.checkpoint-item.checked .checkpoint-detail { color: #9ca3af; }
|
|
|
|
/* Modal */
|
|
.modal-backdrop {
|
|
background: rgba(0,0,0,0.5);
|
|
backdrop-filter: blur(4px);
|
|
animation: fadeIn 0.2s ease-out;
|
|
}
|
|
.modal-content {
|
|
animation: slideUp 0.3s ease-out;
|
|
}
|
|
|
|
/* Progress Bar Animation */
|
|
.progress-bar {
|
|
background: linear-gradient(90deg, #3b82f6, #2563eb);
|
|
transition: width 0.8s ease-out;
|
|
position: relative;
|
|
}
|
|
.progress-bar::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0; left: 0; right: 0; bottom: 0;
|
|
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
|
|
animation: shimmer 2s infinite;
|
|
}
|
|
@keyframes shimmer {
|
|
0% { transform: translateX(-100%); }
|
|
100% { transform: translateX(100%); }
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 1024px) {
|
|
.steps-container {
|
|
padding-left: 1rem;
|
|
padding-right: 1rem;
|
|
}
|
|
.step-card.active {
|
|
width: 16rem;
|
|
}
|
|
.step-wrapper.active .step-progress-wrapper {
|
|
width: 12rem;
|
|
}
|
|
}
|
|
</style>
|
|
@endpush
|
|
|
|
@section('content')
|
|
<div class="max-w-7xl mx-auto">
|
|
<!-- 페이지 헤더 -->
|
|
<div class="mb-6 flex items-center justify-between">
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-gray-800">SAM 영업 시나리오</h1>
|
|
<p class="text-gray-600 mt-1">성공적인 영업을 위한 6단계 프로세스 가이드</p>
|
|
</div>
|
|
<span class="text-xs font-medium px-2.5 py-1 bg-slate-100 text-slate-600 rounded-full">v1.0 Standard</span>
|
|
</div>
|
|
|
|
<!-- 전체 진행 현황 -->
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 mb-8">
|
|
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
|
<div class="flex-1">
|
|
<div class="flex justify-between items-end mb-2">
|
|
<div>
|
|
<h2 class="text-lg font-bold text-gray-900">전체 진행 현황</h2>
|
|
<p class="text-sm text-gray-500">모든 단계의 체크포인트를 완료하여 영업 성공률을 높이세요.</p>
|
|
</div>
|
|
<div class="text-right">
|
|
<span id="overall-percent" class="text-3xl font-extrabold text-blue-600">{{ $progress['percent'] }}%</span>
|
|
<span class="text-sm text-gray-400 ml-1">완료</span>
|
|
</div>
|
|
</div>
|
|
<div class="w-full bg-gray-100 rounded-full h-4 overflow-hidden relative">
|
|
<div id="overall-progress-bar" class="progress-bar h-full rounded-full relative" style="width: {{ $progress['percent'] }}%"></div>
|
|
</div>
|
|
<div class="flex justify-between mt-2 text-xs text-gray-400 font-medium">
|
|
<span>시작</span>
|
|
<span id="progress-text">{{ $progress['checked'] }} / {{ $progress['total'] }} 항목 완료</span>
|
|
<span>완료</span>
|
|
</div>
|
|
</div>
|
|
<div class="hidden md:flex items-center justify-center w-16 h-16 rounded-full bg-blue-50 text-blue-600 border border-blue-100 shrink-0">
|
|
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 단계 카드 (가로 아코디언 스타일) -->
|
|
<div class="steps-container">
|
|
@foreach($steps as $step)
|
|
@php
|
|
$stepChecked = isset($checklist[$step['id']]) ? count($checklist[$step['id']]) : 0;
|
|
$stepTotal = count($step['checkpoints']);
|
|
$stepPercent = $stepTotal > 0 ? round(($stepChecked / $stepTotal) * 100) : 0;
|
|
@endphp
|
|
<div class="step-wrapper {{ $loop->first ? 'active' : '' }}" data-step-id="{{ $step['id'] }}">
|
|
<div class="step-card step-{{ $step['color'] }} {{ $loop->first ? 'active' : '' }}"
|
|
data-step-id="{{ $step['id'] }}"
|
|
onclick="selectStep({{ $step['id'] }})">
|
|
<!-- 단계 번호 -->
|
|
<div class="step-number">{{ $step['id'] }}</div>
|
|
<!-- 아이콘 -->
|
|
<div class="step-icon">
|
|
@include('lab.management.partials.sales-scenario-icon', ['icon' => $step['icon']])
|
|
</div>
|
|
<!-- 제목 -->
|
|
<h3 class="step-title">{{ $step['title'] }}</h3>
|
|
<p class="step-subtitle">{{ $step['subtitle'] }}</p>
|
|
<p class="step-description">{{ $step['description'] }}</p>
|
|
</div>
|
|
<!-- 진행률 바 -->
|
|
<div class="step-progress-wrapper">
|
|
<div class="step-progress-label">
|
|
<span>진행률</span>
|
|
<span id="progress-percent-{{ $step['id'] }}">{{ $stepPercent }}%</span>
|
|
</div>
|
|
<div class="step-progress-bar">
|
|
<div class="step-progress-fill" id="progress-fill-{{ $step['id'] }}"
|
|
style="width: {{ $stepPercent }}%; background: var(--step-accent);"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
|
|
<!-- 상세 패널 -->
|
|
<div id="detail-panel" class="bg-white rounded-xl shadow-lg border border-gray-100 overflow-hidden animate-slide-up">
|
|
@foreach($steps as $step)
|
|
<div class="step-detail {{ $loop->first ? '' : 'hidden' }}" id="detail-{{ $step['id'] }}">
|
|
<div class="p-6 md:p-8 flex flex-col lg:flex-row gap-8">
|
|
<!-- 좌측: 헤더 & 설명 -->
|
|
<div class="lg:w-1/3 space-y-6">
|
|
<div>
|
|
<div class="inline-flex items-center gap-2 px-3 py-1 rounded-full text-xs font-bold mb-4 step-{{ $step['color'] }}" style="background: var(--step-bg); color: var(--step-text);">
|
|
STEP {{ $step['id'] }}
|
|
</div>
|
|
<h2 class="text-3xl font-bold text-gray-900 mb-2">{{ $step['title'] }}</h2>
|
|
<p class="text-lg text-gray-500 font-light">{{ $step['subtitle'] }}</p>
|
|
</div>
|
|
<p class="text-gray-600 leading-relaxed">{{ $step['description'] }}</p>
|
|
<div class="p-4 bg-gray-50 rounded-xl border border-gray-100">
|
|
<h4 class="text-sm font-bold text-gray-800 mb-2 flex items-center gap-2">
|
|
<svg class="w-4 h-4 text-yellow-500" fill="currentColor" viewBox="0 0 24 24">
|
|
<path 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>
|
|
Sales Tip
|
|
</h4>
|
|
<p class="text-sm text-gray-600 italic">"{{ $step['tips'] }}"</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 우측: 체크포인트 -->
|
|
<div class="lg:w-2/3 bg-gray-50 rounded-xl p-6 border border-gray-100">
|
|
<h3 class="text-lg font-bold text-gray-900 mb-4 flex items-center gap-2">
|
|
<svg class="w-5 h-5 text-blue-600" 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 2m-6 9l2 2 4-4" />
|
|
</svg>
|
|
핵심 체크포인트
|
|
</h3>
|
|
<div class="space-y-3">
|
|
@foreach($step['checkpoints'] as $idx => $checkpoint)
|
|
<div class="checkpoint-item relative group {{ isset($checklist[$step['id']]) && in_array($idx, $checklist[$step['id']]) ? 'checked' : '' }}"
|
|
data-step="{{ $step['id'] }}" data-index="{{ $idx }}">
|
|
<label class="flex items-start gap-4 p-4 bg-white rounded-xl border border-gray-200 hover:border-blue-300 hover:shadow-md transition-all cursor-pointer pr-12">
|
|
<input type="checkbox" class="checkpoint-checkbox mt-1"
|
|
data-step="{{ $step['id'] }}"
|
|
data-index="{{ $idx }}"
|
|
{{ isset($checklist[$step['id']]) && in_array($idx, $checklist[$step['id']]) ? 'checked' : '' }}
|
|
onchange="toggleCheckpoint(this)">
|
|
<div class="flex-grow">
|
|
<span class="checkpoint-title block text-sm font-bold text-gray-800 mb-1">{{ $checkpoint['title'] }}</span>
|
|
<span class="checkpoint-detail block text-sm text-gray-600 leading-relaxed">{{ $checkpoint['detail'] }}</span>
|
|
</div>
|
|
</label>
|
|
<!-- 꿀팁 버튼 -->
|
|
<button onclick="showTip({{ $step['id'] }}, {{ $idx }})"
|
|
class="absolute right-4 top-4 p-2 text-gray-400 hover:text-yellow-500 hover:bg-yellow-50 rounded-full transition-all opacity-0 group-hover:opacity-100"
|
|
title="꿀팁 보기">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 꿀팁 모달 -->
|
|
<div id="tip-modal" class="fixed inset-0 z-50 hidden">
|
|
<div class="modal-backdrop absolute inset-0" onclick="closeTipModal()"></div>
|
|
<div class="flex items-center justify-center min-h-screen p-4">
|
|
<div class="modal-content bg-white rounded-2xl shadow-2xl w-full max-w-lg overflow-hidden relative z-10">
|
|
<div class="p-6 border-b border-gray-100 flex justify-between items-center bg-gray-50">
|
|
<h3 class="text-lg font-bold text-gray-900 flex items-center gap-2">
|
|
<svg class="w-5 h-5 text-yellow-500" fill="currentColor" viewBox="0 0 24 24">
|
|
<path 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>
|
|
Sales Pro Tip
|
|
</h3>
|
|
<button onclick="closeTipModal()" class="p-2 hover:bg-gray-200 rounded-full transition-colors">
|
|
<svg class="w-5 h-5 text-gray-500" 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="p-6">
|
|
<h4 id="tip-title" class="text-xl font-bold text-gray-900 mb-3"></h4>
|
|
<p id="tip-detail" class="text-gray-600 mb-6 leading-relaxed"></p>
|
|
<div class="bg-blue-50 border border-blue-100 rounded-xl p-5">
|
|
<div class="flex items-start gap-3">
|
|
<div class="p-2 bg-blue-100 rounded-lg text-blue-600 shrink-0">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5" />
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h5 class="font-bold text-blue-800 mb-1">실전 꿀팁</h5>
|
|
<p id="tip-pro" class="text-blue-700 text-sm leading-relaxed"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="p-4 border-t border-gray-100 bg-gray-50 flex justify-end">
|
|
<button onclick="closeTipModal()" class="px-4 py-2 bg-gray-900 text-white rounded-lg hover:bg-gray-800 font-medium transition-colors">
|
|
확인
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endsection
|
|
|
|
@push('scripts')
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const csrfToken = '{{ csrf_token() }}';
|
|
const stepsData = @json($steps);
|
|
let checklistData = @json($checklist);
|
|
let progressData = @json($progress);
|
|
|
|
// 단계 선택
|
|
window.selectStep = function(stepId) {
|
|
// 모든 wrapper와 card에서 active 제거
|
|
document.querySelectorAll('.step-wrapper').forEach(w => w.classList.remove('active'));
|
|
document.querySelectorAll('.step-card').forEach(card => card.classList.remove('active'));
|
|
|
|
// 선택된 항목에 active 추가
|
|
const activeWrapper = document.querySelector(`.step-wrapper[data-step-id="${stepId}"]`);
|
|
const activeCard = document.querySelector(`.step-card[data-step-id="${stepId}"]`);
|
|
if (activeWrapper) activeWrapper.classList.add('active');
|
|
if (activeCard) activeCard.classList.add('active');
|
|
|
|
// 상세 패널 전환
|
|
document.querySelectorAll('.step-detail').forEach(detail => detail.classList.add('hidden'));
|
|
const detailPanel = document.getElementById(`detail-${stepId}`);
|
|
if (detailPanel) {
|
|
detailPanel.classList.remove('hidden');
|
|
detailPanel.classList.add('animate-slide-up');
|
|
}
|
|
|
|
// 스크롤하여 카드를 가운데로
|
|
if (activeWrapper) {
|
|
activeWrapper.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
|
|
}
|
|
};
|
|
|
|
// 체크포인트 토글
|
|
window.toggleCheckpoint = function(checkbox) {
|
|
const stepId = parseInt(checkbox.dataset.step);
|
|
const index = parseInt(checkbox.dataset.index);
|
|
const isChecked = checkbox.checked;
|
|
|
|
// UI 즉시 업데이트
|
|
const item = checkbox.closest('.checkpoint-item');
|
|
if (isChecked) {
|
|
item.classList.add('checked');
|
|
} else {
|
|
item.classList.remove('checked');
|
|
}
|
|
|
|
// API 호출
|
|
fetch('{{ route("lab.management.sales-scenario.toggle") }}', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': csrfToken
|
|
},
|
|
body: JSON.stringify({
|
|
step_id: stepId,
|
|
checkpoint_index: index,
|
|
is_checked: isChecked
|
|
})
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
checklistData[stepId] = data.data;
|
|
progressData = data.progress;
|
|
updateProgressUI();
|
|
updateStepProgress(stepId);
|
|
} else {
|
|
// 실패시 롤백
|
|
checkbox.checked = !isChecked;
|
|
item.classList.toggle('checked');
|
|
showToast(data.message || '저장에 실패했습니다.', 'error');
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error('API Error:', err);
|
|
checkbox.checked = !isChecked;
|
|
item.classList.toggle('checked');
|
|
showToast('네트워크 오류가 발생했습니다.', 'error');
|
|
});
|
|
};
|
|
|
|
// 진행률 UI 업데이트
|
|
function updateProgressUI() {
|
|
document.getElementById('overall-percent').textContent = progressData.percent + '%';
|
|
document.getElementById('overall-progress-bar').style.width = progressData.percent + '%';
|
|
document.getElementById('progress-text').textContent = `${progressData.checked} / ${progressData.total} 항목 완료`;
|
|
}
|
|
|
|
// 단계별 진행률 업데이트
|
|
function updateStepProgress(stepId) {
|
|
const step = stepsData.find(s => s.id === stepId);
|
|
if (!step) return;
|
|
|
|
const checked = checklistData[stepId] ? checklistData[stepId].length : 0;
|
|
const total = step.checkpoints.length;
|
|
const percent = Math.round((checked / total) * 100);
|
|
|
|
const percentEl = document.getElementById(`progress-percent-${stepId}`);
|
|
const fillEl = document.getElementById(`progress-fill-${stepId}`);
|
|
if (percentEl) percentEl.textContent = percent + '%';
|
|
if (fillEl) fillEl.style.width = percent + '%';
|
|
}
|
|
|
|
// 꿀팁 모달 표시
|
|
window.showTip = function(stepId, index) {
|
|
const step = stepsData.find(s => s.id === stepId);
|
|
if (!step) return;
|
|
|
|
const checkpoint = step.checkpoints[index];
|
|
if (!checkpoint) return;
|
|
|
|
document.getElementById('tip-title').textContent = checkpoint.title;
|
|
document.getElementById('tip-detail').textContent = checkpoint.detail;
|
|
document.getElementById('tip-pro').textContent = checkpoint.pro_tip;
|
|
document.getElementById('tip-modal').classList.remove('hidden');
|
|
};
|
|
|
|
// 꿀팁 모달 닫기
|
|
window.closeTipModal = function() {
|
|
document.getElementById('tip-modal').classList.add('hidden');
|
|
};
|
|
|
|
// ESC 키로 모달 닫기
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape') {
|
|
closeTipModal();
|
|
}
|
|
});
|
|
|
|
// 토스트 메시지 (간단한 구현)
|
|
window.showToast = function(message, type = 'info') {
|
|
console.log(`[${type.toUpperCase()}] ${message}`);
|
|
};
|
|
|
|
// 첫 번째 카드 활성화
|
|
selectStep(1);
|
|
});
|
|
</script>
|
|
@endpush |