Improve sold-out and past-day cell display in rate matrix
- Sold out + last price: show SOLD label above strikethrough price; remove price-index badge (rate no longer bookable, delta misleading) - Sold out, no history: unchanged — shows 'Sold' - Past days: price-index badge now grey/faded (#94a3b8 on #e2e8f0) instead of coloured — historical context only Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d97bfdab90
commit
e472ba65c4
1 changed files with 43 additions and 16 deletions
|
|
@ -1678,16 +1678,28 @@ const RateMatrixTab: React.FC = () => {
|
|||
? formatCurrency(rate.last_available_rate)
|
||||
: null
|
||||
|
||||
// For sold-out / past: show last known available rate with strikethrough
|
||||
// Rate text + strikethrough logic:
|
||||
// Sold out + last price → "SOLD" label above + strikethrough price (no badge)
|
||||
// Sold out, no history → "Sold" text only
|
||||
// Past + last price → strikethrough price (grey/faded badge)
|
||||
// Past, no history → —
|
||||
// Available → live rate + coloured badge
|
||||
let rateText = ''
|
||||
let strikethrough = false
|
||||
let showSoldLabel = false
|
||||
if (!rate) {
|
||||
rateText = ''
|
||||
} else if (isSoldOrPast && lastAvailRate) {
|
||||
} else if (isSoldOut && lastAvailRate) {
|
||||
rateText = lastAvailRate + staleMark
|
||||
strikethrough = true
|
||||
} else if (isSoldOrPast) {
|
||||
rateText = isSoldOut ? 'Sold' : '—'
|
||||
showSoldLabel = true
|
||||
} else if (isSoldOut) {
|
||||
rateText = 'Sold'
|
||||
} else if (isPast && lastAvailRate) {
|
||||
rateText = lastAvailRate + staleMark
|
||||
strikethrough = true
|
||||
} else if (isPast) {
|
||||
rateText = '—'
|
||||
} else if (isAvailable && rate.rate_gross) {
|
||||
rateText = formatCurrency(rate.rate_gross) + staleMark
|
||||
} else {
|
||||
|
|
@ -1699,7 +1711,7 @@ const RateMatrixTab: React.FC = () => {
|
|||
rate.breakfast_included ? 'Breakfast incl.' : null,
|
||||
rate.free_cancellation ? 'Free cancel' : null,
|
||||
rate.rooms_left ? `${rate.rooms_left} left` : null,
|
||||
lastAvailRate && isSoldOrPast ? `Last available: ${lastAvailRate}` : null,
|
||||
lastAvailRate && (isSoldOut || isPast) ? `Last available: ${lastAvailRate}` : null,
|
||||
rate.scraped_at ? `Scraped: ${new Date(rate.scraped_at).toLocaleString('en-GB', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' })}` : null,
|
||||
isStale ? 'Not updated in latest scrape' : null,
|
||||
].filter(Boolean).join(' | ') : ''
|
||||
|
|
@ -1724,11 +1736,22 @@ const RateMatrixTab: React.FC = () => {
|
|||
}
|
||||
}
|
||||
|
||||
// Price index badge for competitor rows (use last_available_rate for sold/past)
|
||||
const rateForIndex = isSoldOrPast ? rate?.last_available_rate : rate?.rate_gross
|
||||
// Price index badge:
|
||||
// Sold out → no badge (price no longer bookable, delta misleading)
|
||||
// Past → grey/faded badge (historical context only)
|
||||
// Future available → coloured badge
|
||||
const rateForIndex = isPast ? rate?.last_available_rate : (!isSoldOut ? rate?.rate_gross : null)
|
||||
let priceIndexBadge: React.ReactNode = null
|
||||
if (hotel.tier === 'competitor' && rateForIndex && ownRateByDate[d]) {
|
||||
const delta = Math.round((rateForIndex / ownRateByDate[d]! - 1) * 100)
|
||||
if (isPast) {
|
||||
priceIndexBadge = (
|
||||
<span style={{ fontSize: 9, fontWeight: 600, color: '#94a3b8', background: '#e2e8f0',
|
||||
borderRadius: 4, padding: '0 3px', lineHeight: '14px' }}>
|
||||
{delta > 0 ? '+' : ''}{delta}%
|
||||
</span>
|
||||
)
|
||||
} else {
|
||||
const bg = delta > 5 ? '#dcfce7' : delta < -15 ? '#fee2e2' : delta < -5 ? '#fef3c7' : '#f1f5f9'
|
||||
const fg = delta > 5 ? '#16a34a' : delta < -15 ? '#dc2626' : delta < -5 ? '#d97706' : '#64748b'
|
||||
priceIndexBadge = (
|
||||
|
|
@ -1738,6 +1761,7 @@ const RateMatrixTab: React.FC = () => {
|
|||
</span>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const showEye = !isPast && !!bookingUrl && !!rate
|
||||
const showHistory = !!rate
|
||||
|
|
@ -1762,6 +1786,9 @@ const RateMatrixTab: React.FC = () => {
|
|||
onMouseLeave={onCellLeave}
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1 }}>
|
||||
{showSoldLabel && (
|
||||
<span style={{ fontSize: 9, fontWeight: 700, letterSpacing: '0.05em', color: '#d97706' }}>SOLD</span>
|
||||
)}
|
||||
<span style={{ textDecoration: strikethrough ? 'line-through' : 'none' }}>
|
||||
{rateText}
|
||||
</span>
|
||||
|
|
@ -1849,7 +1876,7 @@ const RateMatrixTab: React.FC = () => {
|
|||
<div style={styles.legend}>
|
||||
<span style={styles.legendTitle}>Legend:</span>
|
||||
<span style={mergeStyles(styles.legendItem, styles.matrixCellAvailable)}>Available</span>
|
||||
<span style={mergeStyles(styles.legendItem, styles.matrixCellSoldOut)}>Sold Out (strikethrough = last rate)</span>
|
||||
<span style={mergeStyles(styles.legendItem, styles.matrixCellSoldOut)}>Sold Out (SOLD + strikethrough last rate if known)</span>
|
||||
<span style={mergeStyles(styles.legendItem, styles.matrixCellNoRate)}>No Rate</span>
|
||||
<span style={mergeStyles(styles.legendItem, styles.matrixCellPast)}>Past</span>
|
||||
<span style={mergeStyles(styles.legendItem, styles.matrixCellEmpty)}>No Data</span>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue