- 매니저 미지정 시 구독료가 소실되던 버그 → 파트너 수당으로 편입 - deposit/balance 양쪽에서 구독료 이중 계상 → deposit에서만 1회 기록 - 파트너별 결산 탭에 +구독 배지 추가, select에 manager_user_id 포함
786 lines
47 KiB
PHP
786 lines
47 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', '수당지급현황통계')
|
|
|
|
@section('content')
|
|
<div class="px-4 py-6" x-data="{ activeTab: 'monthly' }">
|
|
{{-- 페이지 헤더 + 필터 통합 --}}
|
|
<div class="bg-white rounded-lg shadow-sm px-5 pt-4 pb-4 mb-4">
|
|
<div class="flex items-center justify-between mb-3">
|
|
<div>
|
|
<h1 class="text-lg font-bold text-gray-800">수당지급현황통계</h1>
|
|
<p class="text-xs text-gray-400 mt-0.5">
|
|
@if($filters['startYear'] == $filters['endYear'] && $filters['startMonth'] == $filters['endMonth'])
|
|
{{ $filters['startYear'] }}년 {{ $filters['startMonth'] }}월 수당 종합 통계
|
|
@else
|
|
{{ $filters['startYear'] }}년 {{ $filters['startMonth'] }}월 ~ {{ $filters['endYear'] }}년 {{ $filters['endMonth'] }}월 수당 종합 통계
|
|
@endif
|
|
</p>
|
|
</div>
|
|
<a href="{{ route('finance.settlement', ['tab' => 'payment']) }}"
|
|
class="inline-flex items-center gap-1 px-3 py-1.5 text-gray-500 hover:text-gray-700 bg-gray-50 hover:bg-gray-100 rounded text-xs transition-colors">
|
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
|
|
</svg>
|
|
정산관리
|
|
</a>
|
|
</div>
|
|
<form method="GET" action="{{ route('finance.settlement.payment-stats') }}">
|
|
<div class="flex flex-wrap items-end gap-x-3 gap-y-2">
|
|
{{-- 기간설정 체크박스 --}}
|
|
<div class="flex items-center self-end pb-1.5">
|
|
<label class="flex items-center gap-1.5 cursor-pointer select-none">
|
|
<input type="checkbox" name="date_range" value="1"
|
|
{{ ($filters['startYear'] != $filters['endYear'] || $filters['startMonth'] != $filters['endMonth']) ? 'checked' : '' }}
|
|
onchange="togglePaymentDateRange(this.checked)"
|
|
class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500">
|
|
<span class="text-xs font-medium text-gray-600 whitespace-nowrap">기간설정</span>
|
|
</label>
|
|
</div>
|
|
|
|
{{-- 단일 년/월 (기본) --}}
|
|
<div id="ps-single-date" class="{{ ($filters['startYear'] != $filters['endYear'] || $filters['startMonth'] != $filters['endMonth']) ? 'hidden' : '' }} flex items-end gap-2">
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-500 mb-1">년도</label>
|
|
<select name="year" class="rounded-lg border-gray-300 text-sm py-2 pl-3 pr-8 focus:border-indigo-500 focus:ring-indigo-500">
|
|
@for ($y = now()->year; $y >= now()->year - 3; $y--)
|
|
<option value="{{ $y }}" {{ $filters['endYear'] == $y ? 'selected' : '' }}>{{ $y }}년</option>
|
|
@endfor
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-500 mb-1">월</label>
|
|
<select name="month" class="rounded-lg border-gray-300 text-sm py-2 pl-3 pr-8 focus:border-indigo-500 focus:ring-indigo-500">
|
|
@for ($m = 1; $m <= 12; $m++)
|
|
<option value="{{ $m }}" {{ $filters['endMonth'] == $m ? 'selected' : '' }}>{{ $m }}월</option>
|
|
@endfor
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- 기간 범위 (체크 시 표시) --}}
|
|
<div id="ps-range-date" class="{{ ($filters['startYear'] != $filters['endYear'] || $filters['startMonth'] != $filters['endMonth']) ? '' : 'hidden' }} flex items-end gap-2">
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-500 mb-1">시작</label>
|
|
<select name="start_year" class="rounded-lg border-gray-300 text-sm py-2 pl-3 pr-8 focus:border-indigo-500 focus:ring-indigo-500">
|
|
@for ($y = now()->year; $y >= now()->year - 3; $y--)
|
|
<option value="{{ $y }}" {{ $filters['startYear'] == $y ? 'selected' : '' }}>{{ $y }}년</option>
|
|
@endfor
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-500 mb-1"> </label>
|
|
<select name="start_month" class="rounded-lg border-gray-300 text-sm py-2 pl-3 pr-8 focus:border-indigo-500 focus:ring-indigo-500">
|
|
@for ($m = 1; $m <= 12; $m++)
|
|
<option value="{{ $m }}" {{ $filters['startMonth'] == $m ? 'selected' : '' }}>{{ $m }}월</option>
|
|
@endfor
|
|
</select>
|
|
</div>
|
|
<span class="text-gray-400 pb-2">~</span>
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-500 mb-1">종료</label>
|
|
<select name="end_year" class="rounded-lg border-gray-300 text-sm py-2 pl-3 pr-8 focus:border-indigo-500 focus:ring-indigo-500">
|
|
@for ($y = now()->year; $y >= now()->year - 3; $y--)
|
|
<option value="{{ $y }}" {{ $filters['endYear'] == $y ? 'selected' : '' }}>{{ $y }}년</option>
|
|
@endfor
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-500 mb-1"> </label>
|
|
<select name="end_month" class="rounded-lg border-gray-300 text-sm py-2 pl-3 pr-8 focus:border-indigo-500 focus:ring-indigo-500">
|
|
@for ($m = 1; $m <= 12; $m++)
|
|
<option value="{{ $m }}" {{ $filters['endMonth'] == $m ? 'selected' : '' }}>{{ $m }}월</option>
|
|
@endfor
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- 상태 --}}
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-500 mb-1">상태</label>
|
|
<select name="status" class="rounded-lg border-gray-300 text-sm py-2 pl-3 pr-8 focus:border-indigo-500 focus:ring-indigo-500">
|
|
<option value="">전체</option>
|
|
<option value="pending" {{ $filters['status'] === 'pending' ? 'selected' : '' }}>대기</option>
|
|
<option value="approved" {{ $filters['status'] === 'approved' ? 'selected' : '' }}>승인</option>
|
|
<option value="paid" {{ $filters['status'] === 'paid' ? 'selected' : '' }}>지급완료</option>
|
|
<option value="cancelled" {{ $filters['status'] === 'cancelled' ? 'selected' : '' }}>취소</option>
|
|
</select>
|
|
</div>
|
|
|
|
{{-- 지급유형 --}}
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-500 mb-1">유형</label>
|
|
<select name="payment_type" class="rounded-lg border-gray-300 text-sm py-2 pl-3 pr-8 focus:border-indigo-500 focus:ring-indigo-500">
|
|
<option value="">전체</option>
|
|
<option value="deposit" {{ $filters['paymentType'] === 'deposit' ? 'selected' : '' }}>1차</option>
|
|
<option value="balance" {{ $filters['paymentType'] === 'balance' ? 'selected' : '' }}>2차</option>
|
|
</select>
|
|
</div>
|
|
|
|
{{-- 검색어 --}}
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-500 mb-1">검색</label>
|
|
<input type="text" name="search" value="{{ $filters['search'] }}" placeholder="파트너명, 매니저, 고객사..."
|
|
class="rounded-lg border-gray-300 text-sm py-2 pl-3 pr-3 w-44 focus:border-indigo-500 focus:ring-indigo-500">
|
|
</div>
|
|
|
|
{{-- 조회 / 초기화 --}}
|
|
<div class="flex items-end gap-1.5">
|
|
<button type="submit"
|
|
class="px-5 py-2 bg-indigo-600 hover:bg-indigo-700 text-white text-sm rounded-lg transition-colors">
|
|
조회
|
|
</button>
|
|
<a href="{{ route('finance.settlement.payment-stats') }}"
|
|
class="px-3 py-2 bg-gray-100 hover:bg-gray-200 text-gray-600 text-sm rounded-lg transition-colors">
|
|
초기화
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<script>
|
|
function togglePaymentDateRange(checked) {
|
|
document.getElementById('ps-single-date').classList.toggle('hidden', checked);
|
|
document.getElementById('ps-range-date').classList.toggle('hidden', !checked);
|
|
document.querySelectorAll('#ps-single-date select').forEach(s => s.disabled = checked);
|
|
document.querySelectorAll('#ps-range-date select').forEach(s => s.disabled = !checked);
|
|
}
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const checked = document.querySelector('input[name="date_range"]')?.checked || false;
|
|
document.querySelectorAll('#ps-single-date select').forEach(s => s.disabled = checked);
|
|
document.querySelectorAll('#ps-range-date select').forEach(s => s.disabled = !checked);
|
|
});
|
|
</script>
|
|
|
|
{{-- 통계 카드 8개 --}}
|
|
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
|
{{-- 1. 총 수당 발생액 --}}
|
|
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-blue-500">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-xs text-gray-500">총 수당 발생액</p>
|
|
<p class="text-lg font-bold text-blue-600">{{ number_format($statsCards['total_amount']) }}<span class="text-xs font-normal">원</span></p>
|
|
</div>
|
|
<div class="w-9 h-9 bg-blue-100 rounded-full flex items-center justify-center">
|
|
<svg class="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{-- 2. 지급 완료액 --}}
|
|
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-green-500">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-xs text-gray-500">지급 완료액</p>
|
|
<p class="text-lg font-bold text-green-600">{{ number_format($statsCards['paid_amount']) }}<span class="text-xs font-normal">원</span></p>
|
|
</div>
|
|
<div class="w-9 h-9 bg-green-100 rounded-full flex items-center justify-center">
|
|
<svg class="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{-- 3. 미지급액 --}}
|
|
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-orange-500">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-xs text-gray-500">미지급액</p>
|
|
<p class="text-lg font-bold text-orange-600">{{ number_format($statsCards['unpaid_amount']) }}<span class="text-xs font-normal">원</span></p>
|
|
</div>
|
|
<div class="w-9 h-9 bg-orange-100 rounded-full flex items-center justify-center">
|
|
<svg class="w-4 h-4 text-orange-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{-- 4. 활성 파트너 수 --}}
|
|
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-indigo-500">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-xs text-gray-500">활성 파트너 수</p>
|
|
<p class="text-lg font-bold text-indigo-600">{{ $statsCards['active_partners'] }}<span class="text-xs font-normal">명</span></p>
|
|
</div>
|
|
<div class="w-9 h-9 bg-indigo-100 rounded-full flex items-center justify-center">
|
|
<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="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{-- 5. 파트너 수당 합계 --}}
|
|
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-purple-500">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-xs text-gray-500">파트너 수당</p>
|
|
<p class="text-lg font-bold text-purple-600">{{ number_format($statsCards['partner_sum']) }}<span class="text-xs font-normal">원</span></p>
|
|
</div>
|
|
<div class="w-9 h-9 bg-purple-100 rounded-full flex items-center justify-center">
|
|
<svg class="w-4 h-4 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{-- 6. 매니저 수당 합계 --}}
|
|
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-teal-500">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-xs text-gray-500">매니저 수당</p>
|
|
<p class="text-lg font-bold text-teal-600">{{ number_format($statsCards['manager_sum']) }}<span class="text-xs font-normal">원</span></p>
|
|
</div>
|
|
<div class="w-9 h-9 bg-teal-100 rounded-full flex items-center justify-center">
|
|
<svg class="w-4 h-4 text-teal-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5.121 17.804A13.937 13.937 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{-- 7. 유치 수당 합계 --}}
|
|
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-amber-500">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-xs text-gray-500">유치 수당</p>
|
|
<p class="text-lg font-bold text-amber-600">{{ number_format($statsCards['referrer_sum']) }}<span class="text-xs font-normal">원</span></p>
|
|
</div>
|
|
<div class="w-9 h-9 bg-amber-100 rounded-full flex items-center justify-center">
|
|
<svg class="w-4 h-4 text-amber-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"/></svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{-- 8. 건당 평균 수당 --}}
|
|
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-gray-400">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-xs text-gray-500">건당 평균 수당</p>
|
|
<p class="text-lg font-bold text-gray-700">{{ number_format($statsCards['avg_commission']) }}<span class="text-xs font-normal">원</span></p>
|
|
</div>
|
|
<div class="w-9 h-9 bg-gray-100 rounded-full flex items-center justify-center">
|
|
<svg class="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 12l3-3 3 3 4-4M8 21l4-4 4 4M3 4h18M4 4h16v12a1 1 0 01-1 1H5a1 1 0 01-1-1V4z"/></svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- 차트 영역 --}}
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
|
{{-- 차트 1: 월별 지급 추이 (Stacked Bar) --}}
|
|
<div class="bg-white rounded-lg shadow-sm p-6">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">월별 수당 추이</h3>
|
|
<div class="relative" style="height: 300px;">
|
|
<canvas id="monthlyTrendChart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- 차트 2: 수당 유형별 비율 (Doughnut) --}}
|
|
<div class="bg-white rounded-lg shadow-sm p-6">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">수당 유형별 비율</h3>
|
|
<div class="relative" style="height: 300px;">
|
|
<canvas id="typeRatioChart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- 차트 3: 파트너별 수당 Top 10 (Horizontal Bar) --}}
|
|
<div class="bg-white rounded-lg shadow-sm p-6">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">파트너별 수당 Top 10</h3>
|
|
<div class="relative" style="height: 300px;">
|
|
<canvas id="topPartnersChart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- 차트 4: 상태별 건수 분포 (Doughnut) --}}
|
|
<div class="bg-white rounded-lg shadow-sm p-6">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">상태별 건수 분포</h3>
|
|
<div class="relative" style="height: 300px;">
|
|
<canvas id="statusCountChart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- 차트 5: 상태별 금액 분포 (Bar) --}}
|
|
<div class="bg-white rounded-lg shadow-sm p-6">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">상태별 금액 분포</h3>
|
|
<div class="relative" style="height: 300px;">
|
|
<canvas id="statusAmountChart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- 차트 6: 파트너 vs 매니저 추이 (Line) --}}
|
|
<div class="bg-white rounded-lg shadow-sm p-6">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">파트너 vs 매니저 수당 추이 (지급완료)</h3>
|
|
<div class="relative" style="height: 300px;">
|
|
<canvas id="comparisonChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- 테이블 탭 --}}
|
|
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
|
|
<div class="border-b border-gray-200">
|
|
<nav class="flex -mb-px">
|
|
<button @click="activeTab = 'monthly'"
|
|
:class="activeTab === 'monthly' ? 'border-indigo-500 text-indigo-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'"
|
|
class="px-6 py-3 border-b-2 text-sm font-medium transition-colors">
|
|
월별 요약
|
|
</button>
|
|
<button @click="activeTab = 'partner'"
|
|
:class="activeTab === 'partner' ? 'border-indigo-500 text-indigo-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'"
|
|
class="px-6 py-3 border-b-2 text-sm font-medium transition-colors">
|
|
파트너별 결산
|
|
</button>
|
|
<button @click="activeTab = 'manager'"
|
|
:class="activeTab === 'manager' ? 'border-indigo-500 text-indigo-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'"
|
|
class="px-6 py-3 border-b-2 text-sm font-medium transition-colors">
|
|
매니저별 결산
|
|
</button>
|
|
</nav>
|
|
</div>
|
|
|
|
{{-- 월별 요약 테이블 --}}
|
|
<div x-show="activeTab === 'monthly'" 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">월</th>
|
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">파트너수당</th>
|
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">매니저수당</th>
|
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">유치수당</th>
|
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">지급완료</th>
|
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">미지급</th>
|
|
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">건수</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200">
|
|
@php $gPartner = 0; $gManager = 0; $gReferrer = 0; $gPaid = 0; $gUnpaid = 0; $gCount = 0; @endphp
|
|
@forelse ($monthlyTrend as $row)
|
|
@php
|
|
$gPartner += $row->partner_total;
|
|
$gManager += $row->manager_total;
|
|
$gReferrer += $row->referrer_total;
|
|
$gPaid += $row->paid_total;
|
|
$gUnpaid += $row->unpaid_total;
|
|
$gCount += $row->count;
|
|
@endphp
|
|
<tr class="hover:bg-gray-50">
|
|
<td class="px-4 py-3 text-sm font-medium text-gray-900">{{ \Carbon\Carbon::parse($row->month . '-01')->format('Y년 n월') }}</td>
|
|
<td class="px-4 py-3 text-sm text-right text-gray-900">{{ number_format($row->partner_total) }}원</td>
|
|
<td class="px-4 py-3 text-sm text-right text-gray-900">{{ number_format($row->manager_total) }}원</td>
|
|
<td class="px-4 py-3 text-sm text-right text-gray-900">{{ number_format($row->referrer_total) }}원</td>
|
|
<td class="px-4 py-3 text-sm text-right text-green-600 font-medium">{{ number_format($row->paid_total) }}원</td>
|
|
<td class="px-4 py-3 text-sm text-right text-orange-600 font-medium">{{ number_format($row->unpaid_total) }}원</td>
|
|
<td class="px-4 py-3 text-sm text-center text-gray-900">{{ $row->count }}건</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="7" class="px-4 py-8 text-center text-gray-500">해당 기간의 데이터가 없습니다.</td>
|
|
</tr>
|
|
@endforelse
|
|
@if ($monthlyTrend->isNotEmpty())
|
|
<tr class="bg-gray-50 font-bold">
|
|
<td class="px-4 py-3 text-sm text-gray-900">합계</td>
|
|
<td class="px-4 py-3 text-sm text-right text-gray-900">{{ number_format($gPartner) }}원</td>
|
|
<td class="px-4 py-3 text-sm text-right text-gray-900">{{ number_format($gManager) }}원</td>
|
|
<td class="px-4 py-3 text-sm text-right text-gray-900">{{ number_format($gReferrer) }}원</td>
|
|
<td class="px-4 py-3 text-sm text-right text-green-700">{{ number_format($gPaid) }}원</td>
|
|
<td class="px-4 py-3 text-sm text-right text-orange-700">{{ number_format($gUnpaid) }}원</td>
|
|
<td class="px-4 py-3 text-sm text-center text-gray-900">{{ $gCount }}건</td>
|
|
</tr>
|
|
@endif
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{{-- 파트너별 결산 테이블 (개별 건 표시) --}}
|
|
<div x-show="activeTab === 'partner'" 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">파트너</th>
|
|
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">구분</th>
|
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">고객사</th>
|
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">파트너수당</th>
|
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">매니저수당</th>
|
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">유치수당</th>
|
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">소계</th>
|
|
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">상태</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200">
|
|
@php $gTotal = 0; $gPartner = 0; $gManager = 0; $gReferrer = 0; $gPaid = 0; $gUnpaid = 0; $gCount = 0; @endphp
|
|
@forelse ($partnerSettlement as $group)
|
|
@php
|
|
$gTotal += $group->total_amount;
|
|
$gPartner += $group->items->sum('partner_commission');
|
|
$gManager += $group->items->sum('manager_commission');
|
|
$gReferrer += $group->items->sum(fn($i) => $i->referrer_commission ?? 0);
|
|
$gPaid += $group->paid_amount;
|
|
$gUnpaid += $group->unpaid_amount;
|
|
$gCount += $group->contract_count;
|
|
$completionRate = $group->total_amount > 0 ? round(($group->paid_amount / $group->total_amount) * 100, 1) : 0;
|
|
@endphp
|
|
{{-- 파트너 헤더행 --}}
|
|
<tr class="bg-gray-100">
|
|
<td class="px-4 py-2.5 text-sm font-bold text-gray-900" colspan="2">
|
|
{{ $group->partner_name }}
|
|
<span class="inline-flex ml-1.5 px-2 py-0.5 text-xs rounded-full {{ $group->partner_type === 'corporate' ? 'bg-blue-100 text-blue-700' : 'bg-gray-200 text-gray-600' }}">
|
|
{{ $group->partner_type === 'corporate' ? '법인' : '개인' }}
|
|
</span>
|
|
</td>
|
|
<td class="px-4 py-2.5 text-xs text-gray-500">{{ $group->contract_count }}건</td>
|
|
<td colspan="4" class="px-4 py-2.5 text-right text-xs text-gray-500">
|
|
총 {{ number_format($group->total_amount) }}원
|
|
<span class="ml-2">
|
|
(지급 {{ number_format($group->paid_amount) }}원 / 미지급 {{ number_format($group->unpaid_amount) }}원)
|
|
</span>
|
|
</td>
|
|
<td class="px-4 py-2.5 text-center">
|
|
<div class="flex items-center gap-1.5 justify-center">
|
|
<div class="w-14 h-1.5 bg-gray-200 rounded-full overflow-hidden">
|
|
<div class="h-full rounded-full {{ $completionRate >= 80 ? 'bg-green-500' : ($completionRate >= 50 ? 'bg-yellow-500' : 'bg-red-500') }}"
|
|
style="width: {{ $completionRate }}%"></div>
|
|
</div>
|
|
<span class="text-xs text-gray-500">{{ $completionRate }}%</span>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{{-- 개별 건 행 --}}
|
|
@foreach ($group->items as $item)
|
|
@php $itemTotal = $item->partner_commission + $item->manager_commission + ($item->referrer_commission ?? 0); @endphp
|
|
<tr class="hover:bg-gray-50">
|
|
<td class="px-4 py-2 text-sm text-gray-400 pl-8">└</td>
|
|
<td class="px-4 py-2 text-sm text-center">
|
|
@if ($item->payment_type === 'deposit')
|
|
<span class="inline-flex px-2 py-0.5 text-xs rounded-full bg-indigo-100 text-indigo-700">계약금</span>
|
|
@if ($item->manager_commission > 0 || (!$item->manager_user_id && $item->partner_commission > 0))
|
|
<span class="inline-flex px-1.5 py-0.5 text-[10px] rounded-full bg-orange-100 text-orange-700 ml-0.5">+구독</span>
|
|
@endif
|
|
@else
|
|
<span class="inline-flex px-2 py-0.5 text-xs rounded-full bg-emerald-100 text-emerald-700">잔금</span>
|
|
@endif
|
|
</td>
|
|
<td class="px-4 py-2 text-sm text-gray-700">{{ $item->management?->tenant?->company_name ?? '-' }}</td>
|
|
<td class="px-4 py-2 text-sm text-right text-gray-900">{{ number_format($item->partner_commission) }}원</td>
|
|
<td class="px-4 py-2 text-sm text-right text-gray-900">{{ number_format($item->manager_commission) }}원</td>
|
|
<td class="px-4 py-2 text-sm text-right text-gray-900">{{ number_format($item->referrer_commission ?? 0) }}원</td>
|
|
<td class="px-4 py-2 text-sm text-right font-medium text-gray-900">{{ number_format($itemTotal) }}원</td>
|
|
<td class="px-4 py-2 text-sm text-center">
|
|
@switch($item->status)
|
|
@case('paid')
|
|
<span class="inline-flex px-2 py-0.5 text-xs rounded-full bg-green-100 text-green-700">지급완료</span>
|
|
@break
|
|
@case('approved')
|
|
<span class="inline-flex px-2 py-0.5 text-xs rounded-full bg-blue-100 text-blue-700">승인</span>
|
|
@break
|
|
@case('pending')
|
|
<span class="inline-flex px-2 py-0.5 text-xs rounded-full bg-yellow-100 text-yellow-700">대기</span>
|
|
@break
|
|
@case('cancelled')
|
|
<span class="inline-flex px-2 py-0.5 text-xs rounded-full bg-red-100 text-red-700">취소</span>
|
|
@break
|
|
@endswitch
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
@empty
|
|
<tr>
|
|
<td colspan="8" class="px-4 py-8 text-center text-gray-500">해당 기간의 데이터가 없습니다.</td>
|
|
</tr>
|
|
@endforelse
|
|
@if ($partnerSettlement->isNotEmpty())
|
|
@php $overallRate = $gTotal > 0 ? round(($gPaid / $gTotal) * 100, 1) : 0; @endphp
|
|
<tr class="bg-gray-50 font-bold border-t-2 border-gray-300">
|
|
<td class="px-4 py-3 text-sm text-gray-900">합계</td>
|
|
<td class="px-4 py-3 text-sm text-center text-gray-500">-</td>
|
|
<td class="px-4 py-3 text-sm text-gray-900">{{ $gCount }}건</td>
|
|
<td class="px-4 py-3 text-sm text-right text-gray-900">{{ number_format($gPartner) }}원</td>
|
|
<td class="px-4 py-3 text-sm text-right text-gray-900">{{ number_format($gManager) }}원</td>
|
|
<td class="px-4 py-3 text-sm text-right text-gray-900">{{ number_format($gReferrer) }}원</td>
|
|
<td class="px-4 py-3 text-sm text-right text-gray-900">{{ number_format($gTotal) }}원</td>
|
|
<td class="px-4 py-3 text-center">
|
|
<div class="flex items-center gap-1.5 justify-center">
|
|
<div class="w-14 h-1.5 bg-gray-200 rounded-full overflow-hidden">
|
|
<div class="h-full bg-indigo-500 rounded-full" style="width: {{ $overallRate }}%"></div>
|
|
</div>
|
|
<span class="text-xs text-gray-600">{{ $overallRate }}%</span>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@endif
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{{-- 매니저별 결산 테이블 --}}
|
|
<div x-show="activeTab === 'manager'" 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">매니저</th>
|
|
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">담당건수</th>
|
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">매니저수당</th>
|
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">지급완료</th>
|
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">미지급</th>
|
|
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase" style="min-width:120px">완료율</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200">
|
|
@php $mTotal = 0; $mPaid = 0; $mUnpaid = 0; $mCount = 0; @endphp
|
|
@forelse ($managerSettlement as $row)
|
|
@php
|
|
$mTotal += $row->total_manager;
|
|
$mPaid += $row->paid_amount;
|
|
$mUnpaid += $row->unpaid_amount;
|
|
$mCount += $row->contract_count;
|
|
@endphp
|
|
<tr class="hover:bg-gray-50">
|
|
<td class="px-4 py-3 text-sm font-medium text-gray-900">{{ $row->manager_name }}</td>
|
|
<td class="px-4 py-3 text-sm text-center text-gray-900">{{ $row->contract_count }}건</td>
|
|
<td class="px-4 py-3 text-sm text-right font-bold text-gray-900">{{ number_format($row->total_manager) }}원</td>
|
|
<td class="px-4 py-3 text-sm text-right text-green-600 font-medium">{{ number_format($row->paid_amount) }}원</td>
|
|
<td class="px-4 py-3 text-sm text-right text-orange-600 font-medium">{{ number_format($row->unpaid_amount) }}원</td>
|
|
<td class="px-4 py-3">
|
|
<div class="flex items-center gap-2">
|
|
<div class="flex-1 h-2 bg-gray-200 rounded-full overflow-hidden">
|
|
<div class="h-full rounded-full {{ $row->completion_rate >= 80 ? 'bg-green-500' : ($row->completion_rate >= 50 ? 'bg-yellow-500' : 'bg-red-500') }}"
|
|
style="width: {{ $row->completion_rate }}%"></div>
|
|
</div>
|
|
<span class="text-xs text-gray-600 w-10 text-right">{{ $row->completion_rate }}%</span>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="6" class="px-4 py-8 text-center text-gray-500">해당 기간의 데이터가 없습니다.</td>
|
|
</tr>
|
|
@endforelse
|
|
@if ($managerSettlement->isNotEmpty())
|
|
@php $mOverallRate = $mTotal > 0 ? round(($mPaid / $mTotal) * 100, 1) : 0; @endphp
|
|
<tr class="bg-gray-50 font-bold">
|
|
<td class="px-4 py-3 text-sm text-gray-900">합계</td>
|
|
<td class="px-4 py-3 text-sm text-center text-gray-900">{{ $mCount }}건</td>
|
|
<td class="px-4 py-3 text-sm text-right text-gray-900">{{ number_format($mTotal) }}원</td>
|
|
<td class="px-4 py-3 text-sm text-right text-green-700">{{ number_format($mPaid) }}원</td>
|
|
<td class="px-4 py-3 text-sm text-right text-orange-700">{{ number_format($mUnpaid) }}원</td>
|
|
<td class="px-4 py-3">
|
|
<div class="flex items-center gap-2">
|
|
<div class="flex-1 h-2 bg-gray-200 rounded-full overflow-hidden">
|
|
<div class="h-full bg-indigo-500 rounded-full" style="width: {{ $mOverallRate }}%"></div>
|
|
</div>
|
|
<span class="text-xs text-gray-600 w-10 text-right">{{ $mOverallRate }}%</span>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@endif
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endsection
|
|
|
|
@push('scripts')
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const formatCurrency = (val) => {
|
|
if (val >= 1000000) return (val / 1000000).toFixed(1) + 'M';
|
|
if (val >= 1000) return (val / 1000).toFixed(0) + 'K';
|
|
return val.toString();
|
|
};
|
|
|
|
const defaultOptions = {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: { position: 'bottom', labels: { boxWidth: 12, padding: 15, font: { size: 11 } } }
|
|
}
|
|
};
|
|
|
|
const statusLabels = { pending: '대기', approved: '승인', paid: '지급완료', cancelled: '취소' };
|
|
const statusColors = {
|
|
pending: 'rgba(251, 191, 36, 0.8)',
|
|
approved: 'rgba(59, 130, 246, 0.8)',
|
|
paid: 'rgba(16, 185, 129, 0.8)',
|
|
cancelled: 'rgba(239, 68, 68, 0.8)'
|
|
};
|
|
|
|
// 빈 데이터 플러그인 (doughnut)
|
|
const emptyDoughnutPlugin = {
|
|
id: 'emptyDoughnut',
|
|
afterDraw(chart) {
|
|
const { datasets } = chart.data;
|
|
if (chart.config.type !== 'doughnut') return;
|
|
const hasData = datasets.some(ds => ds.data.some(v => v > 0));
|
|
if (!hasData) {
|
|
const { ctx, chartArea: { left, top, right, bottom } } = chart;
|
|
const cx = (left + right) / 2;
|
|
const cy = (top + bottom) / 2;
|
|
ctx.save();
|
|
ctx.textAlign = 'center';
|
|
ctx.textBaseline = 'middle';
|
|
ctx.font = '14px sans-serif';
|
|
ctx.fillStyle = '#9CA3AF';
|
|
ctx.fillText('데이터 없음', cx, cy);
|
|
ctx.restore();
|
|
}
|
|
}
|
|
};
|
|
Chart.register(emptyDoughnutPlugin);
|
|
|
|
// === 차트 1: 월별 수당 추이 (Stacked Bar) ===
|
|
const monthlyData = @json($monthlyTrend);
|
|
const months = monthlyData.map(d => {
|
|
const parts = d.month.split('-');
|
|
return parseInt(parts[1]) + '월';
|
|
});
|
|
|
|
new Chart(document.getElementById('monthlyTrendChart'), {
|
|
type: 'bar',
|
|
data: {
|
|
labels: months,
|
|
datasets: [
|
|
{ label: '파트너수당', data: monthlyData.map(d => Number(d.partner_total)), backgroundColor: 'rgba(79, 70, 229, 0.8)' },
|
|
{ label: '매니저수당', data: monthlyData.map(d => Number(d.manager_total)), backgroundColor: 'rgba(16, 185, 129, 0.8)' },
|
|
{ label: '유치수당', data: monthlyData.map(d => Number(d.referrer_total)), backgroundColor: 'rgba(245, 158, 11, 0.8)' }
|
|
]
|
|
},
|
|
options: {
|
|
...defaultOptions,
|
|
scales: {
|
|
x: { stacked: true },
|
|
y: { stacked: true, ticks: { callback: val => formatCurrency(val) } }
|
|
}
|
|
}
|
|
});
|
|
|
|
// === 차트 2: 수당 유형별 비율 (Doughnut) ===
|
|
const typeData = @json($typeRatio);
|
|
new Chart(document.getElementById('typeRatioChart'), {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: ['파트너수당', '매니저수당', '유치수당'],
|
|
datasets: [{
|
|
data: [
|
|
Number(typeData?.partner_total || 0),
|
|
Number(typeData?.manager_total || 0),
|
|
Number(typeData?.referrer_total || 0)
|
|
],
|
|
backgroundColor: ['rgba(79, 70, 229, 0.8)', 'rgba(16, 185, 129, 0.8)', 'rgba(245, 158, 11, 0.8)'],
|
|
borderWidth: 2, borderColor: '#fff'
|
|
}]
|
|
},
|
|
options: {
|
|
...defaultOptions,
|
|
plugins: {
|
|
...defaultOptions.plugins,
|
|
tooltip: {
|
|
callbacks: {
|
|
label: function(ctx) {
|
|
const total = ctx.dataset.data.reduce((a, b) => a + b, 0);
|
|
const pct = total > 0 ? ((ctx.raw / total) * 100).toFixed(1) : 0;
|
|
return ctx.label + ': ' + Number(ctx.raw).toLocaleString() + '원 (' + pct + '%)';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// === 차트 3: 파트너별 수당 Top 10 (Horizontal Bar) ===
|
|
const topPartnersData = @json($topPartners['data']);
|
|
const topPartnerNames = @json($topPartners['names']);
|
|
new Chart(document.getElementById('topPartnersChart'), {
|
|
type: 'bar',
|
|
data: {
|
|
labels: topPartnersData.map(d => topPartnerNames[d.partner_id] || 'ID:' + d.partner_id),
|
|
datasets: [{
|
|
label: '총 수당',
|
|
data: topPartnersData.map(d => Number(d.total)),
|
|
backgroundColor: 'rgba(79, 70, 229, 0.7)',
|
|
borderColor: 'rgba(79, 70, 229, 1)',
|
|
borderWidth: 1
|
|
}]
|
|
},
|
|
options: {
|
|
...defaultOptions,
|
|
indexAxis: 'y',
|
|
scales: { x: { ticks: { callback: val => formatCurrency(val) } } },
|
|
plugins: { ...defaultOptions.plugins, legend: { display: false } }
|
|
}
|
|
});
|
|
|
|
// === 차트 4: 상태별 건수 분포 (Doughnut) ===
|
|
const statusData = @json($statusDistribution);
|
|
new Chart(document.getElementById('statusCountChart'), {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: statusData.map(d => statusLabels[d.status] || d.status),
|
|
datasets: [{
|
|
data: statusData.map(d => Number(d.count)),
|
|
backgroundColor: statusData.map(d => statusColors[d.status] || 'rgba(156,163,175,0.8)'),
|
|
borderWidth: 2, borderColor: '#fff'
|
|
}]
|
|
},
|
|
options: {
|
|
...defaultOptions,
|
|
plugins: {
|
|
...defaultOptions.plugins,
|
|
tooltip: {
|
|
callbacks: {
|
|
label: function(ctx) {
|
|
const total = ctx.dataset.data.reduce((a, b) => a + b, 0);
|
|
const pct = total > 0 ? ((ctx.raw / total) * 100).toFixed(1) : 0;
|
|
return ctx.label + ': ' + ctx.raw + '건 (' + pct + '%)';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// === 차트 5: 상태별 금액 분포 (Bar) ===
|
|
new Chart(document.getElementById('statusAmountChart'), {
|
|
type: 'bar',
|
|
data: {
|
|
labels: statusData.map(d => statusLabels[d.status] || d.status),
|
|
datasets: [{
|
|
label: '금액',
|
|
data: statusData.map(d => Number(d.amount)),
|
|
backgroundColor: statusData.map(d => statusColors[d.status] || 'rgba(156,163,175,0.8)'),
|
|
borderWidth: 1,
|
|
borderColor: statusData.map(d => (statusColors[d.status] || 'rgba(156,163,175,1)').replace('0.8', '1'))
|
|
}]
|
|
},
|
|
options: {
|
|
...defaultOptions,
|
|
scales: { y: { ticks: { callback: val => formatCurrency(val) } } },
|
|
plugins: { ...defaultOptions.plugins, legend: { display: false } }
|
|
}
|
|
});
|
|
|
|
// === 차트 6: 파트너 vs 매니저 추이 (Line) ===
|
|
const comparisonData = @json($monthlyComparison);
|
|
const compMonths = comparisonData.map(d => {
|
|
const parts = d.month.split('-');
|
|
return parseInt(parts[1]) + '월';
|
|
});
|
|
new Chart(document.getElementById('comparisonChart'), {
|
|
type: 'line',
|
|
data: {
|
|
labels: compMonths,
|
|
datasets: [
|
|
{
|
|
label: '파트너수당',
|
|
data: comparisonData.map(d => Number(d.partner_total)),
|
|
borderColor: 'rgba(79, 70, 229, 1)',
|
|
backgroundColor: 'rgba(79, 70, 229, 0.1)',
|
|
fill: true, tension: 0.3, pointRadius: 4, pointHoverRadius: 6
|
|
},
|
|
{
|
|
label: '매니저수당',
|
|
data: comparisonData.map(d => Number(d.manager_total)),
|
|
borderColor: 'rgba(16, 185, 129, 1)',
|
|
backgroundColor: 'rgba(16, 185, 129, 0.1)',
|
|
fill: true, tension: 0.3, pointRadius: 4, pointHoverRadius: 6
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
...defaultOptions,
|
|
scales: { y: { ticks: { callback: val => formatCurrency(val) } } }
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
@endpush
|