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:
2026-03-03 01:32:28 +01:00
parent d1576bc4ab
commit c489891d15
106 changed files with 11669 additions and 5091 deletions

View File

@@ -1,155 +0,0 @@
<?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 ?? '') === '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>

View File

@@ -8,18 +8,6 @@ $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 ?? '') === '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)): ?>

View File

@@ -1,57 +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 ?? '') === '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>

View File

@@ -1,189 +0,0 @@
<?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 ?? '') === '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>
</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>
<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>

View File

@@ -1,39 +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 ?? '') === '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>

View File

@@ -0,0 +1,341 @@
<?php
$groupsList = is_array($groups ?? null) ? $groups : [];
$statusesList = is_array($statuses ?? null) ? $statuses : [];
$statusesByGroup = [];
foreach ($groupsList as $groupRow) {
$groupId = max(0, (int) ($groupRow['id'] ?? 0));
if ($groupId <= 0) {
continue;
}
$statusesByGroup[$groupId] = [];
}
foreach ($statusesList as $statusRow) {
$groupId = max(0, (int) ($statusRow['group_id'] ?? 0));
if ($groupId <= 0) {
continue;
}
if (!isset($statusesByGroup[$groupId])) {
$statusesByGroup[$groupId] = [];
}
$statusesByGroup[$groupId][] = $statusRow;
}
?>
<section class="card">
<h2 class="section-title"><?= $e($t('settings.statuses.title')) ?></h2>
<p class="muted mt-12"><?= $e($t('settings.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; ?>
</section>
<section class="card mt-16">
<nav class="content-tabs-nav" aria-label="<?= $e($t('settings.statuses.tabs.label')) ?>">
<button type="button" class="content-tab-btn is-active" data-tab-target="statuses-tab">
<?= $e($t('settings.statuses.tabs.statuses')) ?>
</button>
<button type="button" class="content-tab-btn" data-tab-target="groups-tab">
<?= $e($t('settings.statuses.tabs.groups')) ?>
</button>
</nav>
<div class="content-tab-panel is-active" data-tab-panel="statuses-tab">
<h2 class="section-title"><?= $e($t('settings.statuses.statuses.create_title')) ?></h2>
<form class="statuses-form mt-16" action="/settings/statuses/create" method="post">
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
<label class="form-field">
<span class="field-label"><?= $e($t('settings.statuses.fields.group')) ?></span>
<select name="group_id" class="form-control" required>
<option value=""><?= $e($t('settings.statuses.fields.group_placeholder')) ?></option>
<?php foreach ($groupsList as $group): ?>
<?php $groupId = max(0, (int) ($group['id'] ?? 0)); ?>
<?php if ($groupId <= 0) continue; ?>
<option value="<?= $e((string) $groupId) ?>"><?= $e((string) ($group['name'] ?? ('#' . $groupId))) ?></option>
<?php endforeach; ?>
</select>
</label>
<label class="form-field">
<span class="field-label"><?= $e($t('settings.statuses.fields.name')) ?></span>
<input type="text" name="name" class="form-control" maxlength="120" required>
</label>
<label class="field-inline">
<input type="hidden" name="is_active" value="0">
<input type="checkbox" name="is_active" value="1" checked>
<span><?= $e($t('settings.statuses.fields.is_active')) ?></span>
</label>
<p class="muted statuses-hint"><?= $e($t('settings.statuses.hints.code_auto')) ?></p>
<div class="form-actions">
<button type="submit" class="btn btn--primary"><?= $e($t('settings.statuses.actions.add_status')) ?></button>
</div>
</form>
<h2 class="section-title mt-16"><?= $e($t('settings.statuses.statuses.list_title')) ?></h2>
<?php if ($groupsList === []): ?>
<div class="alert alert--warning mt-12"><?= $e($t('settings.statuses.groups.empty')) ?></div>
<?php endif; ?>
<?php foreach ($groupsList as $group): ?>
<?php
$groupId = max(0, (int) ($group['id'] ?? 0));
if ($groupId <= 0) {
continue;
}
$groupName = (string) ($group['name'] ?? ('#' . $groupId));
$groupColor = (string) ($group['color_hex'] ?? '#64748b');
$groupStatuses = is_array($statusesByGroup[$groupId] ?? null) ? $statusesByGroup[$groupId] : [];
?>
<article class="statuses-group-block mt-16">
<header class="statuses-group-block__head">
<h3 class="statuses-group-block__title">
<span class="statuses-color-dot" style="background: <?= $e($groupColor) ?>;"></span>
<?= $e($groupName) ?>
</h3>
<span class="muted"><?= $e($t('settings.statuses.hints.drag_statuses')) ?> <?= $e($t('settings.statuses.hints.auto_save_order')) ?></span>
</header>
<ul class="statuses-dnd-list js-dnd-list" data-order-scope="statuses-<?= $e((string) $groupId) ?>">
<?php foreach ($groupStatuses as $status): ?>
<?php $statusId = max(0, (int) ($status['id'] ?? 0)); ?>
<?php if ($statusId <= 0) continue; ?>
<li class="statuses-dnd-item" draggable="true" data-id="<?= $e((string) $statusId) ?>">
<div class="statuses-dnd-item__drag" title="<?= $e($t('settings.statuses.hints.drag_handle')) ?>">::</div>
<div class="statuses-dnd-item__content">
<form action="/settings/statuses/update" method="post" class="statuses-inline-form statuses-inline-form--row">
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
<input type="hidden" name="status_id" value="<?= $e((string) $statusId) ?>">
<input type="text" name="name" class="form-control" maxlength="120" required value="<?= $e((string) ($status['name'] ?? '')) ?>" aria-label="<?= $e($t('settings.statuses.fields.name')) ?>">
<select name="group_id" class="form-control" required aria-label="<?= $e($t('settings.statuses.fields.group')) ?>">
<?php foreach ($groupsList as $groupOption): ?>
<?php $groupOptionId = max(0, (int) ($groupOption['id'] ?? 0)); ?>
<?php if ($groupOptionId <= 0) continue; ?>
<option value="<?= $e((string) $groupOptionId) ?>"<?= $groupOptionId === (int) ($status['group_id'] ?? 0) ? ' selected' : '' ?>>
<?= $e((string) ($groupOption['name'] ?? ('#' . $groupOptionId))) ?>
</option>
<?php endforeach; ?>
</select>
<label class="field-inline statuses-inline-check">
<input type="hidden" name="is_active" value="0">
<input type="checkbox" name="is_active" value="1"<?= ((int) ($status['is_active'] ?? 0)) === 1 ? ' checked' : '' ?>>
<span><?= $e($t('settings.statuses.fields.is_active')) ?></span>
</label>
<div class="statuses-code-readonly">
<span class="statuses-code-label"><?= $e($t('settings.statuses.fields.code')) ?>:</span>
<code><?= $e((string) ($status['code'] ?? '')) ?></code>
</div>
<button type="submit" class="btn btn--secondary"><?= $e($t('settings.statuses.actions.save')) ?></button>
</form>
<form action="/settings/statuses/delete" method="post" class="table-inline-action statuses-inline-delete" data-alert-confirm="<?= $e($t('settings.statuses.confirm.delete_status')) ?>" data-alert-confirm-title="<?= $e($t('settings.statuses.confirm.title')) ?>" data-alert-confirm-yes="<?= $e($t('settings.statuses.confirm.confirm')) ?>" data-alert-confirm-no="<?= $e($t('settings.statuses.confirm.cancel')) ?>">
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
<input type="hidden" name="status_id" value="<?= $e((string) $statusId) ?>">
<button type="submit" class="btn btn--danger"><?= $e($t('settings.statuses.actions.delete')) ?></button>
</form>
</div>
</li>
<?php endforeach; ?>
</ul>
<?php if ($groupStatuses === []): ?>
<p class="muted mt-12"><?= $e($t('settings.statuses.statuses.empty')) ?></p>
<?php endif; ?>
<?php if ($groupStatuses !== []): ?>
<form action="/settings/statuses/reorder" method="post" class="statuses-reorder-form js-reorder-form mt-12" data-order-scope="statuses-<?= $e((string) $groupId) ?>">
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
<input type="hidden" name="group_id" value="<?= $e((string) $groupId) ?>">
<div class="js-order-inputs"></div>
</form>
<?php endif; ?>
</article>
<?php endforeach; ?>
</div>
<div class="content-tab-panel" data-tab-panel="groups-tab">
<h2 class="section-title"><?= $e($t('settings.statuses.groups.create_title')) ?></h2>
<form class="statuses-form mt-16" action="/settings/status-groups" method="post">
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
<label class="form-field">
<span class="field-label"><?= $e($t('settings.statuses.fields.name')) ?></span>
<input type="text" name="name" class="form-control" maxlength="120" required>
</label>
<label class="form-field">
<span class="field-label"><?= $e($t('settings.statuses.fields.color')) ?></span>
<input type="color" name="color_hex" class="form-control statuses-color-input" value="#64748b">
</label>
<label class="field-inline">
<input type="hidden" name="is_active" value="0">
<input type="checkbox" name="is_active" value="1" checked>
<span><?= $e($t('settings.statuses.fields.is_active')) ?></span>
</label>
<p class="muted statuses-hint"><?= $e($t('settings.statuses.hints.code_auto')) ?></p>
<div class="form-actions">
<button type="submit" class="btn btn--primary"><?= $e($t('settings.statuses.actions.add_group')) ?></button>
</div>
</form>
<h2 class="section-title mt-16"><?= $e($t('settings.statuses.groups.list_title')) ?></h2>
<p class="muted mt-12"><?= $e($t('settings.statuses.hints.drag_groups')) ?> <?= $e($t('settings.statuses.hints.auto_save_order')) ?></p>
<ul class="statuses-dnd-list js-dnd-list mt-12" data-order-scope="groups">
<?php foreach ($groupsList as $group): ?>
<?php
$groupId = max(0, (int) ($group['id'] ?? 0));
if ($groupId <= 0) {
continue;
}
?>
<li class="statuses-dnd-item" draggable="true" data-id="<?= $e((string) $groupId) ?>">
<div class="statuses-dnd-item__drag" title="<?= $e($t('settings.statuses.hints.drag_handle')) ?>">::</div>
<div class="statuses-dnd-item__content">
<form action="/settings/status-groups/update" method="post" class="statuses-inline-form statuses-inline-form--row-group">
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
<input type="hidden" name="group_id" value="<?= $e((string) $groupId) ?>">
<input type="text" name="name" class="form-control" maxlength="120" required value="<?= $e((string) ($group['name'] ?? '')) ?>" aria-label="<?= $e($t('settings.statuses.fields.name')) ?>">
<input type="color" name="color_hex" class="form-control statuses-color-input" value="<?= $e((string) ($group['color_hex'] ?? '#64748b')) ?>" aria-label="<?= $e($t('settings.statuses.fields.color')) ?>">
<label class="field-inline statuses-inline-check">
<input type="hidden" name="is_active" value="0">
<input type="checkbox" name="is_active" value="1"<?= ((int) ($group['is_active'] ?? 0)) === 1 ? ' checked' : '' ?>>
<span><?= $e($t('settings.statuses.fields.is_active')) ?></span>
</label>
<div class="statuses-code-readonly">
<span class="statuses-code-label"><?= $e($t('settings.statuses.fields.code')) ?>:</span>
<code><?= $e((string) ($group['code'] ?? '')) ?></code>
</div>
<button type="submit" class="btn btn--secondary"><?= $e($t('settings.statuses.actions.save')) ?></button>
</form>
<form action="/settings/status-groups/delete" method="post" class="table-inline-action statuses-inline-delete" data-alert-confirm="<?= $e($t('settings.statuses.confirm.delete_group')) ?>" data-alert-confirm-title="<?= $e($t('settings.statuses.confirm.title')) ?>" data-alert-confirm-yes="<?= $e($t('settings.statuses.confirm.confirm')) ?>" data-alert-confirm-no="<?= $e($t('settings.statuses.confirm.cancel')) ?>">
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
<input type="hidden" name="group_id" value="<?= $e((string) $groupId) ?>">
<button type="submit" class="btn btn--danger"><?= $e($t('settings.statuses.actions.delete')) ?></button>
</form>
</div>
</li>
<?php endforeach; ?>
</ul>
<?php if ($groupsList === []): ?>
<p class="muted mt-12"><?= $e($t('settings.statuses.groups.empty')) ?></p>
<?php endif; ?>
<?php if ($groupsList !== []): ?>
<form action="/settings/status-groups/reorder" method="post" class="statuses-reorder-form js-reorder-form mt-12" data-order-scope="groups">
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
<div class="js-order-inputs"></div>
</form>
<?php endif; ?>
</div>
</section>
<script>
(function() {
var tabStorageKey = 'settings.statuses.active_tab';
var defaultTab = 'statuses-tab';
function activateTab(target) {
var normalizedTarget = (target === 'groups-tab' || target === 'statuses-tab') ? target : defaultTab;
tabButtons.forEach(function(btn) { btn.classList.remove('is-active'); });
tabPanels.forEach(function(panel) { panel.classList.remove('is-active'); });
var activeButton = document.querySelector('[data-tab-target="' + normalizedTarget + '"]');
var activePanel = document.querySelector('[data-tab-panel="' + normalizedTarget + '"]');
if (activeButton) activeButton.classList.add('is-active');
if (activePanel) activePanel.classList.add('is-active');
}
var tabButtons = document.querySelectorAll('[data-tab-target]');
var tabPanels = document.querySelectorAll('[data-tab-panel]');
try {
activateTab(localStorage.getItem(tabStorageKey) || defaultTab);
} catch (e) {
activateTab(defaultTab);
}
tabButtons.forEach(function(button) {
button.addEventListener('click', function() {
var target = button.getAttribute('data-tab-target');
activateTab(target);
try {
localStorage.setItem(tabStorageKey, target || defaultTab);
} catch (e) {}
});
});
function initDndList(list) {
var dragged = null;
var startIndex = -1;
function submitReorderForScope(scope) {
if (scope === '') return;
var form = document.querySelector('.js-reorder-form[data-order-scope="' + scope + '"]');
if (!form || form.getAttribute('data-submitting') === '1') return;
var sourceList = document.querySelector('.js-dnd-list[data-order-scope="' + scope + '"]');
if (!sourceList) return;
var holder = form.querySelector('.js-order-inputs');
if (!holder) return;
holder.innerHTML = '';
sourceList.querySelectorAll('.statuses-dnd-item').forEach(function(item) {
var id = item.getAttribute('data-id') || '';
if (id === '') return;
var input = document.createElement('input');
input.type = 'hidden';
input.name = 'order[]';
input.value = id;
holder.appendChild(input);
});
form.setAttribute('data-submitting', '1');
form.submit();
}
list.querySelectorAll('.statuses-dnd-item').forEach(function(item) {
item.addEventListener('dragstart', function() {
dragged = item;
startIndex = Array.prototype.indexOf.call(list.children, item);
item.classList.add('is-dragging');
});
item.addEventListener('dragend', function() {
item.classList.remove('is-dragging');
var endIndex = Array.prototype.indexOf.call(list.children, item);
var moved = startIndex >= 0 && endIndex >= 0 && startIndex !== endIndex;
var scope = list.getAttribute('data-order-scope') || '';
dragged = null;
startIndex = -1;
if (moved) {
submitReorderForScope(scope);
}
});
item.addEventListener('dragover', function(event) {
event.preventDefault();
if (!dragged || dragged === item) return;
var rect = item.getBoundingClientRect();
var before = event.clientY < rect.top + rect.height / 2;
list.insertBefore(dragged, before ? item : item.nextSibling);
});
});
}
document.querySelectorAll('.js-dnd-list').forEach(initDndList);
document.addEventListener('submit', function(event) {
var form = event.target;
if (!form || !form.matches || !form.matches('form[data-alert-confirm]')) return;
if (form.getAttribute('data-confirmed') === '1') {
form.removeAttribute('data-confirmed');
return;
}
var message = form.getAttribute('data-alert-confirm') || '';
if (message === '') return;
if (!window.OrderProAlerts || typeof window.OrderProAlerts.confirm !== 'function') return;
event.preventDefault();
window.OrderProAlerts.confirm({
title: form.getAttribute('data-alert-confirm-title') || 'Potwierdzenie',
message: message,
confirmLabel: form.getAttribute('data-alert-confirm-yes') || 'Potwierdz',
cancelLabel: form.getAttribute('data-alert-confirm-no') || 'Anuluj',
danger: true
}).then(function(accepted) {
if (!accepted) return;
form.setAttribute('data-confirmed', '1');
form.submit();
});
});
})();
</script>