Port occupancy sync + category ordering from the forecasting version

- Bookability showed no availability because newbook_occupancy_report_data
  was never populated: add reports_occupancy client method and
  sync_occupancy job, run before rates in both Sync Now and the daily
  schedule (single fast API call)
- Category order: default display_order to the Newbook category id on
  sync (was 0 → alphabetical), preserve manual order on re-sync, extend
  PATCH to accept display_order, add up/down reorder arrows in Settings

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-05 14:18:03 +00:00
parent c924d793e8
commit f51e1dcb76
5 changed files with 199 additions and 9 deletions

View file

@ -1,7 +1,7 @@
import { useState } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { Save, RefreshCw, Database, Clock, BedDouble } from 'lucide-react'
import { Save, RefreshCw, Database, Clock, BedDouble, ChevronUp, ChevronDown } from 'lucide-react'
import api from '../api'
const TABS = [
@ -203,6 +203,23 @@ function RoomCategoriesCard() {
onSuccess: () => qc.invalidateQueries({ queryKey: ['room-categories'] }),
})
const reorderCategories = useMutation({
mutationFn: (ordered: RoomCategory[]) =>
Promise.all(ordered.map((cat, idx) =>
api.patch(`/bookability/categories/${cat.id}`, { display_order: (idx + 1) * 10 })
)),
onSuccess: () => qc.invalidateQueries({ queryKey: ['room-categories'] }),
})
const moveCategory = (idx: number, dir: -1 | 1) => {
if (!categories) return
const target = idx + dir
if (target < 0 || target >= categories.length) return
const reordered = [...categories]
;[reordered[idx], reordered[target]] = [reordered[target], reordered[idx]]
reorderCategories.mutate(reordered)
}
return (
<div className="card">
<div className="card-header">
@ -243,10 +260,11 @@ function RoomCategoriesCard() {
<th>Included</th>
<th>Category</th>
<th>Rooms</th>
<th>Order</th>
</tr>
</thead>
<tbody>
{categories.map(cat => (
{categories.map((cat, idx) => (
<tr key={cat.id}>
<td>
<input
@ -258,6 +276,28 @@ function RoomCategoriesCard() {
</td>
<td>{cat.site_name}</td>
<td>{cat.room_count}</td>
<td>
<span style={{ display: 'inline-flex', gap: 4 }}>
<button
className="btn btn-outline btn-sm"
style={{ padding: '2px 6px' }}
onClick={() => moveCategory(idx, -1)}
disabled={idx === 0 || reorderCategories.isPending}
title="Move up"
>
<ChevronUp size={13} strokeWidth={1.75} />
</button>
<button
className="btn btn-outline btn-sm"
style={{ padding: '2px 6px' }}
onClick={() => moveCategory(idx, 1)}
disabled={idx === categories.length - 1 || reorderCategories.isPending}
title="Move down"
>
<ChevronDown size={13} strokeWidth={1.75} />
</button>
</span>
</td>
</tr>
))}
</tbody>