Englische Texte, Kopiericon, Get-Route für suche

This commit is contained in:
2026-03-19 15:39:52 +01:00
parent 1ad3443433
commit e399019001
4 changed files with 205 additions and 5 deletions

View File

@@ -216,6 +216,7 @@
margin: 0 -15px;
padding-left: 15px;
padding-right: 15px;
align-items: flex-start;
}
.record-body .record-field:nth-child(even) {
background: rgba(0,0,0,0.04);
@@ -235,6 +236,23 @@
word-break: break-word;
white-space: pre-line;
margin-left: 16px;
flex: 1;
}
.copy-btn {
margin-left: 8px;
border: none;
background: transparent;
color: #666;
cursor: pointer;
font-size: 14px;
line-height: 1;
padding: 2px 4px;
border-radius: 4px;
}
.copy-btn:hover {
background: rgba(0, 0, 0, 0.08);
color: #222;
}
.price-age {
@@ -390,6 +408,17 @@
searchButton.addEventListener('click', performSearch);
resultsDiv.addEventListener('click', (e) => {
const copyBtn = e.target.closest('.copy-btn');
if (copyBtn) {
e.preventDefault();
const copyText = copyBtn.getAttribute('data-copy') || '';
copyToClipboard(copyText);
const original = copyBtn.textContent;
copyBtn.textContent = '✓';
setTimeout(() => { copyBtn.textContent = original; }, 700);
return;
}
const link = e.target.closest('.part-link');
if (!link) return;
e.preventDefault();
@@ -484,9 +513,11 @@
const DISPLAY_COLUMNS = [
{ key: 'Teil', label: 'Teilenummer' },
{ key: 'Bez', label: 'Bezeichnung' },
{ key: 'Bez2', label: 'Bezeichnung 2' },
{ key: 'Bez2', label: 'Bezeichnung 2' },
{ key: 'Ben8', label: 'Bezeichnung 3' },
{ key: 'Ben7', label: 'Englischer Text' },
{ key: 'EngBez1', label: 'Englischer Text 1' },
{ key: 'EngBez2', label: 'Englischer Text 2' },
{ key: 'Ben7', label: 'Englischer Text 3' },
{ key: 'Hersteller', label: 'Hersteller' },
{ key: 'Text', label: 'Zusatztext' },
{ key: 'PrsVK', label: 'VK in €' },
@@ -533,40 +564,50 @@
if (!(key in record)) continue;
const value = record[key];
let displayValue;
let copyValue = '';
if (key === 'Ersatz' && value != null && String(value).trim() !== '') {
const partNum = String(value).trim();
const safePart = escapeHtml(partNum);
const safePartAttr = safePart.replace(/"/g, '"');
displayValue = `<a href="#" class="part-link" data-part="${safePartAttr}">${safePart}</a>`;
copyValue = partNum;
} else if (key === 'PrsVK') {
const isVkTeil = record.VkTeil != null && Number(record.VkTeil) !== 0;
if (isVkTeil && value != null && String(value).trim() !== '') {
const valueText = String(value);
const priceText = highlightSearchTerm(String(value));
const priceTermStr = record.PrsVkTerm || record.PrsVKTerm;
let ageHtml = '';
let ageText = '';
if (priceTermStr) {
const priceDate = new Date(priceTermStr);
if (!isNaN(priceDate)) {
const now = new Date();
const diffMs = now.getTime() - priceDate.getTime();
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
const ageText = `${diffDays} Tage alt`;
ageText = `${diffDays} Tage alt`;
const isOld = diffDays > 182; // > ca. 6 Monate
const ageClass = isOld ? 'price-age price-age--old' : 'price-age';
ageHtml = ` <span class="${ageClass}">(${escapeHtml(ageText)})</span>`;
}
}
displayValue = `${priceText}${ageHtml}`;
copyValue = ageText ? `${valueText} (${ageText})` : valueText;
} else {
displayValue = escapeHtml('kein Verkaufsteil');
copyValue = 'kein Verkaufsteil';
}
} else {
const plainValue = value === null || value === undefined ? '' : String(value);
displayValue = value === null || value === undefined ? '' : highlightSearchTerm(String(value));
copyValue = plainValue;
}
const safeCopyAttr = escapeHtml(copyValue).replace(/"/g, '&quot;');
html += `
<div class="record-field">
<span class="field-name">${escapeHtml(label)}:</span>
<span class="field-value">${displayValue}</span>
<button class="copy-btn" type="button" data-copy="${safeCopyAttr}" title="Zeile kopieren" aria-label="Zeile kopieren">⧉</button>
</div>`;
}
html += `
@@ -606,8 +647,42 @@
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
} catch (err) {
const ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed';
ta.style.opacity = '0';
document.body.appendChild(ta);
ta.focus();
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
}
}
// Suchbegriff/Status aus URL übernehmen (z.B. /?q=abc&status=aktiv)
function applySearchParamsFromUrl() {
const params = new URLSearchParams(window.location.search);
const initialSearch = (params.get('q') || params.get('search') || '').trim();
const initialStatus = (params.get('status') || '').trim();
if (initialSearch) {
searchInput.value = initialSearch;
}
if (['aktiv', 'pruefbar', 'inaktiv', ''].includes(initialStatus)) {
statusFilterSelect.value = initialStatus;
}
return initialSearch;
}
// Health Check beim Laden
window.addEventListener('load', async () => {
const initialSearch = applySearchParamsFromUrl();
try {
const response = await fetch('/api/health');
const data = await response.json();
@@ -617,6 +692,10 @@
} catch (error) {
console.error('Health check failed:', error);
}
if (initialSearch) {
await performSearch();
}
});
</script>
</body>