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, height: 300,
legend: { orientation: 'h', y: -0.25 }, legend: { orientation: 'h', y: -0.25 },
xaxis: { title: { text: '' }, tickformat: '%d %b %H:%M', type: 'date' }, 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)', plot_bgcolor: 'var(--body-bg)',
paper_bgcolor: 'var(--card-bg)', paper_bgcolor: 'var(--card-bg)',
font: { family: 'inherit', size: 11, color: 'var(--text-dark)' }, font: { family: 'inherit', size: 11, color: 'var(--text-dark)' },

View file

@ -798,13 +798,22 @@ function RoomCategoriesCard() {
// ─── System Tab ─────────────────────────────────────────────────────────────── // ─── System Tab ───────────────────────────────────────────────────────────────
function SystemTab({ config, isLoading }: { config: SystemConfig | undefined; isLoading: boolean }) { 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) { if (isLoading) {
return <div className="loading-state"><div className="spinner" /> Loading</div> return <div className="loading-state"><div className="spinner" /> Loading</div>
} }
const backend = config?.['booking_scraper_backend'] ?? 'playwright_local'
const displayKeys = [ const displayKeys = [
'booking_scraper_enabled', 'booking_scraper_enabled',
'booking_scraper_backend',
'booking_scraper_daily_time', 'booking_scraper_daily_time',
'booking_proxy_enabled', 'booking_proxy_enabled',
'booking_proxy_host', 'booking_proxy_host',
@ -815,7 +824,33 @@ function SystemTab({ config, isLoading }: { config: SystemConfig | undefined; is
] ]
return ( 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">
<div className="card-header">System Configuration</div> <div className="card-header">System Configuration</div>
<div className="table-wrap"> <div className="table-wrap">
@ -836,13 +871,13 @@ function SystemTab({ config, isLoading }: { config: SystemConfig | undefined; is
</span> </span>
</td> </td>
</tr> </tr>
))} ))}
</tbody> </tbody>
</table> </table>
</div> </div>
</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{' '} 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>. <a href="/rates/market" style={{ color: 'var(--gold)' }}>Market View</a>.
</div> </div>