Backend module: CSV import for the VITEC domain models
New "VITEC Import" module under Web: one import page per domain model (v1: Market, Solution, Product - registry-driven, adding a model is one config entry). Workflow: upload a CSV (delimiter and encoding are auto-detected, including German-Excel semicolon/Windows-1252), map CSV columns to DB fields, persist the mapping per model together with the identity field used for matching (new table tx_vitec_import_mapping, no TCA - pure tool configuration), review a unified list of CSV rows matched against the DB records (new / update with differing fields / unchanged / db-only), then apply the checked rows through DataHandler. Each importable row carries an editable JSON payload textarea - what is written is the textarea content, not the raw CSV, so editors can fix values right in the review step. The parsed CSV travels through the form as a hidden JSON field: no session state, no temp files. Importable fields are derived from TCA at runtime (scalar types only; files, categories and other relations are excluded - a flat CSV cannot carry them). Payloads are whitelisted against that field list on apply; new records require a storage pid (prefilled from existing records). BE user permissions apply via DataHandler. The module ships its own CSS (backend-import.css, loaded only by this module) using the frontend button palette from _vitec.scss: orange #f47937 for primary actions, navy #26358c for secondary actions and structure. The stray <h2>Hi</h2> debug leftover in the shared backend layout is removed (also affects the OG Image module). Deliberately out of v1: import log with three-way compare (protection against overwriting manual edits), images/relations, multiple saved mappings per model. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ use TYPO3\CMS\Core\DataHandling\DataHandler;
|
|||||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||||
use TYPO3\CMS\Core\Messaging\FlashMessageService;
|
use TYPO3\CMS\Core\Messaging\FlashMessageService;
|
||||||
|
use TYPO3\CMS\Core\Page\PageRenderer;
|
||||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||||
|
|
||||||
@@ -45,6 +46,7 @@ final class ImportController
|
|||||||
private readonly MappingRepository $mappingRepository,
|
private readonly MappingRepository $mappingRepository,
|
||||||
private readonly CsvReader $csvReader,
|
private readonly CsvReader $csvReader,
|
||||||
private readonly FlashMessageService $flashMessageService,
|
private readonly FlashMessageService $flashMessageService,
|
||||||
|
private readonly PageRenderer $pageRenderer,
|
||||||
private readonly UriBuilder $uriBuilder,
|
private readonly UriBuilder $uriBuilder,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -158,6 +160,7 @@ final class ImportController
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->pageRenderer->addCssFile('EXT:vitec/Resources/Public/Css/backend-import.css');
|
||||||
$view = $this->moduleTemplateFactory->create($request);
|
$view = $this->moduleTemplateFactory->create($request);
|
||||||
$view->assignMultiple([
|
$view->assignMultiple([
|
||||||
'models' => $this->registry->all(),
|
'models' => $this->registry->all(),
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
xmlns:n="http://typo3.org/ns/GeorgRinger/News/ViewHelpers">
|
xmlns:n="http://typo3.org/ns/GeorgRinger/News/ViewHelpers">
|
||||||
<div class="vitec-administration">
|
<div class="vitec-administration">
|
||||||
<f:flashMessages queueIdentifier="core.template.flashMessages" />
|
<f:flashMessages queueIdentifier="core.template.flashMessages" />
|
||||||
<h2>Hi</h2>
|
|
||||||
<f:render section="main"/>
|
<f:render section="main"/>
|
||||||
</div>
|
</div>
|
||||||
</html>
|
</html>
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
<div class="card" style="margin-bottom:1rem;">
|
<div class="card" style="margin-bottom:1rem;">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<strong>Field mapping</strong>
|
<strong>Field mapping</strong>
|
||||||
<f:if condition="{mappingSaved}"><span class="badge bg-info">saved mapping loaded</span></f:if>
|
<f:if condition="{mappingSaved}"><span class="vitec-badge vitec-badge-info">saved mapping loaded</span></f:if>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<table class="table table-striped table-sm" style="max-width:900px;">
|
<table class="table table-striped table-sm" style="max-width:900px;">
|
||||||
@@ -101,10 +101,10 @@
|
|||||||
<div class="card" style="margin-bottom:1rem;">
|
<div class="card" style="margin-bottom:1rem;">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<strong>Records</strong>
|
<strong>Records</strong>
|
||||||
<span class="badge bg-success">{counts.new} new</span>
|
<span class="vitec-badge vitec-badge-new">{counts.new} new</span>
|
||||||
<span class="badge bg-warning text-dark">{counts.update} update</span>
|
<span class="vitec-badge vitec-badge-update">{counts.update} update</span>
|
||||||
<span class="badge bg-secondary">{counts.unchanged} unchanged</span>
|
<span class="vitec-badge vitec-badge-unchanged">{counts.unchanged} unchanged</span>
|
||||||
<span class="badge bg-light text-dark">{counts.dbonly} only in DB</span>
|
<span class="vitec-badge vitec-badge-dbonly">{counts.dbonly} only in DB</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<table class="table table-striped table-sm">
|
<table class="table table-striped table-sm">
|
||||||
@@ -126,10 +126,10 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<f:switch expression="{row.status}">
|
<f:switch expression="{row.status}">
|
||||||
<f:case value="new"><span class="badge bg-success">new</span></f:case>
|
<f:case value="new"><span class="vitec-badge vitec-badge-new">new</span></f:case>
|
||||||
<f:case value="update"><span class="badge bg-warning text-dark">update</span></f:case>
|
<f:case value="update"><span class="vitec-badge vitec-badge-update">update</span></f:case>
|
||||||
<f:case value="unchanged"><span class="badge bg-secondary">unchanged</span></f:case>
|
<f:case value="unchanged"><span class="vitec-badge vitec-badge-unchanged">unchanged</span></f:case>
|
||||||
<f:defaultCase><span class="badge bg-light text-dark">only in DB</span></f:defaultCase>
|
<f:defaultCase><span class="vitec-badge vitec-badge-dbonly">only in DB</span></f:defaultCase>
|
||||||
</f:switch>
|
</f:switch>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
105
packages/vitec/Resources/Public/Css/backend-import.css
vendored
Normal file
105
packages/vitec/Resources/Public/Css/backend-import.css
vendored
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
/**
|
||||||
|
* VITEC Import module - look & feel aligned with the frontend button
|
||||||
|
* palette (Resources/Public/SCSS/_vitec.scss):
|
||||||
|
* orange #f47937 (hover #db6324) - primary actions
|
||||||
|
* navy #26358c (hover #1e2a70) - secondary actions / structure
|
||||||
|
* graphite #313131 - neutral emphasis
|
||||||
|
* light #cccccc / #eeeeee - muted
|
||||||
|
* Loaded only by the import module (ImportController), scoped to
|
||||||
|
* .vitec-administration so other modules stay untouched.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.vitec-administration h2 {
|
||||||
|
color: #26358c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- buttons ------------------------------------------------------- */
|
||||||
|
|
||||||
|
.vitec-administration .btn-primary {
|
||||||
|
background-color: #f47937;
|
||||||
|
border-color: #f47937;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.vitec-administration .btn-primary:hover,
|
||||||
|
.vitec-administration .btn-primary:focus,
|
||||||
|
.vitec-administration .btn-primary:active {
|
||||||
|
background-color: #db6324;
|
||||||
|
border-color: #db6324;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vitec-administration .btn-default {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #26358c;
|
||||||
|
color: #26358c;
|
||||||
|
}
|
||||||
|
.vitec-administration .btn-default:hover,
|
||||||
|
.vitec-administration .btn-default:focus {
|
||||||
|
background-color: #26358c;
|
||||||
|
border-color: #26358c;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- model tabs ---------------------------------------------------- */
|
||||||
|
|
||||||
|
.vitec-administration .nav-tabs .nav-link {
|
||||||
|
color: #26358c;
|
||||||
|
}
|
||||||
|
.vitec-administration .nav-tabs .nav-link.active {
|
||||||
|
color: #0d0d0d;
|
||||||
|
font-weight: 600;
|
||||||
|
border-bottom: 3px solid #f47937;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- cards & tables ------------------------------------------------ */
|
||||||
|
|
||||||
|
.vitec-administration .card {
|
||||||
|
border: 1px solid #d8dbe6;
|
||||||
|
}
|
||||||
|
.vitec-administration .card-header {
|
||||||
|
background-color: #26358c;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.vitec-administration thead th {
|
||||||
|
border-bottom: 2px solid #26358c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- status badges -------------------------------------------------- */
|
||||||
|
|
||||||
|
.vitec-administration .vitec-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 9px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.vitec-administration .vitec-badge-new {
|
||||||
|
background-color: #f47937;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.vitec-administration .vitec-badge-update {
|
||||||
|
background-color: #26358c;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.vitec-administration .vitec-badge-unchanged {
|
||||||
|
background-color: #cccccc;
|
||||||
|
color: #0d0d0d;
|
||||||
|
}
|
||||||
|
.vitec-administration .vitec-badge-dbonly {
|
||||||
|
background-color: #eeeeee;
|
||||||
|
color: #313131;
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
}
|
||||||
|
.vitec-administration .vitec-badge-info {
|
||||||
|
background-color: #313131;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- form focus (frontend uses a 3px orange outline) ---------------- */
|
||||||
|
|
||||||
|
.vitec-administration .form-control:focus,
|
||||||
|
.vitec-administration .form-select:focus {
|
||||||
|
border-color: #f47937;
|
||||||
|
box-shadow: 0 0 0 3px rgba(244, 121, 55, 0.35);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user