Files
sam-manage/resources/views/sales/admin-prospects/index.blade.php
김보곤 11bacef55c feat:영업파트너 고객관리 메뉴 추가 (관리자 전용)
- AdminProspectController 생성 (관리자/슈퍼관리자만 접근)
- 전체 영업파트너의 고객 현황을 한눈에 파악
- 영업파트너별 필터, 상태별 필터 제공
- 영업/매니저 진행률 및 개발 상태 표시
- 상세 모달에서 담당자 정보 및 진행 현황 확인
- AdminProspectMenuSeeder 생성 (메뉴 추가용)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 11:54:37 +09:00

260 lines
13 KiB
PHP

@extends('layouts.app')
@section('title', '영업파트너 고객관리')
@section('content')
<div class="flex flex-col h-full">
<!-- 페이지 헤더 -->
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-6 flex-shrink-0">
<div>
<h1 class="text-2xl font-bold text-gray-800">영업파트너 고객관리</h1>
<p class="text-sm text-gray-500 mt-1">전체 영업파트너의 고객 현황을 관리합니다 (관리자 전용)</p>
</div>
</div>
<!-- 통계 카드 -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4 flex-shrink-0">
<div class="bg-white rounded-lg shadow-sm p-4">
<div class="text-sm text-gray-500">전체 고객</div>
<div class="text-2xl font-bold text-gray-800">{{ number_format($stats['total']) }}</div>
</div>
<div class="bg-blue-50 rounded-lg shadow-sm p-4">
<div class="text-sm text-blue-600">영업 진행중</div>
<div class="text-2xl font-bold text-blue-800">{{ number_format($stats['active']) }}</div>
</div>
<div class="bg-orange-50 rounded-lg shadow-sm p-4">
<div class="text-sm text-orange-600">영업권 만료</div>
<div class="text-2xl font-bold text-orange-800">{{ number_format($stats['expired']) }}</div>
</div>
<div class="bg-emerald-50 rounded-lg shadow-sm p-4">
<div class="text-sm text-emerald-600">계약 완료</div>
<div class="text-2xl font-bold text-emerald-800">{{ number_format($stats['converted']) }}</div>
</div>
</div>
<!-- 필터 영역 -->
<div class="flex-shrink-0 mb-4">
<form method="GET" class="flex flex-wrap gap-2 sm:gap-3 items-center bg-white p-3 rounded-lg shadow-sm">
<!-- 상태 필터 버튼 -->
<div class="flex gap-1 flex-shrink-0">
<a href="{{ route('sales.admin-prospects.index', array_merge(request()->except('status', 'page'), [])) }}"
class="px-3 py-2 rounded-lg text-sm font-medium transition {{ !request('status') ? 'bg-gray-800 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }}">
전체
</a>
<a href="{{ route('sales.admin-prospects.index', array_merge(request()->except('page'), ['status' => 'active'])) }}"
class="px-3 py-2 rounded-lg text-sm font-medium transition {{ request('status') === 'active' ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }}">
진행중
</a>
<a href="{{ route('sales.admin-prospects.index', array_merge(request()->except('page'), ['status' => 'converted'])) }}"
class="px-3 py-2 rounded-lg text-sm font-medium transition {{ request('status') === 'converted' ? 'bg-emerald-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }}">
계약완료
</a>
</div>
<!-- 구분선 -->
<div class="hidden sm:block w-px h-8 bg-gray-300"></div>
<!-- 현재 status 유지 -->
@if(request('status'))
<input type="hidden" name="status" value="{{ request('status') }}">
@endif
<!-- 영업파트너 선택 -->
<div class="w-40 flex-shrink-0">
<select name="registered_by" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm">
<option value="">전체 파트너</option>
@foreach($salesPartners as $partner)
<option value="{{ $partner->id }}" {{ request('registered_by') == $partner->id ? 'selected' : '' }}>
{{ $partner->name }}
</option>
@endforeach
</select>
</div>
<!-- 검색 입력 -->
<div class="flex-1 min-w-0">
<input type="text"
name="search"
value="{{ request('search') }}"
placeholder="업체명, 사업자번호, 대표자, 연락처 검색..."
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm">
</div>
<!-- 검색 버튼 -->
<button type="submit" class="bg-gray-700 hover:bg-gray-800 text-white px-4 py-2 rounded-lg transition text-sm flex-shrink-0">
검색
</button>
</form>
</div>
<!-- 테이블 -->
<div class="bg-white rounded-lg shadow-sm overflow-hidden flex-1 flex flex-col min-h-0">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">업체명</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">담당 파트너</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">담당 매니저</th>
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">영업 진행률</th>
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">매니저 진행률</th>
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">개발 상태</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">상태</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">등록일</th>
<th class="px-4 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($prospects as $prospect)
<tr class="hover:bg-gray-50">
<td class="px-4 py-3 whitespace-nowrap">
<div class="font-medium text-gray-900">{{ $prospect->company_name }}</div>
<div class="text-xs text-gray-500">{{ $prospect->business_number }}</div>
</td>
<td class="px-4 py-3 whitespace-nowrap">
@if($prospect->registeredBy)
<span class="text-sm text-gray-900">{{ $prospect->registeredBy->name }}</span>
@else
<span class="text-sm text-gray-400">-</span>
@endif
</td>
<td class="px-4 py-3 whitespace-nowrap">
@if($prospect->manager_user)
<span class="text-sm text-gray-900">{{ $prospect->manager_user->name }}</span>
@else
<span class="text-sm text-gray-400">미지정</span>
@endif
</td>
<td class="px-4 py-3 whitespace-nowrap text-center">
<div class="flex items-center justify-center gap-2">
<div class="w-16 bg-gray-200 rounded-full h-2">
<div class="bg-blue-500 h-2 rounded-full" style="width: {{ $prospect->sales_progress }}%"></div>
</div>
<span class="text-xs text-gray-600">{{ $prospect->sales_progress }}%</span>
</div>
</td>
<td class="px-4 py-3 whitespace-nowrap text-center">
<div class="flex items-center justify-center gap-2">
<div class="w-16 bg-gray-200 rounded-full h-2">
<div class="bg-green-500 h-2 rounded-full" style="width: {{ $prospect->manager_progress }}%"></div>
</div>
<span class="text-xs text-gray-600">{{ $prospect->manager_progress }}%</span>
</div>
</td>
<td class="px-4 py-3 whitespace-nowrap text-center">
<span class="px-2 py-1 text-xs font-medium rounded-full
@if($prospect->hq_status === 'handover') bg-emerald-100 text-emerald-700
@elseif($prospect->hq_status === 'pending') bg-gray-100 text-gray-600
@else bg-purple-100 text-purple-700 @endif">
{{ $prospect->hq_status_label }}
</span>
</td>
<td class="px-4 py-3 whitespace-nowrap">
<span class="px-2 py-1 text-xs font-medium rounded-full {{ $prospect->status_color }}">
{{ $prospect->status_label }}
</span>
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
{{ $prospect->created_at->format('Y-m-d') }}
</td>
<td class="px-4 py-3 whitespace-nowrap text-right text-sm font-medium">
<button type="button" onclick="openDetailModal({{ $prospect->id }})" class="text-blue-600 hover:text-blue-900">상세</button>
</td>
</tr>
@empty
<tr>
<td colspan="9" class="px-6 py-12 text-center text-gray-500">
등록된 고객이 없습니다.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- 페이지네이션 -->
@if($prospects->hasPages())
<div class="px-6 py-4 border-t border-gray-200">
{{ $prospects->withQueryString()->links() }}
</div>
@endif
</div>
</div>
<!-- 상세 모달 -->
<div id="detailModal" class="hidden fixed inset-0 z-50 overflow-y-auto">
<div class="fixed inset-0 bg-black bg-opacity-50 transition-opacity"></div>
<div class="flex min-h-full items-center justify-center p-4">
<div id="detailModalContent" class="relative bg-white rounded-xl shadow-2xl w-full max-w-3xl">
<div class="p-12 text-center">
<svg class="w-8 h-8 animate-spin text-blue-600 mx-auto" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<p class="mt-2 text-gray-500">로딩 ...</p>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
function openDetailModal(id) {
const modal = document.getElementById('detailModal');
const content = document.getElementById('detailModalContent');
modal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
content.innerHTML = `
<div class="p-12 text-center">
<svg class="w-8 h-8 animate-spin text-blue-600 mx-auto" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<p class="mt-2 text-gray-500">로딩 중...</p>
</div>
`;
fetch(`/sales/admin-prospects/${id}/modal-show`, {
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'text/html'
}
})
.then(response => response.text())
.then(html => {
content.innerHTML = html;
})
.catch(error => {
content.innerHTML = `
<div class="p-6 text-center">
<p class="text-red-500">오류가 발생했습니다.</p>
<button onclick="closeDetailModal()" class="mt-4 px-4 py-2 bg-gray-600 text-white rounded-lg">닫기</button>
</div>
`;
});
}
function closeDetailModal() {
const modal = document.getElementById('detailModal');
modal.classList.add('hidden');
document.body.style.overflow = '';
}
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeDetailModal();
}
});
document.addEventListener('click', function(e) {
if (e.target.closest('[data-close-modal]')) {
e.preventDefault();
closeDetailModal();
}
});
</script>
@endpush