Files
Jacek Pyziak 98a0077204 feat(28-shipment-tracking-ui): badge'e statusow dostawy, linki sledzenia, ustawienia interwalu trackingu
- Kolorowe badge'e statusow dostawy w tabelach paczek (show.php + prepare.php)
- Link sledzenia z carrier detection (InPost, Apaczka, Orlen, Allegro, Google fallback)
- Sekcja Status dostawy w boksie Platnosc i wysylka
- Ustawienie interwalu trackingu crona (5-120 min) w zakladce Ustawienia
- Tekstowe mapowania statusow Apaczka API (NEW, CONFIRMED, etc.)
- Fix: use-statements ApaczkaShipmentService (pre-existing bug)
- Fix: pickup date normalization (next day po 16:00)
- Fix: przycisk Pobierz etykiete (POST zamiast link do prepare)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 23:04:05 +01:00

1217 lines
50 KiB
PHP

<?php
$orderRow = is_array($order ?? null) ? $order : [];
$itemsList = is_array($items ?? null) ? $items : [];
$receiver = is_array($receiverAddr ?? null) ? $receiverAddr : [];
$prefs = is_array($preferences ?? null) ? $preferences : [];
$comp = is_array($company ?? null) ? $company : [];
$services = is_array($deliveryServices ?? null) ? $deliveryServices : [];
$apaczkaSvcList = is_array($apaczkaServices ?? null) ? $apaczkaServices : [];
$packages = is_array($existingPackages ?? null) ? $existingPackages : [];
$pendingPrintIds = is_array($pendingPrintPackageIds ?? null) ? $pendingPrintPackageIds : [];
$servicesError = (string) ($deliveryServicesError ?? '');
$flashSuccessMsg = (string) ($flashSuccess ?? '');
$flashErrorMsg = (string) ($flashError ?? '');
$deliveryMappingDiagnostic = trim((string) ($deliveryMappingDiagnostic ?? ''));
$mapping = is_array($deliveryMapping ?? null) ? $deliveryMapping : [];
$mappedMethodId = trim((string) ($mapping['provider_service_id'] ?? ''));
$mappedCredentialsId = trim((string) ($mapping['provider_account_id'] ?? ''));
$mappedCarrierId = trim((string) ($mapping['provider_carrier_id'] ?? ''));
$mappedProvider = trim((string) ($mapping['provider'] ?? ''));
$mappedServiceName = trim((string) ($mapping['provider_service_name'] ?? ''));
$mappedCarrier = $mappedProvider === 'apaczka' ? 'apaczka' : 'allegro';
if ($mappedCarrier !== 'apaczka' && stripos($mappedCarrierId, 'inpost') !== false) {
$mappedCarrier = 'inpost';
}
$deliveryMethodId = '';
if ($mappedCarrier === 'apaczka' && $mappedMethodId !== '') {
$deliveryMethodId = $mappedMethodId;
} elseif (($mappedCarrier === 'allegro' || $mappedCarrier === 'inpost') && $mappedMethodId !== '') {
$deliveryMethodId = $mappedMethodId;
} elseif ($mappedCarrier !== 'inpost') {
$deliveryMethodId = trim((string) ($prefs['delivery_method_id'] ?? ($orderRow['external_carrier_account_id'] ?? '')));
}
$deliveryMethodName = trim((string) ($orderRow['external_carrier_id'] ?? ''));
$inpostSvcList = is_array($inpostServices ?? null) ? $inpostServices : [];
$preselectedCarrier = $mappedCarrier !== '' ? $mappedCarrier : ($mappedMethodId !== '' ? 'allegro' : '');
$pointId = trim((string) ($receiver['parcel_external_id'] ?? ''));
$pointName = trim((string) ($receiver['parcel_name'] ?? ''));
$totalWithTax = (float) ($orderRow['total_with_tax'] ?? 0);
$currency = strtoupper(trim((string) ($orderRow['currency'] ?? 'PLN')));
$isCod = strtoupper(trim((string) ($orderRow['external_payment_type_id'] ?? ''))) === 'CASH_ON_DELIVERY';
$defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
?>
<section class="card">
<div class="order-details-head">
<div>
<a href="/orders/<?= $e((string) ($orderId ?? 0)) ?>" class="order-back-link">&larr; Powrot do zamowienia</a>
<h2 class="section-title mt-12">Przygotuj przesylke #<?= $e((string) ($orderId ?? 0)) ?></h2>
<div class="order-details-sub mt-4">
<span><?= $e(ucfirst((string) ($orderRow['source'] ?? ''))) ?> <?= $e((string) ($orderRow['external_order_id'] ?? '')) ?></span>
</div>
</div>
</div>
<?php if ($flashSuccessMsg !== ''): ?>
<div class="flash flash--success mt-12"><?= $e($flashSuccessMsg) ?></div>
<?php endif; ?>
<?php if ($flashErrorMsg !== ''): ?>
<div class="flash flash--error mt-12"><?= $e($flashErrorMsg) ?></div>
<?php endif; ?>
</section>
<section class="shipment-presets" id="shipment-presets">
<button type="button" class="shipment-presets__add" id="preset-add-btn">+ Dodaj przycisk dostawy</button>
</section>
<div class="preset-modal" id="preset-modal" style="display:none">
<div class="preset-modal__content">
<h3>Nowy przycisk dostawy</h3>
<label class="form-field mt-12">
<span class="field-label">Nazwa</span>
<input class="form-control" type="text" id="preset-name-input" maxlength="100" placeholder="np. InPost Paczkomat Standard">
</label>
<div class="form-field mt-12">
<span class="field-label">Kolor</span>
<div class="preset-modal__colors" id="preset-color-picker"></div>
</div>
<div class="form-actions mt-16">
<button type="button" class="btn btn--primary" id="preset-save-btn">Zapisz</button>
<button type="button" class="btn btn--secondary" id="preset-cancel-btn">Anuluj</button>
</div>
</div>
</div>
<form method="post" action="/orders/<?= $e((string) ($orderId ?? 0)) ?>/shipment/create" novalidate>
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
<div class="shipment-grid mt-16">
<section class="card">
<h3 class="section-title">Przesylka</h3>
<?php if ($servicesError !== ''): ?>
<div class="flash flash--error mt-12"><?= $e($servicesError) ?></div>
<?php endif; ?>
<div class="form-field mt-12">
<span class="field-label">Przewoznik</span>
<select class="form-control" id="shipment-carrier-select">
<option value="">-- Wybierz --</option>
<option value="allegro"<?= $preselectedCarrier === 'allegro' ? ' selected' : '' ?>>Allegro</option>
<option value="inpost"<?= $preselectedCarrier === 'inpost' ? ' selected' : '' ?>>InPost</option>
<option value="apaczka"<?= $preselectedCarrier === 'apaczka' ? ' selected' : '' ?>>Apaczka</option>
</select>
<?php if ($deliveryMethodName !== ''): ?>
<div class="muted mt-4" style="font-size:12px">Metoda z zamowienia: <strong><?= $e($deliveryMethodName) ?></strong><?php if ($mappedServiceName !== ''): ?> &rarr; <?= $e($mappedCarrier === 'inpost' ? 'InPost' : ($mappedCarrier === 'apaczka' ? 'Apaczka' : 'Allegro')) ?>: <?= $e($mappedServiceName) ?><?php endif; ?></div>
<?php endif; ?>
<?php if ($deliveryMappingDiagnostic !== ''): ?>
<div class="flash flash--error mt-8"><?= $e($deliveryMappingDiagnostic) ?></div>
<?php endif; ?>
</div>
<div class="form-field mt-12">
<span class="field-label">Usluga dostawy</span>
<input type="hidden" name="delivery_method_id" id="shipment-delivery-service" value="" required>
<div id="shipment-allegro-panel" style="<?= $preselectedCarrier !== 'allegro' ? 'display:none' : '' ?>">
<?php if ($servicesError !== ''): ?>
<div class="flash flash--error mt-4"><?= $e($servicesError) ?></div>
<?php endif; ?>
<div class="searchable-select" id="shipment-service-wrapper" data-match-id="<?= $e($deliveryMethodId) ?>">
<input type="text" class="form-control" id="shipment-service-search" placeholder="Szukaj uslugi dostawy Allegro..." autocomplete="off">
<div class="searchable-select__dropdown" id="shipment-service-dropdown">
<?php foreach ($services as $svc): ?>
<?php
$svcId = is_array($svc['id'] ?? null) ? $svc['id'] : [];
$svcMethodId = trim((string) ($svcId['deliveryMethodId'] ?? ''));
$svcCredentialsId = trim((string) ($svcId['credentialsId'] ?? ''));
$svcName = trim((string) ($svc['name'] ?? ''));
$svcCarrierId = trim((string) ($svc['carrierId'] ?? ''));
$svcOwner = trim((string) ($svc['owner'] ?? ''));
?>
<div class="searchable-select__option"
data-value="<?= $e($svcMethodId) ?>"
data-credentials-id="<?= $e($svcCredentialsId) ?>"
data-carrier-id="<?= $e($svcCarrierId) ?>"
data-owner="<?= $e($svcOwner) ?>"
data-label="<?= $e($svcName) ?> (<?= $e($svcOwner) ?>)"
><?= $e($svcName) ?> <span class="muted">(<?= $e($svcOwner) ?>)</span></div>
<?php endforeach; ?>
</div>
</div>
</div>
<div id="shipment-inpost-panel" style="<?= $preselectedCarrier !== 'inpost' ? 'display:none' : '' ?>">
<?php if ($inpostSvcList === []): ?>
<div class="muted">Brak uslug InPost (sprawdz polaczenie z Allegro).</div>
<?php else: ?>
<select class="form-control" id="shipment-inpost-select">
<option value="">-- Wybierz usluge InPost --</option>
<?php foreach ($inpostSvcList as $inSvc): ?>
<?php
$inSvcId = is_array($inSvc['id'] ?? null) ? $inSvc['id'] : [];
$inSvcMethodId = trim((string) ($inSvcId['deliveryMethodId'] ?? ''));
$inSvcCredentialsId = trim((string) ($inSvcId['credentialsId'] ?? ''));
$inSvcCarrierId = trim((string) ($inSvc['carrierId'] ?? ''));
$inSvcName = trim((string) ($inSvc['name'] ?? ''));
$inSvcSelected = $mappedCarrier === 'inpost' && $mappedMethodId === $inSvcMethodId;
?>
<option
value="<?= $e($inSvcMethodId) ?>"
data-credentials-id="<?= $e($inSvcCredentialsId) ?>"
data-carrier-id="<?= $e($inSvcCarrierId) ?>"
<?= $inSvcSelected ? 'selected' : '' ?>>
<?= $e($inSvcName) ?>
</option>
<?php endforeach; ?>
</select>
<?php endif; ?>
</div>
<div id="shipment-apaczka-panel" style="<?= $preselectedCarrier !== 'apaczka' ? 'display:none' : '' ?>">
<?php if ($apaczkaSvcList === []): ?>
<div class="muted">Brak uslug Apaczka (sprawdz konfiguracje App ID/App Secret).</div>
<?php else: ?>
<select class="form-control" id="shipment-apaczka-select">
<option value="">-- Wybierz usluge Apaczka --</option>
<?php foreach ($apaczkaSvcList as $aSvc): ?>
<?php
if (!is_array($aSvc)) {
continue;
}
$aSvcId = trim((string) ($aSvc['service_id'] ?? $aSvc['id'] ?? ''));
$aSvcName = trim((string) ($aSvc['name'] ?? ''));
$aSvcCarrierCode = trim((string) ($aSvc['carrier_code'] ?? ''));
$aSvcSelected = $mappedCarrier === 'apaczka' && $mappedMethodId === $aSvcId;
?>
<option
value="<?= $e($aSvcId) ?>"
data-carrier-id="<?= $e($aSvcCarrierCode) ?>"
<?= $aSvcSelected ? 'selected' : '' ?>>
<?= $e($aSvcName !== '' ? $aSvcName : ('ID ' . $aSvcId)) ?>
</option>
<?php endforeach; ?>
</select>
<?php endif; ?>
</div>
<div id="shipment-empty-panel" class="muted" style="<?= $preselectedCarrier !== '' ? 'display:none' : '' ?>">Wybierz przewoznika</div>
</div>
<input type="hidden" name="credentials_id" id="shipment-credentials-id" value="">
<input type="hidden" name="carrier_id" id="shipment-carrier-id" value="">
<input type="hidden" name="provider_code" id="shipment-provider-code" value="<?= $e($preselectedCarrier === 'apaczka' ? 'apaczka' : 'allegro_wza') ?>">
<label class="form-field">
<span class="field-label">Typ paczki</span>
<select class="form-control" name="package_type">
<option value="PACKAGE" selected>Paczka</option>
<option value="DOX">Dokument</option>
<option value="PALLET">Paleta</option>
</select>
</label>
<div class="form-grid-4">
<label class="form-field">
<span class="field-label">Dlugosc (cm)</span>
<input class="form-control" type="number" name="length_cm" step="0.1" min="0.1" value="<?= $e((string) ($comp['default_package_length_cm'] ?? '25')) ?>">
</label>
<label class="form-field">
<span class="field-label">Szerokosc (cm)</span>
<input class="form-control" type="number" name="width_cm" step="0.1" min="0.1" value="<?= $e((string) ($comp['default_package_width_cm'] ?? '20')) ?>">
</label>
<label class="form-field">
<span class="field-label">Wysokosc (cm)</span>
<input class="form-control" type="number" name="height_cm" step="0.1" min="0.1" value="<?= $e((string) ($comp['default_package_height_cm'] ?? '8')) ?>">
</label>
<label class="form-field">
<span class="field-label">Waga (kg)</span>
<input class="form-control" type="number" name="weight_kg" step="0.001" min="0.001" value="<?= $e((string) ($comp['default_package_weight_kg'] ?? '1')) ?>">
</label>
</div>
<div class="form-grid-2">
<label class="form-field">
<span class="field-label">Ubezpieczenie (<?= $e($currency) ?>)</span>
<input class="form-control" type="number" name="insurance_amount" step="0.01" min="0" value="<?= $e(number_format($totalWithTax, 2, '.', '')) ?>">
<input type="hidden" name="insurance_currency" value="<?= $e($currency) ?>">
</label>
<label class="form-field">
<span class="field-label">Format etykiety</span>
<select class="form-control" name="label_format">
<option value="PDF"<?= ((string) ($comp['default_label_format'] ?? 'PDF')) === 'PDF' ? ' selected' : '' ?>>PDF</option>
<option value="ZPL"<?= ((string) ($comp['default_label_format'] ?? 'PDF')) === 'ZPL' ? ' selected' : '' ?>>ZPL</option>
</select>
</label>
</div>
<div class="form-grid-2">
<label class="form-field">
<span class="field-label">Pobranie (<?= $e($currency) ?>)<?= $isCod ? ' <span class="order-tag is-cod" style="font-size:0.7rem;vertical-align:middle">ZA POBRANIEM</span>' : '' ?></span>
<input class="form-control" type="number" name="cod_amount" step="0.01" min="0" value="<?= $e($defaultCodAmount) ?>">
<input type="hidden" name="cod_currency" value="<?= $e($currency) ?>">
</label>
</div>
<label class="form-field">
<span class="field-label">Punkt nadania (opcjonalnie)</span>
<input class="form-control" type="text" name="sender_point_id" maxlength="64" placeholder="np. KRA010">
</label>
</section>
<section class="card">
<h3 class="section-title">Adres odbiorcy</h3>
<label class="form-field mt-12">
<span class="field-label">Imie i nazwisko</span>
<input class="form-control" type="text" name="receiver_name" maxlength="200" value="<?= $e((string) ($receiver['name'] ?? '')) ?>" required>
</label>
<label class="form-field">
<span class="field-label">Firma (opcjonalnie)</span>
<input class="form-control" type="text" name="receiver_company" maxlength="200" value="<?= $e((string) ($receiver['company_name'] ?? '')) ?>">
</label>
<label class="form-field">
<span class="field-label">Ulica</span>
<input class="form-control" type="text" name="receiver_street" maxlength="200" value="<?= $e((string) ($receiver['street_name'] ?? '')) ?>" required>
</label>
<div class="form-grid-3">
<label class="form-field">
<span class="field-label">Kod pocztowy</span>
<input class="form-control" type="text" name="receiver_postal_code" maxlength="16" value="<?= $e((string) ($receiver['zip_code'] ?? '')) ?>" required>
</label>
<label class="form-field">
<span class="field-label">Miasto</span>
<input class="form-control" type="text" name="receiver_city" maxlength="128" value="<?= $e((string) ($receiver['city'] ?? '')) ?>" required>
</label>
<label class="form-field">
<span class="field-label">Kraj</span>
<input class="form-control" type="text" name="receiver_country_code" maxlength="2" value="<?= $e((string) ($receiver['country'] ?? 'PL')) ?>" required>
</label>
</div>
<div class="form-grid-2">
<label class="form-field">
<span class="field-label">Telefon</span>
<input class="form-control" type="tel" name="receiver_phone" maxlength="64" value="<?= $e((string) ($receiver['phone'] ?? '')) ?>" required>
</label>
<label class="form-field">
<span class="field-label">E-mail</span>
<input class="form-control" type="email" name="receiver_email" maxlength="128" value="<?= $e((string) ($receiver['email'] ?? '')) ?>" required>
</label>
</div>
<?php if ($pointId !== ''): ?>
<label class="form-field">
<span class="field-label">Punkt odbioru<?= $pointName !== '' ? ' (' . $e($pointName) . ')' : '' ?></span>
<input class="form-control" type="text" name="receiver_point_id" maxlength="64" value="<?= $e($pointId) ?>">
</label>
<?php else: ?>
<label class="form-field">
<span class="field-label">Punkt odbioru (opcjonalnie)</span>
<input class="form-control" type="text" name="receiver_point_id" maxlength="64" value="">
</label>
<?php endif; ?>
</section>
</div>
<section class="card mt-16">
<h3 class="section-title">Pozycje zamowienia (<?= $e((string) count($itemsList)) ?>)</h3>
<div class="table-wrap mt-12">
<table class="table table--details">
<thead>
<tr><th>Lp.</th><th>Nazwa</th><th>Ilosc</th><th>Cena</th></tr>
</thead>
<tbody>
<?php foreach ($itemsList as $idx => $item): ?>
<tr>
<td><?= $e((string) ($idx + 1)) ?></td>
<td><?= $e((string) ($item['original_name'] ?? '')) ?></td>
<td><?= $e((string) ($item['quantity'] ?? 0)) ?></td>
<td><?= $e($item['original_price_with_tax'] !== null ? number_format((float) $item['original_price_with_tax'], 2, '.', ' ') : '-') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="form-actions mt-16">
<button type="submit" class="btn btn--primary">Utworz przesylke</button>
<a href="/orders/<?= $e((string) ($orderId ?? 0)) ?>" class="btn btn--secondary">Anuluj</a>
</div>
</section>
</form>
<?php if ($packages !== []): ?>
<section class="card mt-16">
<h3 class="section-title">Utworzone przesylki</h3>
<div class="table-wrap mt-12">
<table class="table table--details">
<thead>
<tr>
<th>ID</th>
<th>Status</th>
<th>Status dostawy</th>
<th>Nr sledzenia</th>
<th>Przewoznik</th>
<th>Etykieta</th>
<th>Utworzono</th>
<th>Akcje</th>
</tr>
</thead>
<tbody>
<?php foreach ($packages as $pkg): ?>
<?php
$pkgId = (int) ($pkg['id'] ?? 0);
$pkgStatus = (string) ($pkg['status'] ?? 'draft');
$pkgTracking = trim((string) ($pkg['tracking_number'] ?? ''));
$pkgCarrier = trim((string) ($pkg['carrier_id'] ?? ''));
$pkgLabelPath = trim((string) ($pkg['label_path'] ?? ''));
$pkgShipmentId = trim((string) ($pkg['shipment_id'] ?? ''));
$pkgError = trim((string) ($pkg['error_message'] ?? ''));
?>
<tr>
<td><?= $e((string) $pkgId) ?></td>
<td>
<span class="order-tag <?= $pkgStatus === 'label_ready' || $pkgStatus === 'created' ? 'is-success' : ($pkgStatus === 'error' ? 'is-danger' : 'is-warn') ?>">
<?= $e($pkgStatus) ?>
</span>
<?php if ($pkgError !== ''): ?>
<div class="muted mt-4" style="font-size:0.75rem"><?= $e($pkgError) ?></div>
<?php endif; ?>
</td>
<td>
<?php
$pkgProvider = trim((string) ($pkg['provider'] ?? ''));
$pkgDeliveryStatus = (string) ($pkg['delivery_status'] ?? 'unknown');
$pkgDeliveryRaw = trim((string) ($pkg['delivery_status_raw'] ?? ''));
$pkgDeliveryLabel = \App\Modules\Shipments\DeliveryStatus::label($pkgDeliveryStatus);
$pkgDeliveryDesc = $pkgDeliveryRaw !== '' ? \App\Modules\Shipments\DeliveryStatus::description($pkgProvider, $pkgDeliveryRaw) : '';
$pkgDeliveryTitle = $pkgDeliveryRaw !== '' ? ($pkgDeliveryRaw . ' — ' . $pkgDeliveryDesc) : '';
?>
<span class="delivery-badge delivery-badge--<?= $e($pkgDeliveryStatus) ?>" title="<?= $e($pkgDeliveryTitle) ?>"><?= $e($pkgDeliveryLabel) ?></span>
</td>
<td style="white-space:nowrap">
<?= $e($pkgTracking !== '' ? $pkgTracking : '-') ?><?php
$pkgTrackUrl = \App\Modules\Shipments\DeliveryStatus::trackingUrl($pkgProvider, $pkgTracking, trim((string) ($pkg['carrier_id'] ?? '')));
if ($pkgTrackUrl !== null): ?> <a href="<?= $e($pkgTrackUrl) ?>" target="_blank" class="tracking-link" title="Sledz przesylke">&#128279;</a><?php endif; ?>
</td>
<td><?= $e($pkgCarrier !== '' ? $pkgCarrier : '-') ?></td>
<td>
<?php if ($pkgLabelPath !== ''): ?>
<?php if ($pkgStatus === 'error'): ?>
-
<?php else: ?>
<form method="post" action="/orders/<?= $e((string) ($orderId ?? 0)) ?>/shipment/<?= $e((string) $pkgId) ?>/label" style="display:inline">
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
<button type="submit" class="btn btn--sm btn--secondary">Pobierz</button>
</form>
<?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>
<?php else: ?>
<button type="button"
class="btn btn--sm btn--secondary btn-print-label"
data-package-id="<?= $e((string) $pkgId) ?>"
data-order-id="<?= $e((string) ($orderId ?? 0)) ?>"
title="Wyslij do drukarki">Drukuj</button>
<?php endif; ?>
<?php endif; ?>
</td>
<td class="text-nowrap"><?= $e((string) ($pkg['created_at'] ?? '')) ?></td>
<td>
<?php
$shouldCheckStatus = $pkgStatus === 'pending' || ($pkgStatus === 'created' && $pkgLabelPath === '');
?>
<?php if ($shouldCheckStatus): ?>
<button type="button"
class="btn btn--sm btn--secondary"
data-check-status="<?= $e((string) $pkgId) ?>"
data-order-id="<?= $e((string) ($orderId ?? 0)) ?>"
data-package-status="<?= $e($pkgStatus) ?>"
data-auto-check="1">Sprawdz status</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</section>
<?php endif; ?>
<script>
(function () {
function enhanceSelect(selectEl) {
if (!selectEl || selectEl.dataset.enhanced) return;
selectEl.dataset.enhanced = '1';
var parent = selectEl.parentNode;
var container = document.createElement('div');
container.className = 'searchable-select';
parent.insertBefore(container, selectEl);
container.appendChild(selectEl);
selectEl.style.display = 'none';
var trigger = document.createElement('div');
trigger.className = 'searchable-select__trigger form-control';
container.appendChild(trigger);
var dd = document.createElement('div');
dd.className = 'searchable-select__dropdown';
container.appendChild(dd);
var search = document.createElement('input');
search.type = 'text';
search.className = 'searchable-select__search form-control';
search.placeholder = 'Szukaj...';
search.autocomplete = 'off';
dd.appendChild(search);
var optEls = [];
Array.from(selectEl.options).forEach(function (opt) {
var div = document.createElement('div');
div.className = 'searchable-select__option';
div.setAttribute('data-value', opt.value);
div.textContent = opt.textContent.trim();
if (opt.selected) div.classList.add('is-selected');
dd.appendChild(div);
optEls.push(div);
});
function syncTrigger() {
var opt = selectEl.options[selectEl.selectedIndex];
var text = opt ? opt.textContent.trim() : '';
trigger.textContent = text;
trigger.classList.toggle('searchable-select__trigger--placeholder', !selectEl.value);
optEls.forEach(function (d) {
d.classList.toggle('is-selected', d.getAttribute('data-value') === selectEl.value);
});
}
syncTrigger();
var isOpen = false;
function open() {
dd.classList.add('is-open');
isOpen = true;
search.value = '';
filterOpts('');
setTimeout(function () { search.focus(); }, 0);
}
function close() {
dd.classList.remove('is-open');
isOpen = false;
}
function pick(div) {
selectEl.value = div.getAttribute('data-value');
syncTrigger();
close();
selectEl.dispatchEvent(new Event('change', { bubbles: true }));
}
function filterOpts(q) {
q = q.toLowerCase().trim();
optEls.forEach(function (d) {
d.style.display = q === '' || d.textContent.toLowerCase().indexOf(q) !== -1 ? '' : 'none';
});
}
trigger.addEventListener('click', function (e) {
e.stopPropagation();
isOpen ? close() : open();
});
search.addEventListener('input', function () {
filterOpts(search.value);
});
optEls.forEach(function (d) {
d.addEventListener('mousedown', function (e) {
e.preventDefault();
pick(d);
});
});
search.addEventListener('blur', function () {
setTimeout(close, 150);
});
search.addEventListener('keydown', function (e) {
if (e.key === 'Escape') close();
});
document.addEventListener('click', function (e) {
if (isOpen && !container.contains(e.target)) close();
});
selectEl._syncTrigger = syncTrigger;
}
var carrierSelect = document.getElementById('shipment-carrier-select');
var inpostSelect = document.getElementById('shipment-inpost-select');
var apaczkaSelect = document.getElementById('shipment-apaczka-select');
document.querySelectorAll('form select.form-control').forEach(function (sel) {
enhanceSelect(sel);
});
var allegroPanel = document.getElementById('shipment-allegro-panel');
var inpostPanel = document.getElementById('shipment-inpost-panel');
var apaczkaPanel = document.getElementById('shipment-apaczka-panel');
var emptyPanel = document.getElementById('shipment-empty-panel');
var wrapper = document.getElementById('shipment-service-wrapper');
var hiddenInput = document.getElementById('shipment-delivery-service');
var searchInput = document.getElementById('shipment-service-search');
var dropdown = document.getElementById('shipment-service-dropdown');
var credentialsInput = document.getElementById('shipment-credentials-id');
var carrierInput = document.getElementById('shipment-carrier-id');
var providerInput = document.getElementById('shipment-provider-code');
if (!carrierSelect || !hiddenInput) return;
var allegroOpts = dropdown ? dropdown.querySelectorAll('.searchable-select__option') : [];
function clearHiddenFields() {
hiddenInput.value = '';
credentialsInput.value = '';
carrierInput.value = '';
}
function showPanel(carrier) {
if (allegroPanel) allegroPanel.style.display = carrier === 'allegro' ? '' : 'none';
if (inpostPanel) inpostPanel.style.display = carrier === 'inpost' ? '' : 'none';
if (apaczkaPanel) apaczkaPanel.style.display = carrier === 'apaczka' ? '' : 'none';
if (emptyPanel) emptyPanel.style.display = carrier === '' ? '' : 'none';
if (providerInput) providerInput.value = carrier === 'apaczka' ? 'apaczka' : 'allegro_wza';
}
carrierSelect.addEventListener('change', function () {
clearHiddenFields();
if (searchInput) searchInput.value = '';
if (inpostSelect) {
inpostSelect.selectedIndex = 0;
if (inpostSelect._syncTrigger) inpostSelect._syncTrigger();
}
if (apaczkaSelect) {
apaczkaSelect.selectedIndex = 0;
if (apaczkaSelect._syncTrigger) apaczkaSelect._syncTrigger();
}
allegroOpts.forEach(function (o) { o.classList.remove('is-selected'); });
showPanel(carrierSelect.value);
});
if (inpostSelect) {
function syncInpostFields() {
var opt = inpostSelect.options[inpostSelect.selectedIndex];
hiddenInput.value = inpostSelect.value;
credentialsInput.value = opt ? (opt.getAttribute('data-credentials-id') || '') : '';
carrierInput.value = opt ? (opt.getAttribute('data-carrier-id') || '') : '';
if (providerInput) providerInput.value = 'allegro_wza';
}
inpostSelect.addEventListener('change', syncInpostFields);
if (carrierSelect.value === 'inpost' && inpostSelect.value !== '') {
syncInpostFields();
}
}
if (apaczkaSelect) {
function syncApaczkaFields() {
var opt = apaczkaSelect.options[apaczkaSelect.selectedIndex];
hiddenInput.value = apaczkaSelect.value;
credentialsInput.value = '';
carrierInput.value = opt ? (opt.getAttribute('data-carrier-id') || '') : '';
if (providerInput) providerInput.value = 'apaczka';
}
apaczkaSelect.addEventListener('change', syncApaczkaFields);
if (carrierSelect.value === 'apaczka' && apaczkaSelect.value !== '') {
syncApaczkaFields();
}
}
if (wrapper && searchInput && dropdown) {
var isAllegroOpen = false;
function selectAllegroOption(opt) {
hiddenInput.value = opt.getAttribute('data-value') || '';
credentialsInput.value = opt.getAttribute('data-credentials-id') || '';
carrierInput.value = opt.getAttribute('data-carrier-id') || '';
if (providerInput) providerInput.value = 'allegro_wza';
searchInput.value = opt.getAttribute('data-label') || '';
closeAllegro();
allegroOpts.forEach(function (o) { o.classList.remove('is-selected'); });
opt.classList.add('is-selected');
}
function openAllegro() {
dropdown.classList.add('is-open');
isAllegroOpen = true;
}
function closeAllegro() {
dropdown.classList.remove('is-open');
isAllegroOpen = false;
}
function filterAllegro(q) {
q = q.toLowerCase().trim();
allegroOpts.forEach(function (opt) {
var label = (opt.getAttribute('data-label') || '').toLowerCase();
opt.style.display = q === '' || label.indexOf(q) !== -1 ? '' : 'none';
});
}
searchInput.addEventListener('focus', function () {
filterAllegro(searchInput.value);
openAllegro();
});
searchInput.addEventListener('input', function () {
filterAllegro(searchInput.value);
if (!isAllegroOpen) openAllegro();
});
allegroOpts.forEach(function (opt) {
opt.addEventListener('mousedown', function (e) {
e.preventDefault();
selectAllegroOption(opt);
});
});
searchInput.addEventListener('blur', function () {
setTimeout(closeAllegro, 150);
});
searchInput.addEventListener('keydown', function (e) {
if (e.key === 'Escape') {
closeAllegro();
searchInput.blur();
}
});
if (carrierSelect.value === 'allegro') {
var matchId = (wrapper.getAttribute('data-match-id') || '').trim();
if (matchId !== '') {
allegroOpts.forEach(function (opt) {
if (opt.getAttribute('data-value') === matchId) {
selectAllegroOption(opt);
}
});
}
}
}
function checkPackageStatus(pkgId, oId, btn, attempt) {
if (btn) {
btn.disabled = true;
btn.textContent = 'Sprawdzam...';
}
fetch('/orders/' + oId + '/shipment/' + pkgId + '/status')
.then(function (r) { return r.json(); })
.then(function (data) {
if (data.status === 'label_ready') {
window.location.reload();
} else if (data.status === 'created' || data.status === 'in_progress' || data.status === 'pending') {
if (attempt < 10) {
var delayCreated = Math.min(2000 * Math.pow(1.5, attempt), 15000);
setTimeout(function () {
checkPackageStatus(pkgId, oId, btn, attempt + 1);
}, delayCreated);
if (btn) btn.textContent = 'Generuje etykiete... (' + (attempt + 1) + ')';
} else if (btn) {
btn.textContent = 'W toku... Odswiez status';
btn.disabled = false;
}
} else if (data.status === 'error') {
if (btn) {
btn.textContent = 'Blad: ' + (data.error || '');
btn.disabled = false;
}
} else {
if (btn) {
btn.textContent = 'W toku... Sprobuj ponownie';
btn.disabled = false;
}
}
})
.catch(function () {
if (btn) {
btn.textContent = 'Blad sieci';
btn.disabled = false;
}
});
}
document.querySelectorAll('[data-check-status]').forEach(function (btn) {
btn.addEventListener('click', function () {
checkPackageStatus(
btn.getAttribute('data-check-status'),
btn.getAttribute('data-order-id'),
btn,
0
);
});
});
// Print label button handler
document.querySelectorAll('.btn-print-label').forEach(function (btn) {
btn.addEventListener('click', function () {
var packageId = btn.getAttribute('data-package-id');
if (!packageId) return;
btn.disabled = true;
var originalText = btn.innerHTML;
btn.innerHTML = 'Wysylam...';
fetch('/api/print/jobs', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: '_token=' + encodeURIComponent(document.querySelector('input[name="_token"]').value)
+ '&package_id=' + encodeURIComponent(packageId)
})
.then(function (r) { return r.json().then(function (d) { return { status: r.status, data: d }; }); })
.then(function (res) {
if (res.status === 201 || res.status === 409) {
btn.innerHTML = 'W kolejce';
btn.disabled = true;
btn.classList.remove('btn--secondary');
btn.classList.add('btn--danger');
} else {
var msg = (res.data && res.data.error) ? res.data.error : 'Nieznany blad';
if (window.OrderProAlerts) {
window.OrderProAlerts.show({ message: msg, type: 'error' });
}
btn.innerHTML = originalText;
btn.disabled = false;
}
})
.catch(function () {
if (window.OrderProAlerts) {
window.OrderProAlerts.show({ message: 'Blad sieci — sprobuj ponownie.', type: 'error' });
}
btn.innerHTML = originalText;
btn.disabled = false;
});
});
});
var params = new URLSearchParams(window.location.search);
var autoCheckId = params.get('check');
document.querySelectorAll('[data-check-status][data-auto-check=\"1\"]').forEach(function (btn, idx) {
var pkgId = btn.getAttribute('data-check-status');
var oId = btn.getAttribute('data-order-id');
var pkgStatus = (btn.getAttribute('data-package-status') || '').toLowerCase();
if (!pkgId || !oId) return;
if (autoCheckId) {
if (pkgId !== autoCheckId) return;
} else {
if (pkgStatus !== 'pending') return;
}
setTimeout(function () {
checkPackageStatus(pkgId, oId, btn, 0);
}, idx * 400);
});
})();
</script>
<script>
(function () {
'use strict';
var PRESET_COLORS = ['#3b82f6','#ef4444','#10b981','#f59e0b','#8b5cf6','#ec4899','#06b6d4','#6b7280'];
var presetsContainer = document.getElementById('shipment-presets');
var addBtn = document.getElementById('preset-add-btn');
var modal = document.getElementById('preset-modal');
var nameInput = document.getElementById('preset-name-input');
var colorPicker = document.getElementById('preset-color-picker');
var saveBtn = document.getElementById('preset-save-btn');
var cancelBtn = document.getElementById('preset-cancel-btn');
if (!presetsContainer || !addBtn || !modal) return;
var selectedColor = PRESET_COLORS[0];
var presetsData = [];
// --- Color picker ---
PRESET_COLORS.forEach(function (color, idx) {
var swatch = document.createElement('div');
swatch.className = 'preset-modal__color-swatch' + (idx === 0 ? ' is-selected' : '');
swatch.style.background = color;
swatch.setAttribute('data-color', color);
swatch.addEventListener('click', function () {
colorPicker.querySelectorAll('.preset-modal__color-swatch').forEach(function (s) { s.classList.remove('is-selected'); });
swatch.classList.add('is-selected');
selectedColor = color;
});
colorPicker.appendChild(swatch);
});
// --- Load presets ---
function loadPresets() {
fetch('/api/shipment-presets', { credentials: 'same-origin' })
.then(function (r) { return r.json(); })
.then(function (data) {
presetsData = data.presets || [];
renderPresets();
})
.catch(function () {});
}
var activeDropdown = null;
function renderPresets() {
var existing = presetsContainer.querySelectorAll('.shipment-presets__btn-wrap');
existing.forEach(function (el) { el.remove(); });
presetsData.forEach(function (preset) {
var wrap = document.createElement('div');
wrap.className = 'shipment-presets__btn-wrap';
var btn = document.createElement('button');
btn.type = 'button';
btn.className = 'shipment-presets__btn';
btn.style.background = preset.color || '#3b82f6';
btn.textContent = preset.name;
btn.setAttribute('data-preset-id', preset.id);
btn.addEventListener('click', function () { applyPreset(preset); });
var editIcon = document.createElement('button');
editIcon.type = 'button';
editIcon.className = 'shipment-presets__edit-icon';
editIcon.textContent = '\u270E';
editIcon.addEventListener('click', function (e) {
e.stopPropagation();
showDropdown(wrap, preset);
});
wrap.appendChild(btn);
wrap.appendChild(editIcon);
presetsContainer.insertBefore(wrap, addBtn);
});
}
// --- Dropdown menu ---
function closeDropdown() {
if (activeDropdown) {
activeDropdown.remove();
activeDropdown = null;
}
}
document.addEventListener('click', function () { closeDropdown(); });
function showDropdown(wrap, preset) {
closeDropdown();
var dd = document.createElement('div');
dd.className = 'shipment-presets__dropdown';
var editItem = document.createElement('div');
editItem.className = 'shipment-presets__dropdown-item';
editItem.textContent = 'Edytuj nazw\u0119 i kolor';
editItem.addEventListener('click', function (e) {
e.stopPropagation();
closeDropdown();
openEditModal(preset);
});
var updateItem = document.createElement('div');
updateItem.className = 'shipment-presets__dropdown-item';
updateItem.textContent = 'Zapisz bie\u017C\u0105ce warto\u015Bci';
updateItem.addEventListener('click', function (e) {
e.stopPropagation();
closeDropdown();
saveCurrentValues(preset);
});
var deleteItem = document.createElement('div');
deleteItem.className = 'shipment-presets__dropdown-item is-danger';
deleteItem.textContent = 'Usu\u0144';
deleteItem.addEventListener('click', function (e) {
e.stopPropagation();
closeDropdown();
deletePreset(preset);
});
dd.appendChild(editItem);
dd.appendChild(updateItem);
dd.appendChild(deleteItem);
wrap.appendChild(dd);
activeDropdown = dd;
}
// --- Apply preset (autofill form) ---
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');
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.
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);
}, 200);
}
function setFieldValue(name, value) {
var field = document.querySelector('[name="' + name + '"]');
if (!field) return;
field.value = value;
if (field.tagName === 'SELECT' && field._syncTrigger) {
field._syncTrigger();
}
}
function selectDeliveryService(preset) {
var carrier = preset.carrier || '';
var methodId = preset.delivery_method_id || '';
var credentialsId = preset.credentials_id || '';
var carrierId = preset.carrier_id || '';
var hiddenInput = document.getElementById('shipment-delivery-service');
var credentialsInput = document.getElementById('shipment-credentials-id');
var carrierInput = document.getElementById('shipment-carrier-id');
if (carrier === 'allegro') {
// Click the matching option in Allegro searchable dropdown
var dropdown = document.getElementById('shipment-service-dropdown');
if (dropdown) {
var opts = dropdown.querySelectorAll('.searchable-select__option');
opts.forEach(function (opt) {
opt.classList.remove('is-selected');
if (opt.getAttribute('data-value') === methodId) {
opt.classList.add('is-selected');
// Update search input to show selected service name
var searchInput = document.getElementById('shipment-service-search');
if (searchInput) searchInput.value = opt.getAttribute('data-label') || opt.textContent.trim();
// Set hidden fields from option data attributes
if (hiddenInput) hiddenInput.value = methodId;
if (credentialsInput) credentialsInput.value = opt.getAttribute('data-credentials-id') || credentialsId;
if (carrierInput) carrierInput.value = opt.getAttribute('data-carrier-id') || carrierId;
}
});
}
} else if (carrier === 'inpost') {
var inpostSelect = document.getElementById('shipment-inpost-select');
if (inpostSelect) {
inpostSelect.value = methodId;
if (inpostSelect._syncTrigger) inpostSelect._syncTrigger();
// Trigger change to set hidden fields via existing handler
inpostSelect.dispatchEvent(new Event('change'));
}
} else if (carrier === 'apaczka') {
var apaczkaSelect = document.getElementById('shipment-apaczka-select');
if (apaczkaSelect) {
apaczkaSelect.value = methodId;
if (apaczkaSelect._syncTrigger) apaczkaSelect._syncTrigger();
// Trigger change to set hidden fields via existing handler
apaczkaSelect.dispatchEvent(new Event('change'));
}
}
}
// --- Modal (create & edit) ---
var editingPresetId = 0;
var modalTitle = modal.querySelector('h3');
function openCreateModal() {
editingPresetId = 0;
nameInput.value = '';
selectedColor = PRESET_COLORS[0];
colorPicker.querySelectorAll('.preset-modal__color-swatch').forEach(function (s, i) {
s.classList.toggle('is-selected', i === 0);
});
if (modalTitle) modalTitle.textContent = 'Nowy przycisk dostawy';
saveBtn.textContent = 'Zapisz';
modal.style.display = '';
nameInput.focus();
}
function openEditModal(preset) {
editingPresetId = preset.id;
nameInput.value = preset.name || '';
selectedColor = preset.color || PRESET_COLORS[0];
colorPicker.querySelectorAll('.preset-modal__color-swatch').forEach(function (s) {
s.classList.toggle('is-selected', s.getAttribute('data-color') === selectedColor);
});
if (modalTitle) modalTitle.textContent = 'Edytuj przycisk dostawy';
saveBtn.textContent = 'Zapisz zmiany';
modal.style.display = '';
nameInput.focus();
}
addBtn.addEventListener('click', function () { openCreateModal(); });
cancelBtn.addEventListener('click', function () {
modal.style.display = 'none';
});
modal.addEventListener('click', function (e) {
if (e.target === modal) modal.style.display = 'none';
});
saveBtn.addEventListener('click', function () {
var name = nameInput.value.trim();
if (!name) {
nameInput.focus();
return;
}
saveBtn.disabled = true;
if (editingPresetId > 0) {
// Edit mode — update name and color only, keep existing params
var editPreset = presetsData.find(function (p) { return p.id === editingPresetId; });
var editPayload = {
id: editingPresetId,
name: name,
color: selectedColor,
carrier: editPreset ? editPreset.carrier : '',
provider_code: editPreset ? editPreset.provider_code : '',
delivery_method_id: editPreset ? editPreset.delivery_method_id : '',
credentials_id: editPreset ? editPreset.credentials_id : '',
carrier_id: editPreset ? editPreset.carrier_id : '',
package_type: editPreset ? editPreset.package_type : 'PACKAGE',
length_cm: editPreset ? editPreset.length_cm : '25',
width_cm: editPreset ? editPreset.width_cm : '20',
height_cm: editPreset ? editPreset.height_cm : '8',
weight_kg: editPreset ? editPreset.weight_kg : '1',
sender_point_id: editPreset ? editPreset.sender_point_id : '',
label_format: editPreset ? editPreset.label_format : 'PDF'
};
postPresetAPI('/api/shipment-presets/update', editPayload);
} else {
// Create mode — use current form values
var payload = buildFormPayload(name, selectedColor);
postPresetAPI('/api/shipment-presets', payload);
}
});
function buildFormPayload(name, color) {
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');
return {
name: name,
color: color,
carrier: carrierSelect ? carrierSelect.value : '',
provider_code: providerInput ? providerInput.value : '',
delivery_method_id: hiddenInput ? hiddenInput.value : '',
credentials_id: credentialsInput ? credentialsInput.value : '',
carrier_id: carrierInput ? carrierInput.value : '',
package_type: getFieldValue('package_type'),
length_cm: getFieldValue('length_cm'),
width_cm: getFieldValue('width_cm'),
height_cm: getFieldValue('height_cm'),
weight_kg: getFieldValue('weight_kg'),
sender_point_id: getFieldValue('sender_point_id'),
label_format: getFieldValue('label_format')
};
}
function postPresetAPI(url, payload) {
var formBody = new URLSearchParams(payload);
fetch(url, {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: formBody.toString()
})
.then(function (r) { return r.json(); })
.then(function (data) {
if (data.preset || data.deleted) {
modal.style.display = 'none';
loadPresets();
}
})
.catch(function () {})
.finally(function () { saveBtn.disabled = false; });
}
// --- Save current form values to existing preset ---
function saveCurrentValues(preset) {
var payload = buildFormPayload(preset.name, preset.color);
payload.id = preset.id;
postPresetAPI('/api/shipment-presets/update', payload);
}
// --- Delete preset ---
function deletePreset(preset) {
if (window.OrderProAlerts && window.OrderProAlerts.confirm) {
window.OrderProAlerts.confirm({
message: 'Usun\u0105\u0107 przycisk "' + preset.name + '"?',
onConfirm: function () { executeDelete(preset.id); }
});
} else if (confirm('Usun\u0105\u0107 przycisk "' + preset.name + '"?')) {
executeDelete(preset.id);
}
}
function executeDelete(id) {
var formBody = new URLSearchParams({ id: id });
fetch('/api/shipment-presets/delete', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: formBody.toString()
})
.then(function (r) { return r.json(); })
.then(function (data) {
if (data.deleted) loadPresets();
})
.catch(function () {});
}
function getFieldValue(name) {
var field = document.querySelector('[name="' + name + '"]');
return field ? field.value : '';
}
// --- Init ---
loadPresets();
})();
</script>