Restructure Support dashboard into 2 KPI groups (Tickets + Contracts) with section dividers, merge escalations and recommendations into "Attention needed". Redesign Sales dashboard with clickable contract timeline and dialog. Add CSS shimmer skeleton loading for all dashboard tabs and aside. Unify vertical rhythm via * + * sibling combinator on tab content containers. Remove ~265 lines of dead CSS (contract cards, escalation styles) and unused JS functions. Refactor hydrateTicketCategoryFilter into generic hydrateSelectFilter. Fix role="button" CSS bleed from shell and remove extra future year from timeline. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
259 lines
10 KiB
PHTML
259 lines
10 KiB
PHTML
<?php
|
|
|
|
$breadcrumbs = [
|
|
['label' => t('Home'), 'path' => 'admin'],
|
|
['label' => t('Imports')],
|
|
];
|
|
|
|
$hasAllowedProfile = !empty($allowedProfileOptions);
|
|
|
|
$renderErrorTable = static function (array $rows): void {
|
|
if (!$rows) {
|
|
?>
|
|
<div class="notice" data-variant="success"><?php e(t('No row errors found')); ?></div>
|
|
<?php
|
|
return;
|
|
}
|
|
?>
|
|
<div class="app-list-table">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th><?php e(t('Row')); ?></th>
|
|
<th><?php e(t('Reference')); ?></th>
|
|
<th><?php e(t('Code')); ?></th>
|
|
<th><?php e(t('Message')); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($rows as $row): ?>
|
|
<tr>
|
|
<td><?php e((string) ($row['row_number'] ?? '')); ?></td>
|
|
<td><?php e((string) ($row['identifier'] ?? '')); ?></td>
|
|
<td><span class="badge" data-variant="neutral"><?php e((string) ($row['code'] ?? '')); ?></span></td>
|
|
<td><?php e((string) ($row['message'] ?? '')); ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php
|
|
};
|
|
?>
|
|
|
|
<div class="app-details-container">
|
|
<section>
|
|
<?php require templatePath('partials/app-breadcrumb.phtml'); ?>
|
|
<div class="app-details-titlebar" data-detail-unsaved-message="<?php e(t('You have unsaved changes. Leave without saving?')); ?>">
|
|
<h1><?php e(t('Imports')); ?></h1>
|
|
</div>
|
|
|
|
<?php if (!empty($errors)): ?>
|
|
<div class="notice" data-variant="error">
|
|
<ul>
|
|
<?php foreach ($errors as $error): ?>
|
|
<li><?php e($error); ?></li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($hasAllowedProfile): ?>
|
|
<div class="app-tabs">
|
|
<div class="app-tabs-nav">
|
|
<?php foreach ($allowedProfileOptions as $option): ?>
|
|
<?php
|
|
$tabType = (string) ($option['key'] ?? '');
|
|
$tabLabel = (string) ($option['label'] ?? $tabType);
|
|
$isActiveTab = $selectedType === $tabType ? 'is-active' : '';
|
|
?>
|
|
<a href="<?php e('admin/imports?type=' . urlencode($tabType)); ?>" class="<?php e($isActiveTab); ?>">
|
|
<?php e($tabLabel); ?>
|
|
</a>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if (!$hasAllowedProfile): ?>
|
|
<div class="notice" data-variant="warning">
|
|
<?php e(t('You do not have permission to run imports.')); ?>
|
|
</div>
|
|
<?php elseif ($step === 'upload'): ?>
|
|
<div>
|
|
<?php if ($selectedType !== ''): ?>
|
|
<blockquote data-variant="info">
|
|
<p><?php e(t('Here is a CSV template for this import profile.')); ?></p>
|
|
<a class="primary" href="<?php e('admin/imports/sample/' . urlencode($selectedType)); ?>">
|
|
<?php e(t('Download sample CSV')); ?>
|
|
</a>
|
|
</blockquote>
|
|
<?php endif; ?>
|
|
<form id="imports-upload-form" method="post" enctype="multipart/form-data" data-standard-detail-form="1">
|
|
<input type="hidden" name="<?php e($csrfKey); ?>" value="<?php e($csrfToken); ?>">
|
|
<input type="hidden" name="stage" value="analyze">
|
|
<input type="hidden" name="type" value="<?php e($selectedType); ?>">
|
|
<div class="grid">
|
|
<label>
|
|
<span><?php e(t('CSV file')); ?></span>
|
|
<input type="file" name="csv_file" accept=".csv,.txt,text/csv" required>
|
|
<small><?php e(t('Max size 10MB, max 20000 rows')); ?></small>
|
|
</label>
|
|
</div>
|
|
<button type="submit"><?php e(t('Analyze file')); ?></button>
|
|
</form>
|
|
</div>
|
|
<?php elseif ($step === 'mapping'): ?>
|
|
<form id="imports-mapping-form" method="post" data-standard-detail-form="1">
|
|
<input type="hidden" name="<?php e($csrfKey); ?>" value="<?php e($csrfToken); ?>">
|
|
<input type="hidden" name="stage" value="preview">
|
|
<input type="hidden" name="type" value="<?php e($selectedType); ?>">
|
|
<input type="hidden" name="token" value="<?php e($token !== '' ? $token : (string) ($state['token'] ?? '')); ?>">
|
|
|
|
<blockquote data-variant="info">
|
|
<p class="muted"><?php e(t('Required fields') . ': ' . $requiredTargetsHint); ?></p>
|
|
</blockquote>
|
|
<div class="app-list-table">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th><?php e(t('CSV header')); ?></th>
|
|
<th><?php e(t('Target field')); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($headers as $header): ?>
|
|
<?php $currentTarget = (string) ($mapping[$header] ?? ''); ?>
|
|
<tr>
|
|
<td><?php e((string) $header); ?></td>
|
|
<td>
|
|
<select style="margin:0;" name="mapping[<?php e((string) $header); ?>]">
|
|
<option value=""><?php e(t('Ignore column')); ?></option>
|
|
<?php foreach ($allowedTargets as $target => $label): ?>
|
|
<option value="<?php e($target); ?>" <?php if ($currentTarget === $target) { ?>selected<?php } ?>>
|
|
<?php e($label); ?><?php if (in_array($target, $requiredTargets, true)) { ?> *<?php } ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<hr>
|
|
<div class="app-list-titlebar-actions">
|
|
<a class="outline secondary" role="button" href="admin/imports"><?php e(t('Back')); ?></a>
|
|
<button type="submit"><?php e(t('Run preview')); ?></button>
|
|
</div>
|
|
</form>
|
|
<?php elseif ($step === 'preview' && is_array($preview)): ?>
|
|
<div>
|
|
<blockquote data-variant="info">
|
|
<p><?php e(t('Preview')); ?></p>
|
|
</blockquote>
|
|
</div>
|
|
<div class="grid">
|
|
<label>
|
|
<span><?php e(t('Rows total')); ?></span>
|
|
<input type="text" value="<?php e((string) ($preview['rows_total'] ?? 0)); ?>" readonly>
|
|
</label>
|
|
<label>
|
|
<span><?php e(t('Valid rows')); ?></span>
|
|
<input type="text" value="<?php e((string) ($preview['valid_rows'] ?? 0)); ?>" readonly>
|
|
</label>
|
|
<label>
|
|
<span><?php e(t('Would create')); ?></span>
|
|
<input type="text" value="<?php e((string) ($preview['would_create'] ?? 0)); ?>" readonly>
|
|
</label>
|
|
<label>
|
|
<span><?php e(t('Would skip')); ?></span>
|
|
<input type="text" value="<?php e((string) ($preview['would_skip'] ?? 0)); ?>" readonly>
|
|
</label>
|
|
<label>
|
|
<span><?php e(t('Would fail')); ?></span>
|
|
<input type="text" value="<?php e((string) ($preview['would_fail'] ?? 0)); ?>" readonly>
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<h3><?php e(t('Row issues')); ?></h3>
|
|
<?php $renderErrorTable(is_array($preview['errors'] ?? null) ? $preview['errors'] : []); ?>
|
|
<hr>
|
|
<form id="imports-commit-form" method="post" data-standard-detail-form="1">
|
|
<input type="hidden" name="<?php e($csrfKey); ?>" value="<?php e($csrfToken); ?>">
|
|
<input type="hidden" name="stage" value="commit">
|
|
<input type="hidden" name="type" value="<?php e($selectedType); ?>">
|
|
<input type="hidden" name="token" value="<?php e($token); ?>">
|
|
<?php foreach ($mapping as $header => $target): ?>
|
|
<input type="hidden" name="mapping[<?php e((string) $header); ?>]" value="<?php e((string) $target); ?>">
|
|
<?php endforeach; ?>
|
|
<div class="app-list-titlebar-actions">
|
|
<a class="outline secondary" role="button" href="admin/imports"><?php e(t('Cancel')); ?></a>
|
|
<button type="submit"><?php e(t('Start import')); ?></button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<?php elseif ($step === 'result' && is_array($result)): ?>
|
|
<div>
|
|
<blockquote data-variant="info">
|
|
<p><?php e(t('Import finished')); ?></p>
|
|
</blockquote>
|
|
</div>
|
|
<div class="grid">
|
|
<label>
|
|
<span><?php e(t('Processed')); ?></span>
|
|
<input type="text" value="<?php e((string) ($result['processed'] ?? 0)); ?>" readonly>
|
|
</label>
|
|
<label>
|
|
<span><?php e(t('Created')); ?></span>
|
|
<input type="text" value="<?php e((string) ($result['created'] ?? 0)); ?>" readonly>
|
|
</label>
|
|
<label>
|
|
<span><?php e(t('Skipped')); ?></span>
|
|
<input type="text" value="<?php e((string) ($result['skipped'] ?? 0)); ?>" readonly>
|
|
</label>
|
|
<label>
|
|
<span><?php e(t('Failed')); ?></span>
|
|
<input type="text" value="<?php e((string) ($result['failed'] ?? 0)); ?>" readonly>
|
|
</label>
|
|
</div>
|
|
|
|
<h3><?php e(t('Row issues')); ?></h3>
|
|
<?php $renderErrorTable(is_array($result['errors'] ?? null) ? $result['errors'] : []); ?>
|
|
<hr>
|
|
<p>
|
|
<a class="outline secondary" role="button" href="admin/imports"><?php e(t('Import another file')); ?></a>
|
|
</p>
|
|
<?php endif; ?>
|
|
</section>
|
|
|
|
<aside id="app-details-aside-section">
|
|
<div class="app-details-aside-section">
|
|
<hgroup>
|
|
<h2><?php e($selectedProfileLabel); ?></h2>
|
|
<p><small><?php e(t('Import')); ?></small></p>
|
|
</hgroup>
|
|
<hr>
|
|
<details open data-details-always-open>
|
|
<summary><?php e(t('How the import works')); ?></summary>
|
|
<hr>
|
|
<p><?php e(t('1. Upload CSV and click analyze')); ?></p>
|
|
<p><?php e(t('2. Map your CSV columns to system fields')); ?></p>
|
|
<p><?php e(t('3. Review preview counters and row errors')); ?></p>
|
|
<p><?php e(t('4. Start import for valid rows')); ?></p>
|
|
</details>
|
|
<hr>
|
|
<details open data-details-always-open>
|
|
<summary><?php e(t('Rules')); ?></summary>
|
|
<hr>
|
|
<p><?php e(t('Create-only: existing entries are skipped')); ?></p>
|
|
<p><?php e(t('Required fields') . ': ' . $requiredTargetsHint); ?></p>
|
|
<?php if (!empty($showAssignmentPermissionHint)): ?>
|
|
<p><?php e(t('Assignment columns require additional permission.')); ?></p>
|
|
<?php endif; ?>
|
|
<p><?php e(t('Max size 10MB, max 20000 rows')); ?></p>
|
|
</details>
|
|
</div>
|
|
</aside>
|
|
</div>
|