feat(events): added view for user to look at theirs event slot entries
Build and Push Docker image / build-and-push (push) Successful in 1m46s
Details
Build and Push Docker image / build-and-push (push) Successful in 1m46s
Details
This commit is contained in:
parent
36e6605647
commit
0fc99bd187
27
README.md
27
README.md
|
@ -17,10 +17,12 @@ Dieses Repository enthält den Quellcode für die StuVe IT Frontend Webseite.
|
||||||
|
|
||||||
## Backend
|
## Backend
|
||||||
|
|
||||||
Als Backend wird ein selbst erweitertes Pocketbase verwendet. Dieses ist in diesem [Repo](https://gitlab.uni-ulm.de/stuve-it/it-tools/backend/)
|
Als Backend wird ein selbst erweitertes Pocketbase verwendet. Dieses ist in
|
||||||
|
diesem [Repo](https://gitlab.uni-ulm.de/stuve-it/it-tools/backend/)
|
||||||
zu finden.
|
zu finden.
|
||||||
|
|
||||||
## Projekt Struktur
|
## Projekt Struktur
|
||||||
|
|
||||||
```
|
```
|
||||||
├── index.html # html Datei die den React Code in die Seite einbindet
|
├── index.html # html Datei die den React Code in die Seite einbindet
|
||||||
├── tsconfig.json # Typescript Config Datei
|
├── tsconfig.json # Typescript Config Datei
|
||||||
|
@ -41,14 +43,31 @@ zu finden.
|
||||||
|
|
||||||
### Pocketbase API
|
### Pocketbase API
|
||||||
|
|
||||||
Um die Kommunikation mit dem Backend zu erleichtern ist in der Datei "s@/lib/pocketbase.ts" ein Pocketbase Client implementiert.
|
Um die Kommunikation mit dem Backend zu erleichtern ist in der Datei "@/lib/pocketbase.ts" ein Pocketbase Client
|
||||||
|
implementiert.
|
||||||
|
|
||||||
Dieser Client ist ein React Hook und kann in jeder React Komponente verwendet werden.
|
Dieser Client ist ein React Hook und kann in jeder React Komponente verwendet werden.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import {usePB} from @/lib/pocketbase";
|
import {usePB} from "@/lib/pocketbase"
|
||||||
|
import {useQuery} from "@tanstack/react-query";
|
||||||
|
|
||||||
const {pb} = usePB();
|
const {pb} = usePB()
|
||||||
|
const user = useUser()
|
||||||
|
|
||||||
|
const query = useQuery({
|
||||||
|
queryKey: ["collection", id],
|
||||||
|
queryFn: async () => {
|
||||||
|
return await pb.collection("collection").getOne(id)
|
||||||
|
},
|
||||||
|
enabled: !!id && !!user
|
||||||
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Pocketbase Modell Types
|
#### Pocketbase Modell Types
|
||||||
|
|
||||||
|
#### Todo
|
||||||
|
|
||||||
|
- todo api rules so that only event admins can set status
|
||||||
|
- todo api rules so that only entries for future events can be created
|
||||||
|
- todo api rule so that entries for past events cant be edited or deleted
|
||||||
|
|
|
@ -39,7 +39,6 @@ export default function FormBuilder({
|
||||||
const [showPreview, setShowPreview] = useDisclosure(false)
|
const [showPreview, setShowPreview] = useDisclosure(false)
|
||||||
|
|
||||||
const formValues = useForm({
|
const formValues = useForm({
|
||||||
// mode: "uncontrolled", todo: fix this
|
|
||||||
initialValues: defaultValue,
|
initialValues: defaultValue,
|
||||||
validateInputOnChange: true,
|
validateInputOnChange: true,
|
||||||
validate: {
|
validate: {
|
||||||
|
|
|
@ -29,7 +29,7 @@ const themeOverride = createTheme({
|
||||||
components: {
|
components: {
|
||||||
Alert: Alert.extend({
|
Alert: Alert.extend({
|
||||||
defaultProps: {
|
defaultProps: {
|
||||||
radius: 'md',
|
radius: 'var(--border-radius)',
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,8 @@ export type EventListSlotEntriesWithUserModel =
|
||||||
slotDescription: string | null,
|
slotDescription: string | null,
|
||||||
eventList: string,
|
eventList: string,
|
||||||
listDescription: string | null,
|
listDescription: string | null,
|
||||||
event: string
|
event: string,
|
||||||
|
eventName: string,
|
||||||
}
|
}
|
||||||
& Pick<EventModel, "defaultEntryQuestionSchema" | "defaultEntryStatusSchema">
|
& Pick<EventModel, "defaultEntryQuestionSchema" | "defaultEntryStatusSchema">
|
||||||
& Pick<EventListModel, "entryQuestionSchema" | "entryStatusSchema">
|
& Pick<EventListModel, "entryQuestionSchema" | "entryStatusSchema">
|
|
@ -4,6 +4,7 @@ import EventOverview from "@/pages/events/EventOverview/index.page.tsx";
|
||||||
import EventView from "./s/EventView.tsx";
|
import EventView from "./s/EventView.tsx";
|
||||||
import EventNavigate from "@/pages/events/EventNavigate.tsx";
|
import EventNavigate from "@/pages/events/EventNavigate.tsx";
|
||||||
import EditEventRouter from "@/pages/events/e/:eventId/EditEventRouter.tsx";
|
import EditEventRouter from "@/pages/events/e/:eventId/EditEventRouter.tsx";
|
||||||
|
import UserEntries from "@/pages/events/entries/UserEntries.tsx";
|
||||||
|
|
||||||
|
|
||||||
export default function EventsRouter() {
|
export default function EventsRouter() {
|
||||||
|
@ -13,6 +14,7 @@ export default function EventsRouter() {
|
||||||
<Route path={":eventId"} element={<EventNavigate/>}/>
|
<Route path={":eventId"} element={<EventNavigate/>}/>
|
||||||
<Route path={"s/:eventId/*"} element={<EventView/>}/>
|
<Route path={"s/:eventId/*"} element={<EventView/>}/>
|
||||||
<Route path={"e/:eventId/*"} element={<EditEventRouter/>}/>
|
<Route path={"e/:eventId/*"} element={<EditEventRouter/>}/>
|
||||||
|
<Route path={"entries"} element={<UserEntries/>}/>
|
||||||
<Route path={"*"} element={<NotFound/>}/>
|
<Route path={"*"} element={<NotFound/>}/>
|
||||||
</Routes>
|
</Routes>
|
||||||
<Outlet/>
|
<Outlet/>
|
||||||
|
|
|
@ -102,7 +102,7 @@ export default function ListSearch({event}: { event: EventModel }) {
|
||||||
|
|
||||||
|
|
||||||
{/*
|
{/*
|
||||||
todo: filter
|
todo: more filter
|
||||||
<div className={"section stack"}>
|
<div className={"section stack"}>
|
||||||
<Title order={4} c={"blue"}>
|
<Title order={4} c={"blue"}>
|
||||||
Filter
|
Filter
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
import {useUser} from "@/lib/user.ts";
|
||||||
|
import PromptLoginModal from "@/components/auth/modals/PromptLoginModal.tsx";
|
||||||
|
import {Link, useNavigate} from "react-router-dom";
|
||||||
|
import {useState} from "react";
|
||||||
|
import {useDebouncedValue, useToggle} from "@mantine/hooks";
|
||||||
|
import {useQuery} from "@tanstack/react-query";
|
||||||
|
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
||||||
|
import {
|
||||||
|
ActionIcon,
|
||||||
|
Anchor,
|
||||||
|
Breadcrumbs,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Loader,
|
||||||
|
LoadingOverlay,
|
||||||
|
Pagination,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
ThemeIcon,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from "@mantine/core";
|
||||||
|
import ShowHelp from "@/components/ShowHelp.tsx";
|
||||||
|
import {IconConfetti, IconDatabaseOff, IconSortAscending, IconSortDescending} from "@tabler/icons-react";
|
||||||
|
import UserEntryRow from "@/pages/events/entries/UserEntryRow.tsx";
|
||||||
|
|
||||||
|
export default function UserEntries() {
|
||||||
|
|
||||||
|
const user = useUser()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
const {pb} = usePB()
|
||||||
|
|
||||||
|
const [page, setPage] = useState(1)
|
||||||
|
|
||||||
|
const [sortDirection, toggleSortDirection] = useToggle(['DESC', 'ASC']);
|
||||||
|
|
||||||
|
const [eventSearch, setEventSearch] = useState("")
|
||||||
|
|
||||||
|
const [debouncedSearch] = useDebouncedValue(eventSearch, 200)
|
||||||
|
|
||||||
|
const entriesQuery = useQuery({
|
||||||
|
queryKey: ["userEntries", user?.id, page, debouncedSearch, sortDirection],
|
||||||
|
queryFn: async () => (
|
||||||
|
await pb.collection("eventListSlotEntriesWithUser").getList(page, 50, {
|
||||||
|
filter: `userId='${user?.id}'${debouncedSearch ? `&&event.name~'${debouncedSearch}'` : ""}`,
|
||||||
|
sort: sortDirection === "ASC" ? "-event.startDate" : "event.startDate"
|
||||||
|
})
|
||||||
|
),
|
||||||
|
enabled: !!user
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return <PromptLoginModal onAbort={() => navigate("/")}/>
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
{entriesQuery.isLoading && <LoadingOverlay/>}
|
||||||
|
|
||||||
|
<div className={"section-transparent"}>
|
||||||
|
<Breadcrumbs>{[
|
||||||
|
{title: "Home", to: "/"},
|
||||||
|
{title: "Events", to: "/events"},
|
||||||
|
{title: "Meine Einträge", to: `/events/entries`},
|
||||||
|
].map(({title, to}) => (
|
||||||
|
<Anchor component={Link} to={to} key={title}>
|
||||||
|
{title}
|
||||||
|
</Anchor>
|
||||||
|
))}</Breadcrumbs>
|
||||||
|
</div>
|
||||||
|
<div className={"section"}>
|
||||||
|
<Title order={1} c={"blue"}>Meine Einträge</Title>
|
||||||
|
</div>
|
||||||
|
<div className={"section-transparent"}>
|
||||||
|
<ShowHelp>
|
||||||
|
Auf dieser Seite findest du alle deine Einträge zu Events und kannst diese verwalten.
|
||||||
|
</ShowHelp>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={"section stack"}>
|
||||||
|
<TextInput
|
||||||
|
value={eventSearch}
|
||||||
|
onChange={ev => setEventSearch(ev.target.value)}
|
||||||
|
leftSection={<IconConfetti/>}
|
||||||
|
rightSection={entriesQuery.isLoading ? <Loader size={"xs"}/> : (
|
||||||
|
<Tooltip label={`${
|
||||||
|
sortDirection === "ASC" ? "Events (alt → neu)" : "Events (neu → alt)"
|
||||||
|
}`} withArrow>
|
||||||
|
<ActionIcon
|
||||||
|
onClick={() => toggleSortDirection()}
|
||||||
|
variant={"transparent"}
|
||||||
|
aria-label={"toggle sort"}
|
||||||
|
>
|
||||||
|
{sortDirection === "ASC" ? <IconSortAscending/> : <IconSortDescending/>}
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
placeholder={"Nach Events suchen ..."}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify={"space-between"}>
|
||||||
|
<Text c={"dimmed"} size={"xs"}>
|
||||||
|
{eventSearch ? `Suche nach Einträgen für Event '${eventSearch}'` : "Alle deine Einträge"}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text c={"dimmed"} size={"xs"}>
|
||||||
|
{entriesQuery.data?.totalItems ?? 0} {eventSearch ? "Ergebnisse" : "Einträge"}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<PocketBaseErrorAlert error={entriesQuery.error}/>
|
||||||
|
|
||||||
|
{entriesQuery.data?.totalItems === 0 && <Stack align={"center"}>
|
||||||
|
<ThemeIcon variant={"transparent"} color={"gray"} size={"md"}>
|
||||||
|
<IconDatabaseOff/>
|
||||||
|
</ThemeIcon>
|
||||||
|
|
||||||
|
<Title order={4} c={"dimmed"}>Keine Einträge vorhanden</Title>
|
||||||
|
</Stack>
|
||||||
|
}
|
||||||
|
|
||||||
|
{entriesQuery.data?.items.map(entry => (
|
||||||
|
<UserEntryRow entry={entry} refetch={() => entriesQuery.refetch()} key={entry.id}/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<Center>
|
||||||
|
<Pagination total={entriesQuery.data?.totalPages ?? 1} value={page} onChange={setPage} size={"xs"}/>
|
||||||
|
</Center>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
.container {
|
||||||
|
background-color: var(--mantine-color-body);
|
||||||
|
border: var(--border);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
padding: var(--mantine-spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: start;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--gap);
|
||||||
|
|
||||||
|
font-size: var(--mantine-font-size-sm);
|
||||||
|
|
||||||
|
& > :nth-child(2) {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > :nth-child(3) {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > :nth-child(4) {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
import {EventListSlotEntriesWithUserModel} from "@/models/EventTypes.ts";
|
||||||
|
import {ActionIcon, Collapse, Group, Text, ThemeIcon, Tooltip} from "@mantine/core";
|
||||||
|
import {IconChevronDown, IconChevronRight, IconConfetti, IconForms, IconList, IconTrash} from "@tabler/icons-react";
|
||||||
|
import TextWithIcon from "@/components/layout/TextWithIcon";
|
||||||
|
import {
|
||||||
|
EventListSlotEntryDetails
|
||||||
|
} from "@/pages/events/e/:eventId/EventLists/:listId/EventListSlotsTable/EventListSlotEntryRow.tsx";
|
||||||
|
|
||||||
|
import {useDisclosure} from "@mantine/hooks";
|
||||||
|
import {RenderDateRange} from "@/pages/events/e/:eventId/EventLists/components/RenderDateRange.tsx";
|
||||||
|
import classes from "./UserEntryRow.module.css"
|
||||||
|
import {humanDeltaFromNow} from "@/lib/datetime.ts";
|
||||||
|
import {useMutation} from "@tanstack/react-query";
|
||||||
|
import {showSuccessNotification} from "@/components/util.tsx";
|
||||||
|
import {useConfirmModal} from "@/components/ConfirmModal.tsx";
|
||||||
|
import {usePB} from "@/lib/pocketbase.tsx";
|
||||||
|
import {
|
||||||
|
UpdateEventListSlotEntryFormModal
|
||||||
|
} from "@/pages/events/e/:eventId/EventLists/components/UpdateEventListSlotEntryFormModal.tsx";
|
||||||
|
|
||||||
|
export default function UserEntryRow({entry, refetch}: {
|
||||||
|
entry: EventListSlotEntriesWithUserModel,
|
||||||
|
refetch: () => void
|
||||||
|
}) {
|
||||||
|
const {pb} = usePB()
|
||||||
|
|
||||||
|
const [expanded, expandedHandler] = useDisclosure(false)
|
||||||
|
|
||||||
|
const [showEditFormModal, showEditFormModalHandler] = useDisclosure(false)
|
||||||
|
|
||||||
|
const delta = humanDeltaFromNow(entry.slotStartDate, entry.slotEndDate)
|
||||||
|
|
||||||
|
const deleteEntryMutation = useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
await pb.collection("eventListSlotEntries").delete(entry.id)
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
refetch()
|
||||||
|
showSuccessNotification("Eintrag gelöscht")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const {ConfirmModal, toggleConfirmModal} = useConfirmModal({
|
||||||
|
title: 'Eintrag löschen',
|
||||||
|
description: `Möchtest du den Eintrag von ${entry.userName} wirklich löschen?`,
|
||||||
|
onConfirm: () => deleteEntryMutation.mutate()
|
||||||
|
})
|
||||||
|
|
||||||
|
return <div className={classes.container}>
|
||||||
|
|
||||||
|
<ConfirmModal/>
|
||||||
|
|
||||||
|
<UpdateEventListSlotEntryFormModal
|
||||||
|
opened={showEditFormModal}
|
||||||
|
close={showEditFormModalHandler.close}
|
||||||
|
refetch={refetch}
|
||||||
|
entry={entry}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className={classes.row}>
|
||||||
|
<Tooltip label={expanded ? "Details verbergen" : "Details anzeigen"} withArrow>
|
||||||
|
<ActionIcon onClick={expandedHandler.toggle} variant={"light"} size={"xs"}>
|
||||||
|
{expanded ? <IconChevronDown/> : <IconChevronRight/>}
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
|
||||||
|
<TextWithIcon icon={
|
||||||
|
<ThemeIcon variant={"transparent"} size={"xs"} color={"gray"}>
|
||||||
|
<IconConfetti/>
|
||||||
|
</ThemeIcon>
|
||||||
|
}>
|
||||||
|
{entry.eventName}
|
||||||
|
</TextWithIcon>
|
||||||
|
|
||||||
|
<TextWithIcon icon={
|
||||||
|
<ThemeIcon variant={"transparent"} size={"xs"} color={"gray"}>
|
||||||
|
<IconList/>
|
||||||
|
</ThemeIcon>
|
||||||
|
}>
|
||||||
|
{entry.listName}
|
||||||
|
</TextWithIcon>
|
||||||
|
|
||||||
|
<Group gap={"xs"}>
|
||||||
|
<RenderDateRange start={new Date(entry.slotStartDate)} end={new Date(entry.slotEndDate)}/>
|
||||||
|
<Text size={"sm"} c={"dimmed"}>
|
||||||
|
{delta.message}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Group gap={"xs"}>
|
||||||
|
<Tooltip
|
||||||
|
label={delta.delta === "PAST" ? "Vergangene Einträge können nicht bearbeiten werden" : "Eintrag bearbeiten"}
|
||||||
|
withArrow>
|
||||||
|
<ActionIcon
|
||||||
|
variant={"light"}
|
||||||
|
size={"xs"}
|
||||||
|
onClick={showEditFormModalHandler.toggle}
|
||||||
|
aria-label={"edit entry form"}
|
||||||
|
disabled={delta.delta === "PAST"}
|
||||||
|
>
|
||||||
|
<IconForms/>
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip
|
||||||
|
label={delta.delta === "PAST" ? "Vergangene Einträge können nicht gelöscht werden" : "Eintrag löschen"}
|
||||||
|
withArrow>
|
||||||
|
<ActionIcon
|
||||||
|
variant={"light"}
|
||||||
|
color={"red"}
|
||||||
|
size={"xs"}
|
||||||
|
onClick={toggleConfirmModal}
|
||||||
|
aria-label={"delete entry"}
|
||||||
|
disabled={delta.delta === "PAST"}
|
||||||
|
>
|
||||||
|
<IconTrash/>
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Collapse in={expanded}>
|
||||||
|
<EventListSlotEntryDetails entry={entry}/>
|
||||||
|
</Collapse>
|
||||||
|
</div>
|
||||||
|
}
|
|
@ -22,6 +22,8 @@ export default function EventListSlotView({slot, refetch}: {
|
||||||
|
|
||||||
const slotIsFull = slot.maxEntries === 0 || slot.maxEntries === null ? false : slot.entriesCount >= slot.maxEntries
|
const slotIsFull = slot.maxEntries === 0 || slot.maxEntries === null ? false : slot.entriesCount >= slot.maxEntries
|
||||||
|
|
||||||
|
const slotIsInPast = new Date(slot.endDate) < new Date()
|
||||||
|
|
||||||
const schema = {
|
const schema = {
|
||||||
fields: [
|
fields: [
|
||||||
...slot.entryQuestionSchema?.fields ?? [],
|
...slot.entryQuestionSchema?.fields ?? [],
|
||||||
|
@ -36,7 +38,7 @@ export default function EventListSlotView({slot, refetch}: {
|
||||||
const createEntryMutation = useMutation({
|
const createEntryMutation = useMutation({
|
||||||
mutationFn: async (data: FieldEntries) => {
|
mutationFn: async (data: FieldEntries) => {
|
||||||
await pb.collection("eventListSlotEntries").create({
|
await pb.collection("eventListSlotEntries").create({
|
||||||
entryQuestionData: data, // todo
|
entryQuestionData: data,
|
||||||
eventListsSlot: slot.id,
|
eventListsSlot: slot.id,
|
||||||
ldapUser: user?.realm === "STUVE" ? user.id : null,
|
ldapUser: user?.realm === "STUVE" ? user.id : null,
|
||||||
guestUser: user?.realm === "GUEST" ? user.id : null
|
guestUser: user?.realm === "GUEST" ? user.id : null
|
||||||
|
@ -73,7 +75,11 @@ export default function EventListSlotView({slot, refetch}: {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
slotIsFull ? <>
|
slotIsInPast ? <>
|
||||||
|
<Alert color={"red"}>
|
||||||
|
Dieser Zeitslot ist bereits vorbei
|
||||||
|
</Alert>
|
||||||
|
</> : slotIsFull ? <>
|
||||||
<Alert color={"red"} title={"Zeitslot voll"}>
|
<Alert color={"red"} title={"Zeitslot voll"}>
|
||||||
Dieser Zeitslot ist bereits voll
|
Dieser Zeitslot ist bereits voll
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import {useParams, useSearchParams} from "react-router-dom";
|
import {Link, useParams, useSearchParams} from "react-router-dom";
|
||||||
import {usePB} from "@/lib/pocketbase.tsx";
|
import {usePB} from "@/lib/pocketbase.tsx";
|
||||||
import {useQuery} from "@tanstack/react-query";
|
import {useQuery} from "@tanstack/react-query";
|
||||||
import NotFound from "../../not-found/index.page.tsx";
|
import NotFound from "../../not-found/index.page.tsx";
|
||||||
import {Accordion, Alert, Button, Center, Group, Loader, Title} from "@mantine/core";
|
import {Accordion, Alert, Anchor, Breadcrumbs, Button, Center, Group, Loader, Title} from "@mantine/core";
|
||||||
import PBAvatar from "@/components/PBAvatar.tsx";
|
import PBAvatar from "@/components/PBAvatar.tsx";
|
||||||
import InnerHtml from "@/components/InnerHtml";
|
import InnerHtml from "@/components/InnerHtml";
|
||||||
import {IconExternalLink, IconLogin, IconPencil, IconSectionSign} from "@tabler/icons-react";
|
import {IconExternalLink, IconLogin, IconPencil, IconSectionSign} from "@tabler/icons-react";
|
||||||
import {useSettings} from "@/lib/settings.ts";
|
|
||||||
import EventData from "@/pages/events/e/:eventId/EventComponents/EventData.tsx";
|
import EventData from "@/pages/events/e/:eventId/EventComponents/EventData.tsx";
|
||||||
import EventListView from "@/pages/events/s/EventListView.tsx";
|
import EventListView from "@/pages/events/s/EventListView.tsx";
|
||||||
import {useEventRights} from "@/pages/events/util.ts";
|
import {useEventRights} from "@/pages/events/util.ts";
|
||||||
|
@ -24,7 +23,6 @@ export default function SharedEvent() {
|
||||||
const [searchParams] = useSearchParams()
|
const [searchParams] = useSearchParams()
|
||||||
const listIds = searchParams.get('lists')?.split(",") ?? []
|
const listIds = searchParams.get('lists')?.split(",") ?? []
|
||||||
|
|
||||||
const settings = useSettings()
|
|
||||||
|
|
||||||
const eventQuery = useQuery({
|
const eventQuery = useQuery({
|
||||||
queryKey: ["event", eventId],
|
queryKey: ["event", eventId],
|
||||||
|
@ -61,6 +59,18 @@ export default function SharedEvent() {
|
||||||
</Alert>
|
</Alert>
|
||||||
</div>}
|
</div>}
|
||||||
|
|
||||||
|
<div className={"section-transparent"}>
|
||||||
|
<Breadcrumbs>{[
|
||||||
|
{title: "Home", to: "/"},
|
||||||
|
{title: "Events", to: "/events"},
|
||||||
|
{title: event.name, to: `/events/${event.id}`},
|
||||||
|
].map(({title, to}) => (
|
||||||
|
<Anchor component={Link} to={to} key={title}>
|
||||||
|
{title}
|
||||||
|
</Anchor>
|
||||||
|
))}</Breadcrumbs>
|
||||||
|
</div>
|
||||||
|
|
||||||
{(canEditEvent || canEditEventList) && <div className={"section-transparent"}>
|
{(canEditEvent || canEditEventList) && <div className={"section-transparent"}>
|
||||||
<Alert color={"green"} title={"Du kannst dieses Event bearbeiten"}>
|
<Alert color={"green"} title={"Du kannst dieses Event bearbeiten"}>
|
||||||
<Button
|
<Button
|
||||||
|
@ -105,7 +115,25 @@ export default function SharedEvent() {
|
||||||
{event.isStuveEvent && (
|
{event.isStuveEvent && (
|
||||||
<Accordion.Item value={"stuve-agb"}>
|
<Accordion.Item value={"stuve-agb"}>
|
||||||
<Accordion.Control icon={<IconSectionSign/>}>AGB der Stuve</Accordion.Control>
|
<Accordion.Control icon={<IconSectionSign/>}>AGB der Stuve</Accordion.Control>
|
||||||
<Accordion.Panel><InnerHtml html={settings?.agb?.value || ""}/></Accordion.Panel>
|
<Accordion.Panel>
|
||||||
|
<Alert title={"StuVe Event"}>
|
||||||
|
Dieses Event wird im Rahmen der StuVe durchgeführt. Die AGB der StuVe sind für
|
||||||
|
dieses Event gültig.
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
mt={"xs"}
|
||||||
|
component={Link}
|
||||||
|
to={`/legal/terms-and-conditions`}
|
||||||
|
variant={"light"}
|
||||||
|
target={"_blank"}
|
||||||
|
leftSection={<IconExternalLink/>}
|
||||||
|
>
|
||||||
|
AGB der StuVe
|
||||||
|
</Button>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
</Accordion.Panel>
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
)}
|
)}
|
||||||
{event.additionalAgb && (
|
{event.additionalAgb && (
|
||||||
|
|
Loading…
Reference in New Issue