Files
breadcrumb-the-shire/pages/admin/scheduled-jobs/edit(default).phtml
fs b749b5d192 feat: centralize breadcrumbs in topbar and remove history navigation
Move breadcrumb rendering from individual page templates into the core
topbar. Each page now sets $breadcrumbs in its action .php file; the
topbar renders it automatically via the shared partial.

- Remove global back/forward buttons and app-nav-history.js component
- Remove Alt+Arrow keyboard shortcuts for history navigation
- Render breadcrumb in topbar-left section (replaces button area)
- Clean up breadcrumb CSS: context-neutral base (flex, no margin)
- Recalculate sticky titlebar offset in details container
- Migrate all 41 pages (core + helpdesk, audit, addressbook, api-docs)
- Add missing breadcrumbs to addressbook detail view
- Update architecture contract tests (nav-history references removed)

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

262 lines
11 KiB
PHTML

<?php
use MintyPHP\Domain\Taxonomy\ScheduledJobStatus;
use MintyPHP\Session;
$job = is_array($job ?? null) ? $job : [];
$form = is_array($form ?? null) ? $form : [];
$canManage = (bool) ($canManage ?? false);
$canRunNow = (bool) ($canRunNow ?? false);
$errors = is_array($errors ?? null) ? $errors : [];
$jobId = (int) ($job['id'] ?? 0);
$readonlyAttr = $canManage ? '' : 'readonly';
$disabledAttr = $canManage ? '' : 'disabled';
$selectedWeekdays = array_map('intval', (array) ($form['schedule_weekdays'] ?? []));
$weekdayOptions = [
1 => t('Mon'),
2 => t('Tue'),
3 => t('Wed'),
4 => t('Thu'),
5 => t('Fri'),
6 => t('Sat'),
7 => t('Sun'),
];
$status = ScheduledJobStatus::tryNormalize((string) ($job['last_run_status'] ?? ''));
$scheduleSummary = (string) ($scheduleSummary ?? '-');
$statusVariant = $status?->badgeVariant() ?? 'neutral';
$asideActions = [];
if ($canRunNow) {
$asideActions[] = [
'type' => 'form',
'method' => 'post',
'action' => 'admin/scheduled-jobs/run/' . $jobId,
'label' => t('Run now'),
];
}
?>
<div class="app-details-container">
<section>
<?php
$titleText = $canManage ? t('Edit scheduled job') : t('View scheduled job');
$titlebar = [
'title' => $titleText,
'backHref' => 'admin/scheduled-jobs',
'backTitle' => t('Back'),
'actions' => $canManage ? [
[
'form' => 'scheduled-job-form',
'name' => 'action',
'value' => 'save',
'class' => 'primary',
'label' => t('Save'),
],
] : [],
];
require templatePath('partials/app-details-titlebar.phtml');
?>
<?php
$validationSummaryErrors = $validationSummaryErrors ?? ($errors ?? []);
require templatePath('partials/app-details-validation-summary.phtml');
?>
<div class="app-details-content">
<form method="post" id="scheduled-job-form" data-details-storage="scheduled-job-edit-v1" data-standard-detail-form="1">
<details open data-details-always-open>
<summary><?php e(t('Master data')); ?></summary>
<hr>
<div class="grid">
<label class="app-field">
<span><?php e(t('Enabled')); ?></span>
<select name="enabled" <?php e($disabledAttr); ?>>
<option value="1" <?php e(((int) ($form['enabled'] ?? 0) === 1) ? 'selected' : ''); ?>><?php e(t('Enabled')); ?></option>
<option value="0" <?php e(((int) ($form['enabled'] ?? 0) !== 1) ? 'selected' : ''); ?>><?php e(t('Disabled')); ?></option>
</select>
</label>
<label class="app-field">
<span><?php e(t('Timezone')); ?></span>
<input type="text" name="timezone" value="<?php e((string) ($form['timezone'] ?? '')); ?>" placeholder="<?php e((string) (defined('APP_TIMEZONE') ? APP_TIMEZONE : 'UTC')); ?>" <?php e($readonlyAttr); ?>>
</label>
</div>
</details>
<hr>
<details open>
<summary><?php e(t('Schedule')); ?></summary>
<hr>
<div class="grid">
<label class="app-field">
<span><?php e(t('Schedule type')); ?></span>
<select name="schedule_type" id="scheduled-job-schedule-type" <?php e($disabledAttr); ?>>
<option value="hourly" <?php e(((string) ($form['schedule_type'] ?? '') === 'hourly') ? 'selected' : ''); ?>><?php e(t('Hourly')); ?></option>
<option value="daily" <?php e(((string) ($form['schedule_type'] ?? '') === 'daily') ? 'selected' : ''); ?>><?php e(t('Daily')); ?></option>
<option value="weekly" <?php e(((string) ($form['schedule_type'] ?? '') === 'weekly') ? 'selected' : ''); ?>><?php e(t('Weekly')); ?></option>
</select>
</label>
<label class="app-field">
<span><?php e(t('Schedule interval')); ?></span>
<input type="number" min="1" max="365" name="schedule_interval" value="<?php e((string) ((int) ($form['schedule_interval'] ?? 1))); ?>" <?php e($readonlyAttr); ?>>
</label>
</div>
<label class="app-field" data-schedule-time-wrap>
<span><?php e(t('Schedule time')); ?></span>
<input type="time" name="schedule_time" value="<?php e((string) ($form['schedule_time'] ?? '')); ?>" <?php e($readonlyAttr); ?>>
</label>
<fieldset data-schedule-weekdays-wrap>
<legend><small><?php e(t('Weekdays')); ?></small></legend>
<div class="grid">
<?php foreach ($weekdayOptions as $weekdayValue => $weekdayLabel): ?>
<label class="app-field app-checkbox">
<input
type="checkbox"
name="schedule_weekdays[]"
value="<?php e((string) $weekdayValue); ?>"
<?php if (in_array((int) $weekdayValue, $selectedWeekdays, true)) { ?>checked<?php } ?>
<?php e($disabledAttr); ?>>
<span><?php e($weekdayLabel); ?></span>
</label>
<?php endforeach; ?>
</div>
</fieldset>
<label class="app-field app-checkbox">
<input type="checkbox" name="catchup_once" value="1" <?php if ((int) ($form['catchup_once'] ?? 0) === 1) { ?>checked<?php } ?> <?php e($disabledAttr); ?>>
<span><?php e(t('Catch up once')); ?></span>
</label>
</details>
<?php Session::getCsrfInput(); ?>
</form>
<hr>
<details>
<summary><?php e(t('Run history')); ?></summary>
<hr>
<div class="app-list-toolbar">
<label class="app-field">
<span><?php e(t('Search')); ?></span>
<input type="search" id="scheduled-job-runs-search" placeholder="<?php e(t('Search...')); ?>">
</label>
<label class="app-field">
<span><?php e(t('Status')); ?></span>
<select id="scheduled-job-runs-status-filter">
<option value=""><?php e(t('All statuses')); ?></option>
<option value="success"><?php e(t('Success')); ?></option>
<option value="failed"><?php e(t('Failed')); ?></option>
<option value="skipped"><?php e(t('Skipped')); ?></option>
</select>
</label>
<label class="app-field">
<span><?php e(t('Trigger')); ?></span>
<select id="scheduled-job-runs-trigger-filter">
<option value=""><?php e(t('All triggers')); ?></option>
<option value="scheduler"><?php e(t('Scheduler')); ?></option>
<option value="manual"><?php e(t('Manual')); ?></option>
</select>
</label>
</div>
<div class="app-list-table">
<div id="scheduled-job-runs-grid"></div>
</div>
</details>
</div>
</section>
<aside id="app-details-aside-section">
<div class="app-details-aside-section">
<hgroup>
<h2><?php e((string) ($job['label'] ?? $job['job_key'] ?? '')); ?></h2>
<p><?php e(t('Scheduled jobs')); ?></p>
</hgroup>
<div class="badge-list">
<span class="badge" data-variant="<?php e($statusVariant); ?>"><?php e($status !== null ? t($status->labelToken()) : '-'); ?></span>
</div>
<hr>
<?php
if ($asideActions) {
require templatePath('partials/app-details-aside-actions.phtml');
}
?>
<details name="scheduled-job-ids">
<summary><?php e(t('IDs')); ?></summary>
<hr>
<div>
<small><?php e(t('ID')); ?></small>
<p>
<span class="badge" data-variant="neutral" data-copy="true" data-copy-value="<?php e((string) ($job['id'] ?? '')); ?>">
<?php e((string) ($job['id'] ?? '-')); ?>
</span>
</p>
</div>
<div>
<small><?php e(t('Job key')); ?></small>
<p>
<span class="badge" data-variant="neutral" data-copy="true" data-copy-value="<?php e((string) ($job['job_key'] ?? '')); ?>">
<?php e((string) ($job['job_key'] ?? '-')); ?>
</span>
</p>
</div>
</details>
<hr>
<details name="scheduled-job-meta" open>
<summary><?php e(t('Status & meta')); ?></summary>
<hr>
<p><small><?php e(t('Description')); ?></small><br><?php e((string) ($job['description'] ?? '') !== '' ? (string) $job['description'] : '-'); ?></p>
<p><small><?php e(t('Schedule')); ?></small><br><?php e($scheduleSummary !== '' ? $scheduleSummary : '-'); ?></p>
<p><small><?php e(t('Timezone')); ?></small><br><?php e((string) ($job['timezone'] ?? '') !== '' ? (string) $job['timezone'] : '-'); ?></p>
<div>
<small><?php e(t('Status')); ?></small>
<p><?php e($status !== null ? t($status->labelToken()) : '-'); ?></p>
</div>
<p><small><?php e(t('Next run')); ?></small><br><?php e(dt((string) ($job['next_run_at'] ?? '')) ?: '-'); ?></p>
<p><small><?php e(t('Last run')); ?></small><br><?php e(dt((string) ($job['last_run_finished_at'] ?? '')) ?: '-'); ?></p>
<div>
<small><?php e(t('Last error')); ?></small>
<p><?php e(trim((string) ($job['last_error_code'] ?? '')) !== '' ? (string) $job['last_error_code'] : ((string) ($job['last_error_message'] ?? '') !== '' ? (string) $job['last_error_message'] : '-')); ?></p>
</div>
</details>
<hr>
<details name="scheduled-job-audit">
<summary><?php e(t('Audit')); ?></summary>
<hr>
<p><small><?php e(t('Created')); ?></small><br><?php e(dt((string) ($job['created_at'] ?? '')) ?: '-'); ?></p>
<p><small><?php e(t('Modified')); ?></small><br><?php e(dt((string) ($job['updated_at'] ?? '')) ?: '-'); ?></p>
</details>
</div>
</aside>
</div>
<?php
$gridLang = json_decode(appBufferValue('grid_lang'), true);
if (!is_array($gridLang)) {
$gridLang = [];
}
$pageConfig = [
'jobId' => (int) $jobId,
'gridLang' => $gridLang,
'search' => [
'input' => '#scheduled-job-runs-search',
'param' => 'search',
'debounce' => 250,
],
'filters' => [
['input' => '#scheduled-job-runs-status-filter', 'param' => 'status'],
['input' => '#scheduled-job-runs-trigger-filter', 'param' => 'trigger_type'],
],
'labels' => [
'started' => t('Started'),
'status' => t('Status'),
'trigger' => t('Trigger'),
'durationMs' => t('Duration (ms)'),
'errorCode' => t('Error code'),
'user' => t('User'),
'runUuid' => t('Run UUID'),
],
];
?>
<script src="<?php e(assetVersion('vendor/gridjs/gridjs.umd.js')); ?>"></script>
<script type="application/json" id="page-config-admin-scheduled-jobs-edit"><?php gridJsonForJs($pageConfig); ?></script>
<script type="module" src="<?php e(assetVersion('js/pages/admin-scheduled-jobs-edit.js')); ?>"></script>