diff options
author | Linnnus <[email protected]> | 2025-02-18 18:01:09 +0100 |
---|---|---|
committer | Linnnus <[email protected]> | 2025-02-18 18:01:09 +0100 |
commit | d60f89487ab3bc2390054efcd5d986a4aadd4291 (patch) | |
tree | 4fe13a768dab5fd80ad3189d237bce4b2068000a /app | |
parent | 2b309097ca145651618234476160fb30405eabe7 (diff) |
Tilføj opgaver (assignments)
Diffstat (limited to 'app')
-rw-r--r-- | app/src/lib/server/assignments.ts | 84 | ||||
-rw-r--r-- | app/src/routes/assignments/+page.server.ts | 16 | ||||
-rw-r--r-- | app/src/routes/assignments/+page.svelte | 21 | ||||
-rw-r--r-- | app/src/routes/assignments/[assignmentId]/+page.server.ts | 27 | ||||
-rw-r--r-- | app/src/routes/assignments/[assignmentId]/+page.svelte | 25 | ||||
-rw-r--r-- | app/src/routes/profile/+page.svelte | 7 |
6 files changed, 178 insertions, 2 deletions
diff --git a/app/src/lib/server/assignments.ts b/app/src/lib/server/assignments.ts new file mode 100644 index 0000000..7622408 --- /dev/null +++ b/app/src/lib/server/assignments.ts @@ -0,0 +1,84 @@ +import type pg from "pg"; + +/** A row from the `cemetary_plot` table. */ +export interface CemetaryPlot { + id: number; + address: string; + ownerId: number; + //assignmentInterval: Date; +} + +/** A row from the `assignments` table. */ +export interface Assignment { + id: number; + gardenerId: number; + cemetaryPlotId: number; + date: Date; +} + +/** + * Retrieves all assignments for the given user. + * + * @param dbConn Connection to database. + * @param userId ID used to identify user. + */ +export async function getAssignments(dbConn: pg.ClientBase, userId: number): Promise<Assignment[]> { + const result = await dbConn.query( + "SELECT * FROM assignments WHERE gardener_id = $1 ORDER BY date", + [userId], + ); + return result.rows.map( + (r) => + ({ + id: r.id, + gardenerId: r.gardener_id, + cemetaryPlotId: r.cemetary_plot_id, + date: r.date, + }) satisfies Assignment, + ); +} + +type GetAssignmentResult = + | { assignment: Assignment; cemetaryPlot: CemetaryPlot } + | { assignment: null; cemetaryPlot: null }; + +/** + * Retrieves a specfic assignment, along with relevant cemetary plot. + */ +export async function getAssignmentAndCemetaryById( + dbConn: pg.ClientBase, + assignmentId: number, +): Promise<GetAssignmentResult> { + const queryText = `SELECT + a.id, + a.gardener_id, + a.cemetary_plot_id, + a.date, + c.id, + c.address, + c.owner_id, + c.assignment_interval + FROM + assignments AS a + INNER JOIN + cemetary_plots AS C ON c.id = a.cemetary_plot_id + WHERE c.id = $1`; + const result = await dbConn.query({ rowMode: "array", text: queryText }, [assignmentId]); + if (result.rowCount == 0) { + return { assignment: null, cemetaryPlot: null }; + } + + const assignment: Assignment = { + id: result.rows[0][0], + gardenerId: result.rows[0][1], + cemetaryPlotId: result.rows[0][2], + date: result.rows[0][3], + }; + const cemetaryPlot: CemetaryPlot = { + id: result.rows[0][4], + address: result.rows[0][5], + ownerId: result.rows[0][6], + //assignmentInterval: result.rows[0][7], + }; + return { assignment, cemetaryPlot }; +} diff --git a/app/src/routes/assignments/+page.server.ts b/app/src/routes/assignments/+page.server.ts new file mode 100644 index 0000000..0b829cd --- /dev/null +++ b/app/src/routes/assignments/+page.server.ts @@ -0,0 +1,16 @@ +import type { PageServerLoad } from "./$types"; +import { getAssignments } from "$lib/server/assignments"; +import { redirect } from "@sveltejs/kit"; + +export const load = (async ({ url, locals }) => { + if (!locals.user) { + redirect(303, `/login?redirectTo=${encodeURIComponent(url.toString())}`); + } + + const assignments = await getAssignments(locals.dbConn, locals.user.id); + + return { + user: locals.user, + assignments, + }; +}) satisfies PageServerLoad; diff --git a/app/src/routes/assignments/+page.svelte b/app/src/routes/assignments/+page.svelte new file mode 100644 index 0000000..f43767d --- /dev/null +++ b/app/src/routes/assignments/+page.svelte @@ -0,0 +1,21 @@ +<!-- The /assignments index page gives an overview of assignments --> + +<script lang="ts"> + import type { PageProps } from "./$types"; + + const { data }: PageProps = $props(); +</script> + +<h1>Kommende opgaver for {data.user.firstName}</h1> + +<ol> + {#each data.assignments as assignment} + <li> + <span>{assignment.date}</span> + <a href={`/assignments/${assignment.id}`}>Mere info</a> + </li> + {/each} +</ol> + +<style> +</style> diff --git a/app/src/routes/assignments/[assignmentId]/+page.server.ts b/app/src/routes/assignments/[assignmentId]/+page.server.ts new file mode 100644 index 0000000..566dcd9 --- /dev/null +++ b/app/src/routes/assignments/[assignmentId]/+page.server.ts @@ -0,0 +1,27 @@ +import { getAssignmentAndCemetaryById } from "$lib/server/assignments"; +import type { PageServerLoad } from "./$types"; +import { error, redirect } from "@sveltejs/kit"; + +export const load = (async ({ params, url, locals }) => { + if (!locals.user) { + redirect(303, `/login?redirectTo=${encodeURIComponent(url.toString())}`); + } + + const { assignment, cemetaryPlot } = await getAssignmentAndCemetaryById( + locals.dbConn, + +params.assignmentId, + ); + if (!assignment) { + return error(404, `Cemetary plot with id ${params.assignmentId} not found`); + } + console.debug("Found assignment: ", assignment); + if (assignment.gardenerId !== locals.user.id) { + return error(403, "This assignment isn't for you!"); + } + + return { + user: locals.user, + assignment, + cemetaryPlot, + }; +}) satisfies PageServerLoad; diff --git a/app/src/routes/assignments/[assignmentId]/+page.svelte b/app/src/routes/assignments/[assignmentId]/+page.svelte new file mode 100644 index 0000000..0b36a19 --- /dev/null +++ b/app/src/routes/assignments/[assignmentId]/+page.svelte @@ -0,0 +1,25 @@ +<script lang="ts"> + import type { PageProps } from "./$types"; + + let { data }: PageProps = $props(); + + const assignmentDate = $derived(new Intl.DateTimeFormat().format(data.assignment.date)); +</script> + +<svelte:head> + <title>Opgave på {data.cemetaryPlot.address}</title> +</svelte:head> + +<h1>Assignment #{data.assignment.id}</h1> +<p> + Gravstedet, der skal vedligeholdes er ved <span class="address">{data.cemetaryPlot.address}</span + >. +</p> +<p>Vedligeholdelsen skal finde sted <time>{assignmentDate}<time>.</time></time></p> + +<style> + .address, + time { + font-weight: bold; + } +</style> diff --git a/app/src/routes/profile/+page.svelte b/app/src/routes/profile/+page.svelte index 0ee18f0..62078cc 100644 --- a/app/src/routes/profile/+page.svelte +++ b/app/src/routes/profile/+page.svelte @@ -8,5 +8,8 @@ <img src="/profile_picture_standin.jpeg" width="255" height="255" alt="Dummy profile picture" /> <p>Hej, {data.user.firstName} {data.user.lastName}!</p> -<style> -</style> +{#if data.user.role === "gardener"} + <p>Tjek dine opgaver her: <a href="/assignments">Opgaver</a></p> +{/if} + +<!-- TODO: Burde også vise noget for owners --> |