Direct-rates sub-row in Market View was dead: the frontend filters
competitors on direct_hotel_id but /matrix and /hotels never returned it.
Add direct_hotel_id to both queries (+ HotelResponse), and only render the
"Direct" sub-row when a hotel actually has a non-null direct rate (was
rendering all-dashes on an empty {} object).
Remove the pause-on-block flow entirely — dormant since rotate-on-block
replaced it (nothing set booking_scraper_paused=true after set_scraper_paused
was dropped): is_scraper_paused, /config/unpause, the /scrape paused guard,
ScraperStatusResponse.paused/pause_until, and the frontend Paused badge +
Unpause button. Trim now-unused datetime import.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
112 lines
2.4 KiB
TypeScript
112 lines
2.4 KiB
TypeScript
export interface User {
|
|
email: string
|
|
name: string
|
|
is_admin: boolean
|
|
caps: string[]
|
|
}
|
|
|
|
export function can(user: User | null, cap: string): boolean {
|
|
if (!user) return false
|
|
return user.is_admin || user.caps.includes(cap)
|
|
}
|
|
|
|
export interface Hotel {
|
|
id: number
|
|
name: string
|
|
tier: 'own' | 'competitor' | 'market'
|
|
star_rating: number | null
|
|
review_score: number | null
|
|
booking_com_url: string | null
|
|
booking_com_id: string | null
|
|
display_order: number
|
|
notes: string | null
|
|
is_active: boolean
|
|
scraped_dates?: number
|
|
last_scraped?: string | null
|
|
}
|
|
|
|
export interface RateMatrixEntry {
|
|
hotel_id: number
|
|
hotel_name: string
|
|
tier: string
|
|
rate_date: string
|
|
rate_gross: number | null
|
|
availability_status: string
|
|
rooms_left: number | null
|
|
scraped_at: string | null
|
|
}
|
|
|
|
export interface ScraperStatus {
|
|
enabled: boolean
|
|
backend: string
|
|
daily_time: string
|
|
last_batch: {
|
|
batch_id: string
|
|
started_at: string
|
|
status: string
|
|
rates_scraped: number
|
|
dates_completed: number
|
|
} | null
|
|
}
|
|
|
|
export interface BookabilityCategory {
|
|
category_id: string
|
|
category_name: string
|
|
room_count: number
|
|
display_order: number
|
|
dates: BookabilityDate[]
|
|
}
|
|
|
|
export interface BookabilityDate {
|
|
date: string
|
|
gross_rate: number | null
|
|
tariffs: TariffSummary[]
|
|
occupancy_pct: number | null
|
|
available_rooms: number | null
|
|
is_bookable: boolean
|
|
}
|
|
|
|
export interface TariffSummary {
|
|
tariff_name: string
|
|
rate: number | null
|
|
min_stay: number | null
|
|
advance_max: number | null
|
|
is_available: boolean
|
|
}
|
|
|
|
export interface AdvancePurchaseCurvePoint {
|
|
lead_bucket: string
|
|
avg_rate: number
|
|
sample_count: number
|
|
}
|
|
|
|
export interface DowAnalysisPoint {
|
|
dow: number
|
|
dow_label: string
|
|
avg_rate: number
|
|
date_count: number
|
|
}
|
|
|
|
export interface StrategySummary {
|
|
advance_discount_pct: number | null
|
|
weekend_premium_pct: number | null
|
|
avg_sold_out_rate_pct: number | null
|
|
strategy_label: string
|
|
}
|
|
|
|
export interface HotelAnalysis {
|
|
hotel: Hotel
|
|
date_range: { from: string; to: string }
|
|
advance_purchase_curve: AdvancePurchaseCurvePoint[]
|
|
dow_analysis: DowAnalysisPoint[]
|
|
sold_out_pattern: Array<{ dow: number; dow_label: string; total_dates: number; sold_out_dates: number }>
|
|
strategy_summary: StrategySummary
|
|
}
|
|
|
|
export interface RateTimelinePoint {
|
|
scraped_at: string
|
|
rate_gross: number | null
|
|
availability_status: string
|
|
rooms_left: number | null
|
|
days_out: number
|
|
}
|