diff --git a/src/client/components/AnnouncementTable.tsx b/src/client/components/AnnouncementTable.tsx new file mode 100644 index 0000000..13a17b4 --- /dev/null +++ b/src/client/components/AnnouncementTable.tsx @@ -0,0 +1,45 @@ +import { + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, +} from "@mui/material"; +import type { Announcement } from "../services"; +import { AnnouncementTableRow } from "./AnnouncementTableRow"; + +export interface AnnouncementTableProps { + announcements: Announcement[]; + openDeleteConfirmation(announcement: Announcement): void; +} + +export function AnnouncementTable({ + announcements, + openDeleteConfirmation, +}: AnnouncementTableProps) { + return ( + + + + + + Title + Text + Publication Date + + + + + {announcements.map((a) => ( + + ))} + +
+
+ ); +} diff --git a/src/client/components/AnnouncementTableRow.tsx b/src/client/components/AnnouncementTableRow.tsx new file mode 100644 index 0000000..fef33f0 --- /dev/null +++ b/src/client/components/AnnouncementTableRow.tsx @@ -0,0 +1,30 @@ +import DeleteIcon from "@mui/icons-material/Delete"; +import { IconButton, TableCell, TableRow } from "@mui/material"; +import type { Announcement } from "../services"; + +export interface AnnouncementTableRowProps { + announcement: Announcement; + openDeleteConfirmation(announcement: Announcement): void; +} + +export function AnnouncementTableRow({ + openDeleteConfirmation, + announcement, +}: AnnouncementTableRowProps) { + return ( + + {announcement.id} + {announcement.title} + + {announcement.text.substring(0, 50) + + (announcement.text.length > 50 ? "..." : "")} + + {announcement.publication_date.toString()} + + openDeleteConfirmation(announcement)}> + + + + + ); +} diff --git a/src/client/components/DeleteAnnouncementDialog.tsx b/src/client/components/DeleteAnnouncementDialog.tsx new file mode 100644 index 0000000..881e60f --- /dev/null +++ b/src/client/components/DeleteAnnouncementDialog.tsx @@ -0,0 +1,45 @@ +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, +} from "@mui/material"; +import type { Announcement } from "../services"; + +export interface DeleteAnnouncementDialogProps { + isDeleteConfirmationOpen: boolean; + denyDelete(): void; + confirmDelete(): void; + announcement: Announcement; +} + +export function DeleteAnnouncementDialog({ + announcement, + confirmDelete, + denyDelete, + isDeleteConfirmationOpen, +}: DeleteAnnouncementDialogProps) { + return ( + + Delete announcement + + + Do you want to delete the announcement{" "} + {announcement.title}? + + + + + + + + ); +} diff --git a/src/client/routes/index.tsx b/src/client/routes/index.tsx index d9bb51b..5e9512f 100644 --- a/src/client/routes/index.tsx +++ b/src/client/routes/index.tsx @@ -1,41 +1,12 @@ -import DeleteIcon from "@mui/icons-material/Delete"; -import { - Alert, - Box, - Button, - CircularProgress, - Dialog, - DialogActions, - DialogContent, - DialogContentText, - DialogTitle, - IconButton, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, - Typography, -} from "@mui/material"; +import { Alert, Box, CircularProgress, Typography } from "@mui/material"; import { useQuery } from "@tanstack/react-query"; import { createFileRoute } from "@tanstack/react-router"; import { useCallback, useEffect, useState } from "react"; -import z from "zod"; +import { AnnouncementTable } from "../components/AnnouncementTable"; +import { DeleteAnnouncementDialog } from "../components/DeleteAnnouncementDialog"; import { RouterButton } from "../components/RouterButton"; import { useBoolean } from "../components/use-boolean"; - -const Announcements = z.array( - z.object({ - id: z.number(), - title: z.string(), - text: z.string(), - publication_date: z.iso.datetime().transform((arg) => new Date(arg)), - image: z.string(), - }), -); - -type Announcement = z.infer[0]; +import { type Announcement, getAnnouncements } from "../services"; export const Route = createFileRoute("/")({ component: RouteComponent, @@ -45,13 +16,7 @@ function RouteComponent() { // the query to retrieve the announcements const announcementsQuery = useQuery({ queryKey: ["announcements"], - queryFn: async () => { - const response = await fetch("/api/announcements"); - const data = await response.json(); - const announcements = Announcements.parse(data); - console.log(announcements); - return announcements; - }, + queryFn: getAnnouncements, retry: false, }); @@ -100,56 +65,18 @@ function RouteComponent() { Error (see console) ) : ( <> - - - - - - Title - Text - Publication Date - - - - - {announcementsQuery.data.map((a) => ( - - {a.id} - {a.title} - - {a.text.substring(0, 50) + - (a.text.length > 50 ? "..." : "")} - - {a.publication_date.toString()} - - openDeleteConfirmation(a)}> - - - - - ))} - -
-
- - Delete announcement - - - Do you want to delete the announcement{" "} - {announcementForLatestDeleteAttempt?.title}? - - - - - - - + + {announcementForLatestDeleteAttempt && ( + + )} )} diff --git a/src/client/services.ts b/src/client/services.ts new file mode 100644 index 0000000..3fcc13c --- /dev/null +++ b/src/client/services.ts @@ -0,0 +1,20 @@ +import z from "zod"; + +const Announcements = z.array( + z.object({ + id: z.number(), + title: z.string(), + text: z.string(), + publication_date: z.iso.datetime().transform((arg) => new Date(arg)), + image: z.string(), + }), +); + +export type Announcement = z.infer[0]; + +export async function getAnnouncements() { + const response = await fetch("/api/announcements"); + const data = await response.json(); + const announcements = Announcements.parse(data); + return announcements; +}