Skip to content
Snippets Groups Projects
Commit 0c5d3948 authored by Aaron Dötsch's avatar Aaron Dötsch
Browse files

update

parent 460ad799
No related branches found
No related tags found
No related merge requests found
Showing with 313 additions and 405 deletions
FROM node:21-alpine
RUN apk update && apk add build-base g++ cairo-dev pango-dev giflib-dev
WORKDIR /app
COPY package*.json ./
COPY .env ./
......
......@@ -2,7 +2,9 @@ version: 3.8
services:
web:
build: .
build:
context: .
dockerfile: Dockerfile
ports:
- 3000:3000
restart: always
......
This diff is collapsed.
......@@ -83,8 +83,8 @@
<Input name="replyTo" bind:value={replyTo} placeholder="Antwortadresse" class="w-full" {disabled} />
{:else}
<Select name="replyTo" value={replyTo} on:change={e=>{
customReplyTo = e.target!.value==="custom";
if(!customReplyTo) replyTo = e.target!.value;
customReplyTo = (e.target! as HTMLSelectElement).value==="custom";
if(!customReplyTo) replyTo = (e.target! as HTMLSelectElement).value;
else if(replyTo === "-") replyTo = "";
}} {disabled}>
<option value={"-"}>keine</option>
......@@ -101,7 +101,7 @@
{#each locales as locale}
<Label class="w-full">
Betreff ({locale})
<Input name="subject[{locale}]" bind:value={subject[locale]} oninput={e=>subject=Object.assign(subject,{[locale]:e.target.value})} {disabled} required={locale==="de"} />
<Input name="subject[{locale}]" bind:value={subject[locale]} on:input={e=>subject=Object.assign(subject,{[locale]:(e.target as HTMLInputElement)!.value})} {disabled} required={locale==="de"} />
</Label>
{/each}
</div>
......@@ -130,7 +130,7 @@
<Span slot="header">Beispiel-Tutor</Span>
<Label>
Studiengang
<Select value={exampleTutor.studyProgram.id} on:change={e=>exampleTutor.studyProgram=studyPrograms.find(s=>s.id==e.target!.value)}>
<Select value={exampleTutor.studyProgram.id} on:change={e=>exampleTutor.studyProgram=(studyPrograms.find(s=>s.id==parseInt((e.target! as HTMLSelectElement).value))!)}>
{#each studyPrograms as studyProgram}
<option value={studyProgram.id}>{studyProgram.name[$locale]}</option>
{/each}
......@@ -160,7 +160,7 @@
</Label>
<Label>
Schulung
<Select value={exampleTutor.training?.id ?? -1} on:change={e=>exampleTutor.training=trainings.find(t=>t.id===e.target!.value)}>
<Select value={exampleTutor.training?.id ?? -1} on:change={e=>exampleTutor.training=trainings.find(t=>t.id===(e.target as HTMLSelectElement)!.value)}>
<option value={-1}>keine</option>
{#each trainings as training}
<option value={training.id}>{training.date}</option>
......@@ -182,6 +182,7 @@
</AccordionItem>
<AccordionItem>
<Span slot="header">Formatierer</Span>
<P class="mb-2">Formatierer können mit Argumenten erstellt werden. Dafür nutzt man <code>formatierer:argument</code>, wobei das Argument gültiges JSON sein muss. Es muss darauf geachtet werden, dass sich kein <code>{"}}"}</code> bildet.</P>
<List tag="ul" position="outside" class="ml-4">
{#each Object.entries(formatters) as [name, {description}]}
<Li><code>{name}</code> - {description}</Li>
......@@ -191,7 +192,7 @@
<AccordionItem>
<Span slot="header">Bedingungen</Span>
<List tag="ul" position="outside" class="ml-4">
{#each conditions as condition}
{#each conditions[type] as condition}
<Li>
<code>{"{{if "}{condition.name}{#if condition.arguments}{" ...args"}{/if}{"}}"}</code> - {condition.description}
{#if condition.arguments}
......@@ -216,6 +217,7 @@
<b>Formatierung</b>
<List tag="ul" position="outside" class="ml-6">
<Li>Werte von Variablen können formatiert werden, indem die Formatierer getrennt durch <code>|</code> nach dem Variablennamen gelistet werden, z.B. <code>{"{{"}variablenname|formatierer1|formatierer2{"}}"}</code>. Die Formatierer werden in der angegebenen Reihenfolge angewandt.</Li>
<Li>Unterstützt ein Formatierer Konstruktorenargumente, so können diese mit <code>:</code> getrennt hinter dem Namen angegeben werden. Das Argument muss gültiges JSON sein und kein frühzeitiges {"}}"} bilden. Beispiel: <code>{"{{"}argument|date:{"{"}"year":"2-digit"{"} "}{"}}"}</code>, wobei das Leerzeichen am Ende wichtig ist, weil sich sonst {"}}}"} bilden würde.</Li>
</List>
</Li>
<Li>
......
......@@ -11,28 +11,10 @@
} & Record<string, any>;
let { items, row, children, ...restProps }: ComponentProps = $props();
const sorting = writable({sorted: items, sortDirection: 1, sorter: null});
const sorting = writable({sorted: items, sortDirection: 1, sorter: ""});
setContext("sorting", sorting);
</script>
<style>
div :global(.sortable) {
cursor: pointer;
position: relative;
}
div :global(.sortable::after) {
content: "";
position: absolute;
padding-left: .7rem;
}
div :global(.sorting-asc::after) {
content: "▲";
}
div :global(.sorting-desc::after) {
content: "▼";
}
</style>
<div>
<Table {...restProps}>
{@render children()}
......
......@@ -5,39 +5,30 @@
import { twMerge } from "tailwind-merge";
type T = $$Generic;
const sorting = getContext("sorting") as Writable<{sorted: T[], sortDirection: 1|-1, sorter?: TableHeadCell}>;
const sorting = getContext("sorting") as Writable<{sorted: T[], sortDirection: 1|-1, sorter: string}>;
let { sort, children, padding="px-6 py-3", defaultDirection="asc", default: def }: { sort: (a: T, b: T)=>number, children: Snippet, padding?: string, defaultDirection?: "asc"|"desc", default?: boolean } = $props();
let self: TableHeadCell|undefined = $state();
let { sort, children, padding="px-6 py-3", defaultDirection="asc", default: def, buttonClass="" }: { sort: (a: T, b: T)=>number, children: Snippet, padding?: string, defaultDirection?: "asc"|"desc", default?: boolean, buttonClass?: string } = $props();
const self = Math.random().toString(36).substring(2);
if(def){
// update directly for SSR
sorting.update(({sorted}) => {
let dir = (defaultDirection === "asc" ? 1 : -1) as 1 | -1;
return {sorted: sorted.sort((a,b)=>dir*sort(a,b)), sortDirection: dir, sorter: self};
});
// update on initialization afte "self" has been initialized
onMount(()=>{
sorting.update(({sorted}) => {
let dir = (defaultDirection === "asc" ? 1 : -1) as 1 | -1;
return {sorted: sorted.sort((a,b)=>dir*sort(a,b)), sortDirection: dir, sorter: self};
});
});
}
function onclick(){
sorting.update(({sorted, sortDirection, sorter}) => {
if(sorter === self){
return {sorted: sorted.sort((a,b)=>-sortDirection*sort(a,b)), sortDirection: -sortDirection as 1 | -1, sorter};
}
let dir = (defaultDirection === "asc" ? 1 : -1) as 1 | -1;
return {sorted: sorted.sort((a,b)=>dir*sort(a,b)), sortDirection: dir, sorter: self};
sortDirection = (sorter === self ? -sortDirection : defaultDirection === "asc" ? 1 : -1) as 1 | -1;
return {sorted: sorted.sort((a,b)=>sortDirection*sort(a,b)), sortDirection, sorter: self};
});
}
let sortDirection = $derived($sorting.sorter === self ? $sorting.sortDirection === 1 ? "asc" : "desc" : null);
</script>
<TableHeadCell bind:this={self} padding="">
<button {onclick} class={twMerge(padding, "sortable w-full text-left", $sorting.sorter === self && `sorting-${$sorting.sortDirection === 1 ? "asc": "desc"}`)}>
<TableHeadCell padding="" aria-sort={sortDirection ? `${sortDirection}ending` : undefined}>
<button {onclick} class={twMerge(padding, "w-full text-left hover:bg-black hover:bg-opacity-10 relative after:absolute after:pl-3", sortDirection === "asc" && "after:content-['▲']", sortDirection === "desc" && "after:content-['▼']", buttonClass)}>
{@render children()}
</button>
</TableHeadCell>
......@@ -5,6 +5,7 @@ import type { Tutor } from "./server/database/entities/Tutor.entity";
import markdownit from "markdown-it";
import type { Localized } from "./utils";
import type { Config } from "./server/database/entities/Config.entity";
import type { DateTimeFormatOptions } from "intl";
const md = markdownit({
breaks: true,
......@@ -21,7 +22,11 @@ export type MailTemplate = {
type: TemplateType;
};
export const formatters: Record<string, { description: string, format: (l: Locale, arg: unknown)=>string }> = {
export const formatters: Record<string, { description: string, format: (l: Locale, arg: unknown, constructorArg: unknown|null)=>string }> = {
date: {
description: "Formatiert ein Datum, Kontruktorargumente sind die Optionen für Intl.DateTimeFormat (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#options)",
format: (locale, arg, constructorArg)=>new Intl.DateTimeFormat(locale, constructorArg as DateTimeFormatOptions ?? undefined).format(arg as Date),
},
dateLong: {
description: "Formatiert ein Datum, z.B. 01. Januar 1970",
format: (locale, arg)=>new Intl.DateTimeFormat(locale, { year: "numeric", month: "long", day: "2-digit" }).format(arg as Date),
......@@ -38,6 +43,10 @@ export const formatters: Record<string, { description: string, format: (l: Local
description: "Gibt den Wochentag eines Datums zurück, z.B. Mo",
format: (locale, arg)=>new Intl.DateTimeFormat(locale, { weekday: "short" }).format(arg as Date),
},
dateRange: {
description: "Formatiert einen Datumsbereich, Konsruktorargumente sind die Optionen für Intl.DateTimeFormat (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#options)",
format: (locale, arg, constructorArg)=>new Intl.DateTimeFormat(locale, constructorArg as DateTimeFormatOptions ?? undefined).formatRange((arg as [Date, Date])[0], (arg as [Date, Date])[1]),
},
dateRangeLong: {
description: "Formatiert einen Datumsbereich, z.B. 1. - 2. Januar 1970",
format: (locale, arg)=>new Intl.DateTimeFormat(locale, { year: "numeric", month: "long", day: "2-digit" }).formatRange((arg as [Date, Date])[0], (arg as [Date, Date])[1]),
......@@ -375,10 +384,15 @@ export function parseTemplate(type: TemplateType, template: Localized|string){
const [variableName, ...formatterNames] = name.split("|");
const variable = variables[type].find(v=>v.name === variableName);
if(!variable) throw new Error(`Unknown variable ${name}`);
const fs = formatterNames.map(f=>formatters[f]);
if(fs.some(f=>!f)) throw new Error(`Unknown formatter ${formatterNames.find((_, i)=>!fs[i])}`);
const fs = formatterNames.map(f=>{
const [formatterName, ...formatterArgs] = f.split(":");
const formatter = formatters[formatterName];
if(!formatter) throw new Error(`Unknown formatter ${formatterName}`);
const arg = formatterArgs.join(":");
return { format: formatter.format, arg: arg ? JSON.parse(arg) : null };
});
const replacement = (t: Tutor, l: Locale, c: PartialConfig)=>{
return fs.reduce((v, f)=>f.format(l, v), variable.replacement(t, l, c));
return fs.reduce((v, f)=>f.format(l, v, f.arg), variable.replacement(t, l, c));
};
parts.push({type: "variable", replacement});
}
......@@ -425,10 +439,15 @@ function consumeCondition(type: TemplateType, tokens: Token[]): [Part[], Part[]]
const [variableName, ...formatterNames] = name.split("|");
const variable = variables[type].find(v=>v.name === variableName);
if(!variable) throw new Error(`Unknown variable ${name}`);
const fs = formatterNames.map(f=>formatters[f]);
if(fs.some(f=>!f)) throw new Error(`Unknown formatter ${formatterNames.find((_, i)=>!fs[i])}`);
const fs = formatterNames.map(f=>{
const [formatterName, ...formatterArgs] = f.split(":");
const formatter = formatters[formatterName];
if(!formatter) throw new Error(`Unknown formatter ${formatterName}`);
const arg = formatterArgs.join(":");
return { format: formatter.format, arg: arg ? JSON.parse(arg) : null };
});
const replacement = (t: Tutor, l: Locale, c: PartialConfig)=>{
return fs.reduce((v, f)=>f.format(l, v), variable.replacement(t, l, c));
return fs.reduce((v, f)=>f.format(l, v, f.arg), variable.replacement(t, l, c));
};
if(inElse) elseParts.push({type: "variable", replacement});
else thenParts.push({type: "variable", replacement});
......
......@@ -96,13 +96,13 @@ export const handleAuthorization: Handle = async ({event, resolve})=>{
event.locals.user = user;
}else if(event.url.pathname.startsWith("/intern/tutor/") || event.url.pathname === "/intern/tutor"){
const session = await event.locals.auth();
if(!session || session.user.type !== "tutor") redirect(303, "/intern#tutor");
if(!session || session.user.type !== "tutor") redirect(303, `/intern?redirect_url=${encodeURIComponent(event.url.href)}#tutor`);
const tutor = await Tutor.getById(session.user.userId);
if(!tutor) redirect(303, "/intern");
event.locals.tutor = tutor;
}else if(event.url.pathname.startsWith("/intern/rallye/") || event.url.pathname === "/intern/rallye"){
const session = await event.locals.auth();
if(!session || session.user.type !== "rally") redirect(303, "/intern#rallye");
if(!session || session.user.type !== "rally") redirect(303, `/intern?redirect_url=${encodeURIComponent(event.url.href)}#rallye`);
const supervisor = await RallyStationSupervisor.getById(session.user.userId);
if(!supervisor) redirect(303, "/intern");
event.locals.supervisor = supervisor;
......
......@@ -177,3 +177,20 @@ CREATE TABLE IF NOT EXISTS master_freshers (
"study_program" TEXT NOT NULL,
"aachen_experience" TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS mrx (
"id" SERIAL PRIMARY KEY
);
CREATE TABLE IF NOT EXISTS mrx_entries (
"id" SERIAL PRIMARY KEY,
"mrx" INT NOT NULL REFERENCES mrx(id),
"date" DATE NOT NULL,
"text" TEXT NOT NULL,
);
CREATE TABLE IF NOT EXISTS mrx_attachments (
"id" SERIAL PRIMARY KEY,
"mrx_entry" INT NOT NULL REFERENCES mrx_entries(id),
"path" TEXT NOT NULL,
);
......@@ -51,8 +51,6 @@
function roundTo5Digits(num: number): number{
return Math.round(num * 1e5) / 1e5;
}
function refocusMap(address: string): void;
function refocusMap(latitude: number, longitude: number): void;
async function refocusMap(){
if(arguments.length === 1){
const [address] = arguments;
......
......@@ -5,66 +5,35 @@
import { Permission } from "$lib/perms.js";
import { toCSV } from "$lib/csv";
import type { Tutor } from "$lib/server/database/entities/Tutor.entity";
import SortableTable from "$lib/components/SortableTable.svelte";
import SortableTableHeadCell from "$lib/components/SortableTableHeadCell.svelte";
export let data;
let sortedTutors = data.tutors;
$: dateFormatter = new Intl.DateTimeFormat($locale, { day: "2-digit", month: "2-digit", year: "numeric" });
type SortProp = string|number|null|undefined;
type SortKey = ObjectToDotProp<typeof data.tutors[number], SortProp>;
let sortKey: SortKey = "firstname";
let sortDirection: -1|1 = 1;
const sortTable = (key: SortKey) => {
if (sortKey === key) {
sortDirection *= -1;
} else {
sortKey = key;
sortDirection = 1;
}
}
$: {
sortedTutors = [...sortedTutors].sort((a, b) => {
const aVal = sortKey!.split(".").reduce((val, key) => val?.[key], a) as unknown as SortProp;
const bVal = sortKey!.split(".").reduce((val, key) => val?.[key], b) as unknown as SortProp;
if(aVal == null && bVal == null) return 0;
else if(aVal == null) return sortDirection;
else if(bVal == null) return -sortDirection;
else if(aVal < bVal) return -sortDirection;
else if(aVal > bVal) return sortDirection;
else return 0;
});
}
function getClassName(key: SortKey, sortKey: SortKey, sortDirection: -1|1){
if(sortKey === key){
return sortDirection === 1 ? "thc sorting-asc" : "thc sorting-desc";
}
return "thc";
}
let showExportModal = false;
let exportType = "json";
const fields: { prop: ObjectToDotProp<Tutor>, label: string, value: string }[] = [
{ prop: "firstname", label: "Vorname", value: "firstname" },
{ prop: "lastname", label: "Nachname", value: "lastname" },
{ prop: "nickname", label: "Spitzname", value: "nickname" },
{ prop: "studyProgram.name.de", label: "Fach", value: "studyProgram" },
{ prop: "training.date", label: "Schulung", value: "training" },
{ prop: "notes", label: "Notiz", value: "notes" },
const fields: { prop: (t: Tutor)=>unknown, label: string, value: string }[] = [
{ prop: t=>t.firstname, label: "Vorname", value: "firstname" },
{ prop: t=>t.lastname, label: "Nachname", value: "lastname" },
{ prop: t=>t.nickname, label: "Spitzname", value: "nickname" },
{ prop: t=>t.studyProgram.name.de, label: "Fach", value: "studyProgram" },
{ prop: t=>t.training?.date, label: "Schulung", value: "training" },
{ prop: t=>t.notes, label: "Notiz", value: "notes" },
...(data.user.permissions.has(Permission.VIEW_TUTOR_DETAILS) ? [
{ prop: "gender", label: "Geschlecht", value: "gender" },
{ prop: "mentor", label: "Mentor", value: "mentor" },
{ prop: "shirtSize", label: "Shirt", value: "shirtSize" },
{ prop: "coTutorWish", label: "Co-Tutor", value: "coTutorWish" },
{ prop: "trained", label: "Geschult", value: "trained" },
{ prop: "degree", label: "Abschluss", value: "degree" },
{ prop: "email", label: "E-Mail", value: "email" },
{ prop: "phone", label: "Telefon", value: "phone" },
{ prop: "address", label: "Adresse", value: "address" },
{ prop: "birthday", label: "Geburtstag", value: "birthday" },
{ prop: "dietaryRestriction", label: "Essgewohnheiten", value: "dietaryRestriction" },
] satisfies {prop: ObjectToDotProp<Tutor>, label: string, value: string}[] : []),
{ prop: t=>t.gender, label: "Geschlecht", value: "gender" },
{ prop: t=>t.mentor, label: "Mentor", value: "mentor" },
{ prop: t=>t.shirtSize, label: "Shirt", value: "shirtSize" },
{ prop: t=>t.coTutorWish, label: "Co-Tutor", value: "coTutorWish" },
{ prop: t=>t.trained, label: "Geschult", value: "trained" },
{ prop: t=>t.degree, label: "Abschluss", value: "degree" },
{ prop: t=>t.email, label: "E-Mail", value: "email" },
{ prop: t=>t.phone, label: "Telefon", value: "phone" },
{ prop: t=>t.address, label: "Adresse", value: "address" },
{ prop: t=>t.birthday, label: "Geburtstag", value: "birthday" },
{ prop: t=>t.dietaryRestriction, label: "Essgewohnheiten", value: "dietaryRestriction" },
] satisfies { prop: (t: Tutor)=>unknown, label: string, value: string }[] : []),
];
let selectedFields: string[] = fields.map(f => f.value);
function exportTutors(){
......@@ -72,7 +41,7 @@
const tutor: Record<string, unknown> = {};
for(const field of fields){
if(selectedFields.includes(field.value)){
tutor[field.value] = field.prop.split(".").reduce((val, key) => val?.[key], t);
tutor[field.value] = field.prop(t);
}
}
return tutor;
......@@ -96,25 +65,6 @@
}
</script>
<style>
div :global(.thc) {
cursor: pointer;
position: relative;
}
div :global(.thc::after) {
content: "";
position: absolute;
right: 0;
padding-right: 1rem;
}
div :global(.sorting-asc::after) {
content: " ▲";
}
div :global(.sorting-desc::after) {
content: " ▼";
}
</style>
<Breadcrumb class="mb-4">
<BreadcrumbItem href="/admin" home>Admin</BreadcrumbItem>
<BreadcrumbItem href="/admin/tutor">Tutoren</BreadcrumbItem>
......@@ -200,19 +150,25 @@
</Table>
{#if data.user.permissions.has(Permission.VIEW_TUTORS)}
<Table class="mb-6" hoverable>
<SortableTable class="mb-6" hoverable items={data.tutors}>
<TableHead>
<TableHeadCell on:click={()=>sortTable("firstname")} class={getClassName("firstname", sortKey, sortDirection)}>Vorname</TableHeadCell>
<TableHeadCell on:click={()=>sortTable("lastname")} class={getClassName("lastname", sortKey, sortDirection)}>Nachname</TableHeadCell>
<TableHeadCell on:click={()=>sortTable(`studyProgram.name.${$locale}`)} class={getClassName(`studyProgram.name.${$locale}`, sortKey, sortDirection)}>Fach</TableHeadCell>
<TableHeadCell on:click={()=>sortTable("shirtSize")} class={getClassName("shirtSize", sortKey, sortDirection)}>Shirt</TableHeadCell>
<TableHeadCell on:click={()=>sortTable("coTutorWish")} class={getClassName("coTutorWish", sortKey, sortDirection)}>Co-Tutor</TableHeadCell>
<TableHeadCell on:click={()=>sortTable("training.date")} class={getClassName("training.date", sortKey, sortDirection)}>Schulung</TableHeadCell>
<TableHeadCell on:click={()=>sortTable("notes")} class={getClassName("notes", sortKey, sortDirection)}>Notiz</TableHeadCell>
<SortableTableHeadCell sort={(a: Tutor, b)=>a.firstname.localeCompare(b.firstname)}>Vorname</SortableTableHeadCell>
<SortableTableHeadCell sort={(a: Tutor, b)=>a.lastname.localeCompare(b.lastname)}>Nachname</SortableTableHeadCell>
<SortableTableHeadCell sort={(a: Tutor, b)=>a.studyProgram.name[$locale].localeCompare(b.studyProgram.name[$locale])}>Fach</SortableTableHeadCell>
<SortableTableHeadCell sort={(a: Tutor, b)=>a.shirtSize.localeCompare(b.shirtSize)}>T-Shirt</SortableTableHeadCell>
<SortableTableHeadCell sort={(a: Tutor, b)=>a.coTutorWish.localeCompare(b.coTutorWish)}>Co-Tutor</SortableTableHeadCell>
<SortableTableHeadCell sort={(a: Tutor, b)=>{
if(a.training && b.training) return a.training.date.localeCompare(b.training.date);
else if(a.training) return -1;
else if(b.training) return 1;
else return 0;
}}>Schulung</SortableTableHeadCell>
<SortableTableHeadCell sort={(a: Tutor, b)=>a.notes.localeCompare(b.notes)}>Notiz</SortableTableHeadCell>
{#if data.user.permissions.has(Permission.UPDATE_TUTOR)}
<TableHeadCell></TableHeadCell>
{/if}
</TableHead>
<TableBody>
{#each sortedTutors as tutor}
{#snippet row({item: tutor})}
<TableBodyRow>
<TableBodyCell>{tutor.firstname}{tutor.nickname?` (${tutor.nickname})`:""}</TableBodyCell>
<TableBodyCell>{tutor.lastname}</TableBodyCell>
......@@ -221,13 +177,12 @@
<TableBodyCell>{tutor.coTutorWish || ""}</TableBodyCell>
<TableBodyCell>{tutor.training?dateFormatter.format(new Date(tutor.training.date)):"Bereits geschult"}</TableBodyCell>
<TableBodyCell>{tutor.notes}</TableBodyCell>
{#if data.user.permissions.has(Permission.UPDATE_TUTOR)}
<TableBodyCell>
<Button href={`/admin/tutor/${tutor.id}`} color="light" size="xs">Bearbeiten</Button>
</TableBodyCell>
{/if}
</TableBodyRow>
{/each}
</TableBody>
</Table>
{/snippet}
</SortableTable>
{/if}
<!-- TODO: auto match co-tutors based on wishes ("npm i modern-diacritics" for names) -->
......@@ -65,7 +65,7 @@ A merge request was created to get this issue fixed: https://github.com/nextauth
}
}}>
<input type="hidden" name="providerId" value="tutor" />
<input type="hidden" name="redirectTo" value="/intern/tutor" />
<input type="hidden" name="redirectTo" value={$page.url.searchParams.get("redirect_url") ?? "/intern/tutor"} />
<Input type="email" name="email" placeholder="E-Mail" required class="mb-2" />
<Input type="password" name="password" placeholder="Passwort" required class="mb-2" />
<Button type="submit">Anmelden</Button>
......@@ -103,7 +103,7 @@ A merge request was created to get this issue fixed: https://github.com/nextauth
}
}}>
<input type="hidden" name="providerId" value="rally" />
<input type="hidden" name="redirectTo" value="/intern/rallye" />
<input type="hidden" name="redirectTo" value={$page.url.searchParams.get("redirect_url") ?? "/intern/rallye"} />
<Input type="email" name="email" placeholder="E-Mail" required class="mb-2" />
<Input type="password" name="password" placeholder="Passwort" required class="mb-2" />
<Button type="submit">Anmelden</Button>
......
<script lang="ts">
import SortableTable from "$lib/components/SortableTable.svelte";
import SortableTableHeadCell from "$lib/components/SortableTableHeadCell.svelte";
import { Button, TableBodyCell, TableBodyRow, TableHead, TableHeadCell } from "flowbite-svelte";
import { Button, Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell } from "flowbite-svelte";
let { data } = $props();
......@@ -19,8 +19,8 @@
<SortableTable {items}>
<TableHead>
<SortableTableHeadCell sort={(a,b)=>a.name.localeCompare(b.name)}>Name</SortableTableHeadCell>
<SortableTableHeadCell sort={(a,b)=>a.age-b.age} defaultDirection="desc" default>Age</SortableTableHeadCell>
<SortableTableHeadCell sort={(a: typeof items[number], b)=>a.name.localeCompare(b.name)}>Name</SortableTableHeadCell>
<SortableTableHeadCell sort={(a: typeof items[number], b)=>a.age-b.age} defaultDirection="desc">Age</SortableTableHeadCell>
<TableHeadCell>Actions</TableHeadCell>
</TableHead>
{#snippet row({item})}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment