feat: surface automation option details
This commit is contained in:
@@ -589,6 +589,67 @@
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.option-facts {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin: 8px 0 2px;
|
||||
}
|
||||
.option-facts-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||
gap: 6px;
|
||||
}
|
||||
.option-fact {
|
||||
padding: 8px 10px;
|
||||
border-radius: 10px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
.option-fact-label {
|
||||
display: block;
|
||||
font-size: 0.58rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.9px;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.option-fact-value {
|
||||
font-size: 0.73rem;
|
||||
color: #fff;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.option-fact-note {
|
||||
font-size: 0.68rem;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.option-detail-section {
|
||||
margin-top: 2px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 10px;
|
||||
background: rgba(255, 255, 255, 0.025);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
.option-detail-title {
|
||||
font-size: 0.58rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.9px;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.option-detail-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
.option-detail-chip {
|
||||
background: var(--surface2);
|
||||
border-radius: 999px;
|
||||
padding: 3px 8px;
|
||||
font-size: 0.65rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.option-link {
|
||||
display: inline-block;
|
||||
font-size: 0.7rem;
|
||||
@@ -1492,6 +1553,135 @@
|
||||
return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value ?? '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function normalizeTextList(value) {
|
||||
const items = Array.isArray(value) ? value : value == null ? [] : [value];
|
||||
return [...new Set(items.flatMap(item => {
|
||||
if (Array.isArray(item)) return item;
|
||||
if (item && typeof item === 'object') {
|
||||
return [
|
||||
item.label,
|
||||
item.name,
|
||||
item.text,
|
||||
item.title,
|
||||
item.value,
|
||||
item.summary,
|
||||
item.description,
|
||||
].filter(Boolean);
|
||||
}
|
||||
return [item];
|
||||
})
|
||||
.map(item => String(item).trim())
|
||||
.filter(Boolean))];
|
||||
}
|
||||
|
||||
function renderTextChips(items) {
|
||||
const chips = normalizeTextList(items);
|
||||
if (!chips.length) return '';
|
||||
return `
|
||||
<div class="option-detail-list">
|
||||
${chips.map(item => `<span class="option-detail-chip">${escapeHtml(item)}</span>`).join('')}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderOptionFacts(opt) {
|
||||
const insights = opt.automationInsights || {};
|
||||
const currentPrice = typeof opt.currentPrice === 'number' ? formatCurrency(opt.currentPrice, insights.currency || 'USD') : '';
|
||||
const priceLabel = insights.displayPrice || currentPrice || 'Not yet tracked';
|
||||
const sourceLabel = insights.source || 'Automation feed';
|
||||
const statusLabel = insights.availability || insights.decisionNote || 'Matched from live search';
|
||||
const overviewItems = normalizeTextList(opt.details);
|
||||
const autoHighlights = normalizeTextList(insights.highlights);
|
||||
const features = normalizeTextList(insights.features);
|
||||
const amenities = normalizeTextList(insights.amenities);
|
||||
const inclusions = normalizeTextList(insights.inclusions);
|
||||
const limitations = normalizeTextList(insights.limitations);
|
||||
|
||||
const sections = [];
|
||||
|
||||
sections.push(`
|
||||
<div class="option-facts-grid">
|
||||
<div class="option-fact">
|
||||
<span class="option-fact-label">Current price</span>
|
||||
<div class="option-fact-value">${escapeHtml(priceLabel)}</div>
|
||||
</div>
|
||||
<div class="option-fact">
|
||||
<span class="option-fact-label">Source</span>
|
||||
<div class="option-fact-value">${escapeHtml(sourceLabel)}</div>
|
||||
</div>
|
||||
<div class="option-fact">
|
||||
<span class="option-fact-label">Status</span>
|
||||
<div class="option-fact-value">${escapeHtml(statusLabel)}</div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
if (overviewItems.length) {
|
||||
sections.push(`
|
||||
<div class="option-detail-section">
|
||||
<div class="option-detail-title">Decision summary</div>
|
||||
${renderTextChips([...overviewItems, ...autoHighlights])}
|
||||
</div>
|
||||
`);
|
||||
} else if (autoHighlights.length) {
|
||||
sections.push(`
|
||||
<div class="option-detail-section">
|
||||
<div class="option-detail-title">Decision summary</div>
|
||||
${renderTextChips(autoHighlights)}
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
if (features.length) {
|
||||
sections.push(`
|
||||
<div class="option-detail-section">
|
||||
<div class="option-detail-title">Features</div>
|
||||
${renderTextChips(features)}
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
if (amenities.length) {
|
||||
sections.push(`
|
||||
<div class="option-detail-section">
|
||||
<div class="option-detail-title">Amenities</div>
|
||||
${renderTextChips(amenities)}
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
if (inclusions.length) {
|
||||
sections.push(`
|
||||
<div class="option-detail-section">
|
||||
<div class="option-detail-title">Inclusions</div>
|
||||
${renderTextChips(inclusions)}
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
if (limitations.length) {
|
||||
sections.push(`
|
||||
<div class="option-detail-section">
|
||||
<div class="option-detail-title">Tradeoffs</div>
|
||||
${renderTextChips(limitations)}
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
return sections.length
|
||||
? `<div class="option-facts">${sections.join('')}</div>`
|
||||
: '';
|
||||
}
|
||||
|
||||
function renderPriceTrend(opt) {
|
||||
const series = Array.isArray(opt.priceHistory)
|
||||
? opt.priceHistory.filter(point => typeof point.price === 'number' && !Number.isNaN(point.price))
|
||||
@@ -1753,9 +1943,7 @@
|
||||
</div>
|
||||
${opt.desc ? `<div class="option-desc">${opt.desc}</div>` : ''}
|
||||
${linkPills}
|
||||
${opt.details && opt.details.length ? `
|
||||
<div class="itin-details">${opt.details.map(d => `<span class="itin-tag">${d}</span>`).join('')}</div>
|
||||
` : ''}
|
||||
${renderOptionFacts(opt)}
|
||||
${renderPriceTrend(opt)}
|
||||
<div class="vote-bar-bg">
|
||||
<div class="vote-bar-fill ${catClass}" style="width:${votePct}%"></div>
|
||||
|
||||
Reference in New Issue
Block a user