- Sidebar now shows only on each role's root landing page (/dashboard/staff, /dashboard/supervisor, /dashboard/admin) and hides on every sub-page beneath them, not just Reports. Navbar's "Dashboard" link always leads back to the role root. - Fixed two dropdown panels (EventsDropdown, the Email/WhatsApp Attendees recipient picker) that could extend past the viewport's right edge on narrow screens; both now stretch to their trigger's width like other dropdowns in the app already do. - Wrapped the Admin Cashup event-costs table in the same overflow-auto container every sibling table on that page already uses, so it scrolls horizontally on narrow screens instead of squeezing its columns. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
62 lines
2.5 KiB
TypeScript
62 lines
2.5 KiB
TypeScript
"use client";
|
|
|
|
import React, { useEffect, useRef, useState } from "react";
|
|
import { ChevronDown } from "lucide-react";
|
|
|
|
export default function EventsDropdown({ options, value, onChange }: {
|
|
options: { value: string; label: string }[];
|
|
value: string[];
|
|
onChange: (v: string[]) => void;
|
|
}) {
|
|
const [open, setOpen] = useState(false);
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
const onClick = (e: MouseEvent) => {
|
|
if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);
|
|
};
|
|
document.addEventListener("mousedown", onClick);
|
|
return () => document.removeEventListener("mousedown", onClick);
|
|
}, []);
|
|
|
|
const toggle = (v: string) => {
|
|
onChange(value.includes(v) ? value.filter(x => x !== v) : [...value, v]);
|
|
};
|
|
|
|
const summary = value.length === 0
|
|
? "No events selected"
|
|
: options.length > 0 && value.length === options.length
|
|
? "All events"
|
|
: value.length === 1
|
|
? (options.find(o => o.value === value[0])?.label || "1 event selected")
|
|
: `${value.length} events selected`;
|
|
|
|
return (
|
|
<div className="relative" ref={ref}>
|
|
<button
|
|
type="button"
|
|
onClick={() => setOpen(o => !o)}
|
|
className="w-full flex items-center justify-between gap-2 border rounded-lg px-3 py-2 text-sm bg-white hover:bg-gray-50"
|
|
>
|
|
<span className="truncate text-left">{summary}</span>
|
|
<ChevronDown className={"w-4 h-4 text-gray-400 shrink-0 transition-transform " + (open ? "rotate-180" : "")} />
|
|
</button>
|
|
{open && (
|
|
<div className="absolute z-20 mt-1 left-0 right-0 bg-white border rounded-lg shadow-lg max-h-64 overflow-auto p-1">
|
|
<div className="flex items-center justify-between px-2 py-1.5 text-xs text-gray-500 border-b mb-1">
|
|
<button type="button" className="hover:underline" onClick={() => onChange(options.map(o => o.value))}>Select all</button>
|
|
<button type="button" className="hover:underline" onClick={() => onChange([])}>Clear</button>
|
|
</div>
|
|
{options.map(opt => (
|
|
<label key={opt.value} className="flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-gray-50 cursor-pointer">
|
|
<input type="checkbox" checked={value.includes(opt.value)} onChange={() => toggle(opt.value)} />
|
|
<span className="truncate">{opt.label}</span>
|
|
</label>
|
|
))}
|
|
{options.length === 0 && <div className="px-2 py-1.5 text-xs text-gray-400">No events available.</div>}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|