Calendar: manage rental/meeting calendars from the dashboard

Add a "Manage Calendars" action (rental/meeting modes) that opens the full
AmenitiesManager for a chosen association in a dialog — so staff can create/
configure rental calendars, manage their bookings, and block/unblock
availability without going to the association overview page. Adding bookings
inline was already supported in these modes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 17:39:44 -04:00
parent 6932c5acbb
commit 4ecbdcfd4d
+38 -1
View File
@@ -4,7 +4,7 @@ import { supabase } from "@/integrations/supabase/client";
import { useAuth } from "@/contexts/AuthContext"; import { useAuth } from "@/contexts/AuthContext";
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import { useGoogleDrive } from "@/hooks/useGoogleDrive"; import { useGoogleDrive } from "@/hooks/useGoogleDrive";
import { Calendar as CalIcon, Plus, ChevronLeft, ChevronRight, Lock, Clock, MoreHorizontal, Edit, Trash2, Download, Loader2, Shield, Eye, EyeOff, Users, Building2, RefreshCw, HardDrive, ListChecks, Search } from "lucide-react"; import { Calendar as CalIcon, Plus, ChevronLeft, ChevronRight, Lock, Clock, MoreHorizontal, Edit, Trash2, Download, Loader2, Shield, Eye, EyeOff, Users, Building2, RefreshCw, HardDrive, ListChecks, Search, Settings2 } from "lucide-react";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
@@ -14,6 +14,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogD
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import AmenitiesManager from "@/components/association/AmenitiesManager";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { fetchAndParseCalendar, validateCalendarUrl } from "@/lib/calendarImportUtils"; import { fetchAndParseCalendar, validateCalendarUrl } from "@/lib/calendarImportUtils";
import { import {
@@ -201,6 +202,10 @@ export default function CalendarPage({ boardAssociationIds }: { boardAssociation
const [manageSelected, setManageSelected] = useState<Set<string>>(new Set()); const [manageSelected, setManageSelected] = useState<Set<string>>(new Set());
const [bulkDeleting, setBulkDeleting] = useState(false); const [bulkDeleting, setBulkDeleting] = useState(false);
const [bookingAmenities, setBookingAmenities] = useState<any[]>([]); const [bookingAmenities, setBookingAmenities] = useState<any[]>([]);
// Manage rental/meeting calendars (configure + bookings + block dates) from the dashboard
const [manageCalOpen, setManageCalOpen] = useState(false);
const [manageCalAssocId, setManageCalAssocId] = useState<string>("");
const [recurrence, setRecurrence] = useState<RecurrenceForm>(defaultRecurrence); const [recurrence, setRecurrence] = useState<RecurrenceForm>(defaultRecurrence);
const isBoardView = !!boardAssociationIds?.length; const isBoardView = !!boardAssociationIds?.length;
@@ -694,6 +699,11 @@ export default function CalendarPage({ boardAssociationIds }: { boardAssociation
<Button variant="outline" className="gap-2" onClick={() => { setManageSelected(new Set()); setManageDialogOpen(true); }}> <Button variant="outline" className="gap-2" onClick={() => { setManageSelected(new Set()); setManageDialogOpen(true); }}>
<ListChecks className="h-4 w-4" /> Manage Events <ListChecks className="h-4 w-4" /> Manage Events
</Button> </Button>
{calendarMode !== "events" && (
<Button variant="outline" className="gap-2" onClick={() => { setManageCalAssocId(associations[0]?.id || ""); setManageCalOpen(true); }}>
<Settings2 className="h-4 w-4" /> Manage Calendars
</Button>
)}
<Button className="gap-2" onClick={() => calendarMode === "events" ? openNew() : openBookingNew()}><Plus className="h-4 w-4" /> {calendarMode === "events" ? "New Event" : "New Booking"}</Button> <Button className="gap-2" onClick={() => calendarMode === "events" ? openNew() : openBookingNew()}><Plus className="h-4 w-4" /> {calendarMode === "events" ? "New Event" : "New Booking"}</Button>
</div> </div>
)} )}
@@ -1289,6 +1299,33 @@ export default function CalendarPage({ boardAssociationIds }: { boardAssociation
</Dialog> </Dialog>
{/* Block Date Dialog */} {/* Block Date Dialog */}
{/* Manage rental/meeting calendars: configure amenities, manage bookings, block availability */}
<Dialog open={manageCalOpen} onOpenChange={setManageCalOpen}>
<DialogContent className="sm:max-w-[760px] max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>Manage {calendarMode === "meetings" ? "Meeting" : "Rental"} Calendars</DialogTitle>
<DialogDescription>
Create or edit calendars, manage their bookings, and block availability without leaving the calendar.
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div>
<Label>Association</Label>
<Select value={manageCalAssocId} onValueChange={setManageCalAssocId}>
<SelectTrigger><SelectValue placeholder="Select an association" /></SelectTrigger>
<SelectContent>{associations.map(a => <SelectItem key={a.id} value={a.id}>{a.name}</SelectItem>)}</SelectContent>
</Select>
</div>
{manageCalAssocId
? <AmenitiesManager associationId={manageCalAssocId} />
: <p className="text-sm text-muted-foreground py-6 text-center">Select an association to manage its calendars.</p>}
</div>
<DialogFooter>
<Button variant="outline" onClick={() => { setManageCalOpen(false); fetchData(); }}>Done</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<Dialog open={blockDialogOpen} onOpenChange={setBlockDialogOpen}> <Dialog open={blockDialogOpen} onOpenChange={setBlockDialogOpen}>
<DialogContent> <DialogContent>
<DialogHeader><DialogTitle>Block Date</DialogTitle></DialogHeader> <DialogHeader><DialogTitle>Block Date</DialogTitle></DialogHeader>