- 한 화면에서 기간·품목구분(봉투/음식물/폐기물)·집계방식·표시컬럼 선택, 제목 지정 후 조회/인쇄 - 컬럼 13종 선택, 상세/품목별 집계 지원, 실판매(bag_sale) 기준 - 계정별(mb_idx) 프리셋 저장/불러오기/삭제 (report_preset 테이블) - report_preset 테이블 없으면 프리셋만 비활성, 리포트는 정상 동작 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
189 lines
9.5 KiB
PHP
189 lines
9.5 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
/**
|
|
* @var array<string, array{label:string,type:string}> $columnCatalog
|
|
* @var list<string> $selected
|
|
* @var string $title
|
|
* @var string $category
|
|
* @var string $mode
|
|
* @var string $startDate
|
|
* @var string $endDate
|
|
* @var list<array<string,mixed>> $rows
|
|
* @var array<string,int> $totals
|
|
* @var list<object> $presets
|
|
* @var int $activePresetIdx
|
|
*/
|
|
$catLabels = ['all' => '전체', 'envelope' => '봉투', 'food' => '음식물', 'waste' => '폐기물'];
|
|
$printTitle = $title !== '' ? $title : '맞춤 집계표';
|
|
$nf = static fn ($n): string => number_format((int) $n);
|
|
?>
|
|
<?= view('components/print_header', [
|
|
'printTitle' => $printTitle,
|
|
'printDate' => date('Y-m-d'),
|
|
'printExtraLines' => ['조회기간: ' . $startDate . ' ~ ' . $endDate . ' · 품목구분: ' . ($catLabels[$category] ?? '전체') . ' · (단위: 매 / 원)'],
|
|
]) ?>
|
|
|
|
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel no-print flex flex-wrap items-center justify-between gap-2">
|
|
<span class="text-sm font-bold text-gray-700">맞춤 집계표</span>
|
|
<button type="button" onclick="window.print()" class="border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
|
</section>
|
|
|
|
<!-- 조회/구성 폼 -->
|
|
<section class="p-3 bg-white border-b border-gray-200 no-print">
|
|
<form method="get" action="<?= mgmt_url('reports/custom') ?>" id="custom-report-form" class="space-y-3">
|
|
<input type="hidden" name="applied" value="1"/>
|
|
<div class="flex flex-wrap items-end gap-3 text-sm">
|
|
<div>
|
|
<label class="block text-xs font-bold text-gray-600 mb-1">표 제목</label>
|
|
<input type="text" name="title" value="<?= esc($title, 'attr') ?>" maxlength="200" placeholder="예: 2026년 음식물 봉투 판매 집계" class="border border-gray-300 rounded px-2 py-1 w-72"/>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-bold text-gray-600 mb-1">시작일</label>
|
|
<input type="date" name="start_date" value="<?= esc($startDate, 'attr') ?>" class="border border-gray-300 rounded px-2 py-1"/>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-bold text-gray-600 mb-1">종료일</label>
|
|
<input type="date" name="end_date" value="<?= esc($endDate, 'attr') ?>" class="border border-gray-300 rounded px-2 py-1"/>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-bold text-gray-600 mb-1">품목구분(종류)</label>
|
|
<select name="category" class="border border-gray-300 rounded px-2 py-1 w-32">
|
|
<?php foreach ($catLabels as $ck => $cl): ?>
|
|
<option value="<?= esc($ck, 'attr') ?>" <?= $category === $ck ? 'selected' : '' ?>><?= esc($cl) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-bold text-gray-600 mb-1">집계 방식</label>
|
|
<select name="mode" class="border border-gray-300 rounded px-2 py-1 w-40">
|
|
<option value="detail" <?= $mode === 'detail' ? 'selected' : '' ?>>상세(건별)</option>
|
|
<option value="summary" <?= $mode === 'summary' ? 'selected' : '' ?>>품목별 집계</option>
|
|
</select>
|
|
</div>
|
|
<button type="submit" class="bg-btn-search text-white px-4 py-1.5 rounded-sm text-sm shadow hover:opacity-90">조회</button>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-xs font-bold text-gray-600 mb-1">표시할 컬럼 선택</label>
|
|
<div class="flex flex-wrap gap-x-4 gap-y-1">
|
|
<?php foreach ($columnCatalog as $key => $meta): ?>
|
|
<label class="inline-flex items-center gap-1 text-sm cursor-pointer">
|
|
<input type="checkbox" name="cols[]" value="<?= esc($key, 'attr') ?>" <?= in_array($key, $selected, true) ? 'checked' : '' ?>/>
|
|
<span><?= esc($meta['label']) ?></span>
|
|
</label>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<p class="text-[11px] text-gray-500 mt-1">집계 방식이 「품목별 집계」이면 일자·판매소 등 건별 컬럼은 빈 값으로 표시됩니다.</p>
|
|
</div>
|
|
</form>
|
|
</section>
|
|
|
|
<!-- 프리셋 (계정별 저장) -->
|
|
<?php if (! empty($presetsEnabled)): ?>
|
|
<section class="p-3 bg-gray-50 border-b border-gray-200 no-print">
|
|
<div class="flex flex-wrap items-end gap-3 text-sm">
|
|
<form method="get" action="<?= mgmt_url('reports/custom') ?>" class="flex items-end gap-2">
|
|
<div>
|
|
<label class="block text-xs font-bold text-gray-600 mb-1">저장된 프리셋 불러오기</label>
|
|
<select name="preset" class="border border-gray-300 rounded px-2 py-1 min-w-[14rem]" onchange="this.form.submit()">
|
|
<option value="">— 프리셋 선택 —</option>
|
|
<?php foreach ($presets as $p): ?>
|
|
<option value="<?= (int) $p->rp_idx ?>" <?= $activePresetIdx === (int) $p->rp_idx ? 'selected' : '' ?>><?= esc((string) $p->rp_name) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<input type="hidden" name="start_date" value="<?= esc($startDate, 'attr') ?>"/>
|
|
<input type="hidden" name="end_date" value="<?= esc($endDate, 'attr') ?>"/>
|
|
</form>
|
|
|
|
<?php if ($activePresetIdx > 0): ?>
|
|
<form method="post" action="<?= mgmt_url('reports/custom/preset/delete') ?>" onsubmit="return confirm('이 프리셋을 삭제할까요?');" class="flex items-end">
|
|
<?= csrf_field() ?>
|
|
<input type="hidden" name="rp_idx" value="<?= (int) $activePresetIdx ?>"/>
|
|
<button type="submit" class="text-red-600 hover:underline text-xs pb-1">현재 프리셋 삭제</button>
|
|
</form>
|
|
<?php endif; ?>
|
|
|
|
<!-- 현재 화면 구성 그대로 저장 (JS로 현재 폼 값 복사) -->
|
|
<form method="post" action="<?= mgmt_url('reports/custom/preset/save') ?>" id="preset-save-form" class="flex items-end gap-2">
|
|
<?= csrf_field() ?>
|
|
<input type="hidden" name="rp_idx" value="<?= (int) $activePresetIdx ?>"/>
|
|
<input type="hidden" name="rp_title" id="ps-title"/>
|
|
<input type="hidden" name="rp_category" id="ps-category"/>
|
|
<input type="hidden" name="rp_mode" id="ps-mode"/>
|
|
<input type="hidden" name="start_date" value="<?= esc($startDate, 'attr') ?>"/>
|
|
<input type="hidden" name="end_date" value="<?= esc($endDate, 'attr') ?>"/>
|
|
<div id="ps-cols-holder"></div>
|
|
<div>
|
|
<label class="block text-xs font-bold text-gray-600 mb-1">프리셋 이름</label>
|
|
<input type="text" name="rp_name" maxlength="100" placeholder="예: 음식물 담당 월집계" class="border border-gray-300 rounded px-2 py-1 w-56" required/>
|
|
</div>
|
|
<button type="submit" class="bg-[#243a5e] text-white px-4 py-1.5 rounded-sm text-sm shadow hover:opacity-90">현재 구성 저장</button>
|
|
</form>
|
|
</div>
|
|
</section>
|
|
<?php endif; ?>
|
|
|
|
<!-- 결과 표 -->
|
|
<div class="p-3">
|
|
<div class="mb-2 text-center no-print">
|
|
<h1 class="text-lg font-bold m-0"><?= esc($printTitle) ?></h1>
|
|
<p class="text-xs text-gray-500 mt-0.5">조회기간 <?= esc($startDate) ?> ~ <?= esc($endDate) ?> · 품목구분 <?= esc($catLabels[$category] ?? '전체') ?> · 건수 <?= $nf(count($rows)) ?></p>
|
|
</div>
|
|
<div class="overflow-auto">
|
|
<table class="w-full data-table text-sm">
|
|
<thead>
|
|
<tr>
|
|
<th class="w-12 text-center">No</th>
|
|
<?php foreach ($selected as $key): ?>
|
|
<th class="<?= ($columnCatalog[$key]['type'] ?? '') === 'num' ? 'text-right' : 'text-left' ?> px-2"><?= esc($columnCatalog[$key]['label']) ?></th>
|
|
<?php endforeach; ?>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if ($rows === []): ?>
|
|
<tr><td colspan="<?= count($selected) + 1 ?>" class="text-center text-gray-400 py-6">조회된 데이터가 없습니다.</td></tr>
|
|
<?php else: ?>
|
|
<?php foreach ($rows as $i => $row): ?>
|
|
<tr>
|
|
<td class="text-center text-gray-500"><?= $i + 1 ?></td>
|
|
<?php foreach ($selected as $key): ?>
|
|
<?php $isNum = ($columnCatalog[$key]['type'] ?? '') === 'num'; ?>
|
|
<td class="<?= $isNum ? 'text-right' : 'text-left' ?> px-2"><?= $isNum ? $nf($row[$key] ?? 0) : esc((string) ($row[$key] ?? '')) ?></td>
|
|
<?php endforeach; ?>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<tr class="bg-amber-50 font-bold">
|
|
<td class="text-center">합계</td>
|
|
<?php foreach ($selected as $key): ?>
|
|
<?php $isNum = ($columnCatalog[$key]['type'] ?? '') === 'num'; ?>
|
|
<td class="<?= $isNum ? 'text-right' : '' ?> px-2"><?= $isNum ? $nf($totals[$key] ?? 0) : '' ?></td>
|
|
<?php endforeach; ?>
|
|
</tr>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
// "현재 구성 저장" 시, 조회 폼의 현재 값(제목/종류/방식/컬럼)을 프리셋 저장 폼으로 복사
|
|
var mainForm = document.getElementById('custom-report-form');
|
|
var saveForm = document.getElementById('preset-save-form');
|
|
if (!mainForm || !saveForm) return;
|
|
saveForm.addEventListener('submit', function () {
|
|
document.getElementById('ps-title').value = mainForm.querySelector('[name="title"]').value;
|
|
document.getElementById('ps-category').value = mainForm.querySelector('[name="category"]').value;
|
|
document.getElementById('ps-mode').value = mainForm.querySelector('[name="mode"]').value;
|
|
var holder = document.getElementById('ps-cols-holder');
|
|
holder.innerHTML = '';
|
|
mainForm.querySelectorAll('input[name="cols[]"]:checked').forEach(function (cb) {
|
|
var h = document.createElement('input');
|
|
h.type = 'hidden'; h.name = 'cols[]'; h.value = cb.value;
|
|
holder.appendChild(h);
|
|
});
|
|
});
|
|
})();
|
|
</script>
|