mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
RV/Boat Lots: link rentals to owner/unit + Notice to Vacate toggle
Phase 2: rental form now has optional Owner and Unit selectors (auto-fills renter info/unit from the chosen owner), persisting owner_id/unit_id. Phase 3: add RvRentalVacateButton on Owner and Unit profiles — shows only when that owner/unit has an active/vacating RV-boat rental, and toggles the rental status between active (renting) and vacating. Active Rentals tab now includes vacating rentals with a badge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,8 @@ export default function RVBoatLotsPage() {
|
||||
const [lots, setLots] = useState<Lot[]>([]);
|
||||
const [rentals, setRentals] = useState<Rental[]>([]);
|
||||
const [waitlist, setWaitlist] = useState<WaitlistEntry[]>([]);
|
||||
const [owners, setOwners] = useState<any[]>([]);
|
||||
const [units, setUnits] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// dialogs
|
||||
@@ -68,7 +70,8 @@ export default function RVBoatLotsPage() {
|
||||
const [lotForm, setLotForm] = useState({ lot_number: "", lot_type: "rv", size: "", monthly_rate: "", notes: "" });
|
||||
const [rentalForm, setRentalForm] = useState({
|
||||
lot_id: "", renter_name: "", renter_email: "", renter_phone: "",
|
||||
vehicle_description: "", start_date: new Date().toISOString().slice(0, 10), monthly_rate: "", notes: ""
|
||||
vehicle_description: "", start_date: new Date().toISOString().slice(0, 10), monthly_rate: "", notes: "",
|
||||
owner_id: "", unit_id: ""
|
||||
});
|
||||
const [waitlistForm, setWaitlistForm] = useState({
|
||||
requester_name: "", requester_email: "", requester_phone: "",
|
||||
@@ -100,10 +103,12 @@ export default function RVBoatLotsPage() {
|
||||
if (!associationId) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
const [lotsRes, rentalsRes, wlRes] = await Promise.all([
|
||||
const [lotsRes, rentalsRes, wlRes, ownersRes, unitsRes] = await Promise.all([
|
||||
supabase.from("rv_boat_lots").select("*").eq("association_id", associationId).order("lot_number"),
|
||||
supabase.from("rv_boat_lot_rentals").select("*").eq("association_id", associationId).order("start_date", { ascending: false }),
|
||||
supabase.from("rv_boat_lot_waitlist").select("*").eq("association_id", associationId).order("position"),
|
||||
supabase.from("owners").select("id, first_name, last_name, unit_id, email, phone").eq("association_id", associationId).eq("status", "active").order("last_name"),
|
||||
supabase.from("units").select("id, unit_number").eq("association_id", associationId).order("unit_number"),
|
||||
]);
|
||||
if (lotsRes.error) throw lotsRes.error;
|
||||
if (rentalsRes.error) throw rentalsRes.error;
|
||||
@@ -111,6 +116,8 @@ export default function RVBoatLotsPage() {
|
||||
setLots(lotsRes.data || []);
|
||||
setRentals(rentalsRes.data || []);
|
||||
setWaitlist(wlRes.data || []);
|
||||
setOwners(ownersRes.data || []);
|
||||
setUnits(unitsRes.data || []);
|
||||
} catch (e: any) {
|
||||
toast.error(e.message || "Failed to load data");
|
||||
} finally {
|
||||
@@ -204,12 +211,14 @@ export default function RVBoatLotsPage() {
|
||||
start_date: rentalForm.start_date,
|
||||
monthly_rate: rentalForm.monthly_rate ? Number(rentalForm.monthly_rate) : null,
|
||||
notes: rentalForm.notes || null,
|
||||
owner_id: rentalForm.owner_id || null,
|
||||
unit_id: rentalForm.unit_id || null,
|
||||
status: "active",
|
||||
});
|
||||
if (error) { toast.error(error.message); return; }
|
||||
toast.success("Rental created");
|
||||
setRentalDialogOpen(false);
|
||||
setRentalForm({ lot_id: "", renter_name: "", renter_email: "", renter_phone: "", vehicle_description: "", start_date: new Date().toISOString().slice(0, 10), monthly_rate: "", notes: "" });
|
||||
setRentalForm({ lot_id: "", renter_name: "", renter_email: "", renter_phone: "", vehicle_description: "", start_date: new Date().toISOString().slice(0, 10), monthly_rate: "", notes: "", owner_id: "", unit_id: "" });
|
||||
load();
|
||||
};
|
||||
|
||||
@@ -546,6 +555,38 @@ export default function RVBoatLotsPage() {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<Label>Link to Owner</Label>
|
||||
<Select value={rentalForm.owner_id || "none"} onValueChange={v => {
|
||||
if (v === "none") { setRentalForm(f => ({ ...f, owner_id: "" })); return; }
|
||||
const o = owners.find(x => x.id === v);
|
||||
setRentalForm(f => ({
|
||||
...f, owner_id: v,
|
||||
unit_id: o?.unit_id || f.unit_id,
|
||||
renter_name: f.renter_name || `${o?.first_name || ""} ${o?.last_name || ""}`.trim(),
|
||||
renter_email: f.renter_email || o?.email || "",
|
||||
renter_phone: f.renter_phone || o?.phone || "",
|
||||
}));
|
||||
}}>
|
||||
<SelectTrigger><SelectValue placeholder="Optional" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="none">None</SelectItem>
|
||||
{owners.map(o => <SelectItem key={o.id} value={o.id}>{`${o.last_name || ""}, ${o.first_name || ""}`.replace(/^, |, $/g, "").trim() || "Unnamed"}</SelectItem>)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Link to Unit</Label>
|
||||
<Select value={rentalForm.unit_id || "none"} onValueChange={v => setRentalForm(f => ({ ...f, unit_id: v === "none" ? "" : v }))}>
|
||||
<SelectTrigger><SelectValue placeholder="Optional" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="none">None</SelectItem>
|
||||
{units.map(u => <SelectItem key={u.id} value={u.id}>Unit {u.unit_number}</SelectItem>)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div><Label>Renter name *</Label><Input value={rentalForm.renter_name} onChange={e => setRentalForm({ ...rentalForm, renter_name: e.target.value })} /></div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div><Label>Email</Label><Input type="email" value={rentalForm.renter_email} onChange={e => setRentalForm({ ...rentalForm, renter_email: e.target.value })} /></div>
|
||||
@@ -566,7 +607,7 @@ export default function RVBoatLotsPage() {
|
||||
</Dialog>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{(() => { const activeRentals = rentals.filter(r => r.status === "active"); return (
|
||||
{(() => { const activeRentals = rentals.filter(r => r.status === "active" || r.status === "vacating"); return (
|
||||
<>
|
||||
{selectedRentalIds.size > 0 && (
|
||||
<div className="flex items-center justify-between rounded-md border bg-muted/40 px-3 py-2 mb-3">
|
||||
@@ -597,6 +638,7 @@ export default function RVBoatLotsPage() {
|
||||
<Badge variant={r.is_owner ? "default" : "secondary"} className="text-[10px]">
|
||||
{r.is_owner ? "Owner" : "Renter"}
|
||||
</Badge>
|
||||
{r.status === "vacating" && <Badge variant="outline" className="text-[10px] border-amber-300 text-amber-700">Vacating</Badge>}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">{r.renter_email}</div>
|
||||
</TableCell>
|
||||
|
||||
Reference in New Issue
Block a user