This commit is contained in:
2026-04-19 22:43:02 +02:00
parent 10cba24727
commit fd1e23eb26
23 changed files with 2320 additions and 72 deletions

View File

@@ -23,6 +23,8 @@ return [
'products' => 'Produkty',
'orders' => 'Zamowienia',
'orders_list' => 'Lista zamowien',
'statistics' => 'Statystyki',
'statistics_orders' => 'Zamowienia',
'marketplace' => 'Marketplace',
'cron' => 'Harmonogram',
'dashboard' => 'Dashboard',
@@ -217,6 +219,31 @@ return [
],
],
],
'statistics' => [
'orders' => [
'title' => 'Statystyki zamowien',
'description' => 'Dzienne podsumowanie ilosci i kwot zamowien z podzialem na kanaly sprzedazy.',
'empty' => 'Brak danych dla wybranych filtrow.',
'filters' => [
'date_from' => 'Data od',
'date_to' => 'Data do',
'channels' => 'Kanaly sprzedazy',
'status_groups' => 'Grupy statusow',
],
'columns' => [
'day' => 'Dzien',
'orders_count' => 'Ilosc',
'total_net' => 'Netto',
'total_gross' => 'Brutto',
'total' => 'Razem',
'summary' => 'Podsumowanie',
],
'actions' => [
'apply_filters' => 'Filtruj',
'reset_filters' => 'Wyczysc',
],
],
],
'users' => [
'title' => 'Zarzadzanie uzytkownikami',
'description' => 'Dodawaj konta dostepowe dla zespolu i zarzadzaj dostepem do panelu.',

View File

@@ -1041,6 +1041,65 @@ h4.section-title {
margin-bottom: 10px;
}
.statistics-orders-page {
padding: 10px;
}
.statistics-orders-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
}
.statistics-orders-filters {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
gap: 10px;
align-items: end;
}
.statistics-orders-filters__actions {
align-self: end;
}
.statistics-orders-multiselect {
min-height: 120px;
height: 120px;
padding-top: 6px;
padding-bottom: 6px;
}
.statistics-orders-table-wrap {
overflow-x: auto;
}
.statistics-orders-table {
min-width: 880px;
thead th {
text-align: center;
white-space: nowrap;
}
tbody td,
tfoot th {
text-align: right;
white-space: nowrap;
}
tbody td:first-child,
tfoot th:first-child {
text-align: left;
}
tfoot th {
border-top: 2px solid #cbd5e1;
background: #f8fafc;
}
}
.orders-head {
display: flex;
align-items: flex-start;

View File

@@ -14,6 +14,7 @@
<?php $currentMenu = (string) ($activeMenu ?? ''); ?>
<?php $currentSettings = (string) ($activeSettings ?? ''); ?>
<?php $currentOrders = (string) ($activeOrders ?? ''); ?>
<?php $currentStatistics = (string) ($activeStatistics ?? ''); ?>
<div class="app-shell" id="js-app-shell">
<aside class="sidebar" id="js-sidebar">
<div class="sidebar__brand">
@@ -47,6 +48,28 @@
</div>
</details>
<details class="sidebar__group<?= $currentMenu === 'statistics' ? ' is-active' : '' ?>"<?= $currentMenu === 'statistics' ? ' open' : '' ?>>
<summary class="sidebar__group-toggle">
<span class="sidebar__icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 20V10"/>
<path d="M10 20V4"/>
<path d="M16 20v-8"/>
<path d="M22 20v-5"/>
</svg>
</span>
<span class="sidebar__label"><?= $e($t('navigation.statistics')) ?></span>
<svg class="sidebar__toggle-arrow" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M6 9l6 6 6-6"/>
</svg>
</summary>
<div class="sidebar__group-links">
<a class="sidebar__sublink<?= $currentMenu === 'statistics' && $currentStatistics === 'orders' ? ' is-active' : '' ?>" href="/statistics/orders">
<?= $e($t('navigation.statistics_orders')) ?>
</a>
</div>
</details>
<a class="sidebar__link<?= ($currentMenu ?? '') === 'accounting' ? ' is-active' : '' ?>" href="/accounting">
<span class="sidebar__icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">

View File

@@ -0,0 +1,162 @@
<?php
declare(strict_types=1);
$filters = is_array($filters ?? null) ? $filters : [];
$channelOptions = is_array($channelOptions ?? null) ? $channelOptions : [];
$statusGroupOptions = is_array($statusGroupOptions ?? null) ? $statusGroupOptions : [];
$table = is_array($table ?? null) ? $table : [];
$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($table['rows'] ?? null) ? $table['rows'] : [];
$totals = is_array($table['totals'] ?? null) ? $table['totals'] : ['channels' => []];
$hasData = (bool) ($table['hasData'] ?? false);
$debugEnabled = (bool) ($debugEnabled ?? false);
$diagnostics = is_array($diagnostics ?? null) ? $diagnostics : [];
$debugMeta = is_array($debugMeta ?? null) ? $debugMeta : [];
$channelMap = [];
foreach ($channelOptions as $channelOption) {
$key = (string) ($channelOption['key'] ?? '');
if ($key === '') {
continue;
}
$channelMap[$key] = (string) ($channelOption['label'] ?? $key);
}
?>
<section class="card statistics-orders-page">
<div class="statistics-orders-head">
<div>
<h2 class="section-title"><?= $e($t('statistics.orders.title')) ?></h2>
<p class="muted mt-12"><?= $e($t('statistics.orders.description')) ?></p>
</div>
</div>
<form method="get" action="/statistics/orders" class="statistics-orders-filters mt-16">
<label class="form-field">
<span class="field-label"><?= $e($t('statistics.orders.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.orders.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.orders.filters.channels')) ?></span>
<select class="form-control statistics-orders-multiselect" name="channels[]" multiple size="6">
<?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.orders.filters.status_groups')) ?></span>
<select class="form-control statistics-orders-multiselect" name="status_groups[]" multiple size="6">
<?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">&nbsp;</span>
<div class="filters-actions">
<button type="submit" class="btn btn--primary"><?= $e($t('statistics.orders.actions.apply_filters')) ?></button>
<a href="/statistics/orders" class="btn btn--secondary"><?= $e($t('statistics.orders.actions.reset_filters')) ?></a>
</div>
</div>
</form>
</section>
<section class="card mt-16 statistics-orders-table-wrap">
<?php if ($debugEnabled): ?>
<div class="alert alert--warning" role="alert">
<strong>DEBUG</strong>
<pre style="margin:8px 0 0;white-space:pre-wrap;"><?= $e(json_encode([
'filters' => $debugMeta,
'diagnostics' => $diagnostics,
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)) ?></pre>
</div>
<?php endif; ?>
<?php if (!$hasData): ?>
<p class="muted"><?= $e($t('statistics.orders.empty')) ?></p>
<?php else: ?>
<div class="table-wrap">
<table class="table statistics-orders-table">
<thead>
<tr>
<th rowspan="2"><?= $e($t('statistics.orders.columns.day')) ?></th>
<?php foreach ($selectedChannels as $channelKey): ?>
<th colspan="3"><?= $e($channelMap[$channelKey] ?? $channelKey) ?></th>
<?php endforeach; ?>
<th colspan="3"><?= $e($t('statistics.orders.columns.total')) ?></th>
</tr>
<tr>
<?php foreach ($selectedChannels as $channelKey): ?>
<th><?= $e($t('statistics.orders.columns.orders_count')) ?></th>
<th><?= $e($t('statistics.orders.columns.total_net')) ?></th>
<th><?= $e($t('statistics.orders.columns.total_gross')) ?></th>
<?php endforeach; ?>
<th><?= $e($t('statistics.orders.columns.orders_count')) ?></th>
<th><?= $e($t('statistics.orders.columns.total_net')) ?></th>
<th><?= $e($t('statistics.orders.columns.total_gross')) ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($rows as $row): ?>
<tr>
<td><?= $e((string) ($row['day'] ?? '')) ?></td>
<?php foreach ($selectedChannels as $channelKey): ?>
<?php $channelStats = is_array($row['channels'][$channelKey] ?? null) ? $row['channels'][$channelKey] : []; ?>
<td><?= $e((string) ((int) ($channelStats['orders_count'] ?? 0))) ?></td>
<td><?= $e(number_format((float) ($channelStats['total_net'] ?? 0), 2, '.', ' ')) ?></td>
<td><?= $e(number_format((float) ($channelStats['total_gross'] ?? 0), 2, '.', ' ')) ?></td>
<?php endforeach; ?>
<td><?= $e((string) ((int) ($row['day_total_orders'] ?? 0))) ?></td>
<td><?= $e(number_format((float) ($row['day_total_net'] ?? 0), 2, '.', ' ')) ?></td>
<td><?= $e(number_format((float) ($row['day_total_gross'] ?? 0), 2, '.', ' ')) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<th><?= $e($t('statistics.orders.columns.summary')) ?></th>
<?php foreach ($selectedChannels as $channelKey): ?>
<?php $channelTotals = is_array($totals['channels'][$channelKey] ?? null) ? $totals['channels'][$channelKey] : []; ?>
<th><?= $e((string) ((int) ($channelTotals['orders_count'] ?? 0))) ?></th>
<th><?= $e(number_format((float) ($channelTotals['total_net'] ?? 0), 2, '.', ' ')) ?></th>
<th><?= $e(number_format((float) ($channelTotals['total_gross'] ?? 0), 2, '.', ' ')) ?></th>
<?php endforeach; ?>
<th><?= $e((string) ((int) ($totals['orders_count'] ?? 0))) ?></th>
<th><?= $e(number_format((float) ($totals['total_net'] ?? 0), 2, '.', ' ')) ?></th>
<th><?= $e(number_format((float) ($totals['total_gross'] ?? 0), 2, '.', ' ')) ?></th>
</tr>
</tfoot>
</table>
</div>
<?php endif; ?>
</section>