This commit is contained in:
2026-04-13 22:31:06 +02:00
parent 38259bc706
commit e15b4ccf45
24 changed files with 1580 additions and 3858 deletions

View File

@@ -1680,6 +1680,92 @@ details[open] > .order-statuses-side__title .order-statuses-side__arrow {
font-size: 12px;
}
.order-payment-shipping {
.section-title-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.btn-edit-inline {
background: transparent;
border: 1px solid transparent;
color: #6b7280;
padding: 3px 5px;
cursor: pointer;
border-radius: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.15s, background-color 0.15s, color 0.15s;
&:hover {
background: #f3f4f6;
color: #111827;
}
}
&:hover .btn-edit-inline {
opacity: 1;
}
}
.order-details-edit-form {
margin-top: 12px;
padding: 10px;
background: #f9fafb;
border: 1px solid #e5e7eb;
border-radius: 6px;
font-size: 12px;
.form-row {
margin-bottom: 8px;
}
label {
display: block;
color: #374151;
font-weight: 500;
input[type="text"] {
display: block;
width: 100%;
margin-top: 3px;
padding: 5px 7px;
border: 1px solid #d1d5db;
border-radius: 4px;
font-size: 12px;
box-sizing: border-box;
}
}
label.checkbox-inline {
display: flex;
align-items: center;
gap: 6px;
font-weight: 400;
input {
margin: 0;
}
code {
background: #eef2ff;
padding: 1px 4px;
border-radius: 3px;
font-size: 11px;
}
}
.form-actions {
display: flex;
gap: 6px;
margin-top: 8px;
}
}
.payment-summary {
display: grid;
gap: 6px;
@@ -2752,3 +2838,16 @@ body.no-scroll {
}
}
// Aged orders row highlight (4-7+ days since ordered_at)
.table-list-table tbody tr.order-row-aged > td {
border-top: 2px solid transparent;
border-bottom: 2px solid transparent;
}
.table-list-table tbody tr.order-row-aged > td:first-child { border-left: 2px solid transparent; }
.table-list-table tbody tr.order-row-aged > td:last-child { border-right: 2px solid transparent; }
.table-list-table tbody tr.order-row-aged-4 > td { border-color: #f8b4b4; }
.table-list-table tbody tr.order-row-aged-5 > td { border-color: #f28282; }
.table-list-table tbody tr.order-row-aged-6 > td { border-color: #e74c3c; }
.table-list-table tbody tr.order-row-aged-7 > td { border-color: #991b1b; }

View File

@@ -217,7 +217,8 @@ $buildUrl = static function (array $params = []) use ($basePath, $query): string
</tr>
<?php else: ?>
<?php foreach ($rows as $row): ?>
<tr>
<?php $rowClass = trim((string) ($row['_row_class'] ?? '')); ?>
<tr<?= $rowClass !== '' ? ' class="' . $e($rowClass) . '"' : '' ?>>
<?php if ($selectable): ?>
<?php $selectValue = $row[$selectValueKey] ?? ''; ?>
<td class="table-select-col">

View File

@@ -216,10 +216,73 @@ foreach ($addressesList as $address) {
</dl>
</article>
<article class="card">
<h3 class="section-title"><?= $e($t('orders.details.payment_shipping')) ?></h3>
<dl class="order-kv mt-12">
<article class="card order-payment-shipping">
<div class="section-title-row">
<h3 class="section-title"><?= $e($t('orders.details.payment_shipping')) ?></h3>
<button type="button" class="btn-edit-inline js-toggle-details-edit" title="Edytuj formę dostawy i płatności" aria-label="Edytuj">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
</button>
</div>
<?php
$deliveryMethodValue = trim((string) ($orderRow['delivery_method'] ?? ''));
$paymentMethodValue = trim((string) ($orderRow['payment_method'] ?? ''));
$paymentTypeCurrent = strtoupper(trim((string) ($orderRow['external_payment_type_id'] ?? '')));
$isCodCurrent = \App\Core\Support\StringHelper::isCodPayment($paymentTypeCurrent);
?>
<form method="POST" action="/orders/<?= (int) $orderId ?>/details/update" class="order-details-edit-form js-details-edit-form" style="display:none">
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
<div class="form-row">
<label>Forma dostawy
<input type="text" name="delivery_method" value="<?= $e($deliveryMethodValue) ?>" maxlength="128" list="js-delivery-methods-list">
</label>
</div>
<div class="form-row">
<label>Forma płatności
<input type="text" name="payment_method" value="<?= $e($paymentMethodValue) ?>" maxlength="128" list="js-payment-methods-list">
</label>
</div>
<div class="form-row">
<label class="checkbox-inline">
<input type="checkbox" name="is_cod" value="1" <?= $isCodCurrent ? 'checked' : '' ?>>
<span>Pobranie (COD) — ustawi typ płatności na <code>CASH_ON_DELIVERY</code></span>
</label>
</div>
<datalist id="js-delivery-methods-list">
<?php foreach (($deliveryMethodSuggestions ?? []) as $sug): ?>
<option value="<?= $e((string) $sug) ?>"></option>
<?php endforeach; ?>
</datalist>
<datalist id="js-payment-methods-list">
<?php foreach (($paymentMethodSuggestions ?? []) as $sug): ?>
<option value="<?= $e((string) $sug) ?>"></option>
<?php endforeach; ?>
</datalist>
<div class="form-actions">
<button type="submit" class="btn btn-primary btn-sm">Zapisz</button>
<button type="button" class="btn btn-sm js-cancel-details-edit">Anuluj</button>
</div>
</form>
<script>
document.addEventListener('DOMContentLoaded', function(){
var toggleBtn = document.querySelector('.js-toggle-details-edit');
var cancelBtn = document.querySelector('.js-cancel-details-edit');
var form = document.querySelector('.js-details-edit-form');
var view = document.querySelector('.js-details-view');
if (!toggleBtn || !form || !view) return;
toggleBtn.addEventListener('click', function(){
form.style.display = 'block';
view.style.display = 'none';
});
if (cancelBtn) cancelBtn.addEventListener('click', function(){
form.style.display = 'none';
view.style.display = '';
});
});
</script>
<dl class="order-kv mt-12 js-details-view">
<dt><?= $e($t('orders.details.fields.payment_status')) ?></dt><dd><?= $e((string) ($orderRow['payment_status'] ?? '-')) ?></dd>
<dt>Forma dostawy</dt><dd><?= $e($deliveryMethodValue !== '' ? $deliveryMethodValue : '-') ?></dd>
<dt>Forma płatności</dt><dd><?= $e($paymentMethodValue !== '' ? $paymentMethodValue : '-') ?></dd>
<dt>Typ platnosci</dt>
<dd>
<?php
@@ -514,12 +577,15 @@ foreach ($addressesList as $address) {
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
<button type="submit" class="btn btn--sm btn--secondary">Pobierz</button>
</form>
<?php elseif ($pkgStatus !== 'error'): ?>
-
<?php endif; ?>
<?php if (in_array($pkgStatus, ['label_ready', 'created'], true)): ?>
<?php if (in_array((int) ($pkg['id'] ?? 0), $pendingPrintIds, true)): ?>
<button type="button" class="btn btn--sm btn--danger" disabled style="white-space:nowrap">W kolejce</button>
<button type="button"
class="btn btn--sm btn--danger js-print-queue-pending"
data-package-id="<?= $e((string) ($pkg['id'] ?? 0)) ?>"
data-order-id="<?= $e((string) ($orderId ?? 0)) ?>"
disabled
style="white-space:nowrap">W kolejce</button>
<?php else: ?>
<button type="button"
class="btn btn--sm btn--secondary btn-print-label"
@@ -831,6 +897,58 @@ foreach ($addressesList as $address) {
});
}
// --- Print queue polling: rewert przycisku "W kolejce" -> "Drukuj" po wydrukowaniu ---
var printQueuePending = {}; // packageId -> btn element
var printQueueTimer = null;
var printQueueTicks = 0;
var PRINT_QUEUE_MAX_TICKS = 120; // 120 * 3s = 6 min
function revertPrintButton(btn) {
if (!btn) return;
btn.innerHTML = 'Drukuj';
btn.disabled = false;
btn.classList.remove('btn--danger');
btn.classList.remove('js-print-queue-pending');
btn.classList.add('btn--secondary');
btn.classList.add('btn-print-label');
btn.setAttribute('title', 'Wyslij do drukarki');
}
function stopPrintQueuePoll() {
if (printQueueTimer) { clearInterval(printQueueTimer); printQueueTimer = null; }
printQueueTicks = 0;
}
function pollPrintQueueTick() {
printQueueTicks += 1;
if (printQueueTicks > PRINT_QUEUE_MAX_TICKS) { stopPrintQueuePoll(); return; }
var ids = Object.keys(printQueuePending);
if (ids.length === 0) { stopPrintQueuePoll(); return; }
fetch('/api/print/jobs/status?package_ids=' + encodeURIComponent(ids.join(',')), { credentials: 'same-origin' })
.then(function (r) { return r.ok ? r.json() : { pending: ids.map(Number) }; })
.then(function (data) {
var pending = (data && Array.isArray(data.pending)) ? data.pending.map(String) : [];
Object.keys(printQueuePending).forEach(function (pid) {
if (pending.indexOf(String(pid)) === -1) {
revertPrintButton(printQueuePending[pid]);
delete printQueuePending[pid];
}
});
if (Object.keys(printQueuePending).length === 0) stopPrintQueuePoll();
})
.catch(function () { /* ignore transient */ });
}
function watchPrintQueueButton(btn) {
var pid = btn.getAttribute('data-package-id');
if (!pid) return;
printQueuePending[pid] = btn;
if (!printQueueTimer) {
printQueueTicks = 0;
printQueueTimer = setInterval(pollPrintQueueTick, 3000);
}
}
// Print label button handler (delegated for dynamically added buttons)
document.addEventListener('click', function (e) {
var btn = e.target.closest('.btn-print-label');
@@ -854,7 +972,10 @@ foreach ($addressesList as $address) {
btn.innerHTML = 'W kolejce';
btn.disabled = true;
btn.classList.remove('btn--secondary');
btn.classList.remove('btn-print-label');
btn.classList.add('btn--danger');
btn.classList.add('js-print-queue-pending');
watchPrintQueueButton(btn);
} else {
var msg = (res.data && res.data.error) ? res.data.error : 'Nieznany blad';
if (window.OrderProAlerts) { window.OrderProAlerts.show({ message: msg, type: 'error' }); }
@@ -869,6 +990,41 @@ foreach ($addressesList as $address) {
});
});
// Przy zaladowaniu strony: uruchom polling dla przyciskow juz w kolejce
document.querySelectorAll('.js-print-queue-pending').forEach(function (btn) {
watchPrintQueueButton(btn);
});
// Auto-click ostatniej etykiety po utworzeniu przesylki (?printLast=1)
(function autoClickLastLabel() {
if (!/[?&]printLast=1\b/.test(location.search)) return;
var attempts = 0;
var MAX_ATTEMPTS = 30; // 30 * 1s = 30s
var timer = setInterval(function () {
attempts += 1;
if (attempts > MAX_ATTEMPTS) {
clearInterval(timer);
cleanupPrintLastParam();
return;
}
var buttons = document.querySelectorAll('.btn-print-label');
if (buttons.length > 0) {
var last = buttons[buttons.length - 1];
clearInterval(timer);
cleanupPrintLastParam();
last.click();
}
}, 1000);
})();
function cleanupPrintLastParam() {
try {
var url = new URL(location.href);
url.searchParams.delete('printLast');
history.replaceState({}, '', url.toString());
} catch (e) {}
}
})();
</script>

View File

@@ -412,12 +412,15 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
<?php endif; ?>
<?php elseif ($pkgShipmentId !== '' && $pkgStatus === 'created'): ?>
<span class="muted">Generowanie etykiety...</span>
<?php else: ?>
-
<?php endif; ?>
<?php if (in_array($pkgStatus, ['label_ready', 'created'], true)): ?>
<?php if (in_array($pkgId, $pendingPrintIds, true)): ?>
<button type="button" class="btn btn--sm btn--danger" disabled style="white-space:nowrap">W kolejce</button>
<button type="button"
class="btn btn--sm btn--danger js-print-queue-pending"
data-package-id="<?= $e((string) $pkgId) ?>"
data-order-id="<?= $e((string) ($orderId ?? 0)) ?>"
disabled
style="white-space:nowrap">W kolejce</button>
<?php else: ?>
<button type="button"
class="btn btn--sm btn--secondary btn-print-label"
@@ -765,6 +768,58 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
});
});
// --- Print queue polling (shared mechanika: rewert "W kolejce" -> "Drukuj") ---
var printQueuePending = {};
var printQueueTimer = null;
var printQueueTicks = 0;
var PRINT_QUEUE_MAX_TICKS = 120;
function revertPrintButton(btn) {
if (!btn) return;
btn.innerHTML = 'Drukuj';
btn.disabled = false;
btn.classList.remove('btn--danger');
btn.classList.remove('js-print-queue-pending');
btn.classList.add('btn--secondary');
btn.classList.add('btn-print-label');
btn.setAttribute('title', 'Wyslij do drukarki');
}
function stopPrintQueuePoll() {
if (printQueueTimer) { clearInterval(printQueueTimer); printQueueTimer = null; }
printQueueTicks = 0;
}
function pollPrintQueueTick() {
printQueueTicks += 1;
if (printQueueTicks > PRINT_QUEUE_MAX_TICKS) { stopPrintQueuePoll(); return; }
var ids = Object.keys(printQueuePending);
if (ids.length === 0) { stopPrintQueuePoll(); return; }
fetch('/api/print/jobs/status?package_ids=' + encodeURIComponent(ids.join(',')), { credentials: 'same-origin' })
.then(function (r) { return r.ok ? r.json() : { pending: ids.map(Number) }; })
.then(function (data) {
var pending = (data && Array.isArray(data.pending)) ? data.pending.map(String) : [];
Object.keys(printQueuePending).forEach(function (pid) {
if (pending.indexOf(String(pid)) === -1) {
revertPrintButton(printQueuePending[pid]);
delete printQueuePending[pid];
}
});
if (Object.keys(printQueuePending).length === 0) stopPrintQueuePoll();
})
.catch(function () { /* ignore */ });
}
function watchPrintQueueButton(btn) {
var pid = btn.getAttribute('data-package-id');
if (!pid) return;
printQueuePending[pid] = btn;
if (!printQueueTimer) {
printQueueTicks = 0;
printQueueTimer = setInterval(pollPrintQueueTick, 3000);
}
}
// Print label button handler
document.querySelectorAll('.btn-print-label').forEach(function (btn) {
btn.addEventListener('click', function () {
@@ -786,7 +841,10 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
btn.innerHTML = 'W kolejce';
btn.disabled = true;
btn.classList.remove('btn--secondary');
btn.classList.remove('btn-print-label');
btn.classList.add('btn--danger');
btn.classList.add('js-print-queue-pending');
watchPrintQueueButton(btn);
} else {
var msg = (res.data && res.data.error) ? res.data.error : 'Nieznany blad';
if (window.OrderProAlerts) {
@@ -806,6 +864,11 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
});
});
// Przy zaladowaniu strony: uruchom polling dla przyciskow juz w kolejce
document.querySelectorAll('.js-print-queue-pending').forEach(function (btn) {
watchPrintQueueButton(btn);
});
var params = new URLSearchParams(window.location.search);
var autoCheckId = params.get('check');
@@ -955,47 +1018,20 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
}
// --- Apply preset (autofill form) ---
// Preset nadpisuje wylacznie rozmiary paczki i wage. Forma dostawy (carrier,
// serwis, punkt nadania, format etykiety) pozostaje bez zmian.
function applyPreset(preset) {
var carrierSelect = document.getElementById('shipment-carrier-select');
var hiddenInput = document.getElementById('shipment-delivery-service');
var credentialsInput = document.getElementById('shipment-credentials-id');
var carrierInput = document.getElementById('shipment-carrier-id');
var providerInput = document.getElementById('shipment-provider-code');
setFieldValue('package_type', preset.package_type || 'PACKAGE');
setFieldValue('length_cm', preset.length_cm || '25');
setFieldValue('width_cm', preset.width_cm || '20');
setFieldValue('height_cm', preset.height_cm || '8');
setFieldValue('weight_kg', preset.weight_kg || '1');
if (!carrierSelect) return;
// Set carrier — use native change so existing handler shows correct panel
carrierSelect.value = preset.carrier || '';
if (carrierSelect._syncTrigger) carrierSelect._syncTrigger();
carrierSelect.dispatchEvent(new Event('change'));
// The existing change handler clears hidden fields and resets selects.
// We must wait for that to finish, then override with preset values.
// Auto-submit after autofill completes
setTimeout(function () {
// Hidden fields — set AFTER the change handler cleared them
if (hiddenInput) hiddenInput.value = preset.delivery_method_id || '';
if (credentialsInput) credentialsInput.value = preset.credentials_id || '';
if (carrierInput) carrierInput.value = preset.carrier_id || '';
if (providerInput) providerInput.value = preset.provider_code || '';
// Package fields
setFieldValue('package_type', preset.package_type || 'PACKAGE');
setFieldValue('length_cm', preset.length_cm || '25');
setFieldValue('width_cm', preset.width_cm || '20');
setFieldValue('height_cm', preset.height_cm || '8');
setFieldValue('weight_kg', preset.weight_kg || '1');
setFieldValue('sender_point_id', preset.sender_point_id || '');
setFieldValue('label_format', preset.label_format || 'PDF');
// Select delivery service in the correct panel
selectDeliveryService(preset);
// Auto-submit after autofill completes
setTimeout(function () {
var form = document.getElementById('shipment-form');
if (form) form.submit();
}, 300);
}, 200);
var form = document.getElementById('shipment-form');
if (form) form.submit();
}, 100);
}
function setFieldValue(name, value) {