Modified by www.SourceFiles.app
This commit is contained in:
@@ -1,109 +0,0 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
|
||||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
||||||
import { supabase } from "@/integrations/supabase/client";
|
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
|
||||||
import { Plus, Trash2, CalendarDays } from "lucide-react";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/_authenticated/calendar")({
|
|
||||||
head: () => ({ meta: [{ title: "Calendar — School Portal" }] }),
|
|
||||||
component: CalendarPage,
|
|
||||||
});
|
|
||||||
|
|
||||||
function CalendarPage() {
|
|
||||||
const { user, roles } = useAuth();
|
|
||||||
const isAdmin = roles.includes("admin");
|
|
||||||
const qc = useQueryClient();
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const [form, setForm] = useState({ title: "", date: "", end_date: "", description: "" });
|
|
||||||
|
|
||||||
const { data: events } = useQuery({
|
|
||||||
queryKey: ["calendar"],
|
|
||||||
queryFn: async () => (await supabase.from("calendar_events").select("*").order("date")).data ?? [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const add = useMutation({
|
|
||||||
mutationFn: async () => {
|
|
||||||
const { error } = await supabase.from("calendar_events").insert({
|
|
||||||
title: form.title, date: form.date, end_date: form.end_date || null, description: form.description || null, created_by: user!.id,
|
|
||||||
});
|
|
||||||
if (error) throw error;
|
|
||||||
},
|
|
||||||
onSuccess: () => { setOpen(false); setForm({ title: "", date: "", end_date: "", description: "" }); qc.invalidateQueries({ queryKey: ["calendar"] }); toast.success("Event added"); },
|
|
||||||
onError: (e: Error) => toast.error(e.message),
|
|
||||||
});
|
|
||||||
|
|
||||||
const del = useMutation({
|
|
||||||
mutationFn: async (id: string) => { const { error } = await supabase.from("calendar_events").delete().eq("id", id); if (error) throw error; },
|
|
||||||
onSuccess: () => qc.invalidateQueries({ queryKey: ["calendar"] }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const today = new Date().toISOString().slice(0, 10);
|
|
||||||
const upcoming = (events ?? []).filter((e) => e.date >= today);
|
|
||||||
const past = (events ?? []).filter((e) => e.date < today);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="p-8 max-w-3xl">
|
|
||||||
<div className="flex justify-between items-center mb-6">
|
|
||||||
<div>
|
|
||||||
<h1 className="text-2xl font-semibold">School calendar</h1>
|
|
||||||
<p className="text-muted-foreground text-sm">Year at a glance.</p>
|
|
||||||
</div>
|
|
||||||
{isAdmin && (
|
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
|
||||||
<DialogTrigger asChild><Button><Plus className="h-4 w-4 mr-1" /> Add event</Button></DialogTrigger>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogHeader><DialogTitle>New calendar event</DialogTitle></DialogHeader>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div><Label>Title</Label><Input value={form.title} onChange={(e) => setForm({ ...form, title: e.target.value })} /></div>
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
|
||||||
<div><Label>Date</Label><Input type="date" value={form.date} onChange={(e) => setForm({ ...form, date: e.target.value })} /></div>
|
|
||||||
<div><Label>End date (optional)</Label><Input type="date" value={form.end_date} onChange={(e) => setForm({ ...form, end_date: e.target.value })} /></div>
|
|
||||||
</div>
|
|
||||||
<div><Label>Description</Label><Textarea value={form.description} onChange={(e) => setForm({ ...form, description: e.target.value })} /></div>
|
|
||||||
<Button className="w-full" disabled={!form.title || !form.date || add.isPending} onClick={() => add.mutate()}>Add</Button>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2 className="font-medium text-sm text-muted-foreground mb-2">Upcoming</h2>
|
|
||||||
<div className="bg-card border rounded-lg divide-y mb-6">
|
|
||||||
{upcoming.map((e) => (
|
|
||||||
<div key={e.id} className="p-3 flex justify-between items-start">
|
|
||||||
<div>
|
|
||||||
<div className="font-medium">{e.title}</div>
|
|
||||||
<div className="text-xs text-muted-foreground">
|
|
||||||
{new Date(e.date).toLocaleDateString(undefined, { weekday: "short", month: "short", day: "numeric" })}
|
|
||||||
{e.end_date && ` – ${new Date(e.end_date).toLocaleDateString()}`}
|
|
||||||
</div>
|
|
||||||
{e.description && <div className="text-sm mt-1">{e.description}</div>}
|
|
||||||
</div>
|
|
||||||
{isAdmin && <Button size="icon" variant="ghost" onClick={() => del.mutate(e.id)}><Trash2 className="h-4 w-4" /></Button>}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{upcoming.length === 0 && <div className="p-6 text-sm text-muted-foreground text-center"><CalendarDays className="h-5 w-5 mx-auto mb-2" /> No upcoming events</div>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{past.length > 0 && (
|
|
||||||
<>
|
|
||||||
<h2 className="font-medium text-sm text-muted-foreground mb-2">Past</h2>
|
|
||||||
<div className="bg-card border rounded-lg divide-y">
|
|
||||||
{past.map((e) => (
|
|
||||||
<div key={e.id} className="p-3 text-sm text-muted-foreground flex justify-between">
|
|
||||||
<span>{e.title}</span><span>{new Date(e.date).toLocaleDateString()}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user