Hide dashboard sidebar on staff/supervisor/admin sub-pages; fix mobile overflow
- 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>
This commit is contained in:
@@ -30,6 +30,7 @@ and this project follows [Semantic Versioning](https://semver.org/).
|
||||
|
||||
- Donations are no longer mutated when assigned to a registration — assignment now creates an immutable "leg" record referencing the original donation, so a partially-used donation keeps its original amount and remains assignable for its remaining balance instead of losing its history.
|
||||
- Email Attendees / WhatsApp Attendees now respond immediately after queuing recipients and send in the background, instead of blocking the page until every message has been sent.
|
||||
- Dashboard: the sidebar (My Events, Profile, Admin, etc.) is now hidden on every sub-page under Staff, Supervisor, and Admin — not just Reports — so those pages get the full width for their own content and navigation. It's still shown on each role's root landing page (`/dashboard/staff`, `/dashboard/supervisor`, `/dashboard/admin`); the top navbar's "Dashboard" link always leads back there.
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -39,6 +40,7 @@ and this project follows [Semantic Versioning](https://semver.org/).
|
||||
- Reports: Master Orders Breakdown's "Donations made" table (donor, amount, used/unused) is now included in the PDF, Excel, Email, and WhatsApp exports — previously only the Orders table was exported and the donations breakdown was visible on screen only. Also fixed a PDF rendering bug where a table title following another table (e.g. "Donations made" below the Orders table) could render at the page's right edge instead of the left margin.
|
||||
- Reports: "Email" export failed with `connect ECONNREFUSED 127.0.0.1:587` — it built its own mail transporter directly from `EMAIL_HOST`/etc. env vars instead of using the shared, DB-configurable SMTP settings (Admin → Site Settings) that the rest of the app already sends through, so it never picked up a working mail server. Now reuses the same shared mailer as tickets and account emails, with matching branded HTML styling.
|
||||
- Reports: "WhatsApp" export surfaced an unhelpful `Request failed with status code 500` on failure. WhatsApp send errors now report the actual reason from the WhatsApp API, and a disconnected WhatsApp session is now detected and auto-recovered the same way it already is for other WhatsApp actions (previously only ticket/text sends had this handling — PDF sends did not).
|
||||
- Dashboard mobile: the Reports page's Events filter dropdown and the Email/WhatsApp Attendees recipient-picker dropdown could extend past the right edge of narrow screens instead of staying within the viewport; they now stretch to match their trigger's width like the other dropdowns in the app already do. The Cashup event-costs table on the Admin Cashup detail page now scrolls horizontally on narrow screens instead of squeezing its columns.
|
||||
|
||||
## [1.3.2] - 2026-08-03
|
||||
|
||||
|
||||
@@ -175,49 +175,51 @@ function CostsTab({ eventId, token, costs, eventOptions, isClosed, onChanged }:
|
||||
|
||||
{isClosed && <div className="text-xs text-gray-500">This event is closed — costs can't be changed until it's reopened.</div>}
|
||||
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="text-left text-gray-500 border-b">
|
||||
<th className="py-1">Label</th>
|
||||
<th className="py-1">Type</th>
|
||||
<th className="py-1">Ticket type</th>
|
||||
<th className="py-1">Paid from</th>
|
||||
<th className="py-1 text-right">Amount</th>
|
||||
<th className="py-1 text-right">Total</th>
|
||||
{!isClosed && <th className="py-1"></th>}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{costs.map(c => (
|
||||
<tr key={c.id} className="border-b last:border-0">
|
||||
<td className="py-1.5">{c.label}</td>
|
||||
<td className="py-1.5">{c.costType === "once_off" ? "Once-off" : "Per item"}</td>
|
||||
<td className="py-1.5">{c.eventOption?.name || "—"}</td>
|
||||
<td className="py-1.5 capitalize">{c.paidFromMethod || "—"}</td>
|
||||
<td className="py-1.5 text-right">{money(c.amount)}</td>
|
||||
<td className="py-1.5 text-right font-medium">{money(c.total ?? c.amount)}</td>
|
||||
{!isClosed && (
|
||||
<td className="py-1.5 text-right whitespace-nowrap">
|
||||
<button className="text-xs text-indigo-600 hover:underline mr-2" onClick={() => startEdit(c)}>Edit</button>
|
||||
<button className="text-xs text-red-600 hover:underline" onClick={() => remove(c.id)}>Delete</button>
|
||||
</td>
|
||||
)}
|
||||
<div className="overflow-auto">
|
||||
<table className="w-full text-sm min-w-[600px]">
|
||||
<thead>
|
||||
<tr className="text-left text-gray-500 border-b">
|
||||
<th className="py-1">Label</th>
|
||||
<th className="py-1">Type</th>
|
||||
<th className="py-1">Ticket type</th>
|
||||
<th className="py-1">Paid from</th>
|
||||
<th className="py-1 text-right">Amount</th>
|
||||
<th className="py-1 text-right">Total</th>
|
||||
{!isClosed && <th className="py-1"></th>}
|
||||
</tr>
|
||||
))}
|
||||
{costs.length === 0 && (
|
||||
<tr><td colSpan={7} className="py-3 text-gray-400 text-center">No costs added yet.</td></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{costs.map(c => (
|
||||
<tr key={c.id} className="border-b last:border-0">
|
||||
<td className="py-1.5">{c.label}</td>
|
||||
<td className="py-1.5">{c.costType === "once_off" ? "Once-off" : "Per item"}</td>
|
||||
<td className="py-1.5">{c.eventOption?.name || "—"}</td>
|
||||
<td className="py-1.5 capitalize">{c.paidFromMethod || "—"}</td>
|
||||
<td className="py-1.5 text-right">{money(c.amount)}</td>
|
||||
<td className="py-1.5 text-right font-medium">{money(c.total ?? c.amount)}</td>
|
||||
{!isClosed && (
|
||||
<td className="py-1.5 text-right whitespace-nowrap">
|
||||
<button className="text-xs text-indigo-600 hover:underline mr-2" onClick={() => startEdit(c)}>Edit</button>
|
||||
<button className="text-xs text-red-600 hover:underline" onClick={() => remove(c.id)}>Delete</button>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
{costs.length === 0 && (
|
||||
<tr><td colSpan={7} className="py-3 text-gray-400 text-center">No costs added yet.</td></tr>
|
||||
)}
|
||||
</tbody>
|
||||
{costs.length > 0 && (
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colSpan={5} className="pt-2 text-right text-gray-500">Total costs</td>
|
||||
<td className="pt-2 text-right font-semibold">{money(totalCosts)}</td>
|
||||
{!isClosed && <td />}
|
||||
</tr>
|
||||
</tfoot>
|
||||
)}
|
||||
</tbody>
|
||||
{costs.length > 0 && (
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colSpan={5} className="pt-2 text-right text-gray-500">Total costs</td>
|
||||
<td className="pt-2 text-right font-semibold">{money(totalCosts)}</td>
|
||||
{!isClosed && <td />}
|
||||
</tr>
|
||||
</tfoot>
|
||||
)}
|
||||
</table>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{editingId !== null && (
|
||||
<div className="border rounded p-3 space-y-2 bg-gray-50">
|
||||
|
||||
@@ -63,10 +63,14 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
||||
);
|
||||
}
|
||||
|
||||
// The Reports page is a full-width, self-contained workspace (its own header, filters, and
|
||||
// navigation) — the dashboard sidebar's section links (My Events, Profile, Admin, etc.) would
|
||||
// just crowd it, so it's hidden there specifically, not app-wide.
|
||||
const hideSidebar = pathname === "/dashboard/supervisor/reports";
|
||||
// Sub-pages within staff/supervisor/admin (e.g. /dashboard/supervisor/reports,
|
||||
// /dashboard/admin/cashup/[id]) are full-width workspaces with their own internal navigation
|
||||
// and header — the dashboard sidebar's section links (My Events, Profile, Admin, etc.) would
|
||||
// just crowd them. The sidebar stays visible only on each role's root landing page; every
|
||||
// deeper sub-page hides it. Navigating back is never a dead end: Navbar's "Dashboard" link is
|
||||
// always present and /dashboard auto-redirects to the role root.
|
||||
const SIDEBAR_ROOTS = ["/dashboard/staff", "/dashboard/supervisor", "/dashboard/admin"];
|
||||
const hideSidebar = !!pathname && SIDEBAR_ROOTS.some(root => pathname !== root && pathname.startsWith(root + "/"));
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col">
|
||||
|
||||
@@ -75,7 +75,7 @@ function AttendeesCheckboxDropdown({
|
||||
)}
|
||||
</button>
|
||||
{open && (
|
||||
<div className="absolute z-10 mt-1 w-64 max-h-72 overflow-auto bg-white border rounded shadow">
|
||||
<div className="absolute z-10 mt-1 left-0 right-0 max-h-72 overflow-auto bg-white border rounded shadow">
|
||||
<div className="px-3 py-2 border-b sticky top-0 bg-white space-y-1">
|
||||
<label className="text-sm flex items-center gap-2">
|
||||
<input type="checkbox" checked={allSelected} onChange={e => toggleAll(e.target.checked)} />
|
||||
|
||||
@@ -86,7 +86,7 @@ function AttendeesCheckboxDropdown({
|
||||
)}
|
||||
</button>
|
||||
{open && (
|
||||
<div className="absolute z-10 mt-1 w-64 max-h-72 overflow-auto bg-white border rounded shadow">
|
||||
<div className="absolute z-10 mt-1 left-0 right-0 max-h-72 overflow-auto bg-white border rounded shadow">
|
||||
<div className="px-3 py-2 border-b sticky top-0 bg-white space-y-1">
|
||||
<label className="text-sm flex items-center gap-2">
|
||||
<input type="checkbox" checked={allSelected} onChange={(e) => toggleAll(e.target.checked)} />
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function EventsDropdown({ options, value, onChange }: {
|
||||
<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 w-full min-w-[240px] bg-white border rounded-lg shadow-lg max-h-64 overflow-auto p-1">
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user