feat: add per-integration content tabs to product edit form with CSS and JS

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 18:21:56 +01:00
parent 571f0a990f
commit a70b327960
3 changed files with 269 additions and 16 deletions

File diff suppressed because one or more lines are too long

View File

@@ -804,6 +804,48 @@ a {
border-radius: 10px;
background: #fff;
padding: 10px;
overflow: hidden;
&__meta {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 8px;
min-width: 0;
}
}
.product-show-image-path {
font-size: 12px;
min-width: 0;
overflow: hidden;
summary {
cursor: pointer;
color: var(--c-muted, #888);
list-style: none;
user-select: none;
white-space: nowrap;
&::-webkit-details-marker {
display: none;
}
&::after {
content: '';
}
}
&[open] summary::after {
content: '';
}
&__url {
margin-top: 4px;
word-break: break-all;
overflow-wrap: break-word;
font-size: 11px;
}
}
.product-show-image {
@@ -814,6 +856,49 @@ a {
border: 1px solid #d9e0ea;
}
.content-tabs-card {
margin-top: 0;
}
.content-tabs-nav {
display: flex;
gap: 4px;
border-bottom: 2px solid var(--c-border);
margin-bottom: 16px;
flex-wrap: wrap;
}
.content-tab-btn {
padding: 8px 16px;
border: none;
background: none;
cursor: pointer;
font-size: 14px;
font-weight: 500;
color: var(--c-text-muted, #6b7280);
border-bottom: 2px solid transparent;
margin-bottom: -2px;
border-radius: 4px 4px 0 0;
transition: color 0.15s, border-color 0.15s;
&:hover {
color: var(--c-text-strong, #111827);
}
&.is-active {
color: var(--c-primary, #2563eb);
border-bottom-color: var(--c-primary, #2563eb);
}
}
.content-tab-panel {
display: none;
&.is-active {
display: block;
}
}
@media (max-width: 768px) {
.app-shell {
grid-template-columns: 1fr;

View File

@@ -1,3 +1,11 @@
<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>
<section class="card">
<h1><?= $e($t('products.edit.title', ['id' => (string) ($productId ?? 0)])) ?></h1>
<p class="muted"><?= $e($t('products.edit.description')) ?></p>
@@ -17,12 +25,95 @@
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
<input type="hidden" name="id" value="<?= $e((string) ($productId ?? 0)) ?>">
<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>
<?php
$activeIntegrations = is_array($activeIntegrations ?? null) ? $activeIntegrations : [];
$integrationTranslationsMap = is_array($integrationTranslationsMap ?? null) ? $integrationTranslationsMap : [];
?>
<div class="content-tabs-card mt-0">
<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>
<div class="form-grid">
<label class="form-field">
<span class="field-label">SKU</span>
<input class="form-control" type="text" name="sku" value="<?= $e((string) ($form['sku'] ?? '')) ?>">
@@ -101,16 +192,6 @@
</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>
@@ -381,3 +462,90 @@
});
})();
</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 nav = document.getElementById('content-tabs-nav');
if (!nav) return;
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;
// deactivate all
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');
});
// activate selected
btn.classList.add('is-active');
var panel = document.getElementById('content-tab-' + tabId);
if (panel) panel.classList.add('is-active');
});
})();
</script>