feat(110): statistics summary
Phase 110 complete: - add Statistics -> Podsumowanie page - add monthly order count and value charts per integration plus total - use Chart.js with table fallback and 04-2026 default history start - update PAUL and DOCS technical documentation
This commit is contained in:
@@ -24,6 +24,7 @@ return [
|
||||
'orders' => 'Zamowienia',
|
||||
'orders_list' => 'Lista zamowien',
|
||||
'statistics' => 'Statystyki',
|
||||
'statistics_summary' => 'Podsumowanie',
|
||||
'statistics_orders' => 'Zamowienia',
|
||||
'marketplace' => 'Marketplace',
|
||||
'cron' => 'Harmonogram',
|
||||
@@ -221,6 +222,29 @@ return [
|
||||
],
|
||||
],
|
||||
'statistics' => [
|
||||
'summary' => [
|
||||
'title' => 'Podsumowanie statystyk',
|
||||
'description' => 'Miesieczne trendy ilosci i wartosci zamowien z podzialem na integracje.',
|
||||
'empty' => 'Brak danych dla wybranych filtrow.',
|
||||
'charts' => [
|
||||
'orders_count' => 'Ilosc zamowien miesiecznie',
|
||||
'orders_value' => 'Wartosc zamowien miesiecznie',
|
||||
],
|
||||
'filters' => [
|
||||
'date_from' => 'Data od',
|
||||
'date_to' => 'Data do',
|
||||
'channels' => 'Kanaly sprzedazy',
|
||||
'status_groups' => 'Grupy statusow',
|
||||
],
|
||||
'columns' => [
|
||||
'month' => 'Miesiac',
|
||||
'total' => 'Razem',
|
||||
],
|
||||
'actions' => [
|
||||
'apply_filters' => 'Filtruj',
|
||||
'reset_filters' => 'Wyczysc',
|
||||
],
|
||||
],
|
||||
'orders' => [
|
||||
'title' => 'Statystyki zamowien',
|
||||
'description' => 'Dzienne podsumowanie ilosci i kwot zamowien z podzialem na kanaly sprzedazy.',
|
||||
|
||||
@@ -1219,6 +1219,74 @@ h4.section-title {
|
||||
}
|
||||
}
|
||||
|
||||
.statistics-summary-page {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.statistics-summary-section {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.statistics-summary-chart-grid,
|
||||
.statistics-summary-table-grid {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
@media (min-width: 1100px) {
|
||||
.statistics-summary-chart-grid,
|
||||
.statistics-summary-table-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
align-items: start;
|
||||
}
|
||||
}
|
||||
|
||||
.statistics-summary-card {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.statistics-summary-card__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.statistics-summary-chart {
|
||||
position: relative;
|
||||
height: 320px;
|
||||
}
|
||||
|
||||
.statistics-summary-chart canvas {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.statistics-summary-fallback {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.statistics-summary-table {
|
||||
min-width: 640px;
|
||||
|
||||
th,
|
||||
td {
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
th:first-child,
|
||||
td:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.orders-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
</svg>
|
||||
</summary>
|
||||
<div class="sidebar__group-links">
|
||||
<a class="sidebar__sublink<?= $currentMenu === 'statistics' && $currentStatistics === 'summary' ? ' is-active' : '' ?>" href="/statistics/summary">
|
||||
<?= $e($t('navigation.statistics_summary')) ?>
|
||||
</a>
|
||||
<a class="sidebar__sublink<?= $currentMenu === 'statistics' && $currentStatistics === 'orders' ? ' is-active' : '' ?>" href="/statistics/orders">
|
||||
<?= $e($t('navigation.statistics_orders')) ?>
|
||||
</a>
|
||||
@@ -188,6 +191,8 @@
|
||||
<script src="/assets/js/modules/jquery-alerts.js?ver=<?= filemtime(dirname(__DIR__, 3) . '/public/assets/js/modules/jquery-alerts.js') ?: 0 ?>"></script>
|
||||
<script src="/assets/js/modules/global-search.js?ver=<?= filemtime(dirname(__DIR__, 3) . '/public/assets/js/modules/global-search.js') ?: 0 ?>"></script>
|
||||
<script src="/assets/js/modules/checkbox-multiselect.js?ver=<?= filemtime(dirname(__DIR__, 3) . '/public/assets/js/modules/checkbox-multiselect.js') ?: 0 ?>"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
|
||||
<script src="/assets/js/modules/statistics-summary-charts.js?ver=<?= filemtime(dirname(__DIR__, 3) . '/public/assets/js/modules/statistics-summary-charts.js') ?: 0 ?>"></script>
|
||||
<script>
|
||||
(function () {
|
||||
var STORAGE_KEY = 'sidebarCollapsed';
|
||||
|
||||
208
resources/views/statistics/summary.php
Normal file
208
resources/views/statistics/summary.php
Normal file
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
$filters = is_array($filters ?? null) ? $filters : [];
|
||||
$channelOptions = is_array($channelOptions ?? null) ? $channelOptions : [];
|
||||
$statusGroupOptions = is_array($statusGroupOptions ?? null) ? $statusGroupOptions : [];
|
||||
$summary = is_array($summary ?? null) ? $summary : [];
|
||||
|
||||
$selectedChannels = is_array($filters['selected_channels'] ?? null) ? $filters['selected_channels'] : [];
|
||||
$selectedStatusGroups = is_array($filters['selected_status_groups'] ?? null) ? $filters['selected_status_groups'] : [];
|
||||
$rows = is_array($summary['rows'] ?? null) ? $summary['rows'] : [];
|
||||
$hasData = (bool) ($summary['hasData'] ?? false);
|
||||
|
||||
$countChart = is_array($summary['countChart'] ?? null) ? $summary['countChart'] : ['labels' => [], 'series' => []];
|
||||
$valueChart = is_array($summary['valueChart'] ?? null) ? $summary['valueChart'] : ['labels' => [], 'series' => []];
|
||||
$chartPayload = [
|
||||
'count' => $countChart,
|
||||
'value' => $valueChart,
|
||||
];
|
||||
|
||||
$chartJson = json_encode(
|
||||
$chartPayload,
|
||||
JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT
|
||||
);
|
||||
if (!is_string($chartJson)) {
|
||||
$chartJson = '{}';
|
||||
}
|
||||
|
||||
$displayMonth = static function (string $month): string {
|
||||
$date = DateTimeImmutable::createFromFormat('Y-m-d', $month . '-01');
|
||||
if (!$date instanceof DateTimeImmutable) {
|
||||
return $month;
|
||||
}
|
||||
|
||||
return $date->format('m-Y');
|
||||
};
|
||||
?>
|
||||
|
||||
<section class="card statistics-summary-page">
|
||||
<div class="statistics-orders-head">
|
||||
<div>
|
||||
<h2 class="section-title"><?= $e($t('statistics.summary.title')) ?></h2>
|
||||
<p class="muted mt-12"><?= $e($t('statistics.summary.description')) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="get" action="/statistics/summary" class="statistics-orders-filters mt-16">
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('statistics.summary.filters.date_from')) ?></span>
|
||||
<input class="form-control" type="date" name="date_from" value="<?= $e((string) ($filters['date_from'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('statistics.summary.filters.date_to')) ?></span>
|
||||
<input class="form-control" type="date" name="date_to" value="<?= $e((string) ($filters['date_to'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('statistics.summary.filters.channels')) ?></span>
|
||||
<select class="form-control statistics-orders-multiselect js-checkbox-multiselect"
|
||||
name="channels[]"
|
||||
multiple
|
||||
size="6"
|
||||
data-checkbox-multiselect
|
||||
data-all-label="Wszystkie"
|
||||
data-empty-label="Nic nie wybrano"
|
||||
data-selected-label-singular="zaznaczono"
|
||||
data-selected-label-plural="zaznaczono">
|
||||
<?php foreach ($channelOptions as $channelOption): ?>
|
||||
<?php
|
||||
$key = (string) ($channelOption['key'] ?? '');
|
||||
$label = (string) ($channelOption['label'] ?? $key);
|
||||
if ($key === '') {
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
<option value="<?= $e($key) ?>"<?= in_array($key, $selectedChannels, true) ? ' selected' : '' ?>>
|
||||
<?= $e($label) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('statistics.summary.filters.status_groups')) ?></span>
|
||||
<select class="form-control statistics-orders-multiselect js-checkbox-multiselect"
|
||||
name="status_groups[]"
|
||||
multiple
|
||||
size="6"
|
||||
data-checkbox-multiselect
|
||||
data-all-label="Wszystkie"
|
||||
data-empty-label="Nic nie wybrano"
|
||||
data-selected-label-singular="zaznaczono"
|
||||
data-selected-label-plural="zaznaczono">
|
||||
<?php foreach ($statusGroupOptions as $groupOption): ?>
|
||||
<?php
|
||||
$groupId = (int) ($groupOption['id'] ?? 0);
|
||||
$groupName = (string) ($groupOption['name'] ?? '');
|
||||
if ($groupId <= 0) {
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
<option value="<?= $e((string) $groupId) ?>"<?= in_array($groupId, $selectedStatusGroups, true) ? ' selected' : '' ?>>
|
||||
<?= $e($groupName) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<div class="form-field statistics-orders-filters__actions">
|
||||
<span class="field-label"> </span>
|
||||
<div class="filters-actions">
|
||||
<button type="submit" class="btn btn--primary"><?= $e($t('statistics.summary.actions.apply_filters')) ?></button>
|
||||
<a href="/statistics/summary" class="btn btn--secondary"><?= $e($t('statistics.summary.actions.reset_filters')) ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="statistics-summary-section mt-16">
|
||||
<?php if (!$hasData): ?>
|
||||
<div class="card statistics-summary-card">
|
||||
<p class="muted"><?= $e($t('statistics.summary.empty')) ?></p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="statistics-summary-chart-grid">
|
||||
<article class="card statistics-summary-card">
|
||||
<div class="statistics-summary-card__head">
|
||||
<h3 class="section-title"><?= $e($t('statistics.summary.charts.orders_count')) ?></h3>
|
||||
</div>
|
||||
<div class="statistics-summary-chart" data-statistics-chart="count">
|
||||
<canvas aria-label="<?= $e($t('statistics.summary.charts.orders_count')) ?>"></canvas>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="card statistics-summary-card">
|
||||
<div class="statistics-summary-card__head">
|
||||
<h3 class="section-title"><?= $e($t('statistics.summary.charts.orders_value')) ?></h3>
|
||||
</div>
|
||||
<div class="statistics-summary-chart" data-statistics-chart="value">
|
||||
<canvas aria-label="<?= $e($t('statistics.summary.charts.orders_value')) ?>"></canvas>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="statistics-summary-table-grid mt-16">
|
||||
<article class="card statistics-summary-card">
|
||||
<div class="statistics-summary-card__head">
|
||||
<h3 class="section-title"><?= $e($t('statistics.summary.charts.orders_count')) ?></h3>
|
||||
</div>
|
||||
<div class="table-wrap statistics-summary-fallback">
|
||||
<table class="table statistics-summary-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= $e($t('statistics.summary.columns.month')) ?></th>
|
||||
<?php foreach ($countChart['series'] ?? [] as $series): ?>
|
||||
<th><?= $e((string) ($series['label'] ?? '')) ?></th>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $rowIndex => $row): ?>
|
||||
<tr>
|
||||
<td><?= $e($displayMonth((string) ($row['month'] ?? ''))) ?></td>
|
||||
<?php foreach ($countChart['series'] ?? [] as $series): ?>
|
||||
<?php $values = is_array($series['values'] ?? null) ? $series['values'] : []; ?>
|
||||
<td><?= $e((string) ((int) ($values[$rowIndex] ?? 0))) ?></td>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="card statistics-summary-card">
|
||||
<div class="statistics-summary-card__head">
|
||||
<h3 class="section-title"><?= $e($t('statistics.summary.charts.orders_value')) ?></h3>
|
||||
</div>
|
||||
<div class="table-wrap statistics-summary-fallback">
|
||||
<table class="table statistics-summary-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= $e($t('statistics.summary.columns.month')) ?></th>
|
||||
<?php foreach ($valueChart['series'] ?? [] as $series): ?>
|
||||
<th><?= $e((string) ($series['label'] ?? '')) ?></th>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $rowIndex => $row): ?>
|
||||
<tr>
|
||||
<td><?= $e($displayMonth((string) ($row['month'] ?? ''))) ?></td>
|
||||
<?php foreach ($valueChart['series'] ?? [] as $series): ?>
|
||||
<?php $values = is_array($series['values'] ?? null) ? $series['values'] : []; ?>
|
||||
<td><?= $e(number_format((float) ($values[$rowIndex] ?? 0), 2, '.', ' ')) ?></td>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<script type="application/json" id="js-statistics-summary-data"><?= $chartJson ?></script>
|
||||
Reference in New Issue
Block a user