feat:가망고객 상태 토글 기능 추가 (영업중 ↔ 완료)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -209,6 +209,7 @@ private function getIndexData(Request $request): array
|
||||
'active' => TenantProspect::where('status', TenantProspect::STATUS_ACTIVE)->count(),
|
||||
'expired' => TenantProspect::where('status', TenantProspect::STATUS_EXPIRED)->count(),
|
||||
'converted' => TenantProspect::where('status', TenantProspect::STATUS_CONVERTED)->count(),
|
||||
'completed' => TenantProspect::where('status', TenantProspect::STATUS_COMPLETED)->count(),
|
||||
'progress_complete' => $progressCompleteCount,
|
||||
];
|
||||
|
||||
@@ -334,6 +335,34 @@ public function updateCommissionDate(int $id, Request $request)
|
||||
return response()->json($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 상태 토글 (영업중 ↔ 완료)
|
||||
*/
|
||||
public function toggleStatus(int $id)
|
||||
{
|
||||
$this->checkAdminAccess();
|
||||
|
||||
$prospect = TenantProspect::findOrFail($id);
|
||||
|
||||
if ($prospect->status === TenantProspect::STATUS_ACTIVE) {
|
||||
$prospect->update(['status' => TenantProspect::STATUS_COMPLETED]);
|
||||
} elseif ($prospect->status === TenantProspect::STATUS_COMPLETED) {
|
||||
$prospect->update(['status' => TenantProspect::STATUS_ACTIVE]);
|
||||
} else {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => '영업중 또는 완료 상태만 변경할 수 있습니다.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'status' => $prospect->status,
|
||||
'status_label' => $prospect->status_label,
|
||||
'status_color' => $prospect->status_color,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 가망고객 삭제 (슈퍼관리자 전용)
|
||||
*/
|
||||
|
||||
@@ -21,6 +21,7 @@ class TenantProspect extends Model
|
||||
public const STATUS_ACTIVE = 'active'; // 영업권 유효
|
||||
public const STATUS_EXPIRED = 'expired'; // 영업권 만료
|
||||
public const STATUS_CONVERTED = 'converted'; // 테넌트 전환 완료
|
||||
public const STATUS_COMPLETED = 'completed'; // 영업 완료
|
||||
|
||||
public const VALIDITY_MONTHS = 2; // 영업권 유효기간 (개월)
|
||||
public const COOLDOWN_MONTHS = 1; // 재등록 대기 기간 (개월)
|
||||
@@ -101,6 +102,14 @@ public function isConverted(): bool
|
||||
return $this->status === self::STATUS_CONVERTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 영업 완료 여부
|
||||
*/
|
||||
public function isCompleted(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_COMPLETED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 재등록 대기 중 여부
|
||||
*/
|
||||
@@ -126,6 +135,10 @@ public function getStatusLabelAttribute(): string
|
||||
return '계약완료';
|
||||
}
|
||||
|
||||
if ($this->isCompleted()) {
|
||||
return '완료';
|
||||
}
|
||||
|
||||
if ($this->isActive()) {
|
||||
return '영업중';
|
||||
}
|
||||
@@ -146,6 +159,10 @@ public function getStatusColorAttribute(): string
|
||||
return 'bg-green-100 text-green-800';
|
||||
}
|
||||
|
||||
if ($this->isCompleted()) {
|
||||
return 'bg-emerald-100 text-emerald-800';
|
||||
}
|
||||
|
||||
if ($this->isActive()) {
|
||||
return 'bg-blue-100 text-blue-800';
|
||||
}
|
||||
|
||||
@@ -101,6 +101,36 @@ class="refresh-btn inline-flex items-center gap-1.5 px-4 py-2 text-sm text-gray-
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
function toggleProspectStatus(prospectId) {
|
||||
const btn = document.getElementById(`status-btn-${prospectId}`);
|
||||
const currentLabel = btn.textContent.trim();
|
||||
const newLabel = currentLabel === '영업중' ? '완료' : '영업중';
|
||||
|
||||
if (!confirm(`상태를 "${currentLabel}"에서 "${newLabel}"(으)로 변경하시겠습니까?`)) return;
|
||||
|
||||
fetch(`/sales/admin-prospects/${prospectId}/toggle-status`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
btn.textContent = result.status_label;
|
||||
btn.className = `px-2 py-1 text-xs font-medium rounded-full cursor-pointer hover:opacity-80 transition ${result.status_color}`;
|
||||
} else {
|
||||
alert(result.message || '상태 변경에 실패했습니다.');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('상태 변경 중 오류가 발생했습니다.');
|
||||
});
|
||||
}
|
||||
|
||||
function updateHqStatus(prospectId, status) {
|
||||
const selectEl = event.target;
|
||||
const originalValue = selectEl.dataset.originalValue || selectEl.value;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{-- 통계 카드 --}}
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4">
|
||||
<div class="grid grid-cols-2 md:grid-cols-5 gap-4 mb-4">
|
||||
<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>
|
||||
@@ -12,6 +12,10 @@
|
||||
<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-teal-50 rounded-lg shadow-sm p-4">
|
||||
<div class="text-sm text-teal-600">완료</div>
|
||||
<div class="text-2xl font-bold text-teal-800">{{ number_format($stats['completed']) }}건</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>
|
||||
@@ -35,6 +39,10 @@ class="px-3 py-2 rounded-lg text-sm font-medium transition {{ request('status')
|
||||
class="px-3 py-2 rounded-lg text-sm font-medium transition {{ request('status') === 'progress_complete' ? 'bg-purple-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' => 'completed'])) }}"
|
||||
class="px-3 py-2 rounded-lg text-sm font-medium transition {{ request('status') === 'completed' ? 'bg-teal-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' }}">
|
||||
계약완료
|
||||
@@ -206,9 +214,18 @@ class="text-xs font-medium rounded-lg px-2 py-1 border cursor-pointer
|
||||
</select>
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
@if(in_array($prospect->status, ['active', 'completed']))
|
||||
<button type="button"
|
||||
id="status-btn-{{ $prospect->id }}"
|
||||
onclick="toggleProspectStatus({{ $prospect->id }})"
|
||||
class="px-2 py-1 text-xs font-medium rounded-full cursor-pointer hover:opacity-80 transition {{ $prospect->status_color }}">
|
||||
{{ $prospect->status_label }}
|
||||
</button>
|
||||
@else
|
||||
<span class="px-2 py-1 text-xs font-medium rounded-full {{ $prospect->status_color }}">
|
||||
{{ $prospect->status_label }}
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
|
||||
{{ $prospect->created_at->format('Y-m-d') }}
|
||||
|
||||
@@ -1152,6 +1152,7 @@
|
||||
Route::post('admin-prospects/{id}/commission-date', [\App\Http\Controllers\Sales\AdminProspectController::class, 'updateCommissionDate'])->name('admin-prospects.update-commission-date');
|
||||
Route::delete('admin-prospects/{id}/commission-date', [\App\Http\Controllers\Sales\AdminProspectController::class, 'clearCommissionDate'])->name('admin-prospects.clear-commission-date');
|
||||
Route::delete('admin-prospects/{id}', [\App\Http\Controllers\Sales\AdminProspectController::class, 'destroy'])->name('admin-prospects.destroy')->middleware('super.admin');
|
||||
Route::post('admin-prospects/{id}/toggle-status', [\App\Http\Controllers\Sales\AdminProspectController::class, 'toggleStatus'])->name('admin-prospects.toggle-status');
|
||||
|
||||
// 영업 시나리오 관리
|
||||
Route::prefix('scenarios')->name('scenarios.')->group(function () {
|
||||
|
||||
Reference in New Issue
Block a user