Add Allegro shipment service and related components

- Implement AllegroShipmentService for managing shipment creation and status checks.
- Create ShipmentController to handle shipment preparation and label downloading.
- Introduce ShipmentPackageRepository for database interactions related to shipment packages.
- Add methods for retrieving delivery services, creating shipments, checking creation status, and downloading labels.
- Implement address validation and token management for Allegro API integration.
This commit is contained in:
2026-03-06 01:06:59 +01:00
parent 9df7a63244
commit 1b5e403c31
46 changed files with 6705 additions and 133 deletions

View File

@@ -46,6 +46,15 @@
<a class="sidebar__sublink<?= $currentMenu === 'settings' && $currentSettings === 'allegro' ? ' is-active' : '' ?>" href="/settings/integrations/allegro">
<?= $e($t('navigation.allegro')) ?>
</a>
<a class="sidebar__sublink<?= $currentMenu === 'settings' && $currentSettings === 'apaczka' ? ' is-active' : '' ?>" href="/settings/integrations/apaczka">
<?= $e($t('navigation.apaczka')) ?>
</a>
<a class="sidebar__sublink<?= $currentMenu === 'settings' && $currentSettings === 'inpost' ? ' is-active' : '' ?>" href="/settings/integrations/inpost">
<?= $e($t('navigation.inpost')) ?>
</a>
<a class="sidebar__sublink<?= $currentMenu === 'settings' && $currentSettings === 'company' ? ' is-active' : '' ?>" href="/settings/company">
<?= $e($t('navigation.company')) ?>
</a>
</div>
</details>
</nav>

View File

@@ -37,48 +37,32 @@
</div>
</section>
<div class="modal-backdrop" data-orders-image-modal hidden>
<div class="modal modal--image-preview" role="dialog" aria-modal="true" aria-label="Podglad zdjecia produktu">
<div class="modal__header">
<h3>Podglad zdjecia</h3>
<button type="button" class="btn btn--secondary" data-orders-image-close>Zamknij</button>
</div>
<div class="modal__body">
<img src="" alt="" class="product-image-preview__img" data-orders-image-preview>
</div>
</div>
</div>
<script>
(function () {
var modal = document.querySelector('[data-orders-image-modal]');
var preview = document.querySelector('[data-orders-image-preview]');
if (!modal || !preview) return;
var POPUP_GAP = 12;
function closeModal() {
modal.setAttribute('hidden', 'hidden');
preview.setAttribute('src', '');
}
document.addEventListener('mouseenter', function (e) {
var wrap = e.target.closest('.orders-image-hover-wrap');
if (!wrap) return;
var popup = wrap.querySelector('.orders-image-hover-popup');
if (!popup) return;
document.addEventListener('click', function (event) {
var trigger = event.target.closest('.js-order-img-open');
if (trigger) {
var imageUrl = trigger.getAttribute('data-image-url') || '';
if (imageUrl === '') return;
preview.setAttribute('src', imageUrl);
modal.removeAttribute('hidden');
return;
var rect = wrap.getBoundingClientRect();
var pw = 350;
var ph = 350;
var left = rect.right + POPUP_GAP;
if (left + pw > window.innerWidth) {
left = rect.left - pw - POPUP_GAP;
}
if (event.target.matches('[data-orders-image-close]') || event.target === modal) {
closeModal();
}
});
var top = rect.top + rect.height / 2 - ph / 2;
if (top < 4) top = 4;
if (top + ph > window.innerHeight - 4) top = window.innerHeight - 4 - ph;
document.addEventListener('keydown', function (event) {
if (event.key === 'Escape' && !modal.hasAttribute('hidden')) {
closeModal();
}
});
popup.style.left = left + 'px';
popup.style.top = top + 'px';
}, true);
})();
</script>

View File

@@ -7,8 +7,13 @@ $shipmentsList = is_array($shipments ?? null) ? $shipments : [];
$documentsList = is_array($documents ?? null) ? $documents : [];
$notesList = is_array($notes ?? null) ? $notes : [];
$historyList = is_array($history ?? null) ? $history : [];
$activityLogList = is_array($activityLog ?? null) ? $activityLog : [];
$statusPanelList = is_array($statusPanel ?? null) ? $statusPanel : [];
$statusPanelTitle = 'Statusy';
$allStatusesList = is_array($allStatuses ?? null) ? $allStatuses : [];
$currentStatusCodeValue = (string) ($currentStatusCode ?? '');
$flashSuccessMsg = (string) ($flashSuccess ?? '');
$flashErrorMsg = (string) ($flashError ?? '');
$addressByType = [
'customer' => null,
@@ -33,25 +38,63 @@ foreach ($addressesList as $address) {
<a href="/orders/list" class="order-back-link">&larr; <?= $e($t('navigation.orders_list')) ?></a>
<h2 class="section-title mt-12"><?= $e($t('orders.details.title')) ?> #<?= $e((string) ($orderId ?? 0)) ?></h2>
<div class="order-details-sub mt-12">
<span><?= $e((string) ($orderRow['source_order_id'] ?? '')) ?></span>
<span><?= $e((string) ($orderRow['external_order_id'] ?? '')) ?></span>
<span><?= $e(ucfirst((string) ($orderRow['source'] ?? ''))) ?> <?= $e((string) ($orderRow['external_order_id'] ?? '')) ?></span>
</div>
</div>
<div class="order-details-actions">
<button type="button" class="btn btn--secondary">Strefa klienta</button>
<button type="button" class="btn btn--secondary">Przygotuj przesylke</button>
<a href="/orders/<?= $e((string) ($orderId ?? 0)) ?>/shipment/prepare" class="btn btn--secondary">Przygotuj przesylke</a>
<button type="button" class="btn btn--secondary">Platnosc</button>
<button type="button" class="btn btn--secondary">Drukuj</button>
<button type="button" class="btn btn--primary">Pakuj</button>
<button type="button" class="btn btn--secondary">Edytuj</button>
</div>
</div>
<div class="order-details-pill mt-12"><?= $e((string) ($statusLabel ?? '-')) ?></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; ?>
<div class="order-status-change mt-12">
<span class="order-details-pill"><?= $e((string) ($statusLabel ?? '-')) ?></span>
<?php if ($allStatusesList !== []): ?>
<form method="post" action="/orders/<?= $e((string) ($orderId ?? 0)) ?>/status" class="order-status-change__form">
<input type="hidden" name="_csrf_token" value="<?= $e((string) ($csrfToken ?? '')) ?>">
<select name="new_status" class="input input--sm order-status-change__select">
<option value=""><?= $e($t('orders.details.status_change.placeholder')) ?></option>
<?php
$lastGroup = null;
foreach ($allStatusesList as $statusOption):
$optCode = (string) ($statusOption['code'] ?? '');
$optName = (string) ($statusOption['name'] ?? $optCode);
$optGroup = (string) ($statusOption['group'] ?? '');
if ($optGroup !== $lastGroup):
if ($lastGroup !== null): ?>
</optgroup>
<?php endif;
if ($optGroup !== ''): ?>
<optgroup label="<?= $e($optGroup) ?>">
<?php endif;
$lastGroup = $optGroup;
endif;
?>
<option value="<?= $e($optCode) ?>"<?= $optCode === $currentStatusCodeValue ? ' selected' : '' ?>><?= $e($optName) ?></option>
<?php endforeach;
if ($lastGroup !== null && $lastGroup !== ''): ?>
</optgroup>
<?php endif; ?>
</select>
<button type="submit" class="btn btn--primary btn--sm"><?= $e($t('orders.details.status_change.save')) ?></button>
</form>
<?php endif; ?>
</div>
</section>
<section class="card mt-16 order-details-tabs">
<button type="button" class="order-details-tab is-active" data-order-tab-target="details"><?= $e($t('orders.details.tabs.details')) ?></button>
<button type="button" class="order-details-tab" data-order-tab-target="history"><?= $e($t('orders.details.tabs.history')) ?> (<?= $e((string) count($historyList)) ?>)</button>
<button type="button" class="order-details-tab" data-order-tab-target="history"><?= $e($t('orders.details.tabs.history')) ?> (<?= $e((string) count($activityLogList)) ?>)</button>
<button type="button" class="order-details-tab" data-order-tab-target="shipments"><?= $e($t('orders.details.tabs.shipments')) ?> (<?= $e((string) count($shipmentsList)) ?>)</button>
<button type="button" class="order-details-tab" data-order-tab-target="payments"><?= $e($t('orders.details.tabs.payments')) ?> (<?= $e((string) count($paymentsList)) ?>)</button>
<button type="button" class="order-details-tab" data-order-tab-target="documents"><?= $e($t('orders.details.tabs.documents')) ?> (<?= $e((string) count($documentsList)) ?>)</button>
@@ -183,8 +226,14 @@ foreach ($addressesList as $address) {
<?php endif; ?>
<?php foreach ($historyList as $event): ?>
<div class="order-event">
<div class="order-event__head"><?= $e((string) ($event['changed_at'] ?? '')) ?></div>
<div class="order-event__body"><?= $e((string) ($event['from_status_id'] ?? '-')) ?> -> <?= $e((string) ($event['to_status_id'] ?? '-')) ?></div>
<div class="order-event__head">
<?= $e((string) ($event['changed_at'] ?? '')) ?>
<?php $changeSource = (string) ($event['change_source'] ?? ''); ?>
<?php if ($changeSource !== ''): ?>
<span class="muted">(<?= $e($changeSource) ?>)</span>
<?php endif; ?>
</div>
<div class="order-event__body"><?= $e((string) ($event['from_label'] ?? '-')) ?> &rarr; <?= $e((string) ($event['to_label'] ?? '-')) ?></div>
</div>
<?php endforeach; ?>
</div>
@@ -195,7 +244,50 @@ foreach ($addressesList as $address) {
<div class="order-tab-panel" data-order-tab-panel="history">
<section class="card mt-16">
<h3 class="section-title"><?= $e($t('orders.details.tabs.history')) ?></h3>
<div class="order-empty-placeholder mt-12"></div>
<div class="table-wrap mt-12">
<table class="table table--details">
<thead>
<tr>
<th><?= $e($t('orders.details.activity.date')) ?></th>
<th><?= $e($t('orders.details.activity.type')) ?></th>
<th><?= $e($t('orders.details.activity.summary')) ?></th>
<th><?= $e($t('orders.details.activity.actor')) ?></th>
</tr>
</thead>
<tbody>
<?php if ($activityLogList === []): ?>
<tr><td colspan="4" class="muted"><?= $e($t('orders.details.activity.empty')) ?></td></tr>
<?php endif; ?>
<?php foreach ($activityLogList as $activity): ?>
<?php
$eventType = (string) ($activity['event_type'] ?? '');
$eventTypeKey = 'orders.details.activity.types.' . $eventType;
$eventTypeLabel = $t($eventTypeKey);
if ($eventTypeLabel === $eventTypeKey) {
$eventTypeLabel = $eventType;
}
$actorType = (string) ($activity['actor_type'] ?? 'system');
$actorName = trim((string) ($activity['actor_name'] ?? ''));
if ($actorName !== '') {
$actorLabel = $actorName;
} else {
$actorKey = 'orders.details.activity.actors.' . $actorType;
$actorLabel = $t($actorKey);
if ($actorLabel === $actorKey) {
$actorLabel = $actorType;
}
}
?>
<tr>
<td class="text-nowrap"><?= $e((string) ($activity['created_at'] ?? '')) ?></td>
<td><span class="activity-type-badge activity-type-badge--<?= $e($eventType) ?>"><?= $e($eventTypeLabel) ?></span></td>
<td><?= $e((string) ($activity['summary'] ?? '')) ?></td>
<td class="muted"><?= $e($actorLabel) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</section>
</div>

View File

@@ -47,6 +47,9 @@ $orderproStatuses = is_array($orderproStatuses ?? null) ? $orderproStatuses : []
<button type="button" class="content-tab-btn<?= $activeTab === 'settings' ? ' is-active' : '' ?>" data-tab-target="allegro-tab-settings">
<?= $e($t('settings.allegro.tabs.settings')) ?>
</button>
<button type="button" class="content-tab-btn<?= $activeTab === 'delivery' ? ' is-active' : '' ?>" data-tab-target="allegro-tab-delivery">
<?= $e($t('settings.allegro.tabs.delivery')) ?>
</button>
</nav>
<div class="content-tab-panel<?= $activeTab === 'integration' ? ' is-active' : '' ?>" data-tab-panel="allegro-tab-integration">
@@ -63,10 +66,11 @@ $orderproStatuses = is_array($orderproStatuses ?? null) ? $orderproStatuses : []
<label class="form-field">
<span class="field-label"><?= $e($t('settings.allegro.fields.environment')) ?></span>
<select class="form-control" name="environment">
<select class="form-control" name="environment" id="allegro-env-select">
<option value="sandbox"<?= $environment === 'sandbox' ? ' selected' : '' ?>><?= $e($t('settings.allegro.environment.sandbox')) ?></option>
<option value="production"<?= $environment === 'production' ? ' selected' : '' ?>><?= $e($t('settings.allegro.environment.production')) ?></option>
</select>
<span class="muted"><?= $e($t('settings.allegro.fields.environment_hint')) ?></span>
</label>
<label class="form-field">
@@ -283,6 +287,125 @@ $orderproStatuses = is_array($orderproStatuses ?? null) ? $orderproStatuses : []
</form>
</section>
</div>
<?php
$dmMappings = is_array($deliveryMappings ?? null) ? $deliveryMappings : [];
$dmOrderMethods = is_array($orderDeliveryMethods ?? null) ? $orderDeliveryMethods : [];
$dmAllegroServices = is_array($allegroDeliveryServices ?? null) ? $allegroDeliveryServices : [];
$dmInpostServices = is_array($inpostDeliveryServices ?? null) ? $inpostDeliveryServices : [];
$dmServicesError = (string) ($allegroDeliveryServicesError ?? '');
$dmMappingsByMethod = [];
foreach ($dmMappings as $dm) {
$dmMappingsByMethod[trim((string) ($dm['order_delivery_method'] ?? ''))] = $dm;
}
?>
<div class="content-tab-panel<?= $activeTab === 'delivery' ? ' is-active' : '' ?>" data-tab-panel="allegro-tab-delivery">
<section class="mt-16">
<h3 class="section-title"><?= $e($t('settings.allegro.delivery.title')) ?></h3>
<p class="muted mt-12"><?= $e($t('settings.allegro.delivery.description')) ?></p>
<?php if ($dmServicesError !== ''): ?>
<div class="alert alert--danger mt-12"><?= $e($dmServicesError) ?></div>
<?php endif; ?>
<?php if ($dmOrderMethods === []): ?>
<p class="muted mt-12"><?= $e($t('settings.allegro.delivery.empty_orders')) ?></p>
<?php else: ?>
<form action="/settings/integrations/allegro/delivery/save" method="post">
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
<div class="table-wrap table-wrap--visible mt-12">
<table class="table">
<thead>
<tr>
<th><?= $e($t('settings.allegro.delivery.fields.order_method')) ?></th>
<th><?= $e($t('settings.allegro.delivery.fields.carrier')) ?></th>
<th><?= $e($t('settings.allegro.delivery.fields.allegro_service')) ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($dmOrderMethods as $rowIdx => $orderMethod): ?>
<?php
$currentMapping = $dmMappingsByMethod[$orderMethod] ?? null;
$currentCarrier = $currentMapping !== null ? trim((string) ($currentMapping['carrier'] ?? 'allegro')) : '';
$currentAllegroId = $currentMapping !== null ? trim((string) ($currentMapping['allegro_delivery_method_id'] ?? '')) : '';
$currentServiceName = $currentMapping !== null ? trim((string) ($currentMapping['allegro_service_name'] ?? '')) : '';
?>
<tr data-dm-row="<?= $rowIdx ?>">
<td>
<strong><?= $e($orderMethod) ?></strong>
<input type="hidden" name="order_delivery_method[]" value="<?= $e($orderMethod) ?>">
</td>
<td>
<select class="form-control dm-carrier-select" name="carrier[]" data-row="<?= $rowIdx ?>">
<option value="">-- <?= $e($t('settings.allegro.delivery.fields.no_mapping')) ?> --</option>
<option value="allegro"<?= $currentCarrier === 'allegro' && $currentAllegroId !== '' ? ' selected' : '' ?>>Allegro</option>
<option value="inpost"<?= $currentCarrier === 'inpost' ? ' selected' : '' ?>>InPost</option>
</select>
</td>
<td>
<div class="dm-service-wrap" data-row="<?= $rowIdx ?>">
<input type="hidden" name="allegro_delivery_method_id[]" class="dm-hidden-method-id" value="<?= $e($currentAllegroId) ?>">
<input type="hidden" name="allegro_credentials_id[]" class="dm-hidden-credentials-id" value="<?= $e(trim((string) ($currentMapping['allegro_credentials_id'] ?? ''))) ?>">
<input type="hidden" name="allegro_carrier_id[]" class="dm-hidden-carrier-id" value="<?= $e(trim((string) ($currentMapping['allegro_carrier_id'] ?? ''))) ?>">
<input type="hidden" name="allegro_service_name[]" class="dm-hidden-service-name" value="<?= $e($currentServiceName) ?>">
<?php // Allegro searchable select ?>
<div class="dm-allegro-panel dm-searchable-select" data-current-id="<?= $e($currentCarrier === 'allegro' ? $currentAllegroId : '') ?>" data-current-name="<?= $e($currentCarrier === 'allegro' ? $currentServiceName : '') ?>" style="<?= $currentCarrier !== 'allegro' || $currentAllegroId === '' && $currentCarrier === '' ? 'display:none' : '' ?>">
<input type="text" class="form-control dm-search-input" placeholder="<?= $e($t('settings.allegro.delivery.fields.search_placeholder')) ?>" value="<?= $e($currentCarrier === 'allegro' ? $currentServiceName : '') ?>" autocomplete="off">
<div class="searchable-select__dropdown dm-dropdown">
<div class="searchable-select__option dm-option-clear" data-value="" data-label="" data-credentials-id="" data-carrier-id="">
<em class="muted">-- <?= $e($t('settings.allegro.delivery.fields.no_mapping')) ?> --</em>
</div>
<?php foreach ($dmAllegroServices 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'] ?? ''));
$svcLabel = $svcName . ' (' . $svcOwner . ')';
?>
<div class="searchable-select__option"
data-value="<?= $e($svcMethodId) ?>"
data-label="<?= $e($svcLabel) ?>"
data-credentials-id="<?= $e($svcCredentialsId) ?>"
data-carrier-id="<?= $e($svcCarrierId) ?>"
><?= $e($svcName) ?> <span class="muted">(<?= $e($svcOwner) ?>)</span></div>
<?php endforeach; ?>
</div>
</div>
<?php // InPost simple select ?>
<div class="dm-inpost-panel" style="<?= $currentCarrier !== 'inpost' ? 'display:none' : '' ?>">
<select class="form-control dm-inpost-select">
<option value="">-- <?= $e($t('settings.allegro.delivery.fields.no_mapping')) ?> --</option>
<?php foreach ($dmInpostServices as $inSvc): ?>
<option value="<?= $e((string) ($inSvc['id'] ?? '')) ?>"<?= $currentCarrier === 'inpost' && $currentAllegroId === (string) ($inSvc['id'] ?? '') ? ' selected' : '' ?>>
<?= $e((string) ($inSvc['name'] ?? '')) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php // Empty state ?>
<div class="dm-empty-panel muted" style="<?= $currentCarrier !== '' ? 'display:none' : ($currentAllegroId !== '' ? 'display:none' : '') ?>">
<?= $e($t('settings.allegro.delivery.fields.select_carrier_first')) ?>
</div>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="form-actions mt-12">
<button type="submit" class="btn btn--primary"><?= $e($t('settings.allegro.delivery.actions.save')) ?></button>
</div>
</form>
<?php endif; ?>
</section>
</div>
</section>
<script>
@@ -293,9 +416,29 @@ $orderproStatuses = is_array($orderproStatuses ?? null) ? $orderproStatuses : []
return;
}
var tabNameMap = {
'allegro-tab-integration': 'integration',
'allegro-tab-statuses': 'statuses',
'allegro-tab-settings': 'settings',
'allegro-tab-delivery': 'delivery'
};
tabs.forEach(function (tab) {
tab.addEventListener('click', function () {
var target = tab.getAttribute('data-tab-target');
var tabName = tabNameMap[target] || 'integration';
var url = new URL(window.location.href);
var currentTab = url.searchParams.get('tab') || 'integration';
url.searchParams.set('tab', tabName);
// Tabs that need server data require a full reload
if (tabName === 'delivery' && currentTab !== 'delivery') {
window.location.href = url.toString();
return;
}
window.history.replaceState(null, '', url.toString());
tabs.forEach(function (node) { node.classList.remove('is-active'); });
panels.forEach(function (panel) { panel.classList.remove('is-active'); });
tab.classList.add('is-active');
@@ -306,4 +449,128 @@ $orderproStatuses = is_array($orderproStatuses ?? null) ? $orderproStatuses : []
});
});
})();
(function () {
var envSelect = document.getElementById('allegro-env-select');
if (!envSelect) return;
envSelect.addEventListener('change', function () {
window.location.href = '/settings/integrations/allegro?env=' + encodeURIComponent(envSelect.value);
});
})();
(function () {
// Carrier switching logic
document.querySelectorAll('.dm-carrier-select').forEach(function (carrierSelect) {
var rowIdx = carrierSelect.getAttribute('data-row');
var serviceWrap = document.querySelector('.dm-service-wrap[data-row="' + rowIdx + '"]');
if (!serviceWrap) return;
var allegroPanel = serviceWrap.querySelector('.dm-allegro-panel');
var inpostPanel = serviceWrap.querySelector('.dm-inpost-panel');
var emptyPanel = serviceWrap.querySelector('.dm-empty-panel');
var hiddenMethodId = serviceWrap.querySelector('.dm-hidden-method-id');
var hiddenCredentialsId = serviceWrap.querySelector('.dm-hidden-credentials-id');
var hiddenCarrierId = serviceWrap.querySelector('.dm-hidden-carrier-id');
var hiddenServiceName = serviceWrap.querySelector('.dm-hidden-service-name');
function showPanel(carrier) {
if (allegroPanel) allegroPanel.style.display = carrier === 'allegro' ? '' : 'none';
if (inpostPanel) inpostPanel.style.display = carrier === 'inpost' ? '' : 'none';
if (emptyPanel) emptyPanel.style.display = carrier === '' ? '' : 'none';
}
carrierSelect.addEventListener('change', function () {
var carrier = carrierSelect.value;
showPanel(carrier);
// Clear hidden values when switching carrier
if (hiddenMethodId) hiddenMethodId.value = '';
if (hiddenCredentialsId) hiddenCredentialsId.value = '';
if (hiddenCarrierId) hiddenCarrierId.value = '';
if (hiddenServiceName) hiddenServiceName.value = '';
// Reset Allegro search input
var allegroInput = allegroPanel ? allegroPanel.querySelector('.dm-search-input') : null;
if (allegroInput) allegroInput.value = '';
// Reset InPost select
var inpostSelect = inpostPanel ? inpostPanel.querySelector('.dm-inpost-select') : null;
if (inpostSelect) inpostSelect.value = '';
});
// InPost select change -> update hidden fields
var inpostSelect = inpostPanel ? inpostPanel.querySelector('.dm-inpost-select') : null;
if (inpostSelect) {
inpostSelect.addEventListener('change', function () {
var opt = inpostSelect.options[inpostSelect.selectedIndex];
if (hiddenMethodId) hiddenMethodId.value = inpostSelect.value;
if (hiddenCredentialsId) hiddenCredentialsId.value = '';
if (hiddenCarrierId) hiddenCarrierId.value = '';
if (hiddenServiceName) hiddenServiceName.value = opt ? opt.textContent.trim() : '';
});
}
});
// Allegro searchable selects
document.querySelectorAll('.dm-searchable-select').forEach(function (wrapper) {
var searchInput = wrapper.querySelector('.dm-search-input');
var dropdown = wrapper.querySelector('.dm-dropdown');
var serviceWrap = wrapper.closest('.dm-service-wrap');
if (!searchInput || !dropdown || !serviceWrap) return;
var hiddenMethodId = serviceWrap.querySelector('.dm-hidden-method-id');
var hiddenCredentialsId = serviceWrap.querySelector('.dm-hidden-credentials-id');
var hiddenCarrierId = serviceWrap.querySelector('.dm-hidden-carrier-id');
var hiddenServiceName = serviceWrap.querySelector('.dm-hidden-service-name');
var options = dropdown.querySelectorAll('.searchable-select__option');
wrapper.style.position = 'relative';
function selectOption(opt) {
hiddenMethodId.value = opt.getAttribute('data-value') || '';
hiddenCredentialsId.value = opt.getAttribute('data-credentials-id') || '';
hiddenCarrierId.value = opt.getAttribute('data-carrier-id') || '';
hiddenServiceName.value = opt.getAttribute('data-label') || '';
searchInput.value = opt.getAttribute('data-label') || '';
dropdown.classList.remove('is-open');
options.forEach(function (o) { o.classList.remove('is-selected'); });
opt.classList.add('is-selected');
}
function filterOptions(query) {
var q = query.toLowerCase().trim();
options.forEach(function (opt) {
var label = (opt.getAttribute('data-label') || '').toLowerCase();
opt.style.display = (q === '' || label.indexOf(q) !== -1) ? '' : 'none';
});
}
searchInput.addEventListener('focus', function () {
filterOptions(searchInput.value);
dropdown.classList.add('is-open');
});
searchInput.addEventListener('input', function () {
filterOptions(searchInput.value);
dropdown.classList.add('is-open');
});
options.forEach(function (opt) {
opt.addEventListener('mousedown', function (e) {
e.preventDefault();
selectOption(opt);
});
});
searchInput.addEventListener('blur', function () {
setTimeout(function () { dropdown.classList.remove('is-open'); }, 150);
});
var currentId = wrapper.getAttribute('data-current-id') || '';
if (currentId !== '') {
options.forEach(function (opt) {
if (opt.getAttribute('data-value') === currentId) {
opt.classList.add('is-selected');
}
});
}
});
})();
</script>

View File

@@ -0,0 +1,34 @@
<?php
$integration = is_array($settings ?? null) ? $settings : [];
$hasApiKey = (bool) ($integration['has_api_key'] ?? false);
?>
<section class="card">
<h2 class="section-title"><?= $e($t('settings.apaczka.title')) ?></h2>
<p class="muted mt-12"><?= $e($t('settings.apaczka.description')) ?></p>
<?php if (!empty($errorMessage)): ?>
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $errorMessage) ?></div>
<?php endif; ?>
<?php if (!empty($successMessage)): ?>
<div class="alert alert--success mt-12" role="status"><?= $e((string) $successMessage) ?></div>
<?php endif; ?>
</section>
<section class="card mt-16">
<h3 class="section-title"><?= $e($t('settings.apaczka.config.title')) ?></h3>
<form class="statuses-form mt-16" action="/settings/integrations/apaczka/save" method="post" novalidate>
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
<label class="form-field">
<span class="field-label"><?= $e($t('settings.apaczka.fields.api_key')) ?></span>
<input class="form-control" type="password" name="api_key" autocomplete="new-password">
<span class="muted"><?= $e($hasApiKey ? $t('settings.apaczka.api_key.saved') : $t('settings.apaczka.api_key.missing')) ?></span>
</label>
<div class="form-actions">
<button type="submit" class="btn btn--primary"><?= $e($t('settings.apaczka.actions.save')) ?></button>
</div>
</form>
</section>

View File

@@ -0,0 +1,116 @@
<?php
$s = is_array($settings ?? null) ? $settings : [];
?>
<section class="card">
<h2 class="section-title"><?= $e($t('settings.company.title')) ?></h2>
<p class="muted mt-12"><?= $e($t('settings.company.description')) ?></p>
<?php if (!empty($errorMessage)): ?>
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $errorMessage) ?></div>
<?php endif; ?>
<?php if (!empty($successMessage)): ?>
<div class="alert alert--success mt-12" role="status"><?= $e((string) $successMessage) ?></div>
<?php endif; ?>
</section>
<section class="card mt-16">
<form action="/settings/company/save" method="post" novalidate>
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
<h3 class="section-title"><?= $e($t('settings.company.section_address')) ?></h3>
<div class="form-grid-2 mt-12">
<label class="form-field">
<span class="field-label"><?= $e($t('settings.company.fields.company_name')) ?></span>
<input class="form-control" type="text" name="company_name" maxlength="200" value="<?= $e((string) ($s['company_name'] ?? '')) ?>">
</label>
<label class="form-field">
<span class="field-label"><?= $e($t('settings.company.fields.person_name')) ?></span>
<input class="form-control" type="text" name="person_name" maxlength="200" value="<?= $e((string) ($s['person_name'] ?? '')) ?>">
</label>
</div>
<label class="form-field">
<span class="field-label"><?= $e($t('settings.company.fields.street')) ?></span>
<input class="form-control" type="text" name="street" maxlength="200" value="<?= $e((string) ($s['street'] ?? '')) ?>">
</label>
<div class="form-grid-3 mt-0">
<label class="form-field">
<span class="field-label"><?= $e($t('settings.company.fields.postal_code')) ?></span>
<input class="form-control" type="text" name="postal_code" maxlength="16" value="<?= $e((string) ($s['postal_code'] ?? '')) ?>">
</label>
<label class="form-field">
<span class="field-label"><?= $e($t('settings.company.fields.city')) ?></span>
<input class="form-control" type="text" name="city" maxlength="128" value="<?= $e((string) ($s['city'] ?? '')) ?>">
</label>
<label class="form-field">
<span class="field-label"><?= $e($t('settings.company.fields.country_code')) ?></span>
<input class="form-control" type="text" name="country_code" maxlength="2" value="<?= $e((string) ($s['country_code'] ?? 'PL')) ?>">
</label>
</div>
<div class="form-grid-2">
<label class="form-field">
<span class="field-label"><?= $e($t('settings.company.fields.phone')) ?></span>
<input class="form-control" type="tel" name="phone" maxlength="64" value="<?= $e((string) ($s['phone'] ?? '')) ?>">
</label>
<label class="form-field">
<span class="field-label"><?= $e($t('settings.company.fields.email')) ?></span>
<input class="form-control" type="email" name="email" maxlength="128" value="<?= $e((string) ($s['email'] ?? '')) ?>">
</label>
</div>
<label class="form-field">
<span class="field-label"><?= $e($t('settings.company.fields.tax_number')) ?></span>
<input class="form-control" type="text" name="tax_number" maxlength="64" value="<?= $e((string) ($s['tax_number'] ?? '')) ?>">
</label>
<h3 class="section-title mt-16"><?= $e($t('settings.company.section_bank')) ?></h3>
<div class="form-grid-2 mt-12">
<label class="form-field">
<span class="field-label"><?= $e($t('settings.company.fields.bank_account')) ?></span>
<input class="form-control" type="text" name="bank_account" maxlength="64" value="<?= $e((string) ($s['bank_account'] ?? '')) ?>">
</label>
<label class="form-field">
<span class="field-label"><?= $e($t('settings.company.fields.bank_owner_name')) ?></span>
<input class="form-control" type="text" name="bank_owner_name" maxlength="200" value="<?= $e((string) ($s['bank_owner_name'] ?? '')) ?>">
</label>
</div>
<h3 class="section-title mt-16"><?= $e($t('settings.company.section_defaults')) ?></h3>
<div class="form-grid-4 mt-12">
<label class="form-field">
<span class="field-label"><?= $e($t('settings.company.fields.length_cm')) ?></span>
<input class="form-control" type="number" name="default_package_length_cm" step="0.1" min="0.1" value="<?= $e((string) ($s['default_package_length_cm'] ?? '25')) ?>">
</label>
<label class="form-field">
<span class="field-label"><?= $e($t('settings.company.fields.width_cm')) ?></span>
<input class="form-control" type="number" name="default_package_width_cm" step="0.1" min="0.1" value="<?= $e((string) ($s['default_package_width_cm'] ?? '20')) ?>">
</label>
<label class="form-field">
<span class="field-label"><?= $e($t('settings.company.fields.height_cm')) ?></span>
<input class="form-control" type="number" name="default_package_height_cm" step="0.1" min="0.1" value="<?= $e((string) ($s['default_package_height_cm'] ?? '8')) ?>">
</label>
<label class="form-field">
<span class="field-label"><?= $e($t('settings.company.fields.weight_kg')) ?></span>
<input class="form-control" type="number" name="default_package_weight_kg" step="0.001" min="0.001" value="<?= $e((string) ($s['default_package_weight_kg'] ?? '1')) ?>">
</label>
</div>
<label class="form-field">
<span class="field-label"><?= $e($t('settings.company.fields.label_format')) ?></span>
<select class="form-control" name="default_label_format">
<option value="PDF"<?= ((string) ($s['default_label_format'] ?? 'PDF')) === 'PDF' ? ' selected' : '' ?>>PDF</option>
<option value="ZPL"<?= ((string) ($s['default_label_format'] ?? 'PDF')) === 'ZPL' ? ' selected' : '' ?>>ZPL</option>
</select>
</label>
<div class="form-actions mt-16">
<button type="submit" class="btn btn--primary"><?= $e($t('settings.company.actions.save')) ?></button>
</div>
</form>
</section>

View File

@@ -0,0 +1,133 @@
<?php
$s = is_array($settings ?? null) ? $settings : [];
$hasToken = (bool) ($s['has_api_token'] ?? false);
$env = (string) ($s['environment'] ?? 'sandbox');
$dispatchMethod = (string) ($s['default_dispatch_method'] ?? 'pop');
$lockerSize = (string) ($s['default_locker_size'] ?? 'small');
$labelFormat = (string) ($s['label_format'] ?? 'Pdf');
?>
<section class="card">
<h2 class="section-title"><?= $e($t('settings.inpost.title')) ?></h2>
<p class="muted mt-12"><?= $e($t('settings.inpost.description')) ?></p>
<?php if (!empty($errorMessage)): ?>
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $errorMessage) ?></div>
<?php endif; ?>
<?php if (!empty($successMessage)): ?>
<div class="alert alert--success mt-12" role="status"><?= $e((string) $successMessage) ?></div>
<?php endif; ?>
</section>
<section class="card mt-16">
<h3 class="section-title"><?= $e($t('settings.inpost.config.title')) ?></h3>
<form action="/settings/integrations/inpost/save" method="post" novalidate>
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
<label class="form-field mt-16">
<span class="field-label"><?= $e($t('settings.inpost.fields.api_token')) ?></span>
<input class="form-control" type="password" name="api_token" autocomplete="new-password">
<span class="muted"><?= $e($hasToken ? $t('settings.inpost.api_token.saved') : $t('settings.inpost.api_token.missing')) ?></span>
</label>
<label class="form-field">
<span class="field-label"><?= $e($t('settings.inpost.fields.organization_id')) ?></span>
<input class="form-control" type="text" name="organization_id" value="<?= $e((string) ($s['organization_id'] ?? '')) ?>">
</label>
<label class="form-field">
<span class="field-label"><?= $e($t('settings.inpost.fields.environment')) ?></span>
<select class="form-control" name="environment">
<option value="sandbox"<?= $env === 'sandbox' ? ' selected' : '' ?>>Sandbox</option>
<option value="production"<?= $env === 'production' ? ' selected' : '' ?>>Production</option>
</select>
</label>
<h4 class="section-title mt-16"><?= $e($t('settings.inpost.sections.dispatch')) ?></h4>
<label class="form-field mt-12">
<span class="field-label"><?= $e($t('settings.inpost.fields.default_dispatch_method')) ?></span>
<select class="form-control" name="default_dispatch_method">
<option value="pop"<?= $dispatchMethod === 'pop' ? ' selected' : '' ?>><?= $e($t('settings.inpost.dispatch_methods.pop')) ?></option>
<option value="parcel_locker"<?= $dispatchMethod === 'parcel_locker' ? ' selected' : '' ?>><?= $e($t('settings.inpost.dispatch_methods.parcel_locker')) ?></option>
<option value="courier"<?= $dispatchMethod === 'courier' ? ' selected' : '' ?>><?= $e($t('settings.inpost.dispatch_methods.courier')) ?></option>
</select>
</label>
<label class="form-field">
<span class="field-label"><?= $e($t('settings.inpost.fields.default_dispatch_point')) ?></span>
<input class="form-control" type="text" name="default_dispatch_point" value="<?= $e((string) ($s['default_dispatch_point'] ?? '')) ?>" placeholder="np. RZE14N">
</label>
<h4 class="section-title mt-16"><?= $e($t('settings.inpost.sections.locker')) ?></h4>
<label class="form-field mt-12">
<span class="field-label"><?= $e($t('settings.inpost.fields.default_locker_size')) ?></span>
<select class="form-control" name="default_locker_size">
<option value="small"<?= $lockerSize === 'small' ? ' selected' : '' ?>>A (8 x 38 x 64 cm)</option>
<option value="medium"<?= $lockerSize === 'medium' ? ' selected' : '' ?>>B (19 x 38 x 64 cm)</option>
<option value="large"<?= $lockerSize === 'large' ? ' selected' : '' ?>>C (41 x 38 x 64 cm)</option>
</select>
</label>
<label class="form-field">
<span class="field-label"><?= $e($t('settings.inpost.fields.default_insurance')) ?></span>
<input class="form-control" type="number" name="default_insurance" step="0.01" min="0" value="<?= $e($s['default_insurance'] !== null ? (string) $s['default_insurance'] : '') ?>" placeholder="<?= $e($t('settings.inpost.fields.insurance_placeholder')) ?>">
</label>
<h4 class="section-title mt-16"><?= $e($t('settings.inpost.sections.courier')) ?></h4>
<div class="form-grid-3 mt-12">
<label class="form-field">
<span class="field-label"><?= $e($t('settings.inpost.fields.courier_length')) ?></span>
<input class="form-control" type="number" name="default_courier_length" min="1" value="<?= $e((string) ($s['default_courier_length'] ?? 20)) ?>">
<span class="muted">cm</span>
</label>
<label class="form-field">
<span class="field-label"><?= $e($t('settings.inpost.fields.courier_width')) ?></span>
<input class="form-control" type="number" name="default_courier_width" min="1" value="<?= $e((string) ($s['default_courier_width'] ?? 15)) ?>">
<span class="muted">cm</span>
</label>
<label class="form-field">
<span class="field-label"><?= $e($t('settings.inpost.fields.courier_height')) ?></span>
<input class="form-control" type="number" name="default_courier_height" min="1" value="<?= $e((string) ($s['default_courier_height'] ?? 8)) ?>">
<span class="muted">cm</span>
</label>
</div>
<h4 class="section-title mt-16"><?= $e($t('settings.inpost.sections.other')) ?></h4>
<label class="form-field mt-12">
<span class="field-label"><?= $e($t('settings.inpost.fields.label_format')) ?></span>
<select class="form-control" name="label_format">
<option value="Pdf"<?= $labelFormat === 'Pdf' ? ' selected' : '' ?>>PDF A6</option>
<option value="Zpl"<?= $labelFormat === 'Zpl' ? ' selected' : '' ?>>ZPL</option>
<option value="Epl"<?= $labelFormat === 'Epl' ? ' selected' : '' ?>>EPL</option>
</select>
</label>
<div class="mt-12">
<label class="form-field form-field--inline">
<input type="checkbox" name="weekend_delivery" value="1"<?= !empty($s['weekend_delivery']) ? ' checked' : '' ?>>
<span class="field-label"><?= $e($t('settings.inpost.fields.weekend_delivery')) ?></span>
</label>
<label class="form-field form-field--inline">
<input type="checkbox" name="auto_insurance_value" value="1"<?= !empty($s['auto_insurance_value']) ? ' checked' : '' ?>>
<span class="field-label"><?= $e($t('settings.inpost.fields.auto_insurance_value')) ?></span>
</label>
<label class="form-field form-field--inline">
<input type="checkbox" name="multi_parcel" value="1"<?= !empty($s['multi_parcel']) ? ' checked' : '' ?>>
<span class="field-label"><?= $e($t('settings.inpost.fields.multi_parcel')) ?></span>
</label>
</div>
<div class="form-actions mt-16">
<button type="submit" class="btn btn--primary"><?= $e($t('settings.inpost.actions.save')) ?></button>
</div>
</form>
</section>

View File

@@ -0,0 +1,627 @@
<?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 : [];
$packages = is_array($existingPackages ?? null) ? $existingPackages : [];
$servicesError = (string) ($deliveryServicesError ?? '');
$flashSuccessMsg = (string) ($flashSuccess ?? '');
$flashErrorMsg = (string) ($flashError ?? '');
$mapping = is_array($deliveryMapping ?? null) ? $deliveryMapping : [];
$mappedMethodId = trim((string) ($mapping['allegro_delivery_method_id'] ?? ''));
$mappedCredentialsId = trim((string) ($mapping['allegro_credentials_id'] ?? ''));
$mappedCarrierId = trim((string) ($mapping['allegro_carrier_id'] ?? ''));
$mappedCarrier = trim((string) ($mapping['carrier'] ?? ''));
$mappedServiceName = trim((string) ($mapping['allegro_service_name'] ?? ''));
$deliveryMethodId = $mappedCarrier === 'allegro' && $mappedMethodId !== ''
? $mappedMethodId
: ($mappedCarrier !== 'inpost' ? 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')));
?>
<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>
<?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>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><?= $e($pkgTracking !== '' ? $pkgTracking : '-') ?></td>
<td><?= $e($pkgCarrier !== '' ? $pkgCarrier : '-') ?></td>
<td>
<?php if ($pkgLabelPath !== ''): ?>
<form method="post" action="/orders/<?= $e((string) ($orderId ?? 0)) ?>/shipment/<?= $e((string) $pkgId) ?>/label" style="display:inline">
<input type="hidden" name="_csrf_token" value="<?= $e($csrfToken ?? '') ?>">
<button type="submit" class="btn btn--sm btn--secondary">Pobierz</button>
</form>
<?php elseif ($pkgShipmentId !== '' && $pkgStatus === 'created'): ?>
<form method="post" action="/orders/<?= $e((string) ($orderId ?? 0)) ?>/shipment/<?= $e((string) $pkgId) ?>/label" style="display:inline">
<input type="hidden" name="_csrf_token" value="<?= $e($csrfToken ?? '') ?>">
<button type="submit" class="btn btn--sm btn--primary">Generuj etykiete</button>
</form>
<?php else: ?>
-
<?php endif; ?>
</td>
<td class="text-nowrap"><?= $e((string) ($pkg['created_at'] ?? '')) ?></td>
<td>
<?php if ($pkgStatus === 'pending'): ?>
<button type="button" class="btn btn--sm btn--secondary" data-check-status="<?= $e((string) $pkgId) ?>" data-order-id="<?= $e((string) ($orderId ?? 0)) ?>">Sprawdz status</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</section>
<?php endif; ?>
<form method="post" action="/orders/<?= $e((string) ($orderId ?? 0)) ?>/shipment/create" novalidate>
<input type="hidden" name="_csrf_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>
</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' : 'Allegro') ?>: <?= $e($mappedServiceName) ?><?php endif; ?></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' : '' ?>">
<select class="form-control" id="shipment-inpost-select">
<option value="">-- Wybierz usluge InPost --</option>
<?php foreach ($inpostSvcList as $inSvc): ?>
<option value="<?= $e((string) ($inSvc['id'] ?? '')) ?>"<?= $mappedCarrier === 'inpost' && $mappedMethodId === (string) ($inSvc['id'] ?? '') ? ' selected' : '' ?>>
<?= $e((string) ($inSvc['name'] ?? '')) ?>
</option>
<?php endforeach; ?>
</select>
</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="">
<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>
<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>
<script>
(function () {
// ── Generic searchable select enhancer ──
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;
}
// ── Enhance all native selects on the page ──
var carrierSelect = document.getElementById('shipment-carrier-select');
var inpostSelect = document.getElementById('shipment-inpost-select');
document.querySelectorAll('form select.form-control').forEach(function (sel) {
enhanceSelect(sel);
});
// ── Carrier / service panel logic ──
var allegroPanel = document.getElementById('shipment-allegro-panel');
var inpostPanel = document.getElementById('shipment-inpost-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');
if (!carrierSelect || !hiddenInput) return;
var allegroOpts = dropdown ? dropdown.querySelectorAll('.searchable-select__option') : [];
function clearHiddenFields() {
hiddenInput.value = '';
credentialsInput.value = '';
carrierInput.value = '';
}
function showPanel(carrier) {
allegroPanel.style.display = carrier === 'allegro' ? '' : 'none';
inpostPanel.style.display = carrier === 'inpost' ? '' : 'none';
emptyPanel.style.display = carrier === '' ? '' : 'none';
}
// --- Carrier select ---
carrierSelect.addEventListener('change', function () {
clearHiddenFields();
if (searchInput) searchInput.value = '';
if (inpostSelect) {
inpostSelect.selectedIndex = 0;
if (inpostSelect._syncTrigger) inpostSelect._syncTrigger();
}
allegroOpts.forEach(function (o) { o.classList.remove('is-selected'); });
showPanel(carrierSelect.value);
});
// --- InPost select ---
if (inpostSelect) {
inpostSelect.addEventListener('change', function () {
hiddenInput.value = inpostSelect.value;
credentialsInput.value = '';
carrierInput.value = '';
});
if (carrierSelect.value === 'inpost' && inpostSelect.value !== '') {
hiddenInput.value = inpostSelect.value;
}
}
// --- Allegro searchable select ---
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') || '';
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);
}
});
}
}
}
// --- Check status ---
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 === 'created') {
window.location.reload();
} else if (data.status === 'error') {
if (btn) {
btn.textContent = 'Blad: ' + (data.error || '');
btn.disabled = false;
}
} else if (attempt < 10) {
var delay = Math.min(2000 * Math.pow(1.5, attempt), 15000);
setTimeout(function () {
checkPackageStatus(pkgId, oId, btn, attempt + 1);
}, delay);
if (btn) btn.textContent = 'Sprawdzam... (' + (attempt + 1) + ')';
} else {
if (btn) {
btn.textContent = 'W toku... Sprobuj ponownie';
btn.disabled = false;
}
}
})
.catch(function () {
if (btn) {
btn.textContent = 'Blad sieci';
btn.disabled = false;
}
});
}
// Manual check buttons
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
);
});
});
// Auto-poll pending packages on page load
var params = new URLSearchParams(window.location.search);
var autoCheckId = params.get('check');
if (autoCheckId) {
var autoBtn = document.querySelector('[data-check-status="' + autoCheckId + '"]');
var autoOrderId = autoBtn ? autoBtn.getAttribute('data-order-id') : params.get('id');
if (autoOrderId) {
checkPackageStatus(autoCheckId, autoOrderId, autoBtn, 0);
}
}
})();
</script>