Initial commit
Next.js + Express event management app for Hope Family Church.
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { apiFetch } from "@/lib/api";
|
||||
|
||||
export default function CashupLandingPage() {
|
||||
const { token } = useAuth();
|
||||
const router = useRouter();
|
||||
|
||||
const [events, setEvents] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [search, setSearch] = useState("");
|
||||
const [showPast, setShowPast] = useState(true);
|
||||
const [showInactive, setShowInactive] = useState(false);
|
||||
const [showClosed, setShowClosed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) return;
|
||||
(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const evs = await apiFetch<any[]>("/api/events/all?includePast=true&includeInactive=true", { authToken: token });
|
||||
setEvents(Array.isArray(evs) ? evs : []);
|
||||
} catch (e: any) {
|
||||
setError(e?.message || "Failed to load events");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
})();
|
||||
}, [token]);
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const now = new Date();
|
||||
return events
|
||||
.filter(ev => showPast || !ev.endDate || new Date(ev.endDate) >= now)
|
||||
.filter(ev => showInactive || ev.isActive !== false)
|
||||
.filter(ev => showClosed || ev.cashupStatus !== "closed")
|
||||
.filter(ev => !search.trim() || ev.title?.toLowerCase().includes(search.trim().toLowerCase()))
|
||||
.sort((a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime());
|
||||
}, [events, showPast, showInactive, showClosed, search]);
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl mx-auto space-y-4">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold">Post-event Cashup</h1>
|
||||
<p className="text-sm text-gray-500 mt-1">Set costs, reconcile takings, and close out an event. Admin only.</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="px-3 py-1.5 text-sm rounded bg-gray-100 hover:bg-gray-200 text-gray-800 shrink-0"
|
||||
onClick={() => router.push("/dashboard")}
|
||||
>Back</button>
|
||||
</div>
|
||||
|
||||
{error && <div className="text-sm text-red-600 bg-red-50 border border-red-100 rounded p-2">{error}</div>}
|
||||
|
||||
<div className="bg-white border rounded-lg p-3 flex flex-wrap items-center gap-3">
|
||||
<input
|
||||
className="border rounded px-3 py-1.5 text-sm flex-1 min-w-48"
|
||||
placeholder="Search events…"
|
||||
value={search}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
/>
|
||||
<label className="flex items-center gap-1.5 text-sm text-gray-600">
|
||||
<input type="checkbox" checked={showPast} onChange={e => setShowPast(e.target.checked)} /> Past events
|
||||
</label>
|
||||
<label className="flex items-center gap-1.5 text-sm text-gray-600">
|
||||
<input type="checkbox" checked={showInactive} onChange={e => setShowInactive(e.target.checked)} /> Inactive events
|
||||
</label>
|
||||
<label className="flex items-center gap-1.5 text-sm text-gray-600">
|
||||
<input type="checkbox" checked={showClosed} onChange={e => setShowClosed(e.target.checked)} /> Closed events
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{loading && <div className="text-sm text-gray-400">Loading…</div>}
|
||||
|
||||
{!loading && (
|
||||
<ul className="space-y-2">
|
||||
{filtered.map(ev => {
|
||||
const isClosed = ev.cashupStatus === "closed";
|
||||
return (
|
||||
<li
|
||||
key={ev.id}
|
||||
className="border rounded-lg p-3 bg-white hover:bg-indigo-50/40 cursor-pointer transition-colors flex items-center justify-between gap-3"
|
||||
onClick={() => router.push(`/dashboard/admin/cashup/${ev.id}`)}
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="font-medium text-sm">{ev.title}</span>
|
||||
<span className={"text-[10px] px-1.5 py-0.5 rounded " + (isClosed ? "bg-rose-50 text-rose-700" : "bg-emerald-50 text-emerald-700")}>
|
||||
{isClosed ? "Closed" : "Open"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 mt-0.5">
|
||||
{ev.startDate ? new Date(ev.startDate).toLocaleDateString() : ""}{ev.endDate ? ` – ${new Date(ev.endDate).toLocaleDateString()}` : ""}
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-xs text-indigo-600 shrink-0">Manage →</span>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{filtered.length === 0 && <div className="text-sm text-gray-400">No events match the current filters.</div>}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user