Files
sam-manage/resources/views/sales/admin-prospects/index.blade.php

254 lines
10 KiB
PHP

@extends('layouts.app')
@section('title', '영업파트너 고객관리')
@push('styles')
<style>
/* 새로고침 버튼 아이콘 상태 */
.refresh-btn [data-refresh-spin] {
display: none;
}
.refresh-btn [data-refresh-icon] {
display: inline-block;
}
/* 로딩 중: 스피너 표시, 아이콘 숨김 */
.refresh-btn.htmx-request [data-refresh-spin] {
display: inline-block;
}
.refresh-btn.htmx-request [data-refresh-icon] {
display: none;
}
</style>
@endpush
@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>
<!-- 새로고침 버튼 -->
<button type="button"
hx-get="{{ route('sales.admin-prospects.refresh', request()->query()) }}"
hx-target="#admin-prospects-content"
hx-swap="innerHTML"
class="refresh-btn inline-flex items-center gap-1.5 px-4 py-2 text-sm text-gray-600 hover:text-blue-600 bg-white hover:bg-blue-50 border border-gray-300 rounded-lg transition-colors shadow-sm"
title="새로고침">
<svg data-refresh-spin class="w-4 h-4 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
<svg data-refresh-icon 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="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
<span>새로고침</span>
</button>
</div>
<!-- 콘텐츠 영역 (HTMX로 새로고침) -->
<div id="admin-prospects-content" class="flex-1 flex flex-col min-h-0">
@include('sales.admin-prospects.partials.content')
</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 updateHqStatus(prospectId, status) {
const selectEl = event.target;
const originalValue = selectEl.dataset.originalValue || selectEl.value;
fetch(`/sales/admin-prospects/${prospectId}/hq-status`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
'Accept': 'application/json'
},
body: JSON.stringify({ hq_status: status })
})
.then(response => response.json())
.then(result => {
if (result.success) {
// 성공 시 색상 클래스 업데이트
selectEl.className = 'text-xs font-medium rounded-lg px-2 py-1 border cursor-pointer ';
if (status === 'handover') {
selectEl.className += 'bg-emerald-100 text-emerald-700 border-emerald-300';
} else if (status === 'pending') {
selectEl.className += 'bg-gray-100 text-gray-600 border-gray-300';
} else {
selectEl.className += 'bg-purple-100 text-purple-700 border-purple-300';
}
selectEl.dataset.originalValue = status;
} else {
alert('상태 변경에 실패했습니다.');
selectEl.value = originalValue;
}
})
.catch(error => {
console.error('Error:', error);
alert('상태 변경 중 오류가 발생했습니다.');
selectEl.value = originalValue;
});
}
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();
}
});
// 수당 날짜 저장 (date input에서 호출)
function saveCommissionDate(prospectId, field, date) {
const input = document.querySelector(`input[data-prospect-id="${prospectId}"][data-field="${field}"]`);
// 날짜가 비어있으면 삭제 처리
if (!date) {
clearCommissionDate(prospectId, field, input);
return;
}
fetch(`/sales/admin-prospects/${prospectId}/commission-date`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
'Accept': 'application/json'
},
body: JSON.stringify({ field: field, date: date })
})
.then(response => response.json())
.then(result => {
if (result.success && input) {
// 입력 스타일 업데이트
updateInputStyle(input, field, true);
} else {
alert(result.message || '날짜 저장에 실패했습니다.');
}
})
.catch(error => {
console.error('Error:', error);
alert('날짜 저장 중 오류가 발생했습니다.');
});
}
// 수당 날짜 삭제
function clearCommissionDate(prospectId, field, input) {
fetch(`/sales/admin-prospects/${prospectId}/commission-date`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
'Accept': 'application/json'
},
body: JSON.stringify({ field: field })
})
.then(response => response.json())
.then(result => {
if (result.success && input) {
// 입력 스타일 업데이트
updateInputStyle(input, field, false);
} else if (!result.success) {
alert(result.message || '날짜 삭제에 실패했습니다.');
}
})
.catch(error => {
console.error('Error:', error);
alert('날짜 삭제 중 오류가 발생했습니다.');
});
}
// 입력 스타일 업데이트
function updateInputStyle(input, field, hasValue) {
// 기본 클래스
input.className = 'commission-date-input w-24 text-xs px-1 py-1 border border-gray-200 rounded text-center focus:outline-none focus:ring-1';
if (hasValue) {
if (field === 'first_payment_at' || field === 'second_payment_at') {
input.className += ' text-emerald-600 font-medium bg-emerald-50 focus:ring-emerald-500 focus:border-emerald-500';
} else if (field === 'first_partner_paid_at' || field === 'second_partner_paid_at') {
input.className += ' text-blue-600 font-medium bg-blue-50 focus:ring-blue-500 focus:border-blue-500';
} else if (field === 'manager_paid_at') {
input.className += ' text-purple-600 font-medium bg-purple-50 focus:ring-purple-500 focus:border-purple-500';
}
} else {
input.className += ' text-gray-400 bg-white';
if (field === 'first_payment_at' || field === 'second_payment_at') {
input.className += ' focus:ring-emerald-500 focus:border-emerald-500';
} else if (field === 'first_partner_paid_at' || field === 'second_partner_paid_at') {
input.className += ' focus:ring-blue-500 focus:border-blue-500';
} else if (field === 'manager_paid_at') {
input.className += ' focus:ring-purple-500 focus:border-purple-500';
}
}
}
</script>
@endpush