-
Rolle
-
Tekst
-
Bilde
-
+
+
+
+
Navn
+
Dato
+
Sted
+
Beskrivelse
+
Bilde
+
Trinn
+
Handlinger
-
- {LTData.map((item) => (
-
-
{item.navn}
-
{item.sted}
-
-
-
+
+ {events.map((event) => (
+
+
{event.navn}
+
+ {formatDate(new Date(event.dato), "PPP", { locale: nb })}
+
+
{event.sted}
+
{event.beskrivelse}
+
{event.bilde}
+
{formatTrinn(event.trinn)}
+
+
+
+
+
))}
+
+
+
+
+
+
);
-};
-
-export default ArrangementComponent;
\ No newline at end of file
+}
diff --git a/frontend/src/components/admin/arrangementForm.tsx b/frontend/src/components/admin/arrangementForm.tsx
new file mode 100644
index 0000000..a0efdd3
--- /dev/null
+++ b/frontend/src/components/admin/arrangementForm.tsx
@@ -0,0 +1,595 @@
+"use client";
+
+import useFetch from "@/hooks/use-fetch";
+import { Arrangement, createArrangementSchema } from "@/schemas/arrangement";
+import { UserPrisma } from "@/schemas/user";
+import { formatTrinn } from "@/utils/arrangement/trinn";
+import { format } from "date-fns";
+import { useEffect, useState } from "react";
+import { Button } from "../ui/button";
+import { BookUser, CalendarIcon, Edit, Trash2, X } from "lucide-react";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "../ui/dialog";
+import { nb } from "date-fns/locale";
+import { uploadArrangementImage } from "@/utils/firebase/upload_arrangementImage";
+import { z } from "zod";
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "../ui/form";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { Input } from "../ui/input";
+import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
+import { cn } from "@/lib/utils";
+import { Calendar } from "../ui/calendar";
+import { Textarea } from "../ui/textarea";
+import { Checkbox } from "../ui/checkbox";
+
+const TRINN_OPTIONS = [
+ { value: 1, label: "1." },
+ { value: 2, label: "2." },
+ { value: 3, label: "3." },
+ { value: 4, label: "4." },
+ { value: 5, label: "5." },
+];
+
+const ArrangementComponentNew = () => {
+ const [events, setEvents] = useState
([]);
+ const [selectedEvent, setSelectedEvent] = useState(null);
+ const [currentEventId, setCurrentEventId] = useState(null);
+ const [eventToDelete, setEventToDelete] = useState(null);
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
+ const [participants, setParticipants] = useState([]);
+ const [imageFile, setImageFile] = useState(null);
+ const [imagePreview, setImagePreview] = useState(null);
+ const [isEditMode, setIsEditMode] = useState(false);
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [refreshKey, setRefreshKey] = useState(0);
+ const { data, loading, error } = useFetch<{ data: Arrangement[] }>(
+ "/api/arrangementer",
+ refreshKey,
+ );
+
+ const form = useForm>({
+ resolver: zodResolver(createArrangementSchema),
+ defaultValues: {
+ navn: "",
+ sted: "",
+ dato: undefined,
+ beskrivelse: "",
+ bilde: "",
+ kapasitet: 100,
+ trinn: [],
+ },
+ });
+
+ useEffect(() => {
+ if (data && data.data) {
+ setEvents(data.data);
+ }
+ }, [data, refreshKey]);
+
+ const handleCreateEvent = () => {
+ setSelectedEvent(null);
+ setIsEditMode(false);
+ setImageFile(null);
+ setIsModalOpen(true);
+ };
+
+ const handleEditEvent = (event: Arrangement) => {
+ setSelectedEvent(event);
+ setIsEditMode(true);
+ setImageFile(null);
+ setIsModalOpen(true);
+
+ form.reset({
+ navn: event.navn,
+ sted: event.sted,
+ dato: new Date(event.dato),
+ beskrivelse: event.beskrivelse,
+ bilde: event.bilde,
+ kapasitet: event.kapasitet,
+ trinn: event.trinn,
+ });
+ };
+
+ const handleDeleteEvent = async (id: string) => {
+ try {
+ const response = await fetch(`/api/arrangementer/${id}`, {
+ method: "DELETE",
+ });
+ if (response.ok) {
+ setEvents(events.filter((event) => event.id !== id));
+ } else {
+ console.error("Failed to delete event");
+ }
+ } catch (error) {
+ console.error("Error deleting event:", error);
+ }
+ setDeleteConfirmOpen(false);
+ setEventToDelete(null);
+ };
+
+ const toggleParticipants = async (id: string) => {
+ if (currentEventId === id) {
+ setCurrentEventId(null);
+ setParticipants([]);
+ } else {
+ setCurrentEventId(id);
+ try {
+ const response = await fetch(`/api/arrangementer/${id}/users`);
+ if (response.ok) {
+ const data = await response.json();
+ setParticipants(data);
+ } else {
+ console.error("Failed to fetch participants");
+ }
+ } catch (error) {
+ console.error("Error fetching participants:", error);
+ }
+ }
+ };
+
+ const handleImageChange = (e: React.ChangeEvent) => {
+ if (e.target.files && e.target.files[0]) {
+ const file = e.target.files[0];
+ setImageFile(file);
+
+ const reader = new FileReader();
+ reader.onloadend = () => {
+ setImagePreview(reader.result as string);
+ };
+ reader.readAsDataURL(file);
+ }
+ };
+
+ const handleDeleteSignup = async (eventId: string, userId: string) => {
+ try {
+ const response = await fetch(`/api/arrangementer/${eventId}/paamelding`, {
+ method: "DELETE",
+ body: JSON.stringify({
+ userId,
+ }),
+ });
+ if (response.ok) {
+ setParticipants(participants.filter((p) => p.id !== userId));
+ } else {
+ console.error("Failed to delete signup");
+ }
+ } catch (error) {
+ console.error("Error deleting signup:", error);
+ }
+ };
+
+ const onSubmit = async (values: z.infer) => {
+ setIsSubmitting(true);
+ let imageUrl = values.bilde || "";
+
+ if (imageFile) {
+ try {
+ imageUrl = await uploadArrangementImage(imageFile);
+ } catch (error) {
+ console.error("Error uploading image:", error);
+ setIsSubmitting(false);
+ return;
+ }
+ }
+
+ const formData = {
+ ...values,
+ bilde: imageUrl,
+ };
+
+ try {
+ let response;
+ if (isEditMode && selectedEvent) {
+ response = await fetch(`/api/arrangementer/${selectedEvent.id}`, {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(formData),
+ });
+ } else {
+ response = await fetch("/api/arrangementer", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(formData),
+ });
+ }
+
+ const responseData = await response.json();
+
+ if (!response.ok) {
+ throw new Error(`Error: ${JSON.stringify(responseData)}`);
+ }
+
+ form.reset();
+ setImageFile(null);
+ setImagePreview(null);
+ setIsModalOpen(false);
+ setRefreshKey((prevState) => prevState + 1);
+ } catch (error) {
+ console.error("Error submitting form:", error);
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ if (loading) return Loading...
;
+ if (error) return Error: {error}
;
+
+ return (
+
+
+
Arrangementer
+
+
+
+
+
+
Navn
+
Dato
+
Sted
+
Beskrivelse
+
Bilde
+
Trinn
+
Handlinger
+
+
+ {events.map((event) => (
+
+
{event.navn}
+
+ {event.dato && !isNaN(new Date(event.dato).getTime())
+ ? format(new Date(event.dato), "PPP", { locale: nb })
+ : "Invalid date"}
+
+
{event.sted}
+
+ {event.beskrivelse}
+
+
+ {event.bilde}
+
+
+ {formatTrinn(event.trinn)}
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default ArrangementComponentNew;
diff --git a/frontend/src/components/ui/dialog.tsx b/frontend/src/components/ui/dialog.tsx
index e72c2b5..22aefac 100644
--- a/frontend/src/components/ui/dialog.tsx
+++ b/frontend/src/components/ui/dialog.tsx
@@ -58,10 +58,7 @@ const DialogHeader = ({
...props
}: React.HTMLAttributes) => (
);
diff --git a/frontend/src/middleware.ts b/frontend/src/middleware.ts
index e53650f..c74e2ae 100644
--- a/frontend/src/middleware.ts
+++ b/frontend/src/middleware.ts
@@ -16,12 +16,10 @@ import { UserRole } from "@prisma/client";
const { auth } = NextAuth(authConfig);
export default auth((req) => {
-
const { nextUrl } = req;
const isLoggedIn = !!req.auth;
// const isAdmin = req.auth?.user.role;
-
const isApiAuthRoute = nextUrl.pathname.startsWith(apiAuthPrefix);
const isPublicRoute = publicRoutes.includes(nextUrl.pathname);
const isAuthRoute = authRoutes.includes(nextUrl.pathname);
diff --git a/frontend/src/schemas/arrangement.ts b/frontend/src/schemas/arrangement.ts
index 200e788..35123d1 100644
--- a/frontend/src/schemas/arrangement.ts
+++ b/frontend/src/schemas/arrangement.ts
@@ -10,20 +10,35 @@ export type ArrangementPaamelding = z.infer;
export const ArrangementSchema = z.object({
id: z.string(),
- navn: z.string(),
- sted: z.string(),
- dato: z.date(),
+ navn: z.string().min(1, "Navn er påkrevd"),
+ sted: z.string().min(1, "Sted er påkrevd"),
+ dato: z.date({
+ required_error: "Dato er påkrevd",
+ invalid_type_error: "Ugyldig datoformat",
+ }),
bilde: z.string().optional(),
- kapasitet: z.number().optional(),
- beskrivelse: z.string(),
+ kapasitet: z
+ .number()
+ .int()
+ .positive("Kapasitet må være et positivt heltall")
+ .optional(),
+ beskrivelse: z.string().min(1, "Beskrivelse er påkrevd"),
trinn: z.array(z.number()).default([]),
- arrangorID: z.string(),
+ arrangorID: z.string().optional(),
paameldinger: z.array(ArrangementPaameldingSchema).default([]),
});
export type Arrangement = z.infer;
-export const createArrangementSchema = ArrangementSchema;
+export const createArrangementSchema = ArrangementSchema.omit({
+ id: true,
+ paameldinger: true,
+}).extend({
+ navn: z.string().min(1, "Navn er påkrevd"),
+ sted: z.string().min(1, "Sted er påkrevd"),
+ beskrivelse: z.string().min(1, "Beskrivelse er påkrevd"),
+ trinn: z.array(z.number().int().min(1).max(5)).default([]),
+});
export const updateArrangementSchema = ArrangementSchema.partial();
diff --git a/frontend/src/utils/actions/createArrangement.ts b/frontend/src/utils/actions/createArrangement.ts
new file mode 100644
index 0000000..f0fcd9d
--- /dev/null
+++ b/frontend/src/utils/actions/createArrangement.ts
@@ -0,0 +1,25 @@
+// "use server";
+
+// import { db } from "@/lib/db";
+// import { createArrangementSchema } from "@/schemas/arrangement";
+// import { z } from "zod";
+
+// export const createArrangement = async (
+// values: z.infer
+// ) => {
+// try {
+// const validatedData = createArrangementSchema.parse(values);
+
+// if (!validatedData)
+// return new Error("I dont know what the fuck is happening");
+
+// await db.arrangement.create({
+// data: validatedData,
+// create: {
+
+// }
+// });
+// } catch (error) {
+// console.log(error);
+// }
+// };
diff --git a/frontend/src/utils/firebase/upload_arrangementImage.ts b/frontend/src/utils/firebase/upload_arrangementImage.ts
new file mode 100644
index 0000000..0585dfe
--- /dev/null
+++ b/frontend/src/utils/firebase/upload_arrangementImage.ts
@@ -0,0 +1,15 @@
+import { storage } from "@/lib/firebase";
+import { getDownloadURL, ref, uploadBytes } from "firebase/storage";
+
+export const uploadArrangementImage = async (file: File): Promise => {
+ // if (!signInAnonymously(auth)){
+ // throw new Error("Not signed in");
+ // }
+
+ const storageRef = ref(storage, `images/arrangementer/${file.name}`);
+
+ const snapshot = await uploadBytes(storageRef, file);
+
+ const downloadURL = await getDownloadURL(snapshot.ref);
+ return downloadURL;
+};