247 lines
8.8 KiB
PHP
247 lines
8.8 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 text-sm">
|
||
<li class="breadcrumb-item"><a href="{{ route('home') }}">ホーム</a></li>
|
||
<li class="breadcrumb-item active">定期利用・契約状況</li>
|
||
</ol>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<section class="content">
|
||
<div class="container-fluid">
|
||
<div class="card card-body mb-3">
|
||
<form class="form-inline" onsubmit="return false;">
|
||
<label class="mr-2 mb-0">駐輪場:</label>
|
||
<select name="park_id" id="park_id" class="form-control mr-3">
|
||
<option value="">全て</option>
|
||
@foreach ($parks as $p)
|
||
<option value="{{ $p->park_id }}" {{ (string)$selectedParkId === (string)$p->park_id ? 'selected' : '' }}>
|
||
{{ $p->park_name }}
|
||
</option>
|
||
@endforeach
|
||
</select>
|
||
</form>
|
||
<small class="text-muted d-block mt-2">
|
||
以下に記載以外の期間情報を参照したい場合は、マスタ管理>定期期間マスタのCSV出力を利用してください。
|
||
</small>
|
||
</div>
|
||
|
||
<div id="stats-sections" style="display:none;">
|
||
<div class="card mb-3">
|
||
<div class="card-header p-2">契約状況</div>
|
||
<div class="card-body p-2">
|
||
<table class="table table-bordered mb-0 text-center">
|
||
<thead>
|
||
<tr>
|
||
<th rowspan="2" class="align-middle">契約状況</th>
|
||
<th rowspan="2" class="align-middle">一般</th>
|
||
<th rowspan="2" class="align-middle">学生</th>
|
||
<th rowspan="2" class="align-middle">利用計</th>
|
||
<th rowspan="2" class="align-middle">空き</th>
|
||
<th rowspan="2" class="align-middle">合計</th>
|
||
<th colspan="2">直近契約</th>
|
||
</tr>
|
||
<tr>
|
||
<th>予約日</th>
|
||
<th>契約日</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tbl-contract-summary"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card mb-3">
|
||
<div class="card-header p-2">空き待ち状況</div>
|
||
<div class="card-body p-2">
|
||
<table class="table table-bordered mb-0 text-center">
|
||
<thead>
|
||
<tr>
|
||
<th rowspan="2" class="align-middle">空き待ち状況</th>
|
||
<th colspan="2">一般</th>
|
||
<th colspan="2">学生</th>
|
||
<th rowspan="2" class="align-middle">合計</th>
|
||
</tr>
|
||
<tr>
|
||
<th>件数</th>
|
||
<th>先頭日</th>
|
||
<th>件数</th>
|
||
<th>先頭日</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tbl-waiting-summary"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-header p-2">更新状況</div>
|
||
<div class="card-body p-2">
|
||
<table class="table table-bordered mb-0 text-center">
|
||
<thead>
|
||
<tr>
|
||
<th rowspan="2" class="align-middle" style="min-width:70px">月</th>
|
||
<th rowspan="2" class="align-middle" style="min-width:60px">更新状況</th>
|
||
<th colspan="3">自転車</th>
|
||
<th colspan="3">原付</th>
|
||
<th colspan="3">その他</th>
|
||
</tr>
|
||
<tr>
|
||
<th>一般</th><th>学生</th><th>計</th>
|
||
<th>一般</th><th>学生</th><th>計</th>
|
||
<th>一般</th><th>学生</th><th>計</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tbl-renewal-summary"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
@endsection
|
||
|
||
@push('scripts')
|
||
<script>
|
||
(function() {
|
||
function $(sel){return document.querySelector(sel)}
|
||
function $tb(id){return document.getElementById(id)}
|
||
function escapeHtml(s){const d=document.createElement('div');d.textContent=(s??'');return d.innerHTML}
|
||
function numberOrDash(v){const n=Number(v);return Number.isFinite(n)?n.toString():'-'}
|
||
function formatDate(d){return d && d !== 'null' ? d : '-'}
|
||
function formatMonth(m){
|
||
if(!m || m === 'null') return '-';
|
||
// YYYY-MM 形式を MM月 形式に変換
|
||
const parts = m.split('-');
|
||
if(parts.length === 2) {
|
||
const monthNum = parseInt(parts[1], 10);
|
||
return `${monthNum}月`;
|
||
}
|
||
return m.replace('-', '/');
|
||
}
|
||
|
||
const park = $('#park_id');
|
||
const stats = $('#stats-sections');
|
||
|
||
park.addEventListener('change', loadStats);
|
||
loadStats();
|
||
|
||
function loadStats(){
|
||
const id = park.value;
|
||
if(!id){ stats.style.display='none'; clearTables(); return; }
|
||
|
||
fetch("{{ route('periodical.listData') }}?park_id="+encodeURIComponent(id), {headers:{'Accept':'application/json'}})
|
||
.then(r => r.ok ? r.json() : Promise.reject(r))
|
||
.then(res => {
|
||
stats.style.display='block';
|
||
|
||
// デバッグ情報があれば表示
|
||
if(res.debug_info) {
|
||
console.log('Debug info:', res.debug_info);
|
||
}
|
||
|
||
renderContractSummary(res.contract_summary||[]);
|
||
renderWaitingSummary(res.waiting_summary||[]);
|
||
renderRenewalSummary(res.renewal_summary||[]);
|
||
})
|
||
.catch(err => {
|
||
stats.style.display='none';
|
||
clearTables();
|
||
});
|
||
}
|
||
function clearTables(){
|
||
['tbl-contract-summary','tbl-waiting-summary','tbl-renewal-summary'].forEach(id => $tb(id).innerHTML='');
|
||
}
|
||
|
||
// 契約状況
|
||
function renderContractSummary(list){
|
||
const tb = $tb('tbl-contract-summary'); tb.innerHTML='';
|
||
if(!Array.isArray(list)||!list.length){
|
||
tb.innerHTML = noDataRow(8);
|
||
return;
|
||
}
|
||
list.forEach(row=>{
|
||
const rowClass = row.type === '計' ? ' class="font-weight-bold"' : '';
|
||
tb.insertAdjacentHTML('beforeend', `
|
||
<tr${rowClass}>
|
||
<td>${escapeHtml(row.type)}</td>
|
||
<td>${formatPair(row.general_count, row.general_extra)}</td>
|
||
<td>${formatPair(row.student_count, row.student_extra)}</td>
|
||
<td>${numberOrDash(row.use_total)}</td>
|
||
<td>${numberOrDash(row.vacancy)}</td>
|
||
<td>${numberOrDash(row.total)}</td>
|
||
<td>${formatDate(row?.last?.reserve_date)}</td>
|
||
<td>${formatDate(row?.last?.contract_date)}</td>
|
||
</tr>`);
|
||
});
|
||
}
|
||
function formatPair(c,e){
|
||
const n = Number.isFinite(Number(c))?Number(c):0;
|
||
const m = Number.isFinite(Number(e))?Number(e):0;
|
||
return `${n}(${m})`;
|
||
}
|
||
|
||
// 空き待ち
|
||
function renderWaitingSummary(list){
|
||
const tb = $tb('tbl-waiting-summary'); tb.innerHTML='';
|
||
if(!Array.isArray(list)||!list.length){ tb.innerHTML = noDataRow(6); return; }
|
||
list.forEach(r=>{
|
||
const rowClass = r.type === '計' ? ' class="font-weight-bold"' : '';
|
||
tb.insertAdjacentHTML('beforeend', `
|
||
<tr${rowClass}>
|
||
<td>${escapeHtml(r.type)}</td>
|
||
<td>${numberOrDash(r.general_count)}</td>
|
||
<td>${formatDate(r.general_first)}</td>
|
||
<td>${numberOrDash(r.student_count)}</td>
|
||
<td>${formatDate(r.student_first)}</td>
|
||
<td>${numberOrDash(r.total)}</td>
|
||
</tr>`);
|
||
});
|
||
}
|
||
|
||
// 更新状況(月ブロック形式)
|
||
function renderRenewalSummary(blocks){
|
||
const tb = $tb('tbl-renewal-summary'); tb.innerHTML='';
|
||
if(!Array.isArray(blocks)||!blocks.length){ tb.innerHTML = noDataRow(11); return; }
|
||
|
||
// 新形式: [{month:'YYYY-MM', rows:[{label,...}]}]
|
||
blocks.forEach(b=>{
|
||
const rows = Array.isArray(b.rows)?b.rows:[];
|
||
if(rows.length === 0) return;
|
||
|
||
rows.forEach((r,idx)=>{
|
||
const monthCell = idx===0 ? `<td rowspan="${rows.length}" class="align-middle">${formatMonth(b.month)}</td>` : '';
|
||
const rowClass = r.label === '計' ? ' class="font-weight-bold"' : '';
|
||
tb.insertAdjacentHTML('beforeend', `
|
||
<tr${rowClass}>
|
||
${monthCell}
|
||
<td>${escapeHtml(r.label)}</td>
|
||
<td>${numberOrDash(r.bicycle_general)}</td>
|
||
<td>${numberOrDash(r.bicycle_student)}</td>
|
||
<td>${numberOrDash(r.bicycle_total)}</td>
|
||
<td>${numberOrDash(r.moped_general)}</td>
|
||
<td>${numberOrDash(r.moped_student)}</td>
|
||
<td>${numberOrDash(r.moped_total)}</td>
|
||
<td>${numberOrDash(r.other_general)}</td>
|
||
<td>${numberOrDash(r.other_student)}</td>
|
||
<td>${numberOrDash(r.other_total)}</td>
|
||
</tr>`);
|
||
});
|
||
});
|
||
}
|
||
|
||
function noDataRow(colspan){ return `<tr><td colspan="${colspan}">データがありません</td></tr>`; }
|
||
})();
|
||
</script>
|
||
@endpush
|