- 정산관리에 수당 지급 탭 추가 (파트너별 그룹핑 지급 대기 목록) - 파트너별 상세 건 목록 HTMX 확장 기능 - 수당지급현황통계 페이지 (Chart.js 4개 차트 + 월별 요약 테이블) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
350 lines
17 KiB
PHP
350 lines
17 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', '수당지급현황통계')
|
|
|
|
@section('content')
|
|
<div class="px-4 py-6">
|
|
{{-- 페이지 헤더 --}}
|
|
<div class="flex flex-col lg:flex-row lg:justify-between lg:items-center gap-4 mb-6">
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-gray-800">수당지급현황통계</h1>
|
|
<p class="text-sm text-gray-500 mt-1">{{ $year }}년 수당 지급 현황 종합 통계</p>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<a href="{{ route('finance.settlement', ['tab' => 'payment']) }}"
|
|
class="inline-flex items-center gap-1 px-4 py-2 text-gray-600 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors text-sm">
|
|
<svg 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="M10 19l-7-7m0 0l7-7m-7 7h18"/>
|
|
</svg>
|
|
정산관리
|
|
</a>
|
|
<form method="GET" action="{{ route('finance.settlement.payment-stats') }}" class="flex items-center gap-2">
|
|
<select name="year" class="border-gray-300 rounded-lg shadow-sm text-sm focus:ring-indigo-500 focus:border-indigo-500">
|
|
@for ($y = now()->year; $y >= now()->year - 3; $y--)
|
|
<option value="{{ $y }}" {{ $year == $y ? 'selected' : '' }}>{{ $y }}년</option>
|
|
@endfor
|
|
</select>
|
|
<button type="submit" class="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg transition-colors text-sm">
|
|
조회
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- 통계 카드 --}}
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
|
<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-sm text-gray-500">총 지급액</p>
|
|
<p class="text-xl font-bold text-green-600">{{ number_format($statsCards['total_paid_amount']) }}원</p>
|
|
</div>
|
|
<div class="w-10 h-10 bg-green-100 rounded-full flex items-center justify-center">
|
|
<svg class="w-5 h-5 text-green-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>
|
|
<p class="text-xs text-gray-400 mt-1">{{ $year }}년 지급 합계</p>
|
|
</div>
|
|
|
|
<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-sm text-gray-500">총 지급 건수</p>
|
|
<p class="text-xl font-bold text-blue-600">{{ number_format($statsCards['total_paid_count']) }}건</p>
|
|
</div>
|
|
<div class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center">
|
|
<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 2"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<p class="text-xs text-gray-400 mt-1">{{ $year }}년 총 건수</p>
|
|
</div>
|
|
|
|
<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-sm text-gray-500">지급 파트너 수</p>
|
|
<p class="text-xl font-bold text-indigo-600">{{ $statsCards['active_partners'] }}명</p>
|
|
</div>
|
|
<div class="w-10 h-10 bg-indigo-100 rounded-full flex items-center justify-center">
|
|
<svg class="w-5 h-5 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>
|
|
<p class="text-xs text-gray-400 mt-1">{{ $year }}년 지급 대상</p>
|
|
</div>
|
|
|
|
<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-sm text-gray-500">건당 평균 수당</p>
|
|
<p class="text-xl font-bold text-purple-600">{{ number_format($statsCards['avg_commission']) }}원</p>
|
|
</div>
|
|
<div class="w-10 h-10 bg-purple-100 rounded-full flex items-center justify-center">
|
|
<svg class="w-5 h-5 text-purple-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>
|
|
<p class="text-xs text-gray-400 mt-1">전체 평균</p>
|
|
</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: 월별 지급 건수 추이 (Line) --}}
|
|
<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="monthlyCountChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- 월별 지급 요약 테이블 --}}
|
|
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<h3 class="text-sm font-semibold text-gray-700">월별 지급 요약</h3>
|
|
</div>
|
|
<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">월</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 $grandPartner = 0; $grandManager = 0; $grandReferrer = 0; $grandTotal = 0; $grandCount = 0; @endphp
|
|
@forelse ($monthlyTrend->reverse() as $row)
|
|
@php
|
|
$rowTotal = $row->partner_total + $row->manager_total + $row->referrer_total;
|
|
$grandPartner += $row->partner_total;
|
|
$grandManager += $row->manager_total;
|
|
$grandReferrer += $row->referrer_total;
|
|
$grandTotal += $rowTotal;
|
|
$grandCount += $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('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 font-bold text-gray-900">{{ number_format($rowTotal) }}원</td>
|
|
<td class="px-4 py-3 text-sm text-center text-gray-900">{{ $row->count }}건</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="6" class="px-4 py-8 text-center text-gray-500">{{ $year }}년 지급 데이터가 없습니다.</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($grandPartner) }}원</td>
|
|
<td class="px-4 py-3 text-sm text-right text-gray-900">{{ number_format($grandManager) }}원</td>
|
|
<td class="px-4 py-3 text-sm text-right text-gray-900">{{ number_format($grandReferrer) }}원</td>
|
|
<td class="px-4 py-3 text-sm text-right text-indigo-700">{{ number_format($grandTotal) }}원</td>
|
|
<td class="px-4 py-3 text-sm text-center text-gray-900">{{ $grandCount }}건</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 monthlyData = @json($monthlyTrend);
|
|
const months = monthlyData.map(d => {
|
|
const parts = d.month.split('-');
|
|
return parseInt(parts[1]) + '월';
|
|
});
|
|
|
|
// 차트 1: 월별 지급 추이 (Stacked Bar)
|
|
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);
|
|
const topPartnerNames = @json($topPartnerNames->map(fn ($p) => $p->user?->name ?? $p->partner_code));
|
|
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: 월별 지급 건수 추이 (Line)
|
|
new Chart(document.getElementById('monthlyCountChart'), {
|
|
type: 'line',
|
|
data: {
|
|
labels: months,
|
|
datasets: [{
|
|
label: '지급 건수',
|
|
data: monthlyData.map(d => Number(d.count)),
|
|
borderColor: 'rgba(79, 70, 229, 1)',
|
|
backgroundColor: 'rgba(79, 70, 229, 0.1)',
|
|
fill: true,
|
|
tension: 0.3,
|
|
pointBackgroundColor: 'rgba(79, 70, 229, 1)',
|
|
pointRadius: 4,
|
|
pointHoverRadius: 6
|
|
}]
|
|
},
|
|
options: {
|
|
...defaultOptions,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
ticks: { stepSize: 1 }
|
|
}
|
|
},
|
|
plugins: {
|
|
...defaultOptions.plugins,
|
|
legend: { display: false }
|
|
}
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
@endpush
|