feat: add robust panel type image normalization, text fallback rendering, and corresponding styles
This commit is contained in:
parent
dcf5477199
commit
9ae7b0631b
5 changed files with 467 additions and 33 deletions
|
|
@ -385,6 +385,168 @@
|
|||
};
|
||||
}
|
||||
|
||||
function normalizePanelTypeCodeForImage(code) {
|
||||
let normalized = String(code || '').trim();
|
||||
if (!normalized) return '';
|
||||
|
||||
const upper = normalized.toUpperCase();
|
||||
if (upper === 'XA') {
|
||||
normalized = 'GXA';
|
||||
} else if (upper === 'XB') {
|
||||
normalized = 'GXB';
|
||||
} else if (upper === 'XD') {
|
||||
normalized = 'GXD';
|
||||
} else if (upper.startsWith('Z') && upper.length > 1) {
|
||||
normalized = normalized.slice(1);
|
||||
}
|
||||
|
||||
return normalized.replace(/["']/g, '').trim();
|
||||
}
|
||||
|
||||
function buildPanelTypeImageCandidates(code) {
|
||||
const normalizedCode = normalizePanelTypeCodeForImage(code);
|
||||
if (!normalizedCode) return [];
|
||||
|
||||
const upperCode = normalizedCode.toUpperCase();
|
||||
const baseSet = new Set([upperCode]);
|
||||
|
||||
const strippedPrefix = upperCode.replace(/^[-_]+/, '');
|
||||
if (strippedPrefix) {
|
||||
baseSet.add(strippedPrefix);
|
||||
baseSet.add(strippedPrefix.replace(/-/g, '_'));
|
||||
}
|
||||
|
||||
if (upperCode.includes(' ')) {
|
||||
baseSet.add(upperCode.replace(/\s+/g, ''));
|
||||
baseSet.add(upperCode.replace(/\s+/g, '_'));
|
||||
}
|
||||
|
||||
if (upperCode.includes('-')) {
|
||||
baseSet.add(upperCode.replace(/-/g, '_'));
|
||||
}
|
||||
|
||||
const uniqueCodes = [];
|
||||
baseSet.forEach(item => {
|
||||
if (!item) return;
|
||||
const up = item.toUpperCase();
|
||||
const title = up.charAt(0) + up.slice(1).toLowerCase();
|
||||
const low = up.toLowerCase();
|
||||
[up, title, low].forEach(c => {
|
||||
if (!uniqueCodes.includes(c)) uniqueCodes.push(c);
|
||||
});
|
||||
});
|
||||
|
||||
const candidates = [];
|
||||
uniqueCodes.forEach(item => {
|
||||
candidates.push(`/static/assets/road_signs/2025/${encodeURIComponent(item)}.svg`);
|
||||
});
|
||||
uniqueCodes.forEach(item => {
|
||||
candidates.push(`/static/assets/road_signs/2025/${encodeURIComponent(item)}.png`);
|
||||
});
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
function renderPanelTextFallback(container, code, options = {}) {
|
||||
if (!container) return;
|
||||
const text = String(code || '').trim();
|
||||
if (!text) {
|
||||
container.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const len = text.length;
|
||||
let fontSize;
|
||||
let lineHeight = '1.1';
|
||||
|
||||
if (len <= 4) {
|
||||
fontSize = '0.72rem';
|
||||
lineHeight = '1.15';
|
||||
} else if (len <= 8) {
|
||||
fontSize = '0.62rem';
|
||||
lineHeight = '1.1';
|
||||
} else if (len <= 14) {
|
||||
fontSize = '0.54rem';
|
||||
lineHeight = '1.05';
|
||||
} else if (len <= 24) {
|
||||
fontSize = '0.48rem';
|
||||
lineHeight = '1.05';
|
||||
} else {
|
||||
fontSize = '0.42rem';
|
||||
lineHeight = '1.0';
|
||||
}
|
||||
|
||||
const isSmallThumb = options.width && parseInt(options.width, 10) < 35;
|
||||
if (isSmallThumb) {
|
||||
fontSize = len <= 4 ? '0.55rem' : '0.40rem';
|
||||
}
|
||||
|
||||
container.innerHTML = '';
|
||||
const fallbackBox = document.createElement('div');
|
||||
fallbackBox.className = 'panel-text-fallback';
|
||||
fallbackBox.title = text;
|
||||
|
||||
const span = document.createElement('span');
|
||||
span.className = 'panel-fallback-text';
|
||||
span.style.fontSize = fontSize;
|
||||
span.style.lineHeight = lineHeight;
|
||||
span.textContent = text;
|
||||
|
||||
fallbackBox.appendChild(span);
|
||||
container.appendChild(fallbackBox);
|
||||
}
|
||||
|
||||
function appendPanelTypeImage(container, code, imageUrl, options = {}) {
|
||||
if (!container) return;
|
||||
|
||||
const imageHeight = options.height || '40px';
|
||||
const imageWidth = options.width || '40px';
|
||||
const objectFit = options.objectFit || 'contain';
|
||||
|
||||
function createImage(src) {
|
||||
const img = document.createElement('img');
|
||||
img.src = src;
|
||||
img.alt = code || '';
|
||||
img.style.maxHeight = '100%';
|
||||
img.style.maxWidth = '100%';
|
||||
img.style.height = imageHeight;
|
||||
img.style.width = imageWidth;
|
||||
img.style.objectFit = objectFit;
|
||||
return img;
|
||||
}
|
||||
|
||||
const candidates = buildPanelTypeImageCandidates(code);
|
||||
let candidateIndex = 0;
|
||||
|
||||
function tryFallbackCandidate() {
|
||||
const src = candidates[candidateIndex++];
|
||||
if (!src) {
|
||||
renderPanelTextFallback(container, code, options);
|
||||
return;
|
||||
}
|
||||
|
||||
const fallbackImg = createImage(src);
|
||||
fallbackImg.onerror = tryFallbackCandidate;
|
||||
container.innerHTML = '';
|
||||
container.appendChild(fallbackImg);
|
||||
}
|
||||
|
||||
if (imageUrl) {
|
||||
const img = createImage(imageUrl);
|
||||
img.onerror = tryFallbackCandidate;
|
||||
container.innerHTML = '';
|
||||
container.appendChild(img);
|
||||
return;
|
||||
}
|
||||
|
||||
tryFallbackCandidate();
|
||||
}
|
||||
|
||||
global.normalizePanelTypeCodeForImage = normalizePanelTypeCodeForImage;
|
||||
global.buildPanelTypeImageCandidates = buildPanelTypeImageCandidates;
|
||||
global.renderPanelTextFallback = renderPanelTextFallback;
|
||||
global.appendPanelTypeImage = appendPanelTypeImage;
|
||||
|
||||
global.SignPanelsShared = {
|
||||
getRenderedPanelHeight,
|
||||
calculatePanelOffsets,
|
||||
|
|
@ -392,5 +554,9 @@
|
|||
handleRoadSignStyleImageMissing,
|
||||
refreshPanelOffsetsForSource,
|
||||
createPanelOffsetRefreshScheduler,
|
||||
normalizePanelTypeCodeForImage,
|
||||
buildPanelTypeImageCandidates,
|
||||
renderPanelTextFallback,
|
||||
appendPanelTypeImage,
|
||||
};
|
||||
})(window);
|
||||
|
|
|
|||
|
|
@ -3127,11 +3127,35 @@ function buildPanelTypeImageCandidates(code) {
|
|||
if (!normalizedCode) return [];
|
||||
|
||||
const upperCode = normalizedCode.toUpperCase();
|
||||
const titleCode = upperCode.charAt(0) + upperCode.slice(1).toLowerCase();
|
||||
const lowerCode = upperCode.toLowerCase();
|
||||
const uniqueCodes = Array.from(new Set([upperCode, titleCode, lowerCode]));
|
||||
const candidates = [];
|
||||
const baseSet = new Set([upperCode]);
|
||||
|
||||
const strippedPrefix = upperCode.replace(/^[-_]+/, '');
|
||||
if (strippedPrefix) {
|
||||
baseSet.add(strippedPrefix);
|
||||
baseSet.add(strippedPrefix.replace(/-/g, '_'));
|
||||
}
|
||||
|
||||
if (upperCode.includes(' ')) {
|
||||
baseSet.add(upperCode.replace(/\s+/g, ''));
|
||||
baseSet.add(upperCode.replace(/\s+/g, '_'));
|
||||
}
|
||||
|
||||
if (upperCode.includes('-')) {
|
||||
baseSet.add(upperCode.replace(/-/g, '_'));
|
||||
}
|
||||
|
||||
const uniqueCodes = [];
|
||||
baseSet.forEach(item => {
|
||||
if (!item) return;
|
||||
const up = item.toUpperCase();
|
||||
const title = up.charAt(0) + up.slice(1).toLowerCase();
|
||||
const low = up.toLowerCase();
|
||||
[up, title, low].forEach(c => {
|
||||
if (!uniqueCodes.includes(c)) uniqueCodes.push(c);
|
||||
});
|
||||
});
|
||||
|
||||
const candidates = [];
|
||||
uniqueCodes.forEach(item => {
|
||||
candidates.push(`/static/assets/road_signs/2025/${encodeURIComponent(item)}.svg`);
|
||||
});
|
||||
|
|
@ -3142,6 +3166,55 @@ function buildPanelTypeImageCandidates(code) {
|
|||
return candidates;
|
||||
}
|
||||
|
||||
function renderPanelTextFallback(container, code, options = {}) {
|
||||
if (!container) return;
|
||||
const text = String(code || '').trim();
|
||||
if (!text) {
|
||||
container.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const len = text.length;
|
||||
let fontSize;
|
||||
let lineHeight = '1.1';
|
||||
|
||||
if (len <= 4) {
|
||||
fontSize = '0.72rem';
|
||||
lineHeight = '1.15';
|
||||
} else if (len <= 8) {
|
||||
fontSize = '0.62rem';
|
||||
lineHeight = '1.1';
|
||||
} else if (len <= 14) {
|
||||
fontSize = '0.54rem';
|
||||
lineHeight = '1.05';
|
||||
} else if (len <= 24) {
|
||||
fontSize = '0.48rem';
|
||||
lineHeight = '1.05';
|
||||
} else {
|
||||
fontSize = '0.42rem';
|
||||
lineHeight = '1.0';
|
||||
}
|
||||
|
||||
const isSmallThumb = options.width && parseInt(options.width, 10) < 35;
|
||||
if (isSmallThumb) {
|
||||
fontSize = len <= 4 ? '0.55rem' : '0.40rem';
|
||||
}
|
||||
|
||||
container.innerHTML = '';
|
||||
const fallbackBox = document.createElement('div');
|
||||
fallbackBox.className = 'panel-text-fallback';
|
||||
fallbackBox.title = text;
|
||||
|
||||
const span = document.createElement('span');
|
||||
span.className = 'panel-fallback-text';
|
||||
span.style.fontSize = fontSize;
|
||||
span.style.lineHeight = lineHeight;
|
||||
span.textContent = text;
|
||||
|
||||
fallbackBox.appendChild(span);
|
||||
container.appendChild(fallbackBox);
|
||||
}
|
||||
|
||||
function appendPanelTypeImage(container, code, imageUrl, options = {}) {
|
||||
if (!container) return;
|
||||
|
||||
|
|
@ -3167,7 +3240,7 @@ function appendPanelTypeImage(container, code, imageUrl, options = {}) {
|
|||
function tryFallbackCandidate() {
|
||||
const src = candidates[candidateIndex++];
|
||||
if (!src) {
|
||||
container.textContent = code || '';
|
||||
renderPanelTextFallback(container, code, options);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -3233,6 +3306,8 @@ function renderSignPanelTypeConditionalFilter(thematicCode, def, selectedValues)
|
|||
#accordion-sign-panel-types .panel-item {
|
||||
transition: all 0.2s cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||
border: 1px solid #dee2e6 !important;
|
||||
overflow: hidden !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
#accordion-sign-panel-types .panel-item:hover {
|
||||
transform: translateY(-3px);
|
||||
|
|
@ -3242,6 +3317,55 @@ function renderSignPanelTypeConditionalFilter(thematicCode, def, selectedValues)
|
|||
#accordion-sign-panel-types .panel-item.bg-primary {
|
||||
box-shadow: 0 4px 12px rgba(13, 110, 253, 0.15) !important;
|
||||
}
|
||||
#accordion-sign-panel-types .panel-img-placeholder {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
overflow: hidden !important;
|
||||
box-sizing: border-box !important;
|
||||
padding: 2px !important;
|
||||
}
|
||||
#accordion-sign-panel-types .panel-text-fallback,
|
||||
.panel-text-fallback {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f8f9fa;
|
||||
border: 1px dashed #d0d5dd;
|
||||
border-radius: 4px;
|
||||
padding: 2px 3px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
transition: background-color 0.15s ease, border-color 0.15s ease;
|
||||
}
|
||||
#accordion-sign-panel-types .panel-fallback-text,
|
||||
.panel-fallback-text {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 4;
|
||||
line-clamp: 4;
|
||||
max-height: 40px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
letter-spacing: -0.2px;
|
||||
}
|
||||
#accordion-sign-panel-types .panel-item.bg-primary .panel-text-fallback,
|
||||
#accordion-sign-panel-types .panel-item.selected .panel-text-fallback {
|
||||
background-color: rgba(255, 255, 255, 0.2) !important;
|
||||
border-color: rgba(255, 255, 255, 0.4) !important;
|
||||
}
|
||||
#accordion-sign-panel-types .panel-item.bg-primary .panel-fallback-text,
|
||||
#accordion-sign-panel-types .panel-item.selected .panel-fallback-text {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
|
|
@ -3257,11 +3381,11 @@ function renderSignPanelTypeConditionalFilter(thematicCode, def, selectedValues)
|
|||
data-id="${escapeHtml(option.value)}"
|
||||
data-code="${escapeHtml(option.code)}"
|
||||
data-image-url="${escapeHtml(option.imageUrl)}"
|
||||
style="cursor: pointer; width: 70px;"
|
||||
style="cursor: pointer; width: 70px; overflow: hidden; box-sizing: border-box;"
|
||||
title="${escapeHtml(title)}">
|
||||
<div class="panel-img-placeholder mb-1"
|
||||
style="height: 50px; display: flex; align-items: center; justify-content: center;"></div>
|
||||
<div class="small text-truncate fw-bold" style="font-size: 0.7rem;">
|
||||
style="height: 50px; width: 100%; display: flex; align-items: center; justify-content: center; overflow: hidden; box-sizing: border-box;"></div>
|
||||
<div class="small text-truncate fw-bold" style="font-size: 0.7rem; width: 100%; max-width: 100%; overflow: hidden;">
|
||||
${escapeHtml(option.code)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -3373,21 +3497,24 @@ function initSignPanelTypePickers() {
|
|||
item.className = 'selected-panel-item d-flex justify-content-between align-items-center bg-white border rounded p-1 mb-1';
|
||||
|
||||
const content = document.createElement('div');
|
||||
content.className = 'd-flex align-items-center';
|
||||
content.className = 'd-flex align-items-center text-truncate me-2';
|
||||
content.style.maxWidth = 'calc(100% - 30px)';
|
||||
|
||||
const thumb = document.createElement('div');
|
||||
thumb.className = 'd-flex align-items-center justify-content-center me-2';
|
||||
thumb.className = 'd-flex align-items-center justify-content-center me-2 flex-shrink-0';
|
||||
thumb.style.width = '28px';
|
||||
thumb.style.height = '28px';
|
||||
thumb.style.overflow = 'hidden';
|
||||
appendPanelTypeImage(thumb, code, imageUrl, { width: '28px', height: '28px' });
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.className = 'small fw-bold ms-1';
|
||||
label.className = 'small fw-bold ms-1 text-truncate';
|
||||
label.title = code;
|
||||
label.textContent = code;
|
||||
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.type = 'button';
|
||||
removeBtn.className = 'btn btn-sm btn-link text-danger p-0 me-1 remove-panel-type';
|
||||
removeBtn.className = 'btn btn-sm btn-link text-danger p-0 me-1 remove-panel-type flex-shrink-0';
|
||||
removeBtn.dataset.id = id;
|
||||
removeBtn.innerHTML = '<i class="bi bi-x"></i>';
|
||||
|
||||
|
|
|
|||
|
|
@ -2370,27 +2370,41 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
const imageUrl = panelItem.dataset.imageUrl;
|
||||
const panelCode = panelItem.dataset.code;
|
||||
|
||||
if (imageUrl) {
|
||||
imageHtml = `<img src="${imageUrl}" style="height: 40px; width: 40px; object-fit: contain; margin-right: 8px;">`;
|
||||
const thumbContainer = document.createElement('div');
|
||||
thumbContainer.style.width = '40px';
|
||||
thumbContainer.style.height = '40px';
|
||||
thumbContainer.style.marginRight = '8px';
|
||||
thumbContainer.style.display = 'flex';
|
||||
thumbContainer.style.alignItems = 'center';
|
||||
thumbContainer.style.justifyContent = 'center';
|
||||
thumbContainer.style.overflow = 'hidden';
|
||||
thumbContainer.style.flexShrink = '0';
|
||||
|
||||
if (typeof appendPanelTypeImage === 'function') {
|
||||
appendPanelTypeImage(thumbContainer, panelCode, imageUrl, { width: '40px', height: '40px' });
|
||||
} else if (imageUrl) {
|
||||
thumbContainer.innerHTML = `<img src="${imageUrl}" style="height: 40px; width: 40px; object-fit: contain;">`;
|
||||
} else if (typeof buildPanelIconElement === 'function') {
|
||||
const img = buildPanelIconElement(panelCode);
|
||||
img.style.height = '40px';
|
||||
img.style.width = '40px';
|
||||
img.style.marginRight = '8px';
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.appendChild(img);
|
||||
imageHtml = tempDiv.innerHTML;
|
||||
thumbContainer.appendChild(img);
|
||||
} else if (typeof renderPanelTextFallback === 'function') {
|
||||
renderPanelTextFallback(thumbContainer, panelCode, { width: '40px', height: '40px' });
|
||||
} else {
|
||||
imageHtml = `<span style="font-size: 0.8rem; margin-right: 8px;">${panelCode}</span>`;
|
||||
thumbContainer.innerHTML = `<span style="font-size: 0.8rem;">${panelCode}</span>`;
|
||||
}
|
||||
const tempWrapper = document.createElement('div');
|
||||
tempWrapper.appendChild(thumbContainer);
|
||||
imageHtml = tempWrapper.innerHTML;
|
||||
}
|
||||
|
||||
const item = document.createElement('div');
|
||||
item.className = 'd-flex align-items-center bg-white border rounded p-2';
|
||||
item.innerHTML = `
|
||||
${imageHtml}
|
||||
<span class="fw-bold">${code}</span>
|
||||
<button type="button" class="btn btn-sm btn-link text-danger p-0 ms-auto remove-panel-type-create">
|
||||
<span class="fw-bold text-truncate me-2" style="max-width: 240px;" title="${code}">${code}</span>
|
||||
<button type="button" class="btn btn-sm btn-link text-danger p-0 ms-auto remove-panel-type-create flex-shrink-0">
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
`;
|
||||
|
|
@ -2423,7 +2437,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
const imageUrl = item.dataset.imageUrl;
|
||||
const code = item.dataset.code;
|
||||
|
||||
if (imageUrl) {
|
||||
if (typeof appendPanelTypeImage === 'function') {
|
||||
appendPanelTypeImage(placeholder, code, imageUrl);
|
||||
} else if (imageUrl) {
|
||||
const img = document.createElement('img');
|
||||
img.src = imageUrl;
|
||||
img.style.maxHeight = '100%';
|
||||
|
|
@ -2434,6 +2450,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
img.style.height = '40px';
|
||||
img.style.width = '40px';
|
||||
placeholder.appendChild(img);
|
||||
} else if (typeof renderPanelTextFallback === 'function') {
|
||||
renderPanelTextFallback(placeholder, code);
|
||||
} else {
|
||||
placeholder.textContent = code;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,6 +124,8 @@
|
|||
border: 1px solid #e9ecef !important;
|
||||
border-radius: 6px !important;
|
||||
background-color: #ffffff !important;
|
||||
overflow: hidden !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
.panel-item:hover, .panel-item-create:hover {
|
||||
|
|
@ -137,6 +139,65 @@
|
|||
background-color: rgba(13, 110, 253, 0.05) !important;
|
||||
box-shadow: 0 4px 12px rgba(13, 110, 253, 0.1) !important;
|
||||
}
|
||||
|
||||
.panel-img-placeholder {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
overflow: hidden !important;
|
||||
box-sizing: border-box !important;
|
||||
padding: 2px !important;
|
||||
}
|
||||
|
||||
.panel-text-fallback {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f8f9fa;
|
||||
border: 1px dashed #d0d5dd;
|
||||
border-radius: 4px;
|
||||
padding: 2px 3px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
transition: background-color 0.15s ease, border-color 0.15s ease;
|
||||
}
|
||||
|
||||
.panel-fallback-text {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 4;
|
||||
line-clamp: 4;
|
||||
max-height: 40px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
letter-spacing: -0.2px;
|
||||
}
|
||||
|
||||
.panel-item.selected .panel-text-fallback,
|
||||
.panel-item-create.selected .panel-text-fallback,
|
||||
.panel-item.bg-primary .panel-text-fallback,
|
||||
.panel-item-create.bg-primary .panel-text-fallback {
|
||||
background-color: rgba(13, 110, 253, 0.12) !important;
|
||||
border-color: rgba(13, 110, 253, 0.35) !important;
|
||||
}
|
||||
|
||||
.panel-item.selected .panel-fallback-text,
|
||||
.panel-item-create.selected .panel-fallback-text {
|
||||
color: #0d6efd !important;
|
||||
}
|
||||
|
||||
.panel-item.bg-primary .panel-fallback-text,
|
||||
.panel-item-create.bg-primary .panel-fallback-text {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock head %}
|
||||
|
||||
|
|
@ -306,12 +367,12 @@
|
|||
data-code="{{ panel.code }}"
|
||||
data-group="{{ group_key }}"
|
||||
data-image-url="{{ panel.image_url|default:'' }}"
|
||||
style="cursor: pointer; width: 68px; transition: transform 0.15s ease;"
|
||||
style="cursor: pointer; width: 68px; overflow: hidden; box-sizing: border-box; transition: transform 0.15s ease;"
|
||||
title="{{ panel.code }} - {{ panel.name }}">
|
||||
<div class="panel-img-placeholder mb-1" style="height: 48px; display: flex; align-items: center; justify-content: center;">
|
||||
<div class="panel-img-placeholder mb-1" style="height: 48px; width: 100%; display: flex; align-items: center; justify-content: center; overflow: hidden; box-sizing: border-box;">
|
||||
<!-- Content will be injected by JS -->
|
||||
</div>
|
||||
<div class="small text-truncate fw-bold text-muted" style="font-size: 0.65rem;">{{ panel.code }}</div>
|
||||
<div class="small text-truncate fw-bold text-muted" style="font-size: 0.65rem; width: 100%; max-width: 100%; overflow: hidden;">{{ panel.code }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
|
@ -2016,7 +2077,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
if (placeholder && placeholder.children.length === 0) {
|
||||
const imageUrl = item.dataset.imageUrl;
|
||||
const code = item.dataset.code;
|
||||
if (imageUrl) {
|
||||
if (typeof appendPanelTypeImage === 'function') {
|
||||
appendPanelTypeImage(placeholder, code, imageUrl);
|
||||
} else if (imageUrl) {
|
||||
const img = document.createElement('img');
|
||||
img.src = imageUrl;
|
||||
img.style.maxHeight = '100%';
|
||||
|
|
@ -2027,6 +2090,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
img.style.height = '40px';
|
||||
img.style.width = '40px';
|
||||
placeholder.appendChild(img);
|
||||
} else if (typeof renderPanelTextFallback === 'function') {
|
||||
renderPanelTextFallback(placeholder, code);
|
||||
} else {
|
||||
placeholder.textContent = code;
|
||||
}
|
||||
|
|
@ -2177,8 +2242,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
const item = document.createElement('div');
|
||||
item.className = 'd-flex justify-content-between align-items-center bg-white border rounded p-1 mb-1 shadow-sm';
|
||||
item.innerHTML = `
|
||||
<span class="small fw-bold ms-1 text-primary">${code}</span>
|
||||
<button type="button" class="btn btn-sm btn-link text-danger p-0 me-1 remove-panel-type" data-id="${id}" title="Retirer">
|
||||
<span class="small fw-bold ms-1 text-primary text-truncate" style="max-width: 200px;" title="${code}">${code}</span>
|
||||
<button type="button" class="btn btn-sm btn-link text-danger p-0 me-1 remove-panel-type flex-shrink-0" data-id="${id}" title="Retirer">
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
`;
|
||||
|
|
@ -2195,8 +2260,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
const item = document.createElement('div');
|
||||
item.className = 'd-flex justify-content-between align-items-center bg-white border rounded p-1 mb-1 shadow-sm';
|
||||
item.innerHTML = `
|
||||
<span class="small fw-bold ms-1 text-primary">${code}</span>
|
||||
<button type="button" class="btn btn-sm btn-link text-danger p-0 me-1 remove-panel-type" data-id="${id}" title="Retirer">
|
||||
<span class="small fw-bold ms-1 text-primary text-truncate" style="max-width: 200px;" title="${code}">${code}</span>
|
||||
<button type="button" class="btn btn-sm btn-link text-danger p-0 me-1 remove-panel-type flex-shrink-0" data-id="${id}" title="Retirer">
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -85,6 +85,8 @@
|
|||
border: 1px solid #e9ecef !important;
|
||||
border-radius: 6px !important;
|
||||
background-color: #ffffff !important;
|
||||
overflow: hidden !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
.panel-item:hover, .panel-item-create:hover {
|
||||
|
|
@ -92,6 +94,62 @@
|
|||
border-color: #0d6efd !important;
|
||||
box-shadow: 0 4px 12px rgba(13, 110, 253, 0.15) !important;
|
||||
}
|
||||
|
||||
.panel-img-placeholder {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
overflow: hidden !important;
|
||||
box-sizing: border-box !important;
|
||||
padding: 2px !important;
|
||||
}
|
||||
|
||||
.panel-text-fallback {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f8f9fa;
|
||||
border: 1px dashed #d0d5dd;
|
||||
border-radius: 4px;
|
||||
padding: 2px 3px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
transition: background-color 0.15s ease, border-color 0.15s ease;
|
||||
}
|
||||
|
||||
.panel-fallback-text {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 4;
|
||||
line-clamp: 4;
|
||||
max-height: 40px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
letter-spacing: -0.2px;
|
||||
}
|
||||
|
||||
.panel-item.bg-primary .panel-text-fallback,
|
||||
.panel-item-create.bg-primary .panel-text-fallback,
|
||||
.panel-item.selected .panel-text-fallback,
|
||||
.panel-item-create.selected .panel-text-fallback {
|
||||
background-color: rgba(255, 255, 255, 0.2) !important;
|
||||
border-color: rgba(255, 255, 255, 0.4) !important;
|
||||
}
|
||||
|
||||
.panel-item.bg-primary .panel-fallback-text,
|
||||
.panel-item-create.bg-primary .panel-fallback-text,
|
||||
.panel-item.selected .panel-fallback-text,
|
||||
.panel-item-create.selected .panel-fallback-text {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Accordion for panel categories -->
|
||||
|
|
@ -119,12 +177,12 @@
|
|||
data-id="{{ panel.id }}"
|
||||
data-code="{{ panel.code }}"
|
||||
data-image-url="{{ panel.image_url|default:'' }}"
|
||||
style="cursor: pointer; width: 68px; transition: transform 0.15s ease;"
|
||||
style="cursor: pointer; width: 68px; overflow: hidden; box-sizing: border-box; transition: transform 0.15s ease;"
|
||||
title="{{ panel.code }} - {{ panel.name }}">
|
||||
<div class="panel-img-placeholder mb-1" style="height: 48px; display: flex; align-items: center; justify-content: center;">
|
||||
<div class="panel-img-placeholder mb-1" style="height: 48px; width: 100%; display: flex; align-items: center; justify-content: center; overflow: hidden; box-sizing: border-box;">
|
||||
<!-- Content will be injected by JS -->
|
||||
</div>
|
||||
<div class="small text-truncate fw-bold text-muted" style="font-size: 0.65rem;">{{ panel.code }}</div>
|
||||
<div class="small text-truncate fw-bold text-muted" style="font-size: 0.65rem; width: 100%; max-width: 100%; overflow: hidden;">{{ panel.code }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
|
|
|||
Loading…
Reference in a new issue