This commit is contained in:
2026-04-22 22:54:26 +02:00
parent cd1ea4a9db
commit c73b2fe9f6
26 changed files with 1377 additions and 34 deletions

View File

@@ -8,6 +8,7 @@
@use "modules/global-search";
@use "modules/order-preview-modal";
@use "modules/project-mappings";
@use "modules/customer-risk-alert";
* {
box-sizing: border-box;
@@ -278,6 +279,19 @@ details[open] > .sidebar__group-toggle .sidebar__toggle-arrow {
}
}
.sidebar__badge {
margin-left: auto;
background: #f59e0b;
color: #1f2937;
font-size: 10.5px;
font-weight: 700;
line-height: 1;
padding: 2px 6px;
border-radius: 10px;
min-width: 18px;
text-align: center;
}
.app-main {
flex: 1;
min-width: 0;

View File

@@ -0,0 +1,97 @@
// Customer return shipment alert — Phase 106
// Banner w szczegolach zamowienia + badge na liscie zamowien + row highlight
.customer-risk-banner {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 10px 12px;
border-radius: 6px;
background: #fff0f0;
border: 1px solid #fecaca;
border-left: 4px solid #d64545;
color: #6b1f1f;
font-size: 13px;
&__icon {
flex-shrink: 0;
font-size: 18px;
line-height: 1;
color: #d64545;
}
&__body {
flex: 1;
min-width: 0;
}
&__text {
margin: 0;
font-weight: 600;
color: #6b1f1f;
}
&__list {
margin-top: 6px;
summary {
cursor: pointer;
color: #9b2c2c;
font-size: 12px;
user-select: none;
}
}
&__table {
width: 100%;
margin-top: 6px;
font-size: 12px;
border-collapse: collapse;
th,
td {
padding: 4px 6px;
border-bottom: 1px solid #f5d6d6;
text-align: left;
color: #3b0f0f;
}
thead th {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.02em;
color: #7a2323;
background: #ffe3e3;
}
tbody tr:last-child th,
tbody tr:last-child td {
border-bottom: 0;
}
a {
color: #b91c1c;
font-weight: 600;
}
}
}
// Badge na liscie zamowien (przy buyer name)
.risk-return-badge {
display: inline-block;
padding: 1px 6px;
background: #d64545;
color: #fff;
font-size: 11px;
font-weight: 600;
border-radius: 3px;
margin-left: 4px;
cursor: default;
vertical-align: middle;
line-height: 1.4;
}
// Row highlight w liscie zamowien — subtelny czerwony pasek po lewej
.table-list-table tbody tr.is-risk-return > td:first-child {
border-left: 3px solid #d64545;
}

View File

@@ -9,3 +9,15 @@
border-radius: 3px;
white-space: nowrap;
}
.dsm-unmapped {
border-left: 4px solid #f59e0b;
.section-title {
color: #b45309;
}
table tbody tr {
background: rgba(245, 158, 11, 0.05);
}
}

View File

@@ -127,8 +127,23 @@
<a class="sidebar__sublink<?= $currentMenu === 'settings' && $currentSettings === 'printing' ? ' is-active' : '' ?>" href="/settings/printing">
Drukowanie
</a>
<?php
$dsmUnmappedCount = 0;
try {
$appInstance = \App\Core\Application::instance();
if ($appInstance !== null) {
$dsmRepo = new \App\Modules\Shipments\DeliveryStatusMappingRepository($appInstance->db());
$dsmUnmappedCount = $dsmRepo->countAllUnmappedForBadge();
}
} catch (\Throwable) {
$dsmUnmappedCount = 0;
}
?>
<a class="sidebar__sublink<?= $currentMenu === 'settings' && $currentSettings === 'delivery-status-mappings' ? ' is-active' : '' ?>" href="/settings/delivery-status-mappings">
Mapowanie statusów dostawy
<?php if ($dsmUnmappedCount > 0): ?>
<span class="sidebar__badge" title="Niezmapowane statusy"><?= (int) $dsmUnmappedCount ?></span>
<?php endif; ?>
</a>
<a class="sidebar__sublink<?= $currentMenu === 'settings' && $currentSettings === 'project-mappings' ? ' is-active' : '' ?>" href="/settings/project-mappings">
<?= $e($t('navigation.project_mapping')) ?>

View File

@@ -79,6 +79,50 @@ foreach ($addressesList as $address) {
<button type="button" class="btn btn--secondary btn--disabled">Edytuj</button>
</div>
</div>
<?php $riskInfo = is_array($customerRiskInfo ?? null) ? $customerRiskInfo : ['count' => 0, 'orders' => [], 'text' => '']; ?>
<?php if ((int) ($riskInfo['count'] ?? 0) >= 1): ?>
<div class="customer-risk-banner mt-12" role="alert">
<span class="customer-risk-banner__icon" aria-hidden="true">&#9888;</span>
<div class="customer-risk-banner__body">
<p class="customer-risk-banner__text"><?= $e((string) ($riskInfo['text'] ?? '')) ?></p>
<?php $riskOrders = is_array($riskInfo['orders'] ?? null) ? $riskInfo['orders'] : []; ?>
<?php if ($riskOrders !== []): ?>
<details class="customer-risk-banner__list">
<summary>Pokaz liste zamowien ze zwrotem (<?= count($riskOrders) ?>)</summary>
<table class="customer-risk-banner__table">
<thead>
<tr>
<th>Nr zamowienia</th>
<th>Data</th>
<th>Nr przesylki</th>
<th>Przewoznik</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php foreach ($riskOrders as $ro): ?>
<?php
$roId = (int) ($ro['order_id'] ?? 0);
$roNumber = trim((string) ($ro['internal_order_number'] ?? ''));
$roLabel = $roNumber !== '' ? $roNumber : ('#' . $roId);
$roDate = trim((string) ($ro['ordered_at'] ?? ''));
$roDateShort = $roDate !== '' ? substr($roDate, 0, 10) : '-';
?>
<tr>
<td><a href="/orders/<?= $roId ?>" target="_blank" rel="noopener"><?= $e($roLabel) ?></a></td>
<td><?= $e($roDateShort) ?></td>
<td><?= $e((string) ($ro['tracking_number'] ?? '')) ?: '-' ?></td>
<td><?= $e((string) ($ro['provider'] ?? '')) ?: '-' ?></td>
<td><?= $e((string) ($ro['delivery_status_raw'] ?? '')) ?: 'returned' ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</details>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<?php if ($flashSuccessMsg !== ''): ?>
<div class="flash flash--success mt-12"><?= $e($flashSuccessMsg) ?></div>
<?php endif; ?>

View File

@@ -2,6 +2,7 @@
$providersList = is_array($providers ?? null) ? $providers : [];
$mappingsList = is_array($mappings ?? null) ? $mappings : [];
$normalizedOptionsList = is_array($normalizedOptions ?? null) ? $normalizedOptions : [];
$unmappedList = is_array($unmappedRawStatuses ?? null) ? $unmappedRawStatuses : [];
$currentProvider = (string) ($provider ?? 'inpost');
?>
@@ -18,6 +19,66 @@ $currentProvider = (string) ($provider ?? 'inpost');
<?php endif; ?>
</section>
<?php if ($unmappedList !== []): ?>
<section class="card mt-16 dsm-unmapped">
<h2 class="section-title">Niezmapowane statusy wykryte w systemie (<?= count($unmappedList) ?>)</h2>
<p class="muted mt-8">Statusy odebrane z API przewoźnika <strong><?= $e((string) ($providersList[$currentProvider] ?? $currentProvider)) ?></strong>, dla których nie ma jeszcze mapowania. Przypisz znormalizowany status, aby paczki przestały być oznaczane jako „Nieznany".</p>
<form action="/settings/delivery-status-mappings/save-bulk" method="post" class="mt-12">
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
<input type="hidden" name="provider" value="<?= $e($currentProvider) ?>">
<div class="table-wrapper">
<table class="table table--compact">
<thead>
<tr>
<th>Status surowy</th>
<th>Liczba paczek</th>
<th>Ostatnio widziany</th>
<th>Opis (własny)</th>
<th>Status znormalizowany</th>
</tr>
</thead>
<tbody>
<?php foreach ($unmappedList as $unmapped): ?>
<?php
$rawStatus = (string) ($unmapped['raw_status'] ?? '');
$count = (int) ($unmapped['count'] ?? 0);
$lastSeen = (string) ($unmapped['last_seen'] ?? '');
?>
<tr>
<td>
<code class="dsm-raw-status"><?= $e($rawStatus) ?></code>
<input type="hidden" name="raw_status[]" value="<?= $e($rawStatus) ?>">
</td>
<td><?= $count ?></td>
<td><?= $e($lastSeen) ?></td>
<td>
<input type="text" name="description[]" class="form-control form-control--sm"
value="<?= $e($rawStatus) ?>" maxlength="255">
</td>
<td>
<select name="normalized_status[]" class="form-control form-control--sm" required>
<?php foreach ($normalizedOptionsList as $optValue => $optLabel): ?>
<option value="<?= $e($optValue) ?>"<?= $optValue === 'unknown' ? ' selected' : '' ?>>
<?= $e($optLabel) ?>
</option>
<?php endforeach; ?>
</select>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="form-actions mt-12">
<button type="submit" class="btn btn--primary">Dodaj do mapowania</button>
</div>
</form>
</section>
<?php endif; ?>
<section class="card mt-16">
<nav class="content-tabs-nav" aria-label="Wybierz przewoźnika">
<?php foreach ($providersList as $provKey => $provLabel): ?>