Add Orders and Order Status repositories with pagination and management features
- Implemented OrdersRepository for handling order data with pagination, filtering, and sorting capabilities. - Added methods for retrieving order status options, quick stats, and detailed order information. - Created OrderStatusRepository for managing order status groups and statuses, including CRUD operations and sorting. - Introduced a bootstrap file for test environment setup and autoloading.
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
<section class="card">
|
||||
<h1><?= $e($t('dashboard.title')) ?></h1>
|
||||
<p class="muted"><?= $e($t('dashboard.description')) ?></p>
|
||||
<?php if (!empty($user['email'])): ?>
|
||||
<p><?= $e($t('dashboard.active_user_label')) ?> <span class="accent"><?= $e($user['email']) ?></span></p>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php $rows = is_array($integrations ?? null) ? $integrations : []; ?>
|
||||
|
||||
<section class="card">
|
||||
<h1><?= $e($t('marketplace.title')) ?></h1>
|
||||
<p class="muted"><?= $e($t('marketplace.description')) ?></p>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<h2 class="section-title"><?= $e($t('marketplace.integrations_title')) ?></h2>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $errorMessage) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($rows === []): ?>
|
||||
<p class="muted mt-12"><?= $e($t('marketplace.empty_integrations')) ?></p>
|
||||
<?php else: ?>
|
||||
<div class="table-wrap mt-12">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th><?= $e($t('marketplace.fields.integration')) ?></th>
|
||||
<th><?= $e($t('marketplace.fields.linked_offers_count')) ?></th>
|
||||
<th><?= $e($t('marketplace.fields.actions')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $row): ?>
|
||||
<?php $integrationId = (int) ($row['id'] ?? 0); ?>
|
||||
<tr>
|
||||
<td><?= $e((string) $integrationId) ?></td>
|
||||
<td><?= $e((string) ($row['name'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ((int) ($row['linked_offers_count'] ?? 0))) ?></td>
|
||||
<td>
|
||||
<a class="btn btn--secondary" href="/marketplace/<?= $e((string) $integrationId) ?>">
|
||||
<?= $e($t('marketplace.actions.open_offers')) ?>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,406 @@
|
||||
<?php $integrationData = is_array($integration ?? null) ? $integration : []; ?>
|
||||
<?php $rows = is_array($offers ?? null) ? $offers : []; ?>
|
||||
<?php $integrationId = (int) ($integrationData['id'] ?? 0); ?>
|
||||
<?php $filters = is_array($filters ?? null) ? $filters : []; ?>
|
||||
<?php $channelOptions = is_array($channelOptions ?? null) ? $channelOptions : []; ?>
|
||||
<?php $pagination = is_array($pagination ?? null) ? $pagination : []; ?>
|
||||
<?php
|
||||
$currentSort = (string) ($filters['sort'] ?? 'updated_at');
|
||||
$currentDir = strtoupper((string) ($filters['sort_dir'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC';
|
||||
$page = max(1, (int) ($pagination['page'] ?? 1));
|
||||
$totalPages = max(1, (int) ($pagination['total_pages'] ?? 1));
|
||||
$total = max(0, (int) ($pagination['total'] ?? count($rows)));
|
||||
$perPage = max(1, (int) ($pagination['per_page'] ?? 20));
|
||||
$buildUrl = static function (array $params = []) use ($integrationId, $filters): string {
|
||||
$merged = array_merge($filters, $params);
|
||||
foreach ($merged as $key => $value) {
|
||||
if ($value === '' || $value === null) {
|
||||
unset($merged[$key]);
|
||||
}
|
||||
}
|
||||
$query = http_build_query($merged);
|
||||
$base = '/marketplace/' . $integrationId;
|
||||
return $query !== '' ? ($base . '?' . $query) : $base;
|
||||
};
|
||||
?>
|
||||
|
||||
<section class="card">
|
||||
<h1><?= $e($t('marketplace.offers_title', ['name' => (string) ($integrationData['name'] ?? '')])) ?></h1>
|
||||
<p class="muted"><?= $e($t('marketplace.offers_description')) ?></p>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<a class="btn btn--secondary" href="/marketplace"><?= $e($t('marketplace.actions.back_to_marketplace')) ?></a>
|
||||
|
||||
<?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; ?>
|
||||
|
||||
<form method="get" action="/marketplace/<?= $e((string) $integrationId) ?>" class="table-list-filters mt-12">
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.filters.search')) ?></span>
|
||||
<input class="form-control" type="text" name="search" value="<?= $e((string) ($filters['search'] ?? '')) ?>" placeholder="Oferta, SKU, EAN, external ID">
|
||||
</label>
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('marketplace.fields.channel')) ?></span>
|
||||
<select class="form-control" name="channel">
|
||||
<option value=""><?= $e($t('products.filters.any')) ?></option>
|
||||
<?php foreach ($channelOptions as $channelName): ?>
|
||||
<option value="<?= $e((string) $channelName) ?>"<?= (string) ($filters['channel'] ?? '') === (string) $channelName ? ' selected' : '' ?>>
|
||||
<?= $e((string) $channelName) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.filters.per_page')) ?></span>
|
||||
<select class="form-control" name="per_page">
|
||||
<?php foreach ([10, 20, 50, 100] as $opt): ?>
|
||||
<option value="<?= $e((string) $opt) ?>"<?= $perPage === $opt ? ' selected' : '' ?>><?= $e((string) $opt) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<input type="hidden" name="sort" value="<?= $e((string) ($filters['sort'] ?? 'updated_at')) ?>">
|
||||
<input type="hidden" name="sort_dir" value="<?= $e((string) ($filters['sort_dir'] ?? 'DESC')) ?>">
|
||||
<input type="hidden" name="page" value="1">
|
||||
<div class="filters-actions">
|
||||
<button class="btn btn--primary" type="submit"><?= $e($t('products.actions.filter')) ?></button>
|
||||
<a class="btn btn--secondary" href="/marketplace/<?= $e((string) $integrationId) ?>"><?= $e($t('products.actions.reset')) ?></a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p class="muted mt-12"><?= $e($t('products.pagination.summary', ['total' => (string) $total])) ?></p>
|
||||
|
||||
<?php if ($rows === []): ?>
|
||||
<p class="muted mt-12"><?= $e($t('marketplace.empty_offers')) ?></p>
|
||||
<?php else: ?>
|
||||
<div class="table-wrap mt-12">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a href="<?= $e($buildUrl(['sort' => 'offer_name', 'sort_dir' => ($currentSort === 'offer_name' && $currentDir === 'ASC') ? 'DESC' : 'ASC', 'page' => 1])) ?>" class="table-sort-link">
|
||||
<?= $e($t('marketplace.fields.offer_name')) ?><?= $currentSort === 'offer_name' ? ($currentDir === 'ASC' ? ' ↑' : ' ↓') : '' ?>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a href="<?= $e($buildUrl(['sort' => 'external_product_id', 'sort_dir' => ($currentSort === 'external_product_id' && $currentDir === 'ASC') ? 'DESC' : 'ASC', 'page' => 1])) ?>" class="table-sort-link">
|
||||
<?= $e($t('marketplace.fields.external_product_id')) ?><?= $currentSort === 'external_product_id' ? ($currentDir === 'ASC' ? ' ↑' : ' ↓') : '' ?>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a href="<?= $e($buildUrl(['sort' => 'external_variant_id', 'sort_dir' => ($currentSort === 'external_variant_id' && $currentDir === 'ASC') ? 'DESC' : 'ASC', 'page' => 1])) ?>" class="table-sort-link">
|
||||
<?= $e($t('marketplace.fields.external_variant_id')) ?><?= $currentSort === 'external_variant_id' ? ($currentDir === 'ASC' ? ' ↑' : ' ↓') : '' ?>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a href="<?= $e($buildUrl(['sort' => 'external_offer_id', 'sort_dir' => ($currentSort === 'external_offer_id' && $currentDir === 'ASC') ? 'DESC' : 'ASC', 'page' => 1])) ?>" class="table-sort-link">
|
||||
<?= $e($t('marketplace.fields.external_offer_id')) ?><?= $currentSort === 'external_offer_id' ? ($currentDir === 'ASC' ? ' ↑' : ' ↓') : '' ?>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a href="<?= $e($buildUrl(['sort' => 'channel_name', 'sort_dir' => ($currentSort === 'channel_name' && $currentDir === 'ASC') ? 'DESC' : 'ASC', 'page' => 1])) ?>" class="table-sort-link">
|
||||
<?= $e($t('marketplace.fields.channel')) ?><?= $currentSort === 'channel_name' ? ($currentDir === 'ASC' ? ' ↑' : ' ↓') : '' ?>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a href="<?= $e($buildUrl(['sort' => 'product_name', 'sort_dir' => ($currentSort === 'product_name' && $currentDir === 'ASC') ? 'DESC' : 'ASC', 'page' => 1])) ?>" class="table-sort-link">
|
||||
<?= $e($t('marketplace.fields.product')) ?><?= $currentSort === 'product_name' ? ($currentDir === 'ASC' ? ' ↑' : ' ↓') : '' ?>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a href="<?= $e($buildUrl(['sort' => 'product_sku', 'sort_dir' => ($currentSort === 'product_sku' && $currentDir === 'ASC') ? 'DESC' : 'ASC', 'page' => 1])) ?>" class="table-sort-link">
|
||||
SKU<?= $currentSort === 'product_sku' ? ($currentDir === 'ASC' ? ' ↑' : ' ↓') : '' ?>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a href="<?= $e($buildUrl(['sort' => 'product_ean', 'sort_dir' => ($currentSort === 'product_ean' && $currentDir === 'ASC') ? 'DESC' : 'ASC', 'page' => 1])) ?>" class="table-sort-link">
|
||||
EAN<?= $currentSort === 'product_ean' ? ($currentDir === 'ASC' ? ' ↑' : ' ↓') : '' ?>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a href="<?= $e($buildUrl(['sort' => 'updated_at', 'sort_dir' => ($currentSort === 'updated_at' && $currentDir === 'ASC') ? 'DESC' : 'ASC', 'page' => 1])) ?>" class="table-sort-link">
|
||||
<?= $e($t('marketplace.fields.updated_at')) ?><?= $currentSort === 'updated_at' ? ($currentDir === 'ASC' ? ' ↑' : ' ↓') : '' ?>
|
||||
</a>
|
||||
</th>
|
||||
<th><?= $e($t('marketplace.fields.actions')) ?></th>
|
||||
<th>Kategorie</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $row): ?>
|
||||
<?php $productId = (int) ($row['product_id'] ?? 0); ?>
|
||||
<?php $externalProductId = (int) ($row['external_product_id'] ?? 0); ?>
|
||||
<tr>
|
||||
<td><?= $e(trim((string) ($row['offer_name'] ?? '')) !== '' ? (string) ($row['offer_name'] ?? '') : '-') ?></td>
|
||||
<td><?= $e((string) ($row['external_product_id'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ($row['external_variant_id'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ($row['external_offer_id'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ($row['channel_name'] ?? '')) ?></td>
|
||||
<td>
|
||||
<a href="/products/<?= $e((string) $productId) ?>">
|
||||
<?= $e((string) ($row['product_name'] ?? '')) ?>
|
||||
</a>
|
||||
</td>
|
||||
<td><?= $e((string) ($row['product_sku'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ($row['product_ean'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ($row['updated_at'] ?? '')) ?></td>
|
||||
<td>
|
||||
<?php if ($externalProductId > 0): ?>
|
||||
<a
|
||||
class="btn btn--secondary btn--sm"
|
||||
href="/marketplace/<?= $e((string) $integrationId) ?>/product/<?= $e((string) $externalProductId) ?>/edit"
|
||||
><?= $e($t('marketplace.actions.edit_offer')) ?></a>
|
||||
<?php else: ?>
|
||||
<span class="muted">-</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn--secondary btn--sm js-assign-categories"
|
||||
data-integration-id="<?= $e((string) $integrationId) ?>"
|
||||
data-product-id="<?= $e((string) ($row['external_product_id'] ?? '')) ?>"
|
||||
>Przypisz kategorie</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="table-list__footer mt-12">
|
||||
<div class="pagination">
|
||||
<?php $startPage = max(1, $page - 2); ?>
|
||||
<?php $endPage = min($totalPages, $page + 2); ?>
|
||||
|
||||
<a class="pagination__item<?= $page <= 1 ? ' is-disabled' : '' ?>" href="<?= $e($buildUrl(['page' => 1])) ?>">«</a>
|
||||
<a class="pagination__item<?= $page <= 1 ? ' is-disabled' : '' ?>" href="<?= $e($buildUrl(['page' => max(1, $page - 1)])) ?>">‹</a>
|
||||
|
||||
<?php for ($i = $startPage; $i <= $endPage; $i++): ?>
|
||||
<a class="pagination__item<?= $i === $page ? ' is-active' : '' ?>" href="<?= $e($buildUrl(['page' => $i])) ?>">
|
||||
<?= $e((string) $i) ?>
|
||||
</a>
|
||||
<?php endfor; ?>
|
||||
|
||||
<a class="pagination__item<?= $page >= $totalPages ? ' is-disabled' : '' ?>" href="<?= $e($buildUrl(['page' => min($totalPages, $page + 1)])) ?>">›</a>
|
||||
<a class="pagination__item<?= $page >= $totalPages ? ' is-disabled' : '' ?>" href="<?= $e($buildUrl(['page' => $totalPages])) ?>">»</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<!-- Modal: przypisywanie kategorii shopPRO -->
|
||||
<div id="cat-modal-backdrop" class="jq-alert-modal-backdrop" style="display:none" aria-hidden="true">
|
||||
<div class="jq-alert-modal" role="dialog" aria-modal="true" aria-labelledby="cat-modal-title" style="max-width:520px;width:100%">
|
||||
<div class="jq-alert-modal__header">
|
||||
<h3 id="cat-modal-title">Przypisz kategorie</h3>
|
||||
</div>
|
||||
<div class="jq-alert-modal__body" style="max-height:420px;overflow-y:auto">
|
||||
<div id="cat-modal-loading">Ładowanie kategorii...</div>
|
||||
<div id="cat-modal-error" class="alert alert--danger" style="display:none"></div>
|
||||
<div id="cat-modal-tree" style="display:none"></div>
|
||||
</div>
|
||||
<div class="jq-alert-modal__footer">
|
||||
<button type="button" class="btn btn--secondary" id="cat-modal-cancel">Anuluj</button>
|
||||
<button type="button" class="btn btn--primary" id="cat-modal-save" style="display:none">Zapisz</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var CSRF = <?= json_encode($csrfToken ?? '') ?>;
|
||||
var backdrop = document.getElementById('cat-modal-backdrop');
|
||||
var treeEl = document.getElementById('cat-modal-tree');
|
||||
var loadingEl = document.getElementById('cat-modal-loading');
|
||||
var errorEl = document.getElementById('cat-modal-error');
|
||||
var saveBtn = document.getElementById('cat-modal-save');
|
||||
var cancelBtn = document.getElementById('cat-modal-cancel');
|
||||
|
||||
var state = { integrationId: 0, productId: 0, cachedCategories: null, cachedIntegrationId: 0 };
|
||||
|
||||
// Open modal on button click
|
||||
document.addEventListener('click', function (e) {
|
||||
var btn = e.target.closest('.js-assign-categories');
|
||||
if (!btn) return;
|
||||
state.integrationId = parseInt(btn.dataset.integrationId, 10) || 0;
|
||||
state.productId = parseInt(btn.dataset.productId, 10) || 0;
|
||||
if (state.integrationId <= 0 || state.productId <= 0) return;
|
||||
openModal();
|
||||
loadData();
|
||||
});
|
||||
|
||||
function openModal() {
|
||||
backdrop.style.display = '';
|
||||
backdrop.getBoundingClientRect(); // force reflow so CSS transition fires
|
||||
backdrop.setAttribute('aria-hidden', 'false');
|
||||
backdrop.classList.add('is-visible');
|
||||
loadingEl.style.display = '';
|
||||
treeEl.style.display = 'none';
|
||||
errorEl.style.display = 'none';
|
||||
saveBtn.style.display = 'none';
|
||||
treeEl.innerHTML = '';
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
backdrop.classList.remove('is-visible');
|
||||
backdrop.style.display = 'none';
|
||||
backdrop.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
|
||||
cancelBtn.addEventListener('click', closeModal);
|
||||
backdrop.addEventListener('click', function (e) { if (e.target === backdrop) closeModal(); });
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Escape' && backdrop.style.display !== 'none') closeModal();
|
||||
});
|
||||
|
||||
// Load categories + current product categories in parallel
|
||||
function loadData() {
|
||||
var iid = state.integrationId;
|
||||
var pid = state.productId;
|
||||
|
||||
var categoriesPromise;
|
||||
if (state.cachedIntegrationId === iid && state.cachedCategories !== null) {
|
||||
categoriesPromise = Promise.resolve(state.cachedCategories);
|
||||
} else {
|
||||
categoriesPromise = fetch('/marketplace/' + iid + '/categories', {
|
||||
headers: { 'Accept': 'application/json' }
|
||||
})
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (d) {
|
||||
if (!d.ok) throw new Error(d.message || 'Błąd pobierania kategorii');
|
||||
state.cachedCategories = d.categories;
|
||||
state.cachedIntegrationId = iid;
|
||||
return d.categories;
|
||||
});
|
||||
}
|
||||
|
||||
var currentPromise = fetch('/marketplace/' + iid + '/product/' + pid + '/categories', {
|
||||
headers: { 'Accept': 'application/json' }
|
||||
})
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (d) {
|
||||
if (!d.ok) throw new Error(d.message || 'Błąd pobierania kategorii produktu');
|
||||
return d.current_category_ids || [];
|
||||
});
|
||||
|
||||
Promise.all([categoriesPromise, currentPromise])
|
||||
.then(function (res) { renderTree(res[0], res[1]); })
|
||||
.catch(function (err) { showError(err.message || 'Nieznany błąd'); });
|
||||
}
|
||||
|
||||
// Build tree from flat list
|
||||
function buildTree(flat) {
|
||||
var map = {}, roots = [];
|
||||
flat.forEach(function (c) { map[c.id] = { id: c.id, parent_id: c.parent_id, title: c.title, children: [] }; });
|
||||
flat.forEach(function (c) {
|
||||
if (c.parent_id && map[c.parent_id]) {
|
||||
map[c.parent_id].children.push(map[c.id]);
|
||||
} else {
|
||||
roots.push(map[c.id]);
|
||||
}
|
||||
});
|
||||
return roots;
|
||||
}
|
||||
|
||||
function renderNode(node, checked) {
|
||||
var li = document.createElement('li');
|
||||
li.style.cssText = 'list-style:none;padding:0';
|
||||
|
||||
var row = document.createElement('div');
|
||||
row.style.cssText = 'display:flex;align-items:center;gap:6px;padding:3px 0';
|
||||
|
||||
if (node.children.length > 0) {
|
||||
var toggle = document.createElement('button');
|
||||
toggle.type = 'button';
|
||||
toggle.textContent = '▶';
|
||||
toggle.style.cssText = 'background:none;border:none;cursor:pointer;font-size:10px;padding:0 2px;color:#666';
|
||||
toggle.addEventListener('click', function () {
|
||||
var sub = li.querySelector('ul');
|
||||
if (sub) { sub.hidden = !sub.hidden; toggle.textContent = sub.hidden ? '▶' : '▼'; }
|
||||
});
|
||||
row.appendChild(toggle);
|
||||
} else {
|
||||
var sp = document.createElement('span');
|
||||
sp.style.display = 'inline-block'; sp.style.width = '16px';
|
||||
row.appendChild(sp);
|
||||
}
|
||||
|
||||
var label = document.createElement('label');
|
||||
label.style.cssText = 'display:flex;align-items:center;gap:5px;cursor:pointer';
|
||||
var cb = document.createElement('input');
|
||||
cb.type = 'checkbox'; cb.value = String(node.id);
|
||||
cb.checked = checked.indexOf(node.id) !== -1;
|
||||
label.appendChild(cb);
|
||||
label.appendChild(document.createTextNode(node.title));
|
||||
row.appendChild(label);
|
||||
li.appendChild(row);
|
||||
|
||||
if (node.children.length > 0) {
|
||||
var ul = document.createElement('ul');
|
||||
ul.style.cssText = 'padding-left:20px;margin:0';
|
||||
node.children.forEach(function (ch) { ul.appendChild(renderNode(ch, checked)); });
|
||||
li.appendChild(ul);
|
||||
}
|
||||
return li;
|
||||
}
|
||||
|
||||
function renderTree(flat, checked) {
|
||||
treeEl.innerHTML = '';
|
||||
var roots = buildTree(flat);
|
||||
var ul = document.createElement('ul');
|
||||
ul.style.cssText = 'padding:0;margin:0';
|
||||
if (roots.length === 0) {
|
||||
treeEl.textContent = 'Brak dostępnych kategorii.';
|
||||
} else {
|
||||
roots.forEach(function (r) { ul.appendChild(renderNode(r, checked)); });
|
||||
treeEl.appendChild(ul);
|
||||
}
|
||||
loadingEl.style.display = 'none';
|
||||
treeEl.style.display = '';
|
||||
saveBtn.style.display = '';
|
||||
}
|
||||
|
||||
function showError(msg) {
|
||||
loadingEl.style.display = 'none';
|
||||
errorEl.textContent = msg;
|
||||
errorEl.style.display = '';
|
||||
}
|
||||
|
||||
// Save
|
||||
saveBtn.addEventListener('click', function () {
|
||||
var cbs = treeEl.querySelectorAll('input[type=checkbox]:checked');
|
||||
var ids = [];
|
||||
cbs.forEach(function (cb) { var id = parseInt(cb.value, 10); if (id > 0) ids.push(id); });
|
||||
|
||||
saveBtn.disabled = true;
|
||||
saveBtn.textContent = 'Zapisuję...';
|
||||
|
||||
fetch('/marketplace/' + state.integrationId + '/product/' + state.productId + '/categories', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
||||
body: JSON.stringify({ _token: CSRF, category_ids: ids })
|
||||
})
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (d) {
|
||||
saveBtn.disabled = false; saveBtn.textContent = 'Zapisz';
|
||||
if (d.ok) {
|
||||
closeModal();
|
||||
if (window.OrderProAlerts) window.OrderProAlerts.show({ type: 'success', message: 'Kategorie zapisane.', timeout: 3000 });
|
||||
} else {
|
||||
if (window.OrderProAlerts) window.OrderProAlerts.show({ type: 'danger', message: d.message || 'Błąd zapisu.', timeout: 5000 });
|
||||
}
|
||||
})
|
||||
.catch(function (err) {
|
||||
saveBtn.disabled = false; saveBtn.textContent = 'Zapisz';
|
||||
if (window.OrderProAlerts) window.OrderProAlerts.show({ type: 'danger', message: 'Błąd sieci: ' + err.message, timeout: 5000 });
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -0,0 +1,28 @@
|
||||
<div class="orders-page">
|
||||
<section class="card orders-head">
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1><?= $e($t('orders.title')) ?></h1>
|
||||
<p class="muted"><?= $e($t('orders.description')) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<section class="card mt-16">
|
||||
<div class="alert alert--danger" role="alert">
|
||||
<?= $e((string) $errorMessage) ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($successMessage)): ?>
|
||||
<section class="card mt-16">
|
||||
<div class="alert alert--success" role="status">
|
||||
<?= $e((string) $successMessage) ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php require __DIR__ . '/../components/table-list.php'; ?>
|
||||
</div>
|
||||
@@ -0,0 +1,139 @@
|
||||
<section class="card">
|
||||
<h1><?= $e($t('products.create.title')) ?></h1>
|
||||
<p class="muted"><?= $e($t('products.create.description')) ?></p>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<?php if (!empty($errors)): ?>
|
||||
<div class="alert alert--danger" role="alert">
|
||||
<?php foreach ((array) $errors as $error): ?>
|
||||
<div><?= $e((string) $error) ?></div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form class="product-form mt-16" method="post" action="/products">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
|
||||
<div class="form-grid">
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.name')) ?></span>
|
||||
<input class="form-control" type="text" name="name" required value="<?= $e((string) ($form['name'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label">SKU</span>
|
||||
<input class="form-control" type="text" name="sku" value="<?= $e((string) ($form['sku'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label">EAN</span>
|
||||
<input class="form-control" type="text" name="ean" value="<?= $e((string) ($form['ean'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.type')) ?></span>
|
||||
<select class="form-control" name="type">
|
||||
<option value="simple"<?= (string) ($form['type'] ?? '') === 'simple' ? ' selected' : '' ?>><?= $e($t('products.type.simple')) ?></option>
|
||||
<option value="variant_parent"<?= (string) ($form['type'] ?? '') === 'variant_parent' ? ' selected' : '' ?>><?= $e($t('products.type.variant_parent')) ?></option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.status')) ?></span>
|
||||
<select class="form-control" name="status">
|
||||
<option value="1"<?= (string) ($form['status'] ?? '1') === '1' ? ' selected' : '' ?>><?= $e($t('products.status.active')) ?></option>
|
||||
<option value="0"<?= (string) ($form['status'] ?? '1') === '0' ? ' selected' : '' ?>><?= $e($t('products.status.inactive')) ?></option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.promoted')) ?></span>
|
||||
<select class="form-control" name="promoted">
|
||||
<option value="0"<?= (string) ($form['promoted'] ?? '0') === '0' ? ' selected' : '' ?>><?= $e($t('products.promoted.no')) ?></option>
|
||||
<option value="1"<?= (string) ($form['promoted'] ?? '0') === '1' ? ' selected' : '' ?>><?= $e($t('products.promoted.yes')) ?></option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.vat')) ?></span>
|
||||
<input class="form-control" type="number" step="0.01" min="0" max="100" name="vat" value="<?= $e((string) ($form['vat'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.quantity')) ?></span>
|
||||
<input class="form-control" type="number" step="0.001" min="0" name="quantity" value="<?= $e((string) ($form['quantity'] ?? '0')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.weight')) ?></span>
|
||||
<input class="form-control" type="number" step="0.001" min="0" name="weight" value="<?= $e((string) ($form['weight'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.price_input_mode')) ?></span>
|
||||
<select class="form-control" name="price_input_mode">
|
||||
<option value="brutto"<?= (string) ($form['price_input_mode'] ?? 'brutto') === 'brutto' ? ' selected' : '' ?>><?= $e($t('products.price_mode.brutto')) ?></option>
|
||||
<option value="netto"<?= (string) ($form['price_input_mode'] ?? 'brutto') === 'netto' ? ' selected' : '' ?>><?= $e($t('products.price_mode.netto')) ?></option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.price_brutto')) ?></span>
|
||||
<input class="form-control" type="number" step="0.01" min="0" name="price_brutto" value="<?= $e((string) ($form['price_brutto'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.price_netto')) ?></span>
|
||||
<input class="form-control" type="number" step="0.01" min="0" name="price_netto" value="<?= $e((string) ($form['price_netto'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.price_brutto_promo')) ?></span>
|
||||
<input class="form-control" type="number" step="0.01" min="0" name="price_brutto_promo" value="<?= $e((string) ($form['price_brutto_promo'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.price_netto_promo')) ?></span>
|
||||
<input class="form-control" type="number" step="0.01" min="0" name="price_netto_promo" value="<?= $e((string) ($form['price_netto_promo'] ?? '')) ?>">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label class="form-field mt-16">
|
||||
<span class="field-label"><?= $e($t('products.fields.short_description')) ?></span>
|
||||
<textarea class="form-control" name="short_description" rows="3"><?= $e((string) ($form['short_description'] ?? '')) ?></textarea>
|
||||
</label>
|
||||
|
||||
<label class="form-field mt-12">
|
||||
<span class="field-label"><?= $e($t('products.fields.description')) ?></span>
|
||||
<textarea class="form-control" name="description" rows="6"><?= $e((string) ($form['description'] ?? '')) ?></textarea>
|
||||
</label>
|
||||
|
||||
<div class="form-grid mt-16">
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.meta_title')) ?></span>
|
||||
<input class="form-control" type="text" name="meta_title" value="<?= $e((string) ($form['meta_title'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.meta_description')) ?></span>
|
||||
<input class="form-control" type="text" name="meta_description" value="<?= $e((string) ($form['meta_description'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.meta_keywords')) ?></span>
|
||||
<input class="form-control" type="text" name="meta_keywords" value="<?= $e((string) ($form['meta_keywords'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.seo_link')) ?></span>
|
||||
<input class="form-control" type="text" name="seo_link" value="<?= $e((string) ($form['seo_link'] ?? '')) ?>">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-actions mt-16">
|
||||
<button class="btn btn--primary" type="submit"><?= $e($t('products.actions.save')) ?></button>
|
||||
<a class="btn btn--secondary" href="/products"><?= $e($t('products.actions.back')) ?></a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -0,0 +1,626 @@
|
||||
<link href="https://cdn.quilljs.com/1.3.7/quill.snow.css" rel="stylesheet">
|
||||
<style>
|
||||
.wysiwyg-wrap { position: relative; z-index: 1; }
|
||||
.wysiwyg-wrap .ql-toolbar { border-radius: 4px 4px 0 0; border-color: var(--c-border, #d1d5db); background: #f8fafc; }
|
||||
.wysiwyg-wrap .ql-container { height: auto; border-radius: 0 0 4px 4px; border-color: var(--c-border, #d1d5db); font-size: 14px; font-family: inherit; }
|
||||
.wysiwyg-wrap .ql-editor { min-height: var(--editor-min-height, 80px); }
|
||||
</style>
|
||||
|
||||
<?php
|
||||
$integrationEditMode = (bool) ($integrationEditMode ?? false);
|
||||
$productFormAction = (string) ($productFormAction ?? '/products/update');
|
||||
$productBackUrl = (string) ($productBackUrl ?? '/products');
|
||||
?>
|
||||
|
||||
<section class="card">
|
||||
<h1><?= $e((string) ($title ?? $t('products.edit.title', ['id' => (string) ($productId ?? 0)]))) ?></h1>
|
||||
<p class="muted"><?= $e($t('products.edit.description')) ?></p>
|
||||
<?php if ($integrationEditMode): ?>
|
||||
<p class="muted mt-8">Tryb integracyjny: zapis aktualizuje bezposrednio produkt w shopPRO i synchronizuje dane lokalne.</p>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<?php if (!empty($errors)): ?>
|
||||
<div class="alert alert--danger" role="alert">
|
||||
<?php foreach ((array) $errors as $error): ?>
|
||||
<div><?= $e((string) $error) ?></div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php $images = is_array($productImages ?? null) ? $productImages : []; ?>
|
||||
<form class="product-form mt-16" method="post" action="<?= $e($productFormAction) ?>" enctype="multipart/form-data">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="id" value="<?= $e((string) ($productId ?? 0)) ?>">
|
||||
<input type="hidden" id="product-image-csrf" value="<?= $e($csrfToken ?? '') ?>">
|
||||
|
||||
<div class="form-grid">
|
||||
<div class="form-field">
|
||||
<span class="field-label">SKU</span>
|
||||
<input class="form-control" type="text" id="product-sku-input" name="sku" value="<?= $e((string) ($form['sku'] ?? '')) ?>">
|
||||
<button type="button" class="btn btn--secondary mt-12" id="product-generate-sku-btn"><?= $e($t('products.actions.generate_next_sku')) ?></button>
|
||||
</div>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label">EAN</span>
|
||||
<input class="form-control" type="text" name="ean" value="<?= $e((string) ($form['ean'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.type')) ?></span>
|
||||
<select class="form-control" name="type">
|
||||
<option value="simple"<?= (string) ($form['type'] ?? '') === 'simple' ? ' selected' : '' ?>><?= $e($t('products.type.simple')) ?></option>
|
||||
<option value="variant_parent"<?= (string) ($form['type'] ?? '') === 'variant_parent' ? ' selected' : '' ?>><?= $e($t('products.type.variant_parent')) ?></option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.status')) ?></span>
|
||||
<select class="form-control" name="status">
|
||||
<option value="1"<?= (string) ($form['status'] ?? '1') === '1' ? ' selected' : '' ?>><?= $e($t('products.status.active')) ?></option>
|
||||
<option value="0"<?= (string) ($form['status'] ?? '1') === '0' ? ' selected' : '' ?>><?= $e($t('products.status.inactive')) ?></option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.promoted')) ?></span>
|
||||
<select class="form-control" name="promoted">
|
||||
<option value="0"<?= (string) ($form['promoted'] ?? '0') === '0' ? ' selected' : '' ?>><?= $e($t('products.promoted.no')) ?></option>
|
||||
<option value="1"<?= (string) ($form['promoted'] ?? '0') === '1' ? ' selected' : '' ?>><?= $e($t('products.promoted.yes')) ?></option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.vat')) ?></span>
|
||||
<input class="form-control" type="number" step="0.01" min="0" max="100" name="vat" value="<?= $e((string) ($form['vat'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.quantity')) ?></span>
|
||||
<input class="form-control" type="number" step="0.001" min="0" name="quantity" value="<?= $e((string) ($form['quantity'] ?? '0')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.weight')) ?></span>
|
||||
<input class="form-control" type="number" step="0.001" min="0" name="weight" value="<?= $e((string) ($form['weight'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.price_input_mode')) ?></span>
|
||||
<select class="form-control" name="price_input_mode">
|
||||
<option value="brutto"<?= (string) ($form['price_input_mode'] ?? 'brutto') === 'brutto' ? ' selected' : '' ?>><?= $e($t('products.price_mode.brutto')) ?></option>
|
||||
<option value="netto"<?= (string) ($form['price_input_mode'] ?? 'brutto') === 'netto' ? ' selected' : '' ?>><?= $e($t('products.price_mode.netto')) ?></option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.price_brutto')) ?></span>
|
||||
<input class="form-control" type="number" step="0.01" min="0" name="price_brutto" value="<?= $e((string) ($form['price_brutto'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.price_netto')) ?></span>
|
||||
<input class="form-control" type="number" step="0.01" min="0" name="price_netto" value="<?= $e((string) ($form['price_netto'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.price_brutto_promo')) ?></span>
|
||||
<input class="form-control" type="number" step="0.01" min="0" name="price_brutto_promo" value="<?= $e((string) ($form['price_brutto_promo'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.price_netto_promo')) ?></span>
|
||||
<input class="form-control" type="number" step="0.01" min="0" name="price_netto_promo" value="<?= $e((string) ($form['price_netto_promo'] ?? '')) ?>">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-grid mt-16">
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.meta_title')) ?></span>
|
||||
<input class="form-control" type="text" name="meta_title" value="<?= $e((string) ($form['meta_title'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.meta_description')) ?></span>
|
||||
<input class="form-control" type="text" name="meta_description" value="<?= $e((string) ($form['meta_description'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.meta_keywords')) ?></span>
|
||||
<input class="form-control" type="text" name="meta_keywords" value="<?= $e((string) ($form['meta_keywords'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.seo_link')) ?></span>
|
||||
<input class="form-control" type="text" name="seo_link" value="<?= $e((string) ($form['seo_link'] ?? '')) ?>">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$activeIntegrations = is_array($activeIntegrations ?? null) ? $activeIntegrations : [];
|
||||
$integrationTranslationsMap = is_array($integrationTranslationsMap ?? null) ? $integrationTranslationsMap : [];
|
||||
?>
|
||||
|
||||
<div class="content-tabs-card mt-16">
|
||||
<div class="content-tabs-nav" id="content-tabs-nav">
|
||||
<button type="button" class="content-tab-btn is-active" data-tab="global">
|
||||
<?= $e($t('products.content_tabs.global')) ?>
|
||||
</button>
|
||||
<?php foreach ($activeIntegrations as $integration): ?>
|
||||
<?php $intId = (int) ($integration['id'] ?? 0); ?>
|
||||
<?php if ($intId <= 0) continue; ?>
|
||||
<button type="button" class="content-tab-btn" data-tab="integration-<?= $e((string) $intId) ?>">
|
||||
<?= $e((string) ($integration['name'] ?? '#' . $intId)) ?>
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- GLOBAL TAB -->
|
||||
<div class="content-tab-panel is-active" id="content-tab-global">
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.name')) ?> *</span>
|
||||
<input class="form-control" type="text" name="name" required value="<?= $e((string) ($form['name'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<div class="form-field mt-12">
|
||||
<span class="field-label"><?= $e($t('products.fields.short_description')) ?></span>
|
||||
<div class="wysiwyg-wrap">
|
||||
<div id="editor-short-description"></div>
|
||||
</div>
|
||||
<textarea name="short_description" id="input-short-description" style="display:none"><?= $e((string) ($form['short_description'] ?? '')) ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-field mt-12">
|
||||
<span class="field-label"><?= $e($t('products.fields.description')) ?></span>
|
||||
<div class="wysiwyg-wrap" style="--editor-min-height:180px">
|
||||
<div id="editor-description"></div>
|
||||
</div>
|
||||
<textarea name="description" id="input-description" style="display:none"><?= $e((string) ($form['description'] ?? '')) ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PER-INTEGRATION TABS -->
|
||||
<?php foreach ($activeIntegrations as $integration): ?>
|
||||
<?php
|
||||
$intId = (int) ($integration['id'] ?? 0);
|
||||
if ($intId <= 0) continue;
|
||||
$intData = $integrationTranslationsMap[$intId] ?? [];
|
||||
$intName = isset($intData['name']) ? (string) $intData['name'] : '';
|
||||
$intShort = isset($intData['short_description']) ? (string) $intData['short_description'] : '';
|
||||
$intDesc = isset($intData['description']) ? (string) $intData['description'] : '';
|
||||
?>
|
||||
<div class="content-tab-panel" id="content-tab-integration-<?= $e((string) $intId) ?>">
|
||||
<p class="muted" style="margin-bottom:8px">
|
||||
Puste pole = używana wartość globalna.
|
||||
</p>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.fields.name')) ?></span>
|
||||
<input class="form-control" type="text"
|
||||
name="integration_content[<?= $e((string) $intId) ?>][name]"
|
||||
value="<?= $e($intName) ?>">
|
||||
</label>
|
||||
|
||||
<div class="form-field mt-12">
|
||||
<span class="field-label"><?= $e($t('products.fields.short_description')) ?></span>
|
||||
<div class="wysiwyg-wrap">
|
||||
<div id="editor-int-short-<?= $e((string) $intId) ?>"></div>
|
||||
</div>
|
||||
<textarea name="integration_content[<?= $e((string) $intId) ?>][short_description]"
|
||||
id="input-int-short-<?= $e((string) $intId) ?>"
|
||||
style="display:none"><?= $e($intShort) ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-field mt-12">
|
||||
<span class="field-label"><?= $e($t('products.fields.description')) ?></span>
|
||||
<div class="wysiwyg-wrap" style="--editor-min-height:180px">
|
||||
<div id="editor-int-desc-<?= $e((string) $intId) ?>"></div>
|
||||
</div>
|
||||
<textarea name="integration_content[<?= $e((string) $intId) ?>][description]"
|
||||
id="input-int-desc-<?= $e((string) $intId) ?>"
|
||||
style="display:none"><?= $e($intDesc) ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!$integrationEditMode): ?>
|
||||
<section class="card mt-16">
|
||||
<h3><?= $e($t('products.images.title')) ?></h3>
|
||||
<p class="muted"><?= $e($t('products.images.description')) ?></p>
|
||||
|
||||
<div class="product-images-grid mt-12" id="product-images-grid" data-product-id="<?= $e((string) ($productId ?? 0)) ?>">
|
||||
<?php foreach ($images as $image): ?>
|
||||
<?php
|
||||
$imageId = (int) ($image['id'] ?? 0);
|
||||
$isMain = (int) ($image['is_main'] ?? 0) === 1;
|
||||
$publicUrl = (string) ($image['public_url'] ?? '');
|
||||
?>
|
||||
<article
|
||||
class="product-image-card<?= $isMain ? ' is-main' : '' ?>"
|
||||
data-image-id="<?= $e((string) $imageId) ?>"
|
||||
data-storage-path="<?= $e((string) ($image['storage_path'] ?? '')) ?>"
|
||||
>
|
||||
<div class="product-image-card__thumb-wrap">
|
||||
<?php if ($publicUrl !== ''): ?>
|
||||
<img class="product-image-card__thumb" src="<?= $e($publicUrl) ?>" alt="<?= $e((string) ($image['alt'] ?? '')) ?>">
|
||||
<?php else: ?>
|
||||
<div class="product-image-card__thumb is-empty">NO IMAGE</div>
|
||||
<?php endif; ?>
|
||||
<span class="product-image-card__badge"><?= $e($t('products.images.main')) ?></span>
|
||||
</div>
|
||||
<div class="product-image-card__meta"><?= $e((string) ($image['storage_path'] ?? '')) ?></div>
|
||||
<div class="product-image-card__actions">
|
||||
<button type="button" class="btn btn--secondary btn-set-main"<?= $isMain ? ' disabled' : '' ?>>
|
||||
<?= $e($t('products.images.set_main')) ?>
|
||||
</button>
|
||||
<button type="button" class="btn btn--danger btn-delete-image">
|
||||
<?= $e($t('products.images.remove')) ?>
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<p class="muted mt-12" id="product-images-empty"<?= $images === [] ? '' : ' style="display:none;"' ?>>
|
||||
<?= $e($t('products.images.empty')) ?>
|
||||
</p>
|
||||
|
||||
<label class="form-field mt-16">
|
||||
<span class="field-label"><?= $e($t('products.images.add_new')) ?></span>
|
||||
<input class="form-control" type="file" id="product-image-upload" name="new_images[]" accept=".jpg,.jpeg,.png,.webp,.gif,image/*" multiple>
|
||||
</label>
|
||||
<p class="muted" id="product-image-upload-status"></p>
|
||||
<p class="muted"><?= $e($t('products.images.main_hint')) ?></p>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="form-actions mt-16">
|
||||
<button class="btn btn--primary" type="submit"><?= $e($t('products.actions.save')) ?></button>
|
||||
<a class="btn btn--secondary" href="<?= $e($productBackUrl) ?>"><?= $e($t('products.actions.back')) ?></a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var skuInput = document.getElementById('product-sku-input');
|
||||
var generateSkuBtn = document.getElementById('product-generate-sku-btn');
|
||||
var tokenInput = document.getElementById('product-image-csrf');
|
||||
if (!skuInput || !generateSkuBtn || !tokenInput) return;
|
||||
|
||||
var csrfToken = tokenInput.value || '';
|
||||
var errTitle = <?= json_encode((string) $t('products.sku_generator.confirm_title'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
var errDefault = <?= json_encode((string) $t('products.sku_generator.failed'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
|
||||
function showError(message) {
|
||||
if (window.OrderProAlerts && typeof window.OrderProAlerts.alert === 'function') {
|
||||
window.OrderProAlerts.alert({
|
||||
title: errTitle,
|
||||
message: message || errDefault,
|
||||
danger: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var uploadStatus = document.getElementById('product-image-upload-status');
|
||||
if (uploadStatus) {
|
||||
uploadStatus.textContent = message || errDefault;
|
||||
}
|
||||
}
|
||||
|
||||
generateSkuBtn.addEventListener('click', async function() {
|
||||
generateSkuBtn.disabled = true;
|
||||
try {
|
||||
var payload = new FormData();
|
||||
payload.append('_token', csrfToken);
|
||||
|
||||
var response = await fetch('/products/next-sku', {
|
||||
method: 'POST',
|
||||
body: payload,
|
||||
credentials: 'same-origin'
|
||||
});
|
||||
var result = await response.json();
|
||||
if (!response.ok || result.ok !== true || !result.sku) {
|
||||
throw new Error(result.message || errDefault);
|
||||
}
|
||||
|
||||
skuInput.value = String(result.sku);
|
||||
} catch (error) {
|
||||
showError((error && error.message) ? error.message : errDefault);
|
||||
} finally {
|
||||
generateSkuBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var grid = document.getElementById('product-images-grid');
|
||||
var emptyState = document.getElementById('product-images-empty');
|
||||
var uploadInput = document.getElementById('product-image-upload');
|
||||
var uploadStatus = document.getElementById('product-image-upload-status');
|
||||
var tokenInput = document.getElementById('product-image-csrf');
|
||||
if (!grid || !uploadInput || !tokenInput) return;
|
||||
|
||||
var productId = grid.getAttribute('data-product-id');
|
||||
var csrfToken = tokenInput.value || '';
|
||||
var txtSetMain = <?= json_encode((string) $t('products.images.set_main'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
var txtRemove = <?= json_encode((string) $t('products.images.remove'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
var txtMain = <?= json_encode((string) $t('products.images.main'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
var txtUploadPending = <?= json_encode((string) $t('products.images.uploading'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
var txtUploadOk = <?= json_encode((string) $t('products.images.uploaded_ok'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
var txtDeleteConfirm = <?= json_encode((string) $t('products.images.confirm_delete'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
var txtConfirmTitle = <?= json_encode((string) $t('products.images.confirm_title'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
var txtConfirmYes = <?= json_encode((string) $t('products.images.confirm_yes'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
var txtConfirmNo = <?= json_encode((string) $t('products.images.confirm_no'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
|
||||
function refreshMainState(mainId) {
|
||||
var cards = grid.querySelectorAll('.product-image-card');
|
||||
cards.forEach(function(card) {
|
||||
var cardId = Number(card.getAttribute('data-image-id') || 0);
|
||||
var isMain = cardId === mainId;
|
||||
card.classList.toggle('is-main', isMain);
|
||||
var setMainBtn = card.querySelector('.btn-set-main');
|
||||
if (setMainBtn) setMainBtn.disabled = isMain;
|
||||
});
|
||||
}
|
||||
|
||||
function updateEmptyState() {
|
||||
if (!emptyState) return;
|
||||
emptyState.style.display = grid.querySelector('.product-image-card') ? 'none' : '';
|
||||
}
|
||||
|
||||
function buildCard(image) {
|
||||
var article = document.createElement('article');
|
||||
article.className = 'product-image-card' + (Number(image.is_main) === 1 ? ' is-main' : '');
|
||||
article.setAttribute('data-image-id', String(image.id));
|
||||
article.setAttribute('data-storage-path', String(image.storage_path || ''));
|
||||
|
||||
var thumbWrap = document.createElement('div');
|
||||
thumbWrap.className = 'product-image-card__thumb-wrap';
|
||||
|
||||
if (image.public_url) {
|
||||
var img = document.createElement('img');
|
||||
img.className = 'product-image-card__thumb';
|
||||
img.src = image.public_url;
|
||||
img.alt = image.alt || '';
|
||||
thumbWrap.appendChild(img);
|
||||
} else {
|
||||
var noimg = document.createElement('div');
|
||||
noimg.className = 'product-image-card__thumb is-empty';
|
||||
noimg.textContent = 'NO IMAGE';
|
||||
thumbWrap.appendChild(noimg);
|
||||
}
|
||||
|
||||
var badge = document.createElement('span');
|
||||
badge.className = 'product-image-card__badge';
|
||||
badge.textContent = txtMain;
|
||||
thumbWrap.appendChild(badge);
|
||||
|
||||
var meta = document.createElement('div');
|
||||
meta.className = 'product-image-card__meta';
|
||||
meta.textContent = image.storage_path || '';
|
||||
|
||||
var actions = document.createElement('div');
|
||||
actions.className = 'product-image-card__actions';
|
||||
|
||||
var setMainBtn = document.createElement('button');
|
||||
setMainBtn.type = 'button';
|
||||
setMainBtn.className = 'btn btn--secondary btn-set-main';
|
||||
setMainBtn.textContent = txtSetMain;
|
||||
if (Number(image.is_main) === 1) setMainBtn.disabled = true;
|
||||
|
||||
var removeBtn = document.createElement('button');
|
||||
removeBtn.type = 'button';
|
||||
removeBtn.className = 'btn btn--danger btn-delete-image';
|
||||
removeBtn.textContent = txtRemove;
|
||||
|
||||
actions.appendChild(setMainBtn);
|
||||
actions.appendChild(removeBtn);
|
||||
article.appendChild(thumbWrap);
|
||||
article.appendChild(meta);
|
||||
article.appendChild(actions);
|
||||
|
||||
return article;
|
||||
}
|
||||
|
||||
async function postForm(url, data) {
|
||||
var response = await fetch(url, { method: 'POST', body: data, credentials: 'same-origin' });
|
||||
var payload = await response.json();
|
||||
if (!response.ok || payload.ok !== true) {
|
||||
throw new Error(payload.message || 'Blad operacji.');
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
uploadInput.addEventListener('change', async function() {
|
||||
if (!uploadInput.files || uploadInput.files.length === 0) return;
|
||||
var formData = new FormData();
|
||||
formData.append('_token', csrfToken);
|
||||
formData.append('id', String(productId));
|
||||
Array.prototype.forEach.call(uploadInput.files, function(file) {
|
||||
formData.append('new_images[]', file);
|
||||
});
|
||||
|
||||
uploadStatus.textContent = txtUploadPending;
|
||||
uploadInput.disabled = true;
|
||||
try {
|
||||
var result = await postForm('/products/images/upload', formData);
|
||||
(result.images || []).forEach(function(image) {
|
||||
grid.appendChild(buildCard(image));
|
||||
if (Number(image.is_main) === 1) refreshMainState(Number(image.id));
|
||||
});
|
||||
uploadStatus.textContent = txtUploadOk;
|
||||
if (result.message) uploadStatus.textContent += ' ' + result.message;
|
||||
updateEmptyState();
|
||||
} catch (error) {
|
||||
uploadStatus.textContent = error.message || 'Blad uploadu.';
|
||||
} finally {
|
||||
uploadInput.value = '';
|
||||
uploadInput.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
grid.addEventListener('click', async function(event) {
|
||||
var target = event.target;
|
||||
if (!target || !(target instanceof HTMLElement)) return;
|
||||
var card = target.closest('.product-image-card');
|
||||
if (!card) return;
|
||||
var imageId = Number(card.getAttribute('data-image-id') || 0);
|
||||
if (imageId <= 0) return;
|
||||
|
||||
if (target.classList.contains('btn-set-main')) {
|
||||
if (target.disabled) return;
|
||||
var dataMain = new FormData();
|
||||
dataMain.append('_token', csrfToken);
|
||||
dataMain.append('id', String(productId));
|
||||
dataMain.append('image_id', String(imageId));
|
||||
|
||||
target.disabled = true;
|
||||
try {
|
||||
await postForm('/products/images/set-main', dataMain);
|
||||
refreshMainState(imageId);
|
||||
} catch (error) {
|
||||
target.disabled = false;
|
||||
if (window.OrderProAlerts && typeof window.OrderProAlerts.alert === 'function') {
|
||||
window.OrderProAlerts.alert({ title: 'Blad', message: error.message || 'Blad operacji.', danger: true });
|
||||
} else if (uploadStatus) {
|
||||
uploadStatus.textContent = error.message || 'Blad operacji.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (target.classList.contains('btn-delete-image')) {
|
||||
var confirmDelete = async function() {
|
||||
var dataDelete = new FormData();
|
||||
dataDelete.append('_token', csrfToken);
|
||||
dataDelete.append('id', String(productId));
|
||||
dataDelete.append('image_id', String(imageId));
|
||||
var result = await postForm('/products/images/delete', dataDelete);
|
||||
card.remove();
|
||||
if (Number(result.main_image_id || 0) > 0) {
|
||||
refreshMainState(Number(result.main_image_id));
|
||||
}
|
||||
updateEmptyState();
|
||||
};
|
||||
|
||||
if (window.OrderProAlerts && typeof window.OrderProAlerts.confirm === 'function') {
|
||||
var accepted = await window.OrderProAlerts.confirm({
|
||||
title: txtConfirmTitle,
|
||||
message: txtDeleteConfirm,
|
||||
confirmLabel: txtConfirmYes,
|
||||
cancelLabel: txtConfirmNo,
|
||||
danger: true
|
||||
});
|
||||
if (!accepted) return;
|
||||
try { await confirmDelete(); } catch (error) {
|
||||
window.OrderProAlerts.alert({ title: 'Blad', message: error.message || 'Blad operacji.', danger: true });
|
||||
}
|
||||
} else {
|
||||
try { await confirmDelete(); } catch (error) {
|
||||
if (uploadStatus) uploadStatus.textContent = error.message || 'Blad operacji.';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script src="https://cdn.quilljs.com/1.3.7/quill.min.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
var toolbarShort = [
|
||||
['bold', 'italic', 'underline'],
|
||||
[{ list: 'bullet' }],
|
||||
['link', 'clean']
|
||||
];
|
||||
var toolbarFull = [
|
||||
[{ header: [1, 2, 3, false] }],
|
||||
['bold', 'italic', 'underline', 'strike'],
|
||||
[{ list: 'ordered' }, { list: 'bullet' }],
|
||||
['link', 'clean']
|
||||
];
|
||||
|
||||
var shortInput = document.getElementById('input-short-description');
|
||||
var descInput = document.getElementById('input-description');
|
||||
|
||||
var quillShort = new Quill('#editor-short-description', { theme: 'snow', modules: { toolbar: toolbarShort } });
|
||||
var quillDesc = new Quill('#editor-description', { theme: 'snow', modules: { toolbar: toolbarFull } });
|
||||
|
||||
if (shortInput && shortInput.value) quillShort.clipboard.dangerouslyPasteHTML(shortInput.value);
|
||||
if (descInput && descInput.value) quillDesc.clipboard.dangerouslyPasteHTML(descInput.value);
|
||||
|
||||
// --- per-integration editors ---
|
||||
var intEditors = []; // array of {shortQuill, descQuill, shortInput, descInput}
|
||||
|
||||
document.querySelectorAll('[id^="editor-int-short-"]').forEach(function(el) {
|
||||
var suffix = el.id.replace('editor-int-short-', '');
|
||||
var shortEl = el;
|
||||
var descEl = document.getElementById('editor-int-desc-' + suffix);
|
||||
var shortInp = document.getElementById('input-int-short-' + suffix);
|
||||
var descInp = document.getElementById('input-int-desc-' + suffix);
|
||||
|
||||
if (!shortEl || !descEl || !shortInp || !descInp) return;
|
||||
|
||||
var qShort = new Quill(shortEl, { theme: 'snow', modules: { toolbar: toolbarShort } });
|
||||
var qDesc = new Quill(descEl, { theme: 'snow', modules: { toolbar: toolbarFull } });
|
||||
|
||||
if (shortInp.value) qShort.clipboard.dangerouslyPasteHTML(shortInp.value);
|
||||
if (descInp.value) qDesc.clipboard.dangerouslyPasteHTML(descInp.value);
|
||||
|
||||
intEditors.push({ shortQuill: qShort, descQuill: qDesc, shortInput: shortInp, descInput: descInp });
|
||||
});
|
||||
|
||||
var form = document.querySelector('.product-form');
|
||||
if (form) {
|
||||
form.addEventListener('submit', function() {
|
||||
if (shortInput) shortInput.value = quillShort.root.innerHTML;
|
||||
if (descInput) descInput.value = quillDesc.root.innerHTML;
|
||||
intEditors.forEach(function(e) {
|
||||
e.shortInput.value = e.shortQuill.root.innerHTML;
|
||||
e.descInput.value = e.descQuill.root.innerHTML;
|
||||
});
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var initialTab = <?= json_encode((string) ($initialContentTab ?? ''), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
var nav = document.getElementById('content-tabs-nav');
|
||||
if (!nav) return;
|
||||
|
||||
function setTab(tabId) {
|
||||
if (!tabId) return;
|
||||
var btn = nav.querySelector('.content-tab-btn[data-tab="' + tabId.replace(/"/g, '\\"') + '"]');
|
||||
if (!btn) return;
|
||||
|
||||
nav.querySelectorAll('.content-tab-btn').forEach(function(b) {
|
||||
b.classList.remove('is-active');
|
||||
});
|
||||
document.querySelectorAll('.content-tab-panel').forEach(function(p) {
|
||||
p.classList.remove('is-active');
|
||||
});
|
||||
|
||||
btn.classList.add('is-active');
|
||||
var panel = document.getElementById('content-tab-' + tabId);
|
||||
if (panel) panel.classList.add('is-active');
|
||||
}
|
||||
|
||||
nav.addEventListener('click', function(e) {
|
||||
var btn = e.target.closest('.content-tab-btn');
|
||||
if (!btn) return;
|
||||
|
||||
var tabId = btn.getAttribute('data-tab');
|
||||
if (!tabId) return;
|
||||
setTab(tabId);
|
||||
});
|
||||
|
||||
if (initialTab) {
|
||||
setTab(initialTab);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@@ -0,0 +1,293 @@
|
||||
<section class="card">
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1><?= $e($t('products.title')) ?></h1>
|
||||
<p class="muted"><?= $e($t('products.description')) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<section class="card mt-16">
|
||||
<div class="alert alert--danger" role="alert">
|
||||
<?= $e((string) $errorMessage) ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($successMessage)): ?>
|
||||
<section class="card mt-16">
|
||||
<div class="alert alert--success" role="status">
|
||||
<?= $e((string) $successMessage) ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php require __DIR__ . '/../components/table-list.php'; ?>
|
||||
|
||||
<?php
|
||||
$integrations = is_array($shopProIntegrations ?? null) ? $shopProIntegrations : [];
|
||||
?>
|
||||
<div class="modal-backdrop" data-modal-backdrop="product-image-preview-modal" hidden>
|
||||
<div class="modal modal--image-preview" role="dialog" aria-modal="true" aria-labelledby="product-image-preview-title">
|
||||
<div class="modal__header">
|
||||
<h3 id="product-image-preview-title">Podglad zdjecia</h3>
|
||||
<button type="button" class="btn btn--secondary" data-close-modal="product-image-preview-modal">Zamknij</button>
|
||||
</div>
|
||||
<div class="modal__body">
|
||||
<img src="" alt="" class="product-image-preview__img" data-product-image-preview-target>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-backdrop" data-modal-backdrop="product-import-modal" hidden>
|
||||
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="product-import-modal-title">
|
||||
<div class="modal__header">
|
||||
<h3 id="product-import-modal-title"><?= $e($t('products.import.title')) ?></h3>
|
||||
<button type="button" class="btn btn--secondary" data-close-modal="product-import-modal"><?= $e($t('products.import.close')) ?></button>
|
||||
</div>
|
||||
|
||||
<form action="/products/import/shoppro" method="post" class="modal__body">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.import.integration')) ?></span>
|
||||
<select class="form-control" name="integration_id" required>
|
||||
<option value=""><?= $e($t('products.import.integration_placeholder')) ?></option>
|
||||
<?php foreach ($integrations as $integration): ?>
|
||||
<option value="<?= $e((string) ($integration['id'] ?? 0)) ?>">
|
||||
<?= $e((string) ($integration['name'] ?? '')) ?> (ID: <?= $e((string) ($integration['id'] ?? 0)) ?>)
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<?php if (empty($integrations)): ?>
|
||||
<p class="muted"><?= $e($t('products.import.no_integrations')) ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.import.mode')) ?></span>
|
||||
<label class="field-inline">
|
||||
<input type="radio" name="import_mode" value="all">
|
||||
<?= $e($t('products.import.mode_all')) ?>
|
||||
</label>
|
||||
<label class="field-inline">
|
||||
<input type="radio" name="import_mode" value="single" checked>
|
||||
<?= $e($t('products.import.mode_single')) ?>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label class="form-field" data-single-id-wrap hidden>
|
||||
<span class="field-label"><?= $e($t('products.import.external_id')) ?></span>
|
||||
<input class="form-control" type="number" min="1" name="external_product_id" data-single-id-input>
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label">
|
||||
<input type="checkbox" name="import_variants" value="1">
|
||||
<?= $e($t('products.import.with_variants')) ?>
|
||||
</span>
|
||||
<small class="muted"><?= $e($t('products.import.with_variants_hint')) ?></small>
|
||||
</label>
|
||||
|
||||
<div class="form-actions mt-16">
|
||||
<button type="submit" class="btn btn--primary"<?= empty($integrations) ? ' disabled' : '' ?>><?= $e($t('products.import.submit')) ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-backdrop" data-modal-backdrop="product-export-modal" hidden>
|
||||
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="product-export-modal-title">
|
||||
<div class="modal__header">
|
||||
<h3 id="product-export-modal-title"><?= $e($t('products.export.title')) ?></h3>
|
||||
<button type="button" class="btn btn--secondary" data-close-modal="product-export-modal"><?= $e($t('products.export.close')) ?></button>
|
||||
</div>
|
||||
|
||||
<form action="/products/export/shoppro" method="post" class="modal__body" data-export-form>
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.export.integration')) ?></span>
|
||||
<select class="form-control" name="integration_id" required>
|
||||
<option value=""><?= $e($t('products.export.integration_placeholder')) ?></option>
|
||||
<?php foreach ($integrations as $integration): ?>
|
||||
<option value="<?= $e((string) ($integration['id'] ?? 0)) ?>">
|
||||
<?= $e((string) ($integration['name'] ?? '')) ?> (ID: <?= $e((string) ($integration['id'] ?? 0)) ?>)
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<?php if (empty($integrations)): ?>
|
||||
<p class="muted"><?= $e($t('products.export.no_integrations')) ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.export.mode')) ?></span>
|
||||
<label class="field-inline">
|
||||
<input type="radio" name="export_mode" value="simple" checked>
|
||||
<?= $e($t('products.export.mode_simple')) ?>
|
||||
</label>
|
||||
<label class="field-inline">
|
||||
<input type="radio" name="export_mode" value="variant">
|
||||
<?= $e($t('products.export.mode_variant')) ?>
|
||||
</label>
|
||||
<small class="muted"><?= $e($t('products.export.mode_hint')) ?></small>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.export.selected_count_label')) ?>: <strong data-export-selected-count>0</strong></span>
|
||||
<small class="muted"><?= $e($t('products.export.selected_hint')) ?></small>
|
||||
</div>
|
||||
|
||||
<div data-export-selected-wrap></div>
|
||||
|
||||
<div class="form-actions mt-16">
|
||||
<button type="submit" class="btn btn--primary"<?= empty($integrations) ? ' disabled' : '' ?>><?= $e($t('products.export.submit')) ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var openBtn = document.querySelector('[data-open-modal="product-import-modal"]');
|
||||
var backdrop = document.querySelector('[data-modal-backdrop="product-import-modal"]');
|
||||
var closeBtn = document.querySelector('[data-close-modal="product-import-modal"]');
|
||||
if (!openBtn || !backdrop || !closeBtn) return;
|
||||
|
||||
var modeInputs = backdrop.querySelectorAll('input[name="import_mode"]');
|
||||
var singleIdWrap = backdrop.querySelector('[data-single-id-wrap]');
|
||||
var singleIdInput = backdrop.querySelector('[data-single-id-input]');
|
||||
|
||||
function syncMode() {
|
||||
var mode = 'single';
|
||||
modeInputs.forEach(function(input) {
|
||||
if (input.checked) mode = input.value;
|
||||
});
|
||||
|
||||
var isSingle = mode === 'single';
|
||||
if (singleIdWrap) singleIdWrap.hidden = !isSingle;
|
||||
if (singleIdInput) {
|
||||
singleIdInput.required = isSingle;
|
||||
if (!isSingle) singleIdInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
openBtn.addEventListener('click', function() {
|
||||
backdrop.hidden = false;
|
||||
syncMode();
|
||||
});
|
||||
|
||||
closeBtn.addEventListener('click', function() {
|
||||
backdrop.hidden = true;
|
||||
});
|
||||
|
||||
backdrop.addEventListener('click', function(event) {
|
||||
if (event.target === backdrop) {
|
||||
backdrop.hidden = true;
|
||||
}
|
||||
});
|
||||
|
||||
modeInputs.forEach(function(input) {
|
||||
input.addEventListener('change', syncMode);
|
||||
});
|
||||
})();
|
||||
|
||||
(function() {
|
||||
var openBtn = document.querySelector('[data-open-modal="product-export-modal"]');
|
||||
var backdrop = document.querySelector('[data-modal-backdrop="product-export-modal"]');
|
||||
var closeBtn = document.querySelector('[data-close-modal="product-export-modal"]');
|
||||
var exportForm = backdrop ? backdrop.querySelector('[data-export-form]') : null;
|
||||
var selectedWrap = backdrop ? backdrop.querySelector('[data-export-selected-wrap]') : null;
|
||||
var selectedCount = backdrop ? backdrop.querySelector('[data-export-selected-count]') : null;
|
||||
|
||||
if (!openBtn || !backdrop || !closeBtn || !exportForm || !selectedWrap || !selectedCount) return;
|
||||
|
||||
function selectedIds() {
|
||||
return Array.prototype.slice.call(document.querySelectorAll('.js-table-select-item:checked'))
|
||||
.map(function(input) { return (input.value || '').trim(); })
|
||||
.filter(function(value) { return value !== ''; });
|
||||
}
|
||||
|
||||
function renderSelectedIds(ids) {
|
||||
selectedWrap.innerHTML = '';
|
||||
ids.forEach(function(id) {
|
||||
var input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'export_product_ids[]';
|
||||
input.value = id;
|
||||
selectedWrap.appendChild(input);
|
||||
});
|
||||
selectedCount.textContent = String(ids.length);
|
||||
}
|
||||
|
||||
function showNoSelectionMessage() {
|
||||
var message = '<?= $e($t('products.export.flash.no_products_selected')) ?>';
|
||||
if (window.OrderProAlerts && typeof window.OrderProAlerts.show === 'function') {
|
||||
window.OrderProAlerts.show({ type: 'error', message: message, timeout: 2500 });
|
||||
return;
|
||||
}
|
||||
window.alert(message);
|
||||
}
|
||||
|
||||
openBtn.addEventListener('click', function() {
|
||||
renderSelectedIds(selectedIds());
|
||||
backdrop.hidden = false;
|
||||
});
|
||||
|
||||
closeBtn.addEventListener('click', function() {
|
||||
backdrop.hidden = true;
|
||||
});
|
||||
|
||||
backdrop.addEventListener('click', function(event) {
|
||||
if (event.target === backdrop) {
|
||||
backdrop.hidden = true;
|
||||
}
|
||||
});
|
||||
|
||||
exportForm.addEventListener('submit', function(event) {
|
||||
var ids = selectedIds();
|
||||
renderSelectedIds(ids);
|
||||
if (ids.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
showNoSelectionMessage();
|
||||
});
|
||||
})();
|
||||
|
||||
(function() {
|
||||
var previewBackdrop = document.querySelector('[data-modal-backdrop="product-image-preview-modal"]');
|
||||
if (!previewBackdrop) return;
|
||||
|
||||
var previewImage = previewBackdrop.querySelector('[data-product-image-preview-target]');
|
||||
var closeBtn = previewBackdrop.querySelector('[data-close-modal="product-image-preview-modal"]');
|
||||
if (!previewImage || !closeBtn) return;
|
||||
|
||||
function closePreview() {
|
||||
previewBackdrop.hidden = true;
|
||||
previewImage.setAttribute('src', '');
|
||||
}
|
||||
|
||||
document.addEventListener('click', function(event) {
|
||||
var trigger = event.target.closest('[data-product-image-preview]');
|
||||
if (!trigger) return;
|
||||
|
||||
var imageUrl = trigger.getAttribute('data-product-image-preview') || '';
|
||||
if (imageUrl === '') return;
|
||||
|
||||
previewImage.setAttribute('src', imageUrl);
|
||||
previewBackdrop.hidden = false;
|
||||
});
|
||||
|
||||
closeBtn.addEventListener('click', closePreview);
|
||||
|
||||
previewBackdrop.addEventListener('click', function(event) {
|
||||
if (event.target === previewBackdrop) {
|
||||
closePreview();
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -0,0 +1,268 @@
|
||||
<?php $item = is_array($product ?? null) ? $product : []; ?>
|
||||
<?php $links = is_array($productLinks ?? null) ? $productLinks : []; ?>
|
||||
<?php $integrations = is_array($linkIntegrations ?? null) ? $linkIntegrations : []; ?>
|
||||
<?php $offers = is_array($linkOffers ?? null) ? $linkOffers : []; ?>
|
||||
<?php $eventsByMap = is_array($productLinkEventsByMap ?? null) ? $productLinkEventsByMap : []; ?>
|
||||
<?php $selectedIntegrationId = (int) ($selectedLinksIntegrationId ?? 0); ?>
|
||||
<?php $linksQueryValue = (string) ($linksQuery ?? ''); ?>
|
||||
<?php $productIdValue = (int) ($productId ?? 0); ?>
|
||||
|
||||
<section class="card">
|
||||
<h1><?= $e($t('products.links.page_title', ['id' => (string) ($productId ?? 0)])) ?></h1>
|
||||
<p class="muted"><?= $e($t('products.links.description')) ?></p>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<div class="product-tabs-nav">
|
||||
<a class="btn btn--secondary" href="/products/<?= $e((string) $productIdValue) ?>"><?= $e($t('products.tabs.details')) ?></a>
|
||||
<span class="btn btn--primary"><?= $e($t('products.tabs.links')) ?></span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<div class="product-links-head">
|
||||
<div>
|
||||
<strong><?= $e($t('products.fields.name')) ?>:</strong>
|
||||
<?= $e((string) ($item['name'] ?? '')) ?>
|
||||
</div>
|
||||
<div>
|
||||
<strong>SKU:</strong>
|
||||
<?= $e((string) ($item['sku'] ?? '')) ?>
|
||||
</div>
|
||||
<div>
|
||||
<strong>EAN:</strong>
|
||||
<?= $e((string) ($item['ean'] ?? '')) ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<h3><?= $e($t('products.links.title')) ?></h3>
|
||||
|
||||
<?php if (!empty($linksErrorMessage)): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $linksErrorMessage) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($linksSuccessMessage)): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e((string) $linksSuccessMessage) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<h4 class="section-title mt-16"><?= $e($t('products.links.current_links')) ?></h4>
|
||||
<?php if ($links === []): ?>
|
||||
<p class="muted mt-12"><?= $e($t('products.links.empty_links')) ?></p>
|
||||
<?php else: ?>
|
||||
<div class="table-wrap mt-12">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= $e($t('products.links.fields.integration')) ?></th>
|
||||
<th><?= $e($t('products.links.fields.channel')) ?></th>
|
||||
<th><?= $e($t('products.links.fields.external_product_id')) ?></th>
|
||||
<th><?= $e($t('products.links.fields.external_variant_id')) ?></th>
|
||||
<th><?= $e($t('products.links.fields.link_type')) ?></th>
|
||||
<th><?= $e($t('products.links.fields.confidence')) ?></th>
|
||||
<th><?= $e($t('products.links.fields.link_status')) ?></th>
|
||||
<th><?= $e($t('products.links.fields.updated_at')) ?></th>
|
||||
<th><?= $e($t('products.links.fields.history')) ?></th>
|
||||
<th><?= $e($t('products.links.fields.actions')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($links as $link): ?>
|
||||
<?php
|
||||
$mapId = (int) ($link['id'] ?? 0);
|
||||
$linkStatus = (string) ($link['link_status'] ?? '');
|
||||
$isActive = $linkStatus === 'active';
|
||||
$confidence = $link['confidence'] ?? null;
|
||||
$hasMissingAlert = ($link['has_missing_alert'] ?? false) === true;
|
||||
$missingAlertMessage = trim((string) ($link['missing_alert_message'] ?? ''));
|
||||
if ($missingAlertMessage === '') {
|
||||
$missingAlertMessage = (string) $t('products.links.alerts.missing_remote_link');
|
||||
}
|
||||
$missingAlertFirstDetectedAt = trim((string) ($link['missing_alert_first_detected_at'] ?? ''));
|
||||
$missingAlertTooltip = $missingAlertMessage;
|
||||
if ($missingAlertFirstDetectedAt !== '') {
|
||||
$missingAlertTooltip .= ' ' . (string) $t('products.links.alerts.alert_since', [
|
||||
'date' => $missingAlertFirstDetectedAt,
|
||||
]);
|
||||
}
|
||||
$lastChangeAt = trim((string) ($link['updated_at'] ?? ''));
|
||||
if ($lastChangeAt === '') {
|
||||
$lastChangeAt = trim((string) ($link['linked_at'] ?? ''));
|
||||
}
|
||||
?>
|
||||
<tr>
|
||||
<td><?= $e((string) (($link['integration_name'] ?? '') !== '' ? $link['integration_name'] : ('#' . (string) ($link['integration_id'] ?? 0)))) ?></td>
|
||||
<td><?= $e((string) ($link['channel_name'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ($link['external_product_id'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ($link['external_variant_id'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ($link['link_type'] ?? '')) ?></td>
|
||||
<td><?= $e($confidence === null ? '-' : ((string) $confidence . '%')) ?></td>
|
||||
<td>
|
||||
<div class="product-link-status-cell">
|
||||
<span class="status-pill<?= $isActive ? ' is-active' : '' ?>">
|
||||
<?= $e($linkStatus) ?>
|
||||
</span>
|
||||
<?php if ($hasMissingAlert): ?>
|
||||
<span class="product-link-alert-indicator" title="<?= $e($missingAlertTooltip) ?>" aria-label="<?= $e($missingAlertTooltip) ?>">!</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
<td><?= $e($lastChangeAt === '' ? '-' : $lastChangeAt) ?></td>
|
||||
<td>
|
||||
<?php $events = is_array($eventsByMap[$mapId] ?? null) ? $eventsByMap[$mapId] : []; ?>
|
||||
<?php if ($events === []): ?>
|
||||
<span class="muted">-</span>
|
||||
<?php else: ?>
|
||||
<ul class="product-link-events-list">
|
||||
<?php foreach ($events as $event): ?>
|
||||
<li>
|
||||
<span class="product-link-events-type"><?= $e((string) ($event['event_type'] ?? '')) ?></span>
|
||||
<span class="product-link-events-date"><?= $e((string) ($event['created_at'] ?? '')) ?></span>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<div class="product-links-actions-row">
|
||||
<form action="/products/<?= $e((string) $productIdValue) ?>/links/<?= $e((string) $mapId) ?>/relink" method="post" class="product-links-inline-form product-links-relink-form">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="product_id" value="<?= $e((string) ($productId ?? 0)) ?>">
|
||||
<input type="hidden" name="map_id" value="<?= $e((string) $mapId) ?>">
|
||||
<input type="hidden" name="integration_id" value="<?= $e((string) ((int) ($link['integration_id'] ?? 0))) ?>">
|
||||
<input class="form-control" type="text" name="external_product_id" required value="<?= $e((string) ($link['external_product_id'] ?? '')) ?>">
|
||||
<input class="form-control" type="text" name="external_variant_id" value="<?= $e((string) ($link['external_variant_id'] ?? '')) ?>" placeholder="<?= $e($t('products.links.fields.external_variant_id_optional')) ?>">
|
||||
<button type="submit" class="btn btn--secondary" data-links-action="relink"><?= $e($t('products.links.actions.relink')) ?></button>
|
||||
</form>
|
||||
|
||||
<form action="/products/<?= $e((string) $productIdValue) ?>/links/<?= $e((string) $mapId) ?>/unlink" method="post" class="product-links-unlink-form">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="product_id" value="<?= $e((string) ($productId ?? 0)) ?>">
|
||||
<input type="hidden" name="map_id" value="<?= $e((string) $mapId) ?>">
|
||||
<button type="submit" class="btn btn--danger" data-links-action="unlink"><?= $e($t('products.links.actions.unlink')) ?></button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<h4 class="section-title mt-16"><?= $e($t('products.links.search_title')) ?></h4>
|
||||
<form class="product-links-search-form mt-12" action="/products/<?= $e((string) $productIdValue) ?>/links" method="get">
|
||||
<input type="hidden" name="id" value="<?= $e((string) ($productId ?? 0)) ?>">
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.links.fields.integration')) ?></span>
|
||||
<select class="form-control" name="links_integration_id" required>
|
||||
<option value="0"><?= $e($t('products.links.integration_placeholder')) ?></option>
|
||||
<?php foreach ($integrations as $integration): ?>
|
||||
<?php $integrationId = (int) ($integration['id'] ?? 0); ?>
|
||||
<option value="<?= $e((string) $integrationId) ?>"<?= $integrationId === $selectedIntegrationId ? ' selected' : '' ?>>
|
||||
<?= $e((string) ($integration['name'] ?? '')) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('products.links.fields.search')) ?></span>
|
||||
<input class="form-control" type="text" name="links_query" value="<?= $e($linksQueryValue) ?>" placeholder="<?= $e($t('products.links.search_placeholder')) ?>">
|
||||
</label>
|
||||
<button type="submit" class="btn btn--primary"><?= $e($t('products.links.actions.search')) ?></button>
|
||||
</form>
|
||||
|
||||
<?php if ($offers === []): ?>
|
||||
<p class="muted mt-12"><?= $e($t('products.links.empty_offers')) ?></p>
|
||||
<?php else: ?>
|
||||
<div class="table-wrap mt-12">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= $e($t('products.links.fields.offer_name')) ?></th>
|
||||
<th>SKU</th>
|
||||
<th>EAN</th>
|
||||
<th><?= $e($t('products.links.fields.external_product_id')) ?></th>
|
||||
<th><?= $e($t('products.links.fields.external_variant_id')) ?></th>
|
||||
<th><?= $e($t('products.links.fields.match_hint')) ?></th>
|
||||
<th><?= $e($t('products.links.fields.confidence')) ?></th>
|
||||
<th><?= $e($t('products.links.fields.actions')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($offers as $offer): ?>
|
||||
<tr>
|
||||
<td><?= $e((string) ($offer['name'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ($offer['sku'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ($offer['ean'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ($offer['external_product_id'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ($offer['external_variant_id'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ($offer['match_hint'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ((int) ($offer['match_confidence'] ?? 0)) . '%') ?></td>
|
||||
<td>
|
||||
<form action="/products/<?= $e((string) $productIdValue) ?>/links" method="post">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="product_id" value="<?= $e((string) ($productId ?? 0)) ?>">
|
||||
<input type="hidden" name="integration_id" value="<?= $e((string) ($offer['integration_id'] ?? 0)) ?>">
|
||||
<input type="hidden" name="external_product_id" value="<?= $e((string) ($offer['external_product_id'] ?? '')) ?>">
|
||||
<input type="hidden" name="external_variant_id" value="<?= $e((string) ($offer['external_variant_id'] ?? '')) ?>">
|
||||
<button type="submit" class="btn btn--primary"><?= $e($t('products.links.actions.link')) ?></button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<a class="btn btn--secondary" href="/products"><?= $e($t('products.actions.back')) ?></a>
|
||||
<a class="btn btn--secondary" href="/products/<?= $e((string) $productIdValue) ?>"><?= $e($t('products.actions.preview')) ?></a>
|
||||
<a class="btn btn--primary" href="/products/edit?id=<?= $e((string) ($productId ?? 0)) ?>"><?= $e($t('products.actions.edit')) ?></a>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var unlinkForms = document.querySelectorAll('.product-links-unlink-form');
|
||||
var relinkForms = document.querySelectorAll('.product-links-relink-form');
|
||||
var unlinkMessage = <?= json_encode((string) $t('products.links.confirm.unlink_message'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
var relinkMessage = <?= json_encode((string) $t('products.links.confirm.relink_message'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
var confirmTitle = <?= json_encode((string) $t('products.links.confirm.title'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
var confirmYes = <?= json_encode((string) $t('products.links.confirm.yes'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
var confirmNo = <?= json_encode((string) $t('products.links.confirm.no'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
|
||||
async function handleConfirmSubmit(event, message, danger) {
|
||||
if (!window.OrderProAlerts || typeof window.OrderProAlerts.confirm !== 'function') {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
var accepted = await window.OrderProAlerts.confirm({
|
||||
title: confirmTitle,
|
||||
message: message,
|
||||
confirmLabel: confirmYes,
|
||||
cancelLabel: confirmNo,
|
||||
danger: danger === true
|
||||
});
|
||||
if (!accepted) {
|
||||
return;
|
||||
}
|
||||
event.target.submit();
|
||||
}
|
||||
|
||||
unlinkForms.forEach(function (form) {
|
||||
form.addEventListener('submit', function (event) {
|
||||
handleConfirmSubmit(event, unlinkMessage, true);
|
||||
});
|
||||
});
|
||||
|
||||
relinkForms.forEach(function (form) {
|
||||
form.addEventListener('submit', function (event) {
|
||||
handleConfirmSubmit(event, relinkMessage, false);
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -0,0 +1,197 @@
|
||||
<section class="card">
|
||||
<h1><?= $e($t('products.show.title', ['id' => (string) ($productId ?? 0)])) ?></h1>
|
||||
<p class="muted"><?= $e($t('products.show.description')) ?></p>
|
||||
</section>
|
||||
|
||||
<?php $item = is_array($product ?? null) ? $product : []; ?>
|
||||
<?php $images = is_array($productImages ?? null) ? $productImages : []; ?>
|
||||
<?php $variants = is_array($productVariants ?? null) ? $productVariants : []; ?>
|
||||
<?php $importWarning = is_array($productImportWarning ?? null) ? $productImportWarning : null; ?>
|
||||
<?php $productIdValue = (int) ($productId ?? 0); ?>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<section class="card mt-16">
|
||||
<div class="alert alert--danger" role="alert">
|
||||
<?= $e((string) $errorMessage) ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($successMessage)): ?>
|
||||
<section class="card mt-16">
|
||||
<div class="alert alert--success" role="status">
|
||||
<?= $e((string) $successMessage) ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<section class="card mt-16">
|
||||
<div class="product-tabs-nav">
|
||||
<span class="btn btn--primary"><?= $e($t('products.tabs.details')) ?></span>
|
||||
<a class="btn btn--secondary" href="/products/<?= $e((string) $productIdValue) ?>/links"><?= $e($t('products.tabs.links')) ?></a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<?php if ($importWarning !== null && !empty($importWarning['messages'])): ?>
|
||||
<div class="alert alert--danger" role="alert">
|
||||
<div><strong><?= $e($t('products.variants.import_warning_title')) ?></strong></div>
|
||||
<?php foreach ((array) ($importWarning['messages'] ?? []) as $warning): ?>
|
||||
<div><?= $e((string) $warning) ?></div>
|
||||
<?php endforeach; ?>
|
||||
<?php if (!empty($importWarning['created_at'])): ?>
|
||||
<div class="muted mt-8"><?= $e($t('products.variants.import_warning_date')) ?>: <?= $e((string) $importWarning['created_at']) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<h3><?= $e($t('products.show.details')) ?></h3>
|
||||
<table class="table table--details mt-12">
|
||||
<tbody>
|
||||
<tr><th>ID</th><td><?= $e((string) ($item['id'] ?? '')) ?></td></tr>
|
||||
<tr><th><?= $e($t('products.fields.name')) ?></th><td><?= $e((string) ($item['name'] ?? '')) ?></td></tr>
|
||||
<tr><th>SKU</th><td><?= $e((string) ($item['sku'] ?? '')) ?></td></tr>
|
||||
<tr>
|
||||
<th>EAN</th>
|
||||
<td>
|
||||
<?php if (trim((string) ($item['ean'] ?? '')) !== ''): ?>
|
||||
<?= $e((string) $item['ean']) ?>
|
||||
<?php else: ?>
|
||||
<span class="muted">—</span>
|
||||
<form method="post" action="/products/<?= $e((string) $productIdValue) ?>/assign-ean" style="display:inline; margin-left:8px;">
|
||||
<input type="hidden" name="_token" value="<?= $e((string) ($csrfToken ?? '')) ?>">
|
||||
<button type="submit" class="btn btn--primary btn--sm"><?= $e($t('products.gs1.assign_ean')) ?></button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><th><?= $e($t('products.fields.type')) ?></th><td><?= $e((string) ($item['type'] ?? '')) ?></td></tr>
|
||||
<tr><th><?= $e($t('products.fields.status')) ?></th><td><?= $e((string) ($item['status'] ?? '')) ?></td></tr>
|
||||
<tr><th><?= $e($t('products.fields.promoted')) ?></th><td><?= $e((string) ($item['promoted'] ?? '')) ?></td></tr>
|
||||
<tr><th><?= $e($t('products.fields.vat')) ?></th><td><?= $e((string) ($item['vat'] ?? '')) ?></td></tr>
|
||||
<tr><th><?= $e($t('products.fields.weight')) ?></th><td><?= $e((string) ($item['weight'] ?? '')) ?></td></tr>
|
||||
<tr><th><?= $e($t('products.fields.quantity')) ?></th><td><?= $e((string) ($item['quantity'] ?? '')) ?></td></tr>
|
||||
<tr><th><?= $e($t('products.fields.price_brutto')) ?></th><td><?= $e((string) ($item['price_brutto'] ?? '')) ?></td></tr>
|
||||
<tr><th><?= $e($t('products.fields.price_netto')) ?></th><td><?= $e((string) ($item['price_netto'] ?? '')) ?></td></tr>
|
||||
<tr><th><?= $e($t('products.fields.price_brutto_promo')) ?></th><td><?= $e((string) ($item['price_brutto_promo'] ?? '')) ?></td></tr>
|
||||
<tr><th><?= $e($t('products.fields.price_netto_promo')) ?></th><td><?= $e((string) ($item['price_netto_promo'] ?? '')) ?></td></tr>
|
||||
<tr><th><?= $e($t('products.fields.short_description')) ?></th><td><?= $e((string) ($item['short_description'] ?? '')) ?></td></tr>
|
||||
<tr><th><?= $e($t('products.fields.description')) ?></th><td><?= $e((string) ($item['description'] ?? '')) ?></td></tr>
|
||||
<tr><th><?= $e($t('products.fields.meta_title')) ?></th><td><?= $e((string) ($item['meta_title'] ?? '')) ?></td></tr>
|
||||
<tr><th><?= $e($t('products.fields.meta_description')) ?></th><td><?= $e((string) ($item['meta_description'] ?? '')) ?></td></tr>
|
||||
<tr><th><?= $e($t('products.fields.meta_keywords')) ?></th><td><?= $e((string) ($item['meta_keywords'] ?? '')) ?></td></tr>
|
||||
<tr><th><?= $e($t('products.fields.seo_link')) ?></th><td><?= $e((string) ($item['seo_link'] ?? '')) ?></td></tr>
|
||||
<tr><th><?= $e($t('products.fields.updated_at')) ?></th><td><?= $e((string) ($item['updated_at'] ?? '')) ?></td></tr>
|
||||
<?php if (!empty($item['producer_name'])): ?>
|
||||
<tr><th>Producent</th><td><?= $e((string) $item['producer_name']) ?></td></tr>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($item['security_information'])): ?>
|
||||
<tr>
|
||||
<th>GPSR — informacje o bezpieczeństwie</th>
|
||||
<td><?= $e((string) ($item['security_information'] ?? '')) ?></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
$customFields = [];
|
||||
if (!empty($item['custom_fields_json'])) {
|
||||
$decoded = json_decode((string) $item['custom_fields_json'], true);
|
||||
$customFields = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
?>
|
||||
<?php if ($customFields !== []): ?>
|
||||
<tr>
|
||||
<th>Dodatkowe pola</th>
|
||||
<td>
|
||||
<?php foreach ($customFields as $cf): ?>
|
||||
<div><?= $e((string) ($cf['name'] ?? '')) ?> (<?= $e((string) ($cf['type'] ?? '')) ?><?= (int) ($cf['is_required'] ?? 0) === 1 ? ', wymagane' : '' ?>)</div>
|
||||
<?php endforeach; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<h3><?= $e($t('products.variants.title')) ?></h3>
|
||||
<?php if ($variants === []): ?>
|
||||
<p class="muted"><?= $e($t('products.variants.empty')) ?></p>
|
||||
<?php else: ?>
|
||||
<div class="table-wrap mt-12">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>SKU</th>
|
||||
<th>EAN</th>
|
||||
<th><?= $e($t('products.fields.price_brutto')) ?></th>
|
||||
<th><?= $e($t('products.fields.price_netto')) ?></th>
|
||||
<th><?= $e($t('products.fields.weight')) ?></th>
|
||||
<th><?= $e($t('products.fields.status')) ?></th>
|
||||
<th><?= $e($t('products.variants.attributes')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($variants as $variant): ?>
|
||||
<?php
|
||||
$attributes = is_array($variant['attributes'] ?? null) ? $variant['attributes'] : [];
|
||||
$attributeText = [];
|
||||
foreach ($attributes as $attribute) {
|
||||
$attributeName = (string) ($attribute['attribute_name'] ?? '');
|
||||
$valueName = (string) ($attribute['value_name'] ?? '');
|
||||
if ($attributeName === '' || $valueName === '') {
|
||||
continue;
|
||||
}
|
||||
$attributeText[] = $attributeName . ': ' . $valueName;
|
||||
}
|
||||
?>
|
||||
<tr>
|
||||
<td><?= $e((string) ($variant['id'] ?? 0)) ?></td>
|
||||
<td><?= $e((string) ($variant['sku'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ($variant['ean'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ($variant['price_brutto'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ($variant['price_netto'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ($variant['weight'] ?? '')) ?></td>
|
||||
<td><?= $e(((int) ($variant['status'] ?? 0)) === 1 ? $t('products.status.active') : $t('products.status.inactive')) ?></td>
|
||||
<td><?= $e($attributeText !== [] ? implode(', ', $attributeText) : '-') ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<h3><?= $e($t('products.images.title')) ?></h3>
|
||||
<?php if ($images === []): ?>
|
||||
<p class="muted"><?= $e($t('products.images.empty')) ?></p>
|
||||
<?php else: ?>
|
||||
<div class="product-show-images-grid mt-12">
|
||||
<?php foreach ($images as $image): ?>
|
||||
<div class="product-show-image-card">
|
||||
<div class="product-show-image-card__meta">
|
||||
<span><strong>ID:</strong> <?= $e((string) ($image['id'] ?? 0)) ?><?= ((int) ($image['is_main'] ?? 0) === 1) ? ' | <strong>' . $e($t('products.images.main')) . '</strong>' : '' ?></span>
|
||||
<?php if ((string) ($image['storage_path'] ?? '') !== ''): ?>
|
||||
<details class="product-show-image-path">
|
||||
<summary><?= $e($t('products.images.path')) ?></summary>
|
||||
<div class="product-show-image-path__url muted"><?= $e((string) ($image['storage_path'] ?? '')) ?></div>
|
||||
</details>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php if ((string) ($image['public_url'] ?? '') !== ''): ?>
|
||||
<div class="mt-8">
|
||||
<img src="<?= $e((string) $image['public_url']) ?>" alt="<?= $e((string) ($image['alt'] ?? '')) ?>" class="product-show-image">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<a class="btn btn--secondary" href="/products"><?= $e($t('products.actions.back')) ?></a>
|
||||
<a class="btn btn--secondary" href="/products/<?= $e((string) $productIdValue) ?>/links"><?= $e($t('products.actions.links')) ?></a>
|
||||
<a class="btn btn--primary" href="/products/edit?id=<?= $e((string) $productIdValue) ?>"><?= $e($t('products.actions.edit')) ?></a>
|
||||
</section>
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
$schedulesList = is_array($schedules ?? null) ? $schedules : [];
|
||||
$futureList = is_array($futureJobs ?? null) ? $futureJobs : [];
|
||||
$pastList = is_array($pastJobs ?? null) ? $pastJobs : [];
|
||||
$runOnWeb = ($runOnWebEnabled ?? false) === true;
|
||||
$webLimit = max(1, (int) ($webCronLimit ?? 5));
|
||||
?>
|
||||
|
||||
<section class="card">
|
||||
<h1><?= $e($t('settings.title')) ?></h1>
|
||||
<p class="muted"><?= $e($t('settings.description')) ?></p>
|
||||
<nav class="settings-nav mt-16" aria-label="<?= $e($t('settings.submenu_label')) ?>">
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'database' ? ' is-active' : '' ?>" href="/settings/database"><?= $e($t('settings.database.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'integrations' ? ' is-active' : '' ?>" href="/settings/integrations/shoppro"><?= $e($t('settings.integrations.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'order_statuses' ? ' is-active' : '' ?>" href="/settings/order-statuses"><?= $e($t('settings.order_statuses.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'cron' ? ' is-active' : '' ?>" href="/settings/cron"><?= $e($t('settings.cron.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'gs1' ? ' is-active' : '' ?>" href="/settings/gs1"><?= $e($t('settings.gs1.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'products' ? ' is-active' : '' ?>" href="/settings/products"><?= $e($t('settings.products.title')) ?></a>
|
||||
</nav>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<h2 class="section-title"><?= $e($t('settings.cron.run_on_web_title')) ?></h2>
|
||||
<p class="muted mt-12"><?= $e($t('settings.cron.run_on_web_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; ?>
|
||||
|
||||
<form action="/settings/cron/save" method="post" class="mt-16">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label">
|
||||
<input type="checkbox" name="cron_run_on_web" value="1"<?= $runOnWeb ? ' checked' : '' ?>>
|
||||
<?= $e($t('settings.cron.run_on_web_label')) ?>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label class="form-field mt-12">
|
||||
<span class="field-label"><?= $e($t('settings.cron.web_limit')) ?></span>
|
||||
<input class="form-control" type="number" min="1" max="100" name="cron_web_limit" value="<?= $e((string) $webLimit) ?>">
|
||||
</label>
|
||||
|
||||
<div class="form-actions mt-16">
|
||||
<button type="submit" class="btn btn--primary"><?= $e($t('settings.cron.actions.save')) ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<h2 class="section-title"><?= $e($t('settings.cron.schedules_title')) ?></h2>
|
||||
<div class="table-wrap mt-12">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= $e($t('settings.cron.fields.job_type')) ?></th>
|
||||
<th><?= $e($t('settings.cron.fields.enabled')) ?></th>
|
||||
<th><?= $e($t('settings.cron.fields.interval')) ?></th>
|
||||
<th><?= $e($t('settings.cron.fields.priority')) ?></th>
|
||||
<th><?= $e($t('settings.cron.fields.next_run_at')) ?></th>
|
||||
<th><?= $e($t('settings.cron.fields.last_run_at')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($schedulesList === []): ?>
|
||||
<tr><td colspan="6" class="muted"><?= $e($t('settings.cron.empty_schedules')) ?></td></tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($schedulesList as $row): ?>
|
||||
<tr>
|
||||
<td><?= $e((string) ($row['job_type'] ?? '')) ?></td>
|
||||
<td><?= $e(((bool) ($row['enabled'] ?? false)) ? $t('settings.cron.enabled.yes') : $t('settings.cron.enabled.no')) ?></td>
|
||||
<td><?= $e((string) ((int) ($row['interval_seconds'] ?? 0))) ?></td>
|
||||
<td><?= $e((string) ((int) ($row['priority'] ?? 0))) ?></td>
|
||||
<td><?= $e((string) (($row['next_run_at'] ?? null) ?? '-')) ?></td>
|
||||
<td><?= $e((string) (($row['last_run_at'] ?? null) ?? '-')) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<h2 class="section-title"><?= $e($t('settings.cron.future_jobs_title')) ?></h2>
|
||||
<div class="table-wrap mt-12">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th><?= $e($t('settings.cron.fields.job_type')) ?></th>
|
||||
<th><?= $e($t('settings.cron.fields.status')) ?></th>
|
||||
<th><?= $e($t('settings.cron.fields.priority')) ?></th>
|
||||
<th><?= $e($t('settings.cron.fields.scheduled_at')) ?></th>
|
||||
<th><?= $e($t('settings.cron.fields.attempts')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($futureList === []): ?>
|
||||
<tr><td colspan="6" class="muted"><?= $e($t('settings.cron.empty_future_jobs')) ?></td></tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($futureList as $row): ?>
|
||||
<tr>
|
||||
<td><?= $e((string) ((int) ($row['id'] ?? 0))) ?></td>
|
||||
<td><?= $e((string) ($row['job_type'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ($row['status'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ((int) ($row['priority'] ?? 0))) ?></td>
|
||||
<td><?= $e((string) (($row['scheduled_at'] ?? null) ?? '-')) ?></td>
|
||||
<td><?= $e((string) ((int) ($row['attempts'] ?? 0)) . '/' . (string) ((int) ($row['max_attempts'] ?? 0))) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<h2 class="section-title"><?= $e($t('settings.cron.past_jobs_title')) ?></h2>
|
||||
<div class="table-wrap mt-12">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th><?= $e($t('settings.cron.fields.job_type')) ?></th>
|
||||
<th><?= $e($t('settings.cron.fields.status')) ?></th>
|
||||
<th><?= $e($t('settings.cron.fields.scheduled_at')) ?></th>
|
||||
<th><?= $e($t('settings.cron.fields.completed_at')) ?></th>
|
||||
<th><?= $e($t('settings.cron.fields.last_error')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($pastList === []): ?>
|
||||
<tr><td colspan="6" class="muted"><?= $e($t('settings.cron.empty_past_jobs')) ?></td></tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($pastList as $row): ?>
|
||||
<?php $error = trim((string) ($row['last_error'] ?? '')); ?>
|
||||
<tr>
|
||||
<td><?= $e((string) ((int) ($row['id'] ?? 0))) ?></td>
|
||||
<td><?= $e((string) ($row['job_type'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ($row['status'] ?? '')) ?></td>
|
||||
<td><?= $e((string) (($row['scheduled_at'] ?? null) ?? '-')) ?></td>
|
||||
<td><?= $e((string) (($row['completed_at'] ?? null) ?? '-')) ?></td>
|
||||
<td><?= $e($error === '' ? '-' : $error) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
$migrationStatus = is_array($status ?? null) ? $status : [];
|
||||
$pending = (int) ($migrationStatus['pending'] ?? 0);
|
||||
$total = (int) ($migrationStatus['total'] ?? 0);
|
||||
$applied = (int) ($migrationStatus['applied'] ?? 0);
|
||||
$pendingFiles = (array) ($migrationStatus['pending_files'] ?? []);
|
||||
$logs = (array) ($runLogs ?? []);
|
||||
?>
|
||||
|
||||
<section class="card">
|
||||
<h1><?= $e($t('settings.title')) ?></h1>
|
||||
<p class="muted"><?= $e($t('settings.description')) ?></p>
|
||||
<nav class="settings-nav mt-16" aria-label="<?= $e($t('settings.submenu_label')) ?>">
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'database' ? ' is-active' : '' ?>" href="/settings/database"><?= $e($t('settings.database.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'integrations' ? ' is-active' : '' ?>" href="/settings/integrations/shoppro"><?= $e($t('settings.integrations.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'order_statuses' ? ' is-active' : '' ?>" href="/settings/order-statuses"><?= $e($t('settings.order_statuses.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'cron' ? ' is-active' : '' ?>" href="/settings/cron"><?= $e($t('settings.cron.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'gs1' ? ' is-active' : '' ?>" href="/settings/gs1"><?= $e($t('settings.gs1.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'products' ? ' is-active' : '' ?>" href="/settings/products"><?= $e($t('settings.products.title')) ?></a>
|
||||
</nav>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<h2 class="section-title"><?= $e($t('settings.database.title')) ?></h2>
|
||||
|
||||
<?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; ?>
|
||||
|
||||
<div class="settings-grid mt-16">
|
||||
<div class="settings-stat">
|
||||
<span class="settings-stat__label"><?= $e($t('settings.database.stats.total')) ?></span>
|
||||
<strong class="settings-stat__value"><?= $e((string) $total) ?></strong>
|
||||
</div>
|
||||
<div class="settings-stat">
|
||||
<span class="settings-stat__label"><?= $e($t('settings.database.stats.applied')) ?></span>
|
||||
<strong class="settings-stat__value"><?= $e((string) $applied) ?></strong>
|
||||
</div>
|
||||
<div class="settings-stat">
|
||||
<span class="settings-stat__label"><?= $e($t('settings.database.stats.pending')) ?></span>
|
||||
<strong class="settings-stat__value"><?= $e((string) $pending) ?></strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($pending > 0): ?>
|
||||
<div class="alert alert--warning mt-16" role="status">
|
||||
<?= $e($t('settings.database.state.needs_update')) ?>
|
||||
</div>
|
||||
|
||||
<form class="mt-16" action="/settings/database/migrate" method="post">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<button type="submit" class="btn btn--primary"><?= $e($t('settings.database.actions.run_update')) ?></button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<div class="alert alert--success mt-16" role="status">
|
||||
<?= $e($t('settings.database.state.up_to_date')) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<h2 class="section-title"><?= $e($t('settings.database.pending_files_title')) ?></h2>
|
||||
<div class="table-wrap mt-12">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= $e($t('settings.database.fields.filename')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($pendingFiles)): ?>
|
||||
<tr>
|
||||
<td class="muted"><?= $e($t('settings.database.pending_files_empty')) ?></td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($pendingFiles as $filename): ?>
|
||||
<tr>
|
||||
<td><?= $e((string) $filename) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php if (!empty($logs)): ?>
|
||||
<section class="card mt-16">
|
||||
<h2 class="section-title"><?= $e($t('settings.database.last_run_logs')) ?></h2>
|
||||
<pre class="settings-logs mt-12"><?php foreach ($logs as $line): ?><?= $e((string) $line) . "\n" ?><?php endforeach; ?></pre>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
@@ -0,0 +1,58 @@
|
||||
<section class="card">
|
||||
<h1><?= $e($t('settings.title')) ?></h1>
|
||||
<p class="muted"><?= $e($t('settings.description')) ?></p>
|
||||
<nav class="settings-nav mt-16" aria-label="<?= $e($t('settings.submenu_label')) ?>">
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'database' ? ' is-active' : '' ?>" href="/settings/database"><?= $e($t('settings.database.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'integrations' ? ' is-active' : '' ?>" href="/settings/integrations/shoppro"><?= $e($t('settings.integrations.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'order_statuses' ? ' is-active' : '' ?>" href="/settings/order-statuses"><?= $e($t('settings.order_statuses.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'cron' ? ' is-active' : '' ?>" href="/settings/cron"><?= $e($t('settings.cron.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'gs1' ? ' is-active' : '' ?>" href="/settings/gs1"><?= $e($t('settings.gs1.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'products' ? ' is-active' : '' ?>" href="/settings/products"><?= $e($t('settings.products.title')) ?></a>
|
||||
</nav>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<h2 class="section-title"><?= $e($t('settings.gs1.title')) ?></h2>
|
||||
<p class="muted mt-12"><?= $e($t('settings.gs1.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; ?>
|
||||
|
||||
<form action="/settings/gs1/save" method="post" class="mt-16">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('settings.gs1.fields.api_login')) ?></span>
|
||||
<input class="form-control" type="text" name="gs1_api_login" value="<?= $e((string) ($gs1ApiLogin ?? '')) ?>" autocomplete="off">
|
||||
</label>
|
||||
|
||||
<label class="form-field mt-12">
|
||||
<span class="field-label"><?= $e($t('settings.gs1.fields.api_password')) ?></span>
|
||||
<input class="form-control" type="password" name="gs1_api_password" value="<?= $e((string) ($gs1ApiPassword ?? '')) ?>" autocomplete="off">
|
||||
</label>
|
||||
|
||||
<label class="form-field mt-12">
|
||||
<span class="field-label"><?= $e($t('settings.gs1.fields.prefix')) ?></span>
|
||||
<input class="form-control" type="text" name="gs1_prefix" value="<?= $e((string) ($gs1Prefix ?? '590532390')) ?>" maxlength="12">
|
||||
</label>
|
||||
|
||||
<label class="form-field mt-12">
|
||||
<span class="field-label"><?= $e($t('settings.gs1.fields.default_brand')) ?></span>
|
||||
<input class="form-control" type="text" name="gs1_default_brand" value="<?= $e((string) ($gs1DefaultBrand ?? 'marianek.pl')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field mt-12">
|
||||
<span class="field-label"><?= $e($t('settings.gs1.fields.default_gpc_code')) ?></span>
|
||||
<input class="form-control" type="text" name="gs1_default_gpc_code" value="<?= $e((string) ($gs1DefaultGpcCode ?? '10008365')) ?>">
|
||||
</label>
|
||||
|
||||
<div class="form-actions mt-16">
|
||||
<button type="submit" class="btn btn--primary"><?= $e($t('settings.gs1.actions.save')) ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
$list = (array) ($integrations ?? []);
|
||||
$selected = is_array($selectedIntegration ?? null) ? $selectedIntegration : null;
|
||||
$formValues = is_array($form ?? null) ? $form : [];
|
||||
$tests = (array) ($recentTests ?? []);
|
||||
$isEdit = ((int) ($formValues['integration_id'] ?? 0)) > 0;
|
||||
?>
|
||||
|
||||
<section class="card">
|
||||
<h1><?= $e($t('settings.title')) ?></h1>
|
||||
<p class="muted"><?= $e($t('settings.description')) ?></p>
|
||||
<nav class="settings-nav mt-16" aria-label="<?= $e($t('settings.submenu_label')) ?>">
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'database' ? ' is-active' : '' ?>" href="/settings/database"><?= $e($t('settings.database.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'integrations' ? ' is-active' : '' ?>" href="/settings/integrations/shoppro"><?= $e($t('settings.integrations.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'order_statuses' ? ' is-active' : '' ?>" href="/settings/order-statuses"><?= $e($t('settings.order_statuses.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'cron' ? ' is-active' : '' ?>" href="/settings/cron"><?= $e($t('settings.cron.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'gs1' ? ' is-active' : '' ?>" href="/settings/gs1"><?= $e($t('settings.gs1.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'products' ? ' is-active' : '' ?>" href="/settings/products"><?= $e($t('settings.products.title')) ?></a>
|
||||
</nav>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<h2 class="section-title"><?= $e($t('settings.integrations.list_title')) ?></h2>
|
||||
|
||||
<?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; ?>
|
||||
|
||||
<div class="table-wrap mt-12">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th><?= $e($t('settings.integrations.fields.name')) ?></th>
|
||||
<th><?= $e($t('settings.integrations.fields.base_url')) ?></th>
|
||||
<th><?= $e($t('settings.integrations.fields.active')) ?></th>
|
||||
<th><?= $e($t('settings.integrations.fields.last_test')) ?></th>
|
||||
<th><?= $e($t('settings.integrations.fields.actions')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($list)): ?>
|
||||
<tr>
|
||||
<td colspan="6" class="muted"><?= $e($t('settings.integrations.empty')) ?></td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($list as $item): ?>
|
||||
<?php
|
||||
$status = (string) ($item['last_test_status'] ?? '');
|
||||
$statusLabel = $status === 'ok'
|
||||
? $t('settings.integrations.test_status.ok')
|
||||
: ($status === 'error' ? $t('settings.integrations.test_status.error') : $t('settings.integrations.test_status.never'));
|
||||
?>
|
||||
<tr>
|
||||
<td><?= $e((string) ($item['id'] ?? 0)) ?></td>
|
||||
<td><?= $e((string) ($item['name'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ($item['base_url'] ?? '')) ?></td>
|
||||
<td>
|
||||
<?php if ((bool) ($item['is_active'] ?? false)): ?>
|
||||
<span class="status-pill is-active"><?= $e($t('settings.integrations.active.yes')) ?></span>
|
||||
<?php else: ?>
|
||||
<span class="status-pill"><?= $e($t('settings.integrations.active.no')) ?></span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<div><?= $e($statusLabel) ?></div>
|
||||
<?php if (!empty($item['last_test_at'])): ?>
|
||||
<small class="muted"><?= $e((string) $item['last_test_at']) ?><?php if (($item['last_test_http_code'] ?? null) !== null): ?> | HTTP <?= $e((string) $item['last_test_http_code']) ?><?php endif; ?></small>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<a class="btn btn--secondary" href="/settings/integrations/shoppro?id=<?= $e((string) ($item['id'] ?? 0)) ?>"><?= $e($t('settings.integrations.actions.edit')) ?></a>
|
||||
<form action="/settings/integrations/shoppro/test" method="post" style="display:inline-block; margin-left:6px;">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="integration_id" value="<?= $e((string) ($item['id'] ?? 0)) ?>">
|
||||
<button type="submit" class="btn btn--primary"><?= $e($t('settings.integrations.actions.test')) ?></button>
|
||||
</form>
|
||||
<form action="/settings/integrations/shoppro/import-offers-cache" method="post" style="display:inline-block; margin-left:6px;">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="integration_id" value="<?= $e((string) ($item['id'] ?? 0)) ?>">
|
||||
<button type="submit" class="btn btn--secondary"><?= $e($t('settings.integrations.actions.import_offers_cache')) ?></button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<h2 class="section-title">
|
||||
<?= $e($isEdit ? $t('settings.integrations.edit_title') : $t('settings.integrations.create_title')) ?>
|
||||
</h2>
|
||||
|
||||
<form class="mt-16" action="/settings/integrations/shoppro/save" method="post">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="integration_id" value="<?= $e((string) ($formValues['integration_id'] ?? '0')) ?>">
|
||||
|
||||
<div class="form-grid">
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('settings.integrations.fields.name')) ?></span>
|
||||
<input class="form-control" type="text" name="name" value="<?= $e((string) ($formValues['name'] ?? '')) ?>" required>
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('settings.integrations.fields.base_url')) ?></span>
|
||||
<input class="form-control" type="url" name="base_url" value="<?= $e((string) ($formValues['base_url'] ?? '')) ?>" placeholder="https://shoppro.project-dc.pl/" required>
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('settings.integrations.fields.api_key')) ?></span>
|
||||
<input class="form-control" type="password" name="api_key" value="" placeholder="<?= $e($isEdit ? $t('settings.integrations.api_key_placeholder_edit') : '') ?>">
|
||||
<?php if ($isEdit): ?>
|
||||
<small class="muted">
|
||||
<?= $e(($selected['has_api_key'] ?? false) ? $t('settings.integrations.api_key_saved') : $t('settings.integrations.api_key_missing')) ?>
|
||||
</small>
|
||||
<?php endif; ?>
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('settings.integrations.fields.timeout_seconds')) ?></span>
|
||||
<input class="form-control" type="number" min="3" max="60" name="timeout_seconds" value="<?= $e((string) ($formValues['timeout_seconds'] ?? '10')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('settings.integrations.fields.orders_fetch_start_date')) ?></span>
|
||||
<input class="form-control" type="date" name="orders_fetch_start_date" value="<?= $e((string) ($formValues['orders_fetch_start_date'] ?? '')) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('settings.integrations.fields.order_status_sync_direction')) ?></span>
|
||||
<select class="form-control" name="order_status_sync_direction">
|
||||
<?php $syncDirection = (string) ($formValues['order_status_sync_direction'] ?? 'shoppro_to_orderpro'); ?>
|
||||
<option value="shoppro_to_orderpro"<?= $syncDirection === 'shoppro_to_orderpro' ? ' selected' : '' ?>>
|
||||
<?= $e($t('settings.integrations.fields.order_status_sync_direction_shoppro_to_orderpro')) ?>
|
||||
</option>
|
||||
<option value="orderpro_to_shoppro"<?= $syncDirection === 'orderpro_to_shoppro' ? ' selected' : '' ?>>
|
||||
<?= $e($t('settings.integrations.fields.order_status_sync_direction_orderpro_to_shoppro')) ?>
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label class="form-field mt-12">
|
||||
<span class="field-label">
|
||||
<input type="checkbox" name="is_active" value="1"<?= ((string) ($formValues['is_active'] ?? '1')) === '1' ? ' checked' : '' ?>>
|
||||
<?= $e($t('settings.integrations.fields.active_checkbox')) ?>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label class="form-field mt-12">
|
||||
<span class="field-label">
|
||||
<input type="checkbox" name="orders_fetch_enabled" value="1"<?= ((string) ($formValues['orders_fetch_enabled'] ?? '0')) === '1' ? ' checked' : '' ?>>
|
||||
<?= $e($t('settings.integrations.fields.orders_fetch_enabled_checkbox')) ?>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<div class="form-actions mt-16">
|
||||
<button type="submit" class="btn btn--primary"><?= $e($t('settings.integrations.actions.save')) ?></button>
|
||||
<a href="/settings/integrations/shoppro" class="btn btn--secondary"><?= $e($t('settings.integrations.actions.new')) ?></a>
|
||||
<?php if ($isEdit): ?>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn--secondary"
|
||||
formaction="/settings/integrations/shoppro/test"
|
||||
formmethod="post"
|
||||
><?= $e($t('settings.integrations.actions.test_now')) ?></button>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn--secondary"
|
||||
formaction="/settings/integrations/shoppro/import-offers-cache"
|
||||
formmethod="post"
|
||||
><?= $e($t('settings.integrations.actions.import_offers_cache')) ?></button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php if ($selected !== null && !empty($tests)): ?>
|
||||
<h3 class="section-title mt-16"><?= $e($t('settings.integrations.logs_title')) ?></h3>
|
||||
<div class="table-wrap mt-12">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= $e($t('settings.integrations.logs.fields.tested_at')) ?></th>
|
||||
<th><?= $e($t('settings.integrations.logs.fields.status')) ?></th>
|
||||
<th><?= $e($t('settings.integrations.logs.fields.http_code')) ?></th>
|
||||
<th><?= $e($t('settings.integrations.logs.fields.message')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($tests as $test): ?>
|
||||
<?php $httpCode = $test['http_code'] ?? null; ?>
|
||||
<tr>
|
||||
<td><?= $e((string) ($test['tested_at'] ?? '')) ?></td>
|
||||
<td><?= $e((string) ($test['status'] ?? '')) ?></td>
|
||||
<td><?= $e($httpCode === null ? '-' : (string) $httpCode) ?></td>
|
||||
<td><?= $e((string) ($test['message'] ?? '')) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</section>
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
$integrationsList = is_array($integrations ?? null) ? $integrations : [];
|
||||
$selectedIntegrationId = max(0, (int) ($selectedIntegrationId ?? 0));
|
||||
$shopProStatusesList = is_array($shopProStatuses ?? null) ? $shopProStatuses : [];
|
||||
$orderProOptions = is_array($orderProStatusOptions ?? null) ? $orderProStatusOptions : [];
|
||||
?>
|
||||
|
||||
<section class="card">
|
||||
<h1><?= $e($t('settings.title')) ?></h1>
|
||||
<p class="muted"><?= $e($t('settings.description')) ?></p>
|
||||
<nav class="settings-nav mt-16" aria-label="<?= $e($t('settings.submenu_label')) ?>">
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'database' ? ' is-active' : '' ?>" href="/settings/database"><?= $e($t('settings.database.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'integrations' ? ' is-active' : '' ?>" href="/settings/integrations/shoppro"><?= $e($t('settings.integrations.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'order_statuses' ? ' is-active' : '' ?>" href="/settings/order-statuses"><?= $e($t('settings.order_statuses.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'cron' ? ' is-active' : '' ?>" href="/settings/cron"><?= $e($t('settings.cron.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'gs1' ? ' is-active' : '' ?>" href="/settings/gs1"><?= $e($t('settings.gs1.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'products' ? ' is-active' : '' ?>" href="/settings/products"><?= $e($t('settings.products.title')) ?></a>
|
||||
</nav>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<h2 class="section-title"><?= $e($t('settings.order_statuses.title')) ?></h2>
|
||||
<p class="muted mt-12"><?= $e($t('settings.order_statuses.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; ?>
|
||||
|
||||
<form method="get" action="/settings/order-statuses" class="mt-16">
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('settings.order_statuses.integration')) ?></span>
|
||||
<select class="form-control" name="integration_id" onchange="this.form.submit()">
|
||||
<?php if ($integrationsList === []): ?>
|
||||
<option value="0"><?= $e($t('settings.order_statuses.no_integrations')) ?></option>
|
||||
<?php else: ?>
|
||||
<?php foreach ($integrationsList as $integration): ?>
|
||||
<?php $id = max(0, (int) ($integration['id'] ?? 0)); ?>
|
||||
<?php if ($id <= 0) continue; ?>
|
||||
<option value="<?= $e((string) $id) ?>"<?= $id === $selectedIntegrationId ? ' selected' : '' ?>>
|
||||
<?= $e((string) ($integration['name'] ?? ('#' . $id))) ?> (ID: <?= $e((string) $id) ?>)
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
</label>
|
||||
</form>
|
||||
|
||||
<?php if ($selectedIntegrationId > 0): ?>
|
||||
<form action="/settings/order-statuses/save" method="post" class="mt-16">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="integration_id" value="<?= $e((string) $selectedIntegrationId) ?>">
|
||||
|
||||
<div class="table-wrap">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= $e($t('settings.order_statuses.fields.shoppro_code')) ?></th>
|
||||
<th><?= $e($t('settings.order_statuses.fields.shoppro_name')) ?></th>
|
||||
<th><?= $e($t('settings.order_statuses.fields.orderpro_status')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($shopProStatusesList === []): ?>
|
||||
<tr>
|
||||
<td colspan="3" class="muted"><?= $e($t('settings.order_statuses.empty')) ?></td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($shopProStatusesList as $status): ?>
|
||||
<?php
|
||||
$shopCode = trim((string) ($status['code'] ?? ''));
|
||||
if ($shopCode === '') continue;
|
||||
$shopName = trim((string) ($status['name'] ?? $shopCode));
|
||||
$selectedOrderPro = trim((string) ($status['mapped_orderpro_status'] ?? ''));
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<?= $e($shopCode) ?>
|
||||
<input type="hidden" name="shoppro_names[<?= $e($shopCode) ?>]" value="<?= $e($shopName) ?>">
|
||||
</td>
|
||||
<td><?= $e($shopName) ?></td>
|
||||
<td>
|
||||
<select class="form-control" name="mappings[<?= $e($shopCode) ?>]">
|
||||
<option value=""><?= $e($t('settings.order_statuses.fields.no_mapping')) ?></option>
|
||||
<?php foreach ($orderProOptions as $orderProCode => $orderProLabel): ?>
|
||||
<option value="<?= $e((string) $orderProCode) ?>"<?= $selectedOrderPro === (string) $orderProCode ? ' selected' : '' ?>>
|
||||
<?= $e((string) $orderProLabel) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="form-actions mt-16">
|
||||
<button type="submit" class="btn btn--primary"><?= $e($t('settings.order_statuses.actions.save')) ?></button>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
@@ -0,0 +1,40 @@
|
||||
<section class="card">
|
||||
<h1><?= $e($t('settings.title')) ?></h1>
|
||||
<p class="muted"><?= $e($t('settings.description')) ?></p>
|
||||
|
||||
<nav class="settings-nav mt-16" aria-label="<?= $e($t('settings.submenu_label')) ?>">
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'database' ? ' is-active' : '' ?>" href="/settings/database"><?= $e($t('settings.database.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'integrations' ? ' is-active' : '' ?>" href="/settings/integrations/shoppro"><?= $e($t('settings.integrations.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'order_statuses' ? ' is-active' : '' ?>" href="/settings/order-statuses"><?= $e($t('settings.order_statuses.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'cron' ? ' is-active' : '' ?>" href="/settings/cron"><?= $e($t('settings.cron.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'gs1' ? ' is-active' : '' ?>" href="/settings/gs1"><?= $e($t('settings.gs1.title')) ?></a>
|
||||
<a class="settings-nav__link<?= ($activeSettings ?? '') === 'products' ? ' is-active' : '' ?>" href="/settings/products"><?= $e($t('settings.products.title')) ?></a>
|
||||
</nav>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<h2 class="section-title"><?= $e($t('settings.products.title')) ?></h2>
|
||||
<p class="muted mt-12"><?= $e($t('settings.products.description')) ?></p>
|
||||
|
||||
<form action="/settings/products/save" method="post" class="mt-16">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label"><?= $e($t('settings.products.fields.sku_format')) ?></span>
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="products_sku_format"
|
||||
maxlength="128"
|
||||
value="<?= $e((string) ($productsSkuFormat ?? 'PP000000')) ?>"
|
||||
placeholder="PP000000"
|
||||
>
|
||||
</label>
|
||||
|
||||
<p class="muted mt-12"><?= $e($t('settings.products.sku_format_hint')) ?></p>
|
||||
|
||||
<div class="form-actions mt-16">
|
||||
<button type="submit" class="btn btn--primary"><?= $e($t('settings.products.actions.save')) ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
Reference in New Issue
Block a user