krgm.so-manager-dev.com/resources/views/admin/information/dashboard.blade.php
OU.ZAIKOU 13d2ecfceb
All checks were successful
Deploy main / deploy (push) Successful in 25s
【ログイン】二重認証実装
2026-01-21 22:37:38 +09:00

381 lines
17 KiB
PHP

@extends('layouts.app')
@section('title', '総合ダッシュボード')
@section('content')
<div class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1 class="m-0 text-dark">総合ダッシュボード</h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="{{ route('information') }}">ホーム</a></li>
<li class="breadcrumb-item active">総合ダッシュボード</li>
</ol>
</div>
</div>
</div>
</div>
<section class="content">
<div class="container-fluid">
<!-- 全体統計情報 -->
<div class="row">
<div class="col-12">
<div class="card card-primary">
<div class="card-header">
<h3 class="card-title">全体統計</h3>
</div>
<div class="card-body">
<div class="row">
<div class="col-lg-2 col-6">
<div class="small-box bg-info">
<div class="inner">
<h3>{{ $totalStats['total_cities'] }}</h3>
<p>自治体数</p>
</div>
<div class="icon">
<i class="fa fa-map-marker"></i>
</div>
</div>
</div>
<div class="col-lg-2 col-6">
<div class="small-box bg-success">
<div class="inner">
<h3>{{ $totalStats['total_parks'] }}</h3>
<p>駐輪場数</p>
</div>
<div class="icon">
<i class="fa fa-building"></i>
</div>
</div>
</div>
<div class="col-lg-2 col-6">
<div class="small-box bg-warning">
<div class="inner">
<h3>{{ number_format($totalStats['total_contracts']) }}</h3>
<p>総契約数</p>
</div>
<div class="icon">
<i class="fa fa-file-text"></i>
</div>
</div>
</div>
<div class="col-lg-2 col-6">
<div class="small-box bg-danger">
<div class="inner">
<h3>{{ number_format($totalStats['total_waiting']) }}</h3>
<p>総予約待ち</p>
</div>
<div class="icon">
<i class="fa fa-clock-o"></i>
</div>
</div>
</div>
<div class="col-lg-2 col-6">
<div class="small-box bg-purple">
<div class="inner">
<h3>{{ number_format($totalStats['total_capacity']) }}</h3>
<p>総収容台数</p>
</div>
<div class="icon">
<i class="fa fa-bicycle"></i>
</div>
</div>
</div>
<div class="col-lg-2 col-6">
<div class="small-box bg-teal">
<div class="inner">
<h3>{{ $totalStats['total_utilization_rate'] }}%</h3>
<p>全体利用率</p>
</div>
<div class="icon">
<i class="fa fa-pie-chart"></i>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 自治体別統計表 -->
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">自治体別統計</h3>
<div class="card-tools">
<button type="button" class="btn btn-tool" data-card-widget="collapse">
<i class="fa fa-minus"></i>
</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered table-striped table-hover" id="cityStatsTable">
<thead class="thead-dark">
<tr>
<th>自治体名</th>
<th class="text-center">駐輪場数</th>
<th class="text-center">契約数</th>
<th class="text-center">利用者数</th>
<th class="text-center">予約待ち</th>
<th class="text-center">収容台数</th>
<th class="text-center">利用率</th>
<th class="text-center">空き台数</th>
<th class="text-center">操作</th>
</tr>
</thead>
<tbody>
@foreach($cityStats as $stat)
<tr>
<td>
<strong>{{ $stat['city']->city_name }}</strong>
</td>
<td class="text-center">
{{ $stat['parks_count'] }}
</td>
<td class="text-center">
{{ number_format($stat['contracts_count']) }}
</td>
<td class="text-center">
{{ number_format($stat['users_count']) }}
</td>
<td class="text-center">
@if($stat['waiting_count'] > 0)
<span class="badge badge-warning">{{ number_format($stat['waiting_count']) }}</span>
@else
<span class="badge badge-success">0</span>
@endif
</td>
<td class="text-center">
{{ number_format($stat['capacity']) }}
</td>
<td class="text-center">
@php
$rate = $stat['utilization_rate'];
$badgeClass = $rate >= 90 ? 'danger' : ($rate >= 70 ? 'warning' : 'success');
@endphp
<span class="badge badge-{{ $badgeClass }}">{{ $rate }}%</span>
</td>
<td class="text-center">
@if($stat['available_spaces'] > 0)
<span class="text-success">{{ number_format($stat['available_spaces']) }}</span>
@else
<span class="text-danger">満車</span>
@endif
</td>
<td class="text-center">
<a href="{{ route('city_dashboard', ['city_id' => $stat['city']->city_id]) }}"
class="btn btn-sm btn-primary">
<i class="fa fa-eye"></i> 詳細
</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- 利用率チャート -->
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h3 class="card-title">自治体別利用率</h3>
</div>
<div class="card-body">
<canvas id="utilizationChart" width="400" height="300"></canvas>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h3 class="card-title">予約待ち状況</h3>
</div>
<div class="card-body">
<canvas id="waitingChart" width="400" height="300"></canvas>
</div>
</div>
</div>
</div>
<!-- アラート表示 -->
<div class="row">
<div class="col-12">
<div class="card card-warning">
<div class="card-header">
<h3 class="card-title">⚠️ 注意が必要な自治体</h3>
</div>
<div class="card-body">
@php
$alertCities = collect($cityStats)->filter(function($stat) {
return $stat['utilization_rate'] >= 90 || $stat['waiting_count'] > 10;
});
@endphp
@if($alertCities->count() > 0)
<div class="row">
@foreach($alertCities as $stat)
<div class="col-md-4">
<div class="alert alert-warning">
<h5><i class="icon fa fa-exclamation-triangle"></i> {{ $stat['city']->city_name }}</h5>
@if($stat['utilization_rate'] >= 90)
<p>利用率が {{ $stat['utilization_rate'] }}% と高くなっています</p>
@endif
@if($stat['waiting_count'] > 10)
<p>予約待ちが {{ $stat['waiting_count'] }} います</p>
@endif
<a href="{{ route('city_dashboard', ['city_id' => $stat['city']->city_id]) }}"
class="btn btn-sm btn-warning">詳細確認</a>
</div>
</div>
@endforeach
</div>
@else
<div class="alert alert-success">
<i class="icon fa fa-check"></i> 現在、特に注意が必要な自治体はありません。
</div>
@endif
</div>
</div>
</div>
</div>
</div>
</section>
@endsection
@push('scripts')
<script src="{{ asset('plugins/chart.js/Chart.min.js') }}"></script>
<script>
$(document).ready(function() {
// データテーブル初期化
$('#cityStatsTable').DataTable({
"language": {
"url": "//cdn.datatables.net/plug-ins/1.10.21/i18n/Japanese.json"
},
"pageLength": 10,
"order": [[ 6, "desc" ]], // 利用率で降順ソート
"columnDefs": [
{ "orderable": false, "targets": 8 } // 操作列はソート無効
]
});
// 利用率チャート(円グラフ)
var ctxUtil = document.getElementById('utilizationChart').getContext('2d');
var utilizationChart = new Chart(ctxUtil, {
type: 'pie',
data: {
labels: [
@foreach($cityStats as $stat)
'{{ $stat['city']->city_name }} ({{ $stat['utilization_rate'] }}%)',
@endforeach
],
datasets: [{
label: '利用率',
data: [
@foreach($cityStats as $stat)
{{ $stat['utilization_rate'] }},
@endforeach
],
backgroundColor: [
'rgba(255, 99, 132, 0.8)',
'rgba(54, 162, 235, 0.8)',
'rgba(255, 206, 86, 0.8)',
'rgba(75, 192, 192, 0.8)',
'rgba(153, 102, 255, 0.8)',
'rgba(255, 159, 64, 0.8)',
'rgba(199, 199, 199, 0.8)',
'rgba(83, 102, 255, 0.8)',
'rgba(255, 193, 7, 0.8)',
'rgba(220, 53, 69, 0.8)',
'rgba(108, 117, 125, 0.8)',
'rgba(40, 167, 69, 0.8)'
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)',
'rgba(199, 199, 199, 1)',
'rgba(83, 102, 255, 1)',
'rgba(255, 193, 7, 1)',
'rgba(220, 53, 69, 1)',
'rgba(108, 117, 125, 1)',
'rgba(40, 167, 69, 1)'
],
borderWidth: 2
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'bottom',
labels: {
boxWidth: 12,
padding: 10
}
},
tooltip: {
callbacks: {
label: function(context) {
var label = context.label.split(' (')[0]; // 自治体名のみ取得
var value = context.parsed;
var total = context.dataset.data.reduce((a, b) => a + b, 0);
var percentage = ((value / total) * 100).toFixed(1);
return label + ': ' + value + '% (全体の' + percentage + '%)';
}
}
}
}
}
});
// 予約待ちチャート
var ctxWait = document.getElementById('waitingChart').getContext('2d');
var waitingChart = new Chart(ctxWait, {
type: 'doughnut',
data: {
labels: [
@foreach($cityStats as $stat)
'{{ $stat['city']->city_name }}',
@endforeach
],
datasets: [{
label: '予約待ち人数',
data: [
@foreach($cityStats as $stat)
{{ $stat['waiting_count'] }},
@endforeach
],
backgroundColor: [
'rgba(255, 99, 132, 0.8)',
'rgba(54, 162, 235, 0.8)',
'rgba(255, 206, 86, 0.8)',
'rgba(75, 192, 192, 0.8)',
'rgba(153, 102, 255, 0.8)',
'rgba(255, 159, 64, 0.8)',
'rgba(199, 199, 199, 0.8)',
'rgba(83, 102, 255, 0.8)'
]
}]
},
options: {
responsive: true
}
});
});
</script>
@endpush