Changes
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
co-authored by
renee-png
parent
b9f0a5c58d
commit
7d1668f544
@@ -964,7 +964,17 @@ function WeekStrip({
|
||||
);
|
||||
}
|
||||
|
||||
function ListView({ events, onEdit }: { events: Evt[]; onEdit?: (e: Evt) => void }) {
|
||||
function ListView({
|
||||
events,
|
||||
onEdit,
|
||||
staff,
|
||||
onBulkUpdated,
|
||||
}: {
|
||||
events: Evt[];
|
||||
onEdit?: (e: Evt) => void;
|
||||
staff: StaffRow[];
|
||||
onBulkUpdated: () => void | Promise<void>;
|
||||
}) {
|
||||
const today = startOfDay(new Date());
|
||||
const upcoming = [...events]
|
||||
.filter((e) => parseISO(e.start) >= today)
|
||||
@@ -976,10 +986,119 @@ function ListView({ events, onEdit }: { events: Evt[]; onEdit?: (e: Evt) => void
|
||||
arr.push(e);
|
||||
grouped.set(k, arr);
|
||||
});
|
||||
|
||||
// Bulk-edit state. Only rows with source === "event" can be bulk updated
|
||||
// because they're the only ones backed by the editable `events` table.
|
||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||
const [bulkType, setBulkType] = useState<string>("");
|
||||
const [bulkAssignee, setBulkAssignee] = useState<string>("");
|
||||
const [applying, setApplying] = useState(false);
|
||||
|
||||
const editableUpcoming = upcoming.filter((e) => e.source === "event");
|
||||
const allEditableSelected =
|
||||
editableUpcoming.length > 0 && editableUpcoming.every((e) => selected.has(e.id));
|
||||
|
||||
const toggleOne = (id: string) => {
|
||||
setSelected((s) => {
|
||||
const n = new Set(s);
|
||||
if (n.has(id)) n.delete(id);
|
||||
else n.add(id);
|
||||
return n;
|
||||
});
|
||||
};
|
||||
const toggleAll = () => {
|
||||
if (allEditableSelected) setSelected(new Set());
|
||||
else setSelected(new Set(editableUpcoming.map((e) => e.id)));
|
||||
};
|
||||
|
||||
const applyBulk = async () => {
|
||||
if (selected.size === 0) {
|
||||
toast.error("Select at least one event");
|
||||
return;
|
||||
}
|
||||
if (!bulkType && !bulkAssignee) {
|
||||
toast.error("Choose a category or assignee to update");
|
||||
return;
|
||||
}
|
||||
setApplying(true);
|
||||
const patch: Record<string, string> = {};
|
||||
if (bulkType) patch.event_type = bulkType;
|
||||
if (bulkAssignee) patch.assigned_to = bulkAssignee;
|
||||
const { error } = await supabase
|
||||
.from("events")
|
||||
.update(patch)
|
||||
.in("id", Array.from(selected));
|
||||
setApplying(false);
|
||||
if (error) {
|
||||
toast.error(error.message);
|
||||
return;
|
||||
}
|
||||
toast.success(`Updated ${selected.size} event${selected.size === 1 ? "" : "s"}`);
|
||||
setSelected(new Set());
|
||||
setBulkType("");
|
||||
setBulkAssignee("");
|
||||
await onBulkUpdated();
|
||||
};
|
||||
|
||||
if (upcoming.length === 0)
|
||||
return <p className="text-sm text-muted-foreground py-8 text-center">No upcoming events.</p>;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Bulk action toolbar */}
|
||||
<div className="flex flex-wrap items-center gap-2 border rounded-md p-2 bg-muted/30">
|
||||
<label className="flex items-center gap-2 text-xs cursor-pointer pl-1">
|
||||
<Checkbox
|
||||
checked={allEditableSelected}
|
||||
onCheckedChange={toggleAll}
|
||||
disabled={editableUpcoming.length === 0}
|
||||
/>
|
||||
<span className="text-muted-foreground">
|
||||
{selected.size > 0 ? `${selected.size} selected` : "Select all"}
|
||||
</span>
|
||||
</label>
|
||||
<div className="h-5 w-px bg-border mx-1" />
|
||||
<Select value={bulkType} onValueChange={setBulkType}>
|
||||
<SelectTrigger className="h-8 w-[170px] text-xs">
|
||||
<SelectValue placeholder="Set category…" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{EVENT_TYPES.map((t) => (
|
||||
<SelectItem key={t.key} value={t.key}>
|
||||
<span className="flex items-center gap-2">
|
||||
<span className={cn("h-2.5 w-2.5 rounded-full", t.bg)} />
|
||||
{t.label}
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select value={bulkAssignee} onValueChange={setBulkAssignee}>
|
||||
<SelectTrigger className="h-8 w-[180px] text-xs">
|
||||
<SelectValue placeholder="Assign to…" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{staff.map((s) => (
|
||||
<SelectItem key={s.id} value={s.id}>
|
||||
{s.full_name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={applyBulk}
|
||||
disabled={applying || selected.size === 0 || (!bulkType && !bulkAssignee)}
|
||||
>
|
||||
{applying ? "Applying…" : "Apply"}
|
||||
</Button>
|
||||
{selected.size > 0 && (
|
||||
<Button size="sm" variant="ghost" onClick={() => setSelected(new Set())}>
|
||||
Clear
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{Array.from(grouped.entries()).map(([day, evts]) => (
|
||||
<div key={day}>
|
||||
<h4 className="font-serif text-sm font-semibold mb-2">
|
||||
@@ -987,7 +1106,20 @@ function ListView({ events, onEdit }: { events: Evt[]; onEdit?: (e: Evt) => void
|
||||
</h4>
|
||||
<ul className="space-y-2">
|
||||
{evts.map((e) => (
|
||||
<EventRow key={`${e.source}-${e.id}`} evt={e} onEdit={onEdit} />
|
||||
<li key={`${e.source}-${e.id}`} className="flex items-start gap-2">
|
||||
{e.source === "event" ? (
|
||||
<Checkbox
|
||||
className="mt-3 ml-1"
|
||||
checked={selected.has(e.id)}
|
||||
onCheckedChange={() => toggleOne(e.id)}
|
||||
/>
|
||||
) : (
|
||||
<span className="w-4 ml-1" />
|
||||
)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<EventRow evt={e} onEdit={onEdit} />
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user