Englische Texte, Kopiericon, Get-Route für suche
This commit is contained in:
@@ -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();
|
||||
@@ -485,6 +514,8 @@
|
||||
{ key: 'Teil', label: 'Teilenummer' },
|
||||
{ key: 'Bez', label: 'Bezeichnung' },
|
||||
{ key: 'Bez2', label: 'Bezeichnung 2' },
|
||||
{ key: 'EngBez1', label: 'Englische Bezeichnung 1' },
|
||||
{ key: 'EngBez2', label: 'Englische Bezeichnung 2' },
|
||||
{ key: 'Ben8', label: 'Bezeichnung 3' },
|
||||
{ key: 'Ben7', label: 'Englischer Text' },
|
||||
{ key: 'Hersteller', label: 'Hersteller' },
|
||||
@@ -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, '"');
|
||||
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,6 +647,22 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Health Check beim Laden
|
||||
window.addEventListener('load', async () => {
|
||||
try {
|
||||
|
||||
@@ -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, '"');
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user