Initial commit: HK Planner app

Housekeeping workload and hours planning app — port of the WP hotel
housekeeping hours calculator plugin. 7-day occupancy planner with
Newbook PMS integration, staff rota, time requirements, and pickup tracking.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-02 09:24:14 +00:00
commit 4abae5eda1
28 changed files with 2475 additions and 0 deletions

96
frontend/src/api.ts Normal file
View file

@ -0,0 +1,96 @@
import type { BookingsData, StaffMember, GeneralTask, PickupData, TimeReqs } from './types'
const BASE = '/hk-planner/api'
export interface ConfigData {
time_requirements: TimeReqs
staff_data: StaffMember[]
pickup_data: PickupData
general_tasks: GeneralTask[]
last_reviewed: string
tolerance_minutes: number
}
export interface CategoryConfig {
id: string
name: string
room_count: number
excluded: boolean
}
async function request<T>(path: string, opts?: RequestInit): Promise<T> {
const res = await fetch(BASE + path, { credentials: 'include', ...opts })
if (!res.ok) {
const body = await res.json().catch(() => ({})) as { error?: string }
throw new Error(body.error || `HTTP ${res.status}`)
}
return res.json() as Promise<T>
}
export function getBookings(weekStart?: string, lastViewed?: string, forceRefresh = false): Promise<BookingsData> {
const p = new URLSearchParams()
if (weekStart) p.set('week_start', weekStart)
if (lastViewed) p.set('last_viewed', lastViewed)
if (forceRefresh) p.set('force_refresh', '1')
return request<BookingsData>(`/bookings?${p}`)
}
export function getConfig(): Promise<ConfigData> {
return request<ConfigData>('/config')
}
export function putTimeReq(cat: string, action: string, value: number): Promise<{ ok: boolean }> {
return request('/config/time-requirements', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ cat, action, value }),
})
}
export function putStaff(staff_data: StaffMember[]): Promise<{ ok: boolean }> {
return request('/config/staff', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ staff_data }),
})
}
export function putPickup(pickup_data: PickupData): Promise<{ ok: boolean }> {
return request('/config/pickup', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pickup_data }),
})
}
export function putGeneralTasks(general_tasks: GeneralTask[]): Promise<{ ok: boolean }> {
return request('/config/general-tasks', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ general_tasks }),
})
}
export function putLastReviewed(date: string): Promise<{ ok: boolean }> {
return request('/config/last-reviewed', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ date }),
})
}
export function getCategories(): Promise<{ categories: CategoryConfig[] }> {
return request('/categories')
}
export function putCategories(order: string[], excluded: string[]): Promise<{ ok: boolean }> {
return request('/categories', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ order, excluded }),
})
}
export function testNewbook(): Promise<{ ok: boolean; message?: string; error?: string }> {
return request('/newbook/test', { method: 'POST' })
}