mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
RV/Boat Lots: sync lots to public rental_calendar amenities
Add a "Sync to Public Page" button that creates/updates one rental_calendar amenity per lot (name, size · rate · availability in the description, rate in booking_config, shown on the public page). Idempotent via amenities.source_rv_lot_id; removes synced amenities for deleted lots. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -110,6 +110,7 @@ export default function RVBoatLotsPage() {
|
||||
const [mapZoom, setMapZoom] = useState<number>(17);
|
||||
const [mapLockedView, setMapLockedView] = useState<{ lat: number; lng: number; zoom: number } | null>(null);
|
||||
const [savingMap, setSavingMap] = useState(false);
|
||||
const [syncingPublic, setSyncingPublic] = useState(false);
|
||||
|
||||
const toggleSet = (setter: React.Dispatch<React.SetStateAction<Set<string>>>, id: string) =>
|
||||
setter(prev => { const n = new Set(prev); n.has(id) ? n.delete(id) : n.add(id); return n; });
|
||||
@@ -278,6 +279,54 @@ export default function RVBoatLotsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const syncLotsToPublic = async () => {
|
||||
if (!associationId || lots.length === 0) { toast.error("No lots to sync"); return; }
|
||||
setSyncingPublic(true);
|
||||
try {
|
||||
const db = supabase as any;
|
||||
const { data: existing } = await db
|
||||
.from("amenities").select("id, source_rv_lot_id")
|
||||
.eq("association_id", associationId).not("source_rv_lot_id", "is", null);
|
||||
const byLot = new Map((existing || []).map((a: any) => [a.source_rv_lot_id, a.id]));
|
||||
const lotIds = new Set(lots.map(l => l.id));
|
||||
let count = 0;
|
||||
for (const lot of lots) {
|
||||
const occupied = lot.status === "occupied" ||
|
||||
rentals.some(r => r.lot_id === lot.id && (r.status === "active" || r.status === "vacating"));
|
||||
const payload: any = {
|
||||
association_id: associationId,
|
||||
name: `Lot ${lot.lot_number}${lot.lot_type ? ` (${vehicleTypeLabel(lot.lot_type)})` : ""}`,
|
||||
description: [
|
||||
`Size: ${lot.size || "—"}`,
|
||||
`Rate: ${lot.monthly_rate ? `$${lot.monthly_rate}/mo` : "—"}`,
|
||||
`Availability: ${occupied ? "Occupied" : "Available"}`,
|
||||
].join(" · "),
|
||||
amenity_type: "rental_calendar",
|
||||
booking_config: { slot_duration: 60, start_time: "09:00", end_time: "17:00", rental_fee: lot.monthly_rate ?? null },
|
||||
show_on_public_page: true,
|
||||
is_active: true,
|
||||
source_rv_lot_id: lot.id,
|
||||
updated_at: new Date().toISOString(),
|
||||
};
|
||||
const existingId = byLot.get(lot.id);
|
||||
if (existingId) {
|
||||
await db.from("amenities").update(payload).eq("id", existingId);
|
||||
} else {
|
||||
await db.from("amenities").insert({ ...payload, sort_order: 999 });
|
||||
}
|
||||
count++;
|
||||
}
|
||||
// Clean up synced amenities whose lot was deleted
|
||||
const stale = (existing || []).filter((a: any) => !lotIds.has(a.source_rv_lot_id)).map((a: any) => a.id);
|
||||
if (stale.length) await db.from("amenities").delete().in("id", stale);
|
||||
toast.success(`Synced ${count} lot${count === 1 ? "" : "s"} to the public page`);
|
||||
} catch (e: any) {
|
||||
toast.error(e.message || "Sync failed");
|
||||
} finally {
|
||||
setSyncingPublic(false);
|
||||
}
|
||||
};
|
||||
|
||||
const saveMap = async () => {
|
||||
if (!associationId) return;
|
||||
setSavingMap(true);
|
||||
@@ -510,6 +559,10 @@ export default function RVBoatLotsPage() {
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<CardTitle>Lot Inventory</CardTitle>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button size="sm" variant="outline" onClick={syncLotsToPublic} disabled={syncingPublic}>
|
||||
{syncingPublic ? "Syncing…" : "Sync to Public Page"}
|
||||
</Button>
|
||||
<Dialog open={lotDialogOpen} onOpenChange={setLotDialogOpen}>
|
||||
<DialogTrigger asChild><Button size="sm"><Plus className="h-4 w-4 mr-1" /> Add lot</Button></DialogTrigger>
|
||||
<DialogContent>
|
||||
@@ -540,6 +593,7 @@ export default function RVBoatLotsPage() {
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{selectedLotIds.size > 0 && (
|
||||
|
||||
Reference in New Issue
Block a user