Fix Plotly £ format warning and add scraper backend selector to Settings

- MarketView: split tickformat '£,.0f' into tickprefix+'£' + tickformat ',.0f' (Plotly d3 format doesn't accept £ prefix inline)
- Settings System tab: add editable dropdown for booking_scraper_backend (hotel page vs search results) with description of the tradeoff; backend was previously read-only in the table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-09 23:45:33 +00:00
parent f2e11bf585
commit 121976b3db
2 changed files with 40 additions and 5 deletions

View file

@ -1252,7 +1252,7 @@ const RateHistoryModalComponent: React.FC<{ modal: RateHistoryModal; onClose: ()
height: 300,
legend: { orientation: 'h', y: -0.25 },
xaxis: { title: { text: '' }, tickformat: '%d %b %H:%M', type: 'date' },
yaxis: { title: { text: 'Rate (£)' }, tickformat: '£,.0f' },
yaxis: { title: { text: 'Rate (£)' }, tickprefix: '£', tickformat: ',.0f' },
plot_bgcolor: 'var(--body-bg)',
paper_bgcolor: 'var(--card-bg)',
font: { family: 'inherit', size: 11, color: 'var(--text-dark)' },

View file

@ -798,13 +798,22 @@ function RoomCategoriesCard() {
// ─── System Tab ───────────────────────────────────────────────────────────────
function SystemTab({ config, isLoading }: { config: SystemConfig | undefined; isLoading: boolean }) {
const qc = useQueryClient()
const saveMutation = useMutation({
mutationFn: (payload: { key: string; value: string }) =>
api.post('/competitors/config/system', payload),
onSuccess: () => qc.invalidateQueries({ queryKey: ['system-config'] }),
})
if (isLoading) {
return <div className="loading-state"><div className="spinner" /> Loading</div>
}
const backend = config?.['booking_scraper_backend'] ?? 'playwright_local'
const displayKeys = [
'booking_scraper_enabled',
'booking_scraper_backend',
'booking_scraper_daily_time',
'booking_proxy_enabled',
'booking_proxy_host',
@ -815,7 +824,33 @@ function SystemTab({ config, isLoading }: { config: SystemConfig | undefined; is
]
return (
<div style={{ maxWidth: 700 }}>
<div style={{ maxWidth: 700, display: 'flex', flexDirection: 'column', gap: 20 }}>
<div className="card">
<div className="card-header">Scraper Backend</div>
<div className="card-body" style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
<p style={{ fontSize: 13, color: 'var(--text-mid)', margin: 0 }}>
Controls how Booking.com rates are fetched. <strong>Hotel page</strong> scrapes each
competitor's own property page captures all room types and rate plan variants (meal
plan, cancellation policy, availability). <strong>Search results</strong> scrapes the
location search page faster but only returns the single best-available rate per hotel
with no room-type breakdown.
</p>
<div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
<select
style={{ width: 260 }}
value={backend}
onChange={e => saveMutation.mutate({ key: 'booking_scraper_backend', value: e.target.value })}
disabled={saveMutation.isPending}
>
<option value="playwright_hotel_page">Hotel page (all rooms + rate plans)</option>
<option value="playwright_local">Search results (best available only)</option>
</select>
{saveMutation.isPending && <span style={{ fontSize: 12, color: 'var(--text-mid)' }}>Saving</span>}
{saveMutation.isSuccess && <span style={{ fontSize: 12, color: 'var(--success)' }}> Saved</span>}
</div>
</div>
</div>
<div className="card">
<div className="card-header">System Configuration</div>
<div className="table-wrap">
@ -836,13 +871,13 @@ function SystemTab({ config, isLoading }: { config: SystemConfig | undefined; is
</span>
</td>
</tr>
))}
))}
</tbody>
</table>
</div>
</div>
<div style={{ marginTop: 16, fontSize: 12, color: 'var(--text-mid)' }}>
<div style={{ fontSize: 12, color: 'var(--text-mid)' }}>
To configure the Booking.com scraper location and hotel tiers, use the Settings tab inside{' '}
<a href="/rates/market" style={{ color: 'var(--gold)' }}>Market View</a>.
</div>