feat(events): privileged users can now see entries
Build and Push Docker image / build-and-push (push) Successful in 1m53s Details

This commit is contained in:
Valentin Kolb 2024-05-29 14:00:10 +02:00
parent 594a7f42fd
commit 21edac4e43
15 changed files with 191 additions and 108 deletions

View File

@ -17,6 +17,7 @@ import "@fontsource/overpass"
import "@fontsource/fira-code" import "@fontsource/fira-code"
import {Notifications} from "@mantine/notifications"; import {Notifications} from "@mantine/notifications";
import {ModalsProvider} from "@mantine/modals"; import {ModalsProvider} from "@mantine/modals";
import {ReactQueryDevtools} from "@tanstack/react-query-devtools";
export const queryClient = new QueryClient() export const queryClient = new QueryClient()
@ -45,8 +46,8 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
<PocketBaseProvider> <PocketBaseProvider>
<Router/> <Router/>
</PocketBaseProvider> </PocketBaseProvider>
{/*
<ReactQueryDevtools initialIsOpen={false}/> <ReactQueryDevtools initialIsOpen={false}/>
{/*
*/} */}
</QueryClientProvider> </QueryClientProvider>
</ModalsProvider> </ModalsProvider>

View File

@ -19,11 +19,11 @@ export default function EventNavigate() {
const eventQuery = useQuery({ const eventQuery = useQuery({
queryKey: ["event", eventId], queryKey: ["event", eventId],
queryFn: async () => (await pb.collection("events").getOne(eventId, { queryFn: async () => (await pb.collection("events").getOne(eventId, {
expand: "eventAdmins, privilegedLists" expand: "eventAdmins, privilegedLists, privilegedLists.eventListSlots_via_eventList.eventListSlotEntries_via_eventListsSlot"
})) }))
}) })
const {canEditEventList, canEditEvent} = useEventRights(eventQuery.data) const {isPrivilegedUser, canEditEvent} = useEventRights(eventQuery.data)
if (eventQuery.isLoading) { if (eventQuery.isLoading) {
return <LoadingOverlay/> return <LoadingOverlay/>
@ -37,7 +37,7 @@ export default function EventNavigate() {
return <Navigate to={`/events/e/${eventId}`} replace/> return <Navigate to={`/events/e/${eventId}`} replace/>
} }
if (canEditEventList) { if (isPrivilegedUser) {
return <Navigate to={`/events/e/${eventId}/lists`} replace/> return <Navigate to={`/events/e/${eventId}/lists`} replace/>
} }

View File

@ -43,7 +43,7 @@ import {useEventRights} from "@/pages/events/util.ts";
*/ */
const EventRow = ({event}: { event: EventModel }) => { const EventRow = ({event}: { event: EventModel }) => {
const {canEditEventList, canEditEvent} = useEventRights(event) const {isPrivilegedUser, canEditEvent} = useEventRights(event)
const [opened, handlers] = useDisclosure(false) const [opened, handlers] = useDisclosure(false)
@ -108,12 +108,13 @@ const EventRow = ({event}: { event: EventModel }) => {
<IconUserStar/> <IconUserStar/>
</ThemeIcon> </ThemeIcon>
</Tooltip> </Tooltip>
) : canEditEventList ? ) : isPrivilegedUser ?
<Tooltip label={"Du bist Listen Admin"} position={"left"} color={"green"} withArrow> <Tooltip label={"Du kannst das Event einsehen"} position={"left"} color={"green"}
withArrow>
<ThemeIcon <ThemeIcon
color={"green"} color={"green"}
variant={"transparent"} variant={"transparent"}
aria-label={"you are list admin"} aria-label={"you are a privileged user"}
size={"xs"} size={"xs"}
mr={"sm"} mr={"sm"}
> >
@ -185,7 +186,8 @@ export const EventList = () => {
return await pb.collection("events").getList(activePage, 10, { return await pb.collection("events").getList(activePage, 10, {
sort: sort, sort: sort,
filter: [`hideFromPublic = false`, ...filter].join(" && ") filter: [`hideFromPublic = false`, ...filter].join(" && "),
expand: "privilegedLists.eventListSlots_via_eventList.eventListSlotEntries_via_eventListsSlot"
} }
) )
} }

View File

@ -89,11 +89,11 @@ export default function EditEventRouter() {
const eventQuery = useQuery({ const eventQuery = useQuery({
queryKey: ["event", eventId], queryKey: ["event", eventId],
queryFn: async () => (await pb.collection("events").getOne(eventId, { queryFn: async () => (await pb.collection("events").getOne(eventId, {
expand: "eventAdmins, privilegedLists" expand: "eventAdmins, privilegedLists, privilegedLists.eventListSlots_via_eventList.eventListSlotEntries_via_eventListsSlot"
})) }))
}) })
const {canEditEventList, canEditEvent} = useEventRights(eventQuery.data) const {isPrivilegedUser, canEditEvent} = useEventRights(eventQuery.data)
if (eventQuery.isLoading) { if (eventQuery.isLoading) {
return <LoadingOverlay/> return <LoadingOverlay/>
@ -103,7 +103,7 @@ export default function EditEventRouter() {
return <NotFound/> return <NotFound/>
} }
if (!(canEditEvent || canEditEventList)) { if (!(canEditEvent || isPrivilegedUser)) {
return <Navigate to={`/events/s/${eventId}`} replace/> return <Navigate to={`/events/s/${eventId}`} replace/>
} }

View File

@ -22,6 +22,8 @@ import ListEntryStatusSettings
from "@/pages/events/e/:eventId/EventLists/:listId/ListSettings/ListEntryStatusSettings.tsx"; from "@/pages/events/e/:eventId/EventLists/:listId/ListSettings/ListEntryStatusSettings.tsx";
import ShowDebug from "@/components/ShowDebug.tsx"; import ShowDebug from "@/components/ShowDebug.tsx";
import {pprintDateTime} from "@/lib/datetime.ts"; import {pprintDateTime} from "@/lib/datetime.ts";
import {useEventRights} from "@/pages/events/util.ts";
import NotFound from "@/pages/not-found/index.page.tsx";
export default function EventListRouter({event}: { event: EventModel }) { export default function EventListRouter({event}: { event: EventModel }) {
@ -35,6 +37,8 @@ export default function EventListRouter({event}: { event: EventModel }) {
queryFn: async () => (await pb.collection("eventLists").getOne(listId)) queryFn: async () => (await pb.collection("eventLists").getOne(listId))
}) })
const {canEditEvent} = useEventRights(event)
if (listQuery.isLoading) { if (listQuery.isLoading) {
return <LoadingOverlay/> return <LoadingOverlay/>
} }
@ -45,6 +49,34 @@ export default function EventListRouter({event}: { event: EventModel }) {
const list = listQuery.data const list = listQuery.data
const nav = [
{
icon: <IconClockCog/>,
to: `/events/e/${event.id}/lists/overview/${list.id}/slots`,
title: "Zeitslots"
}
]
if (canEditEvent) {
nav.push(...[
{
icon: <IconSettings/>,
to: `/events/e/${event.id}/lists/overview/${list.id}/settings`,
title: "Einstellungen"
},
{
icon: <IconForms/>,
to: `/events/e/${event.id}/lists/overview/${list.id}/questions`,
title: "Formular"
},
{
icon: <IconCheckupList/>,
to: `/events/e/${event.id}/lists/overview/${list.id}/status`,
title: "Eintrag Status"
}
])
}
return <div className={"stack"}> return <div className={"stack"}>
<Breadcrumbs> <Breadcrumbs>
<Link to={`/events/e/${event.id}/lists/overview`}> <Link to={`/events/e/${event.id}/lists/overview`}>
@ -97,28 +129,7 @@ export default function EventListRouter({event}: { event: EventModel }) {
</div> </div>
<Group> <Group>
{[ {nav.map(({icon, to, title}) => (
{
icon: <IconClockCog/>,
to: `/events/e/${event.id}/lists/overview/${list.id}/slots`,
title: "Zeitslots"
},
{
icon: <IconSettings/>,
to: `/events/e/${event.id}/lists/overview/${list.id}/settings`,
title: "Einstellungen"
},
{
icon: <IconForms/>,
to: `/events/e/${event.id}/lists/overview/${list.id}/questions`,
title: "Formular"
},
{
icon: <IconCheckupList/>,
to: `/events/e/${event.id}/lists/overview/${list.id}/status`,
title: "Eintrag Status"
}
].map(({icon, to, title}) => (
<NavLink to={to} key={title} end> <NavLink to={to} key={title} end>
{({isActive}) => {({isActive}) =>
<Button <Button
@ -134,10 +145,15 @@ export default function EventListRouter({event}: { event: EventModel }) {
<Routes> <Routes>
<Route index element={<Navigate to={"slots"} replace/>}/> <Route index element={<Navigate to={"slots"} replace/>}/>
<Route path={"slots"} element={<ListSlots list={list}/>}/> <Route path={"slots"} element={<ListSlots event={event} list={list}/>}/>
<Route path={"settings"} element={<ListSettings list={list} event={event}/>}/>
<Route path={"questions"} element={<ListEntryQuestionSettings list={list} event={event}/>}/> {canEditEvent && <>
<Route path={"status"} element={<ListEntryStatusSettings list={list} event={event}/>}/> <Route path={"settings"} element={<ListSettings list={list} event={event}/>}/>
<Route path={"questions"} element={<ListEntryQuestionSettings list={list} event={event}/>}/>
<Route path={"status"} element={<ListEntryStatusSettings list={list} event={event}/>}/>
</>}
<Route path={"*"} element={<NotFound/>}/>
</Routes> </Routes>
</div> </div>
} }

View File

@ -1,4 +1,4 @@
import {EventListModel, EventListSlotsWithEntriesCountModel} from "@/models/EventTypes.ts"; import {EventListModel, EventListSlotsWithEntriesCountModel, EventModel} from "@/models/EventTypes.ts";
import {useDisclosure} from "@mantine/hooks"; import {useDisclosure} from "@mantine/hooks";
import classes from "./ListSlotRow.module.css"; import classes from "./ListSlotRow.module.css";
import {ActionIcon, Alert, Code, Group, Modal} from "@mantine/core"; import {ActionIcon, Alert, Code, Group, Modal} from "@mantine/core";
@ -10,13 +10,16 @@ import ShowDebug from "@/components/ShowDebug.tsx";
import UpsertSlot from "@/pages/events/e/:eventId/EventLists/EventListComponents/UpsertSlot.tsx"; import UpsertSlot from "@/pages/events/e/:eventId/EventLists/EventListComponents/UpsertSlot.tsx";
import SlotProgress from "@/pages/events/e/:eventId/EventLists/EventListComponents/SlotProgress.tsx"; import SlotProgress from "@/pages/events/e/:eventId/EventLists/EventListComponents/SlotProgress.tsx";
import {pprintDateTime} from "@/lib/datetime.ts"; import {pprintDateTime} from "@/lib/datetime.ts";
import {useEventRights} from "@/pages/events/util.ts";
export const ListSlotRow = ({slot, list, refetch}: { export const ListSlotRow = ({slot, list, refetch, event}: {
slot: EventListSlotsWithEntriesCountModel, slot: EventListSlotsWithEntriesCountModel,
list: EventListModel, list: EventListModel,
refetch: () => void refetch: () => void,
event: EventModel
}) => { }) => {
const {canEditEvent} = useEventRights(event)
const [showEditModal, showEditModalHandler] = useDisclosure(false) const [showEditModal, showEditModalHandler] = useDisclosure(false)
return ( return (
@ -32,7 +35,6 @@ export const ListSlotRow = ({slot, list, refetch}: {
}} onAbort={showEditModalHandler.close}/> }} onAbort={showEditModalHandler.close}/>
</Modal> </Modal>
<div className={classes.slotRow}> <div className={classes.slotRow}>
<RenderDateRange start={new Date(slot.startDate)} end={new Date(slot.endDate)}/> <RenderDateRange start={new Date(slot.startDate)} end={new Date(slot.endDate)}/>
<SlotProgress slot={slot}/> <SlotProgress slot={slot}/>
<Group gap={4} justify="right" wrap="nowrap"> <Group gap={4} justify="right" wrap="nowrap">
@ -40,6 +42,7 @@ export const ListSlotRow = ({slot, list, refetch}: {
size="sm" size="sm"
variant="subtle" variant="subtle"
color="blue" color="blue"
disabled={!canEditEvent}
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
showEditModalHandler.toggle() showEditModalHandler.toggle()

View File

@ -1,4 +1,4 @@
import {EventListModel} from "@/models/EventTypes.ts"; import {EventListModel, EventModel} from "@/models/EventTypes.ts";
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx"; import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
import {useQuery} from "@tanstack/react-query"; import {useQuery} from "@tanstack/react-query";
import {Box, Button, Center, Pagination, Title} from "@mantine/core"; import {Box, Button, Center, Pagination, Title} from "@mantine/core";
@ -16,7 +16,7 @@ import UpsertSlot from "@/pages/events/e/:eventId/EventLists/EventListComponents
* @param event - event the list belongs to * @param event - event the list belongs to
* @constructor * @constructor
*/ */
export default function ListSlots({list}: { list: EventListModel }) { export default function ListSlots({list, event}: { list: EventListModel, event: EventModel }) {
const {pb} = usePB() const {pb} = usePB()
@ -40,6 +40,7 @@ export default function ListSlots({list}: { list: EventListModel }) {
<div className={classes.table}> <div className={classes.table}>
{query.data?.items.map(slot => ( {query.data?.items.map(slot => (
<ListSlotRow <ListSlotRow
event={event}
key={slot.id} list={list} slot={slot} key={slot.id} list={list} slot={slot}
refetch={query.refetch} refetch={query.refetch}
/> />

View File

@ -1,4 +1,4 @@
import {EventListSlotEntriesWithUserModel} from "@/models/EventTypes.ts"; import {EventListSlotEntriesWithUserModel, EventModel} from "@/models/EventTypes.ts";
import {useMutation} from "@tanstack/react-query"; import {useMutation} from "@tanstack/react-query";
import {showSuccessNotification} from "@/components/util.tsx"; import {showSuccessNotification} from "@/components/util.tsx";
import {useConfirmModal} from "@/components/ConfirmModal.tsx"; import {useConfirmModal} from "@/components/ConfirmModal.tsx";
@ -13,16 +13,19 @@ import {
} from "@/pages/events/e/:eventId/EventLists/EventListComponents/UpdateEntryStatusModal.tsx"; } from "@/pages/events/e/:eventId/EventLists/EventListComponents/UpdateEntryStatusModal.tsx";
import {MoveEntryModal} from "@/pages/events/e/:eventId/EventLists/EventListComponents/MoveEntryModal.tsx"; import {MoveEntryModal} from "@/pages/events/e/:eventId/EventLists/EventListComponents/MoveEntryModal.tsx";
import EntryStatusSpoiler from "@/pages/events/e/:eventId/EventLists/EventListComponents/EntryStatusSpoiler.tsx"; import EntryStatusSpoiler from "@/pages/events/e/:eventId/EventLists/EventListComponents/EntryStatusSpoiler.tsx";
import {getListSchemas} from "@/pages/events/util.ts"; import {getListSchemas, useEventRights} from "@/pages/events/util.ts";
export default function EditSlotEntryMenu({entry, refetch}: { export default function EditSlotEntryMenu({entry, refetch, event}: {
refetch: () => void, refetch: () => void,
entry: EventListSlotEntriesWithUserModel entry: EventListSlotEntriesWithUserModel,
event: EventModel
}) { }) {
const {pb} = usePB() const {pb} = usePB()
const {canEditEvent} = useEventRights(event)
const [showStatusEditModal, showStatusEditModalHandler] = useDisclosure(false) const [showStatusEditModal, showStatusEditModalHandler] = useDisclosure(false)
const [showMoveEntryModal, showMoveEntryModalHandler] = useDisclosure(false) const [showMoveEntryModal, showMoveEntryModalHandler] = useDisclosure(false)
@ -107,6 +110,7 @@ export default function EditSlotEntryMenu({entry, refetch}: {
<Menu.Item <Menu.Item
color={"red"} leftSection={<IconTrash size={16}/>} color={"red"} leftSection={<IconTrash size={16}/>}
onClick={toggleConfirmModal} onClick={toggleConfirmModal}
disabled={!canEditEvent}
> >
Löschen Löschen
</Menu.Item> </Menu.Item>

View File

@ -26,6 +26,7 @@ import {IconArrowRight, IconListSearch, IconLock, IconLockOpen, IconPlus} from "
import {useRef, useState} from "react"; import {useRef, useState} from "react";
import {useDebouncedState} from "@mantine/hooks"; import {useDebouncedState} from "@mantine/hooks";
import {onlyUnique} from "@/lib/util.ts"; import {onlyUnique} from "@/lib/util.ts";
import {useEventRights} from "@/pages/events/util.ts";
/** /**
* Renders a single List as clickable row (link) * Renders a single List as clickable row (link)
@ -124,53 +125,57 @@ export default function EventListsOverview({event}: { event: EventModel }) {
const newListNameRef = useRef<HTMLInputElement>(null) const newListNameRef = useRef<HTMLInputElement>(null)
const {canEditEvent} = useEventRights(event)
return ( return (
<div className={"stack"}> <div className={"stack"}>
<form {canEditEvent &&
className={"section stack"} <form
onSubmit={formValues.onSubmit(() => createListMutation.mutate())} className={"section stack"}
> onSubmit={formValues.onSubmit(() => createListMutation.mutate())}
<Title order={3} c={"blue"}> >
<Group> <Title order={3} c={"blue"}>
<ActionIcon <Group>
mb={"5"} variant={"transparent"} <ActionIcon
onClick={() => newListNameRef.current?.focus()} mb={"5"} variant={"transparent"}
> onClick={() => newListNameRef.current?.focus()}
<IconPlus/> >
</ActionIcon> <IconPlus/>
Neue Liste Erstellen </ActionIcon>
</Group> Neue Liste Erstellen
</Title> </Group>
</Title>
<PocketBaseErrorAlert error={createListMutation.error}/> <PocketBaseErrorAlert error={createListMutation.error}/>
<TextInput <TextInput
ref={newListNameRef} ref={newListNameRef}
variant={"filled"} variant={"filled"}
placeholder={"Name der neuen Liste ..."} placeholder={"Name der neuen Liste ..."}
{...formValues.getInputProps("name")} {...formValues.getInputProps("name")}
/>
<Collapse in={!!formValues.values.name}>
<Title order={4} c={"blue"}>Beschreibung</Title>
<TextEditor
mt={"sm"}
value={formValues.values.description}
onChange={(value) => formValues.setFieldValue("description", value)}
/> />
<Box mt={"sm"}> <Collapse in={!!formValues.values.name}>
<Button <Title order={4} c={"blue"}>Beschreibung</Title>
type={"submit"}
loading={createListMutation.isPending} <TextEditor
disabled={!formValues.isTouched()} mt={"sm"}
> value={formValues.values.description}
Liste Erstellen onChange={(value) => formValues.setFieldValue("description", value)}
</Button> />
</Box>
</Collapse> <Box mt={"sm"}>
</form> <Button
type={"submit"}
loading={createListMutation.isPending}
disabled={!formValues.isTouched()}
>
Liste Erstellen
</Button>
</Box>
</Collapse>
</form>
}
<Autocomplete <Autocomplete
placeholder={"Nach einer Liste suchen ..."} placeholder={"Nach einer Liste suchen ..."}

View File

@ -11,9 +11,11 @@ import EditDefaultEntryStatusSchema
from "@/pages/events/e/:eventId/EventLists/GeneralListSettings/EditDefaultEntryStatusSchema.tsx"; from "@/pages/events/e/:eventId/EventLists/GeneralListSettings/EditDefaultEntryStatusSchema.tsx";
import EditDefaultEntryQuestionSchema import EditDefaultEntryQuestionSchema
from "@/pages/events/e/:eventId/EventLists/GeneralListSettings/EditDefaultEntryQuestionSchema.tsx"; from "@/pages/events/e/:eventId/EventLists/GeneralListSettings/EditDefaultEntryQuestionSchema.tsx";
import {useEventRights} from "@/pages/events/util.ts";
import NotFound from "@/pages/not-found/index.page.tsx";
const nav = [ const viewNav = [
{ {
label: "Listenaktionen", label: "Listenaktionen",
children: [ children: [
@ -34,6 +36,10 @@ const nav = [
} }
] ]
}, },
]
const editNav = [
{ {
label: "Einstellungen", label: "Einstellungen",
children: [ children: [
@ -48,7 +54,7 @@ const nav = [
to: "e/status" to: "e/status"
} }
] ]
}, }
] ]
/** /**
@ -58,6 +64,13 @@ const nav = [
*/ */
export const EventListsMenu = ({event, target}: { event: EventModel, target: ReactNode }) => { export const EventListsMenu = ({event, target}: { event: EventModel, target: ReactNode }) => {
const {canEditEvent} = useEventRights(event)
const nav = [...viewNav]
if (canEditEvent) {
nav.push(...editNav)
}
return <> return <>
<Menu <Menu
withArrow shadow="md" width={200} trigger="click-hover" loop={false} withArrow shadow="md" width={200} trigger="click-hover" loop={false}
@ -89,6 +102,8 @@ export const EventListsMenu = ({event, target}: { event: EventModel, target: Rea
export default function EventListsRouter({event}: { event: EventModel }) { export default function EventListsRouter({event}: { event: EventModel }) {
const {canEditEvent} = useEventRights(event)
return <> return <>
<Routes> <Routes>
<Route index element={<Navigate to={"overview"} replace/>}/> <Route index element={<Navigate to={"overview"} replace/>}/>
@ -96,11 +111,15 @@ export default function EventListsRouter({event}: { event: EventModel }) {
<Route path={"search"} element={<EventListSearch event={event}/>}/> <Route path={"search"} element={<EventListSearch event={event}/>}/>
<Route path={"share"} element={<ShareEventLists event={event}/>}/> <Route path={"share"} element={<ShareEventLists event={event}/>}/>
<Route path={"e/status"} element={<EditDefaultEntryStatusSchema event={event}/>}/> {canEditEvent && <>
<Route path={"e/questions"} element={<EditDefaultEntryQuestionSchema event={event}/>}/> <Route path={"e/status"} element={<EditDefaultEntryStatusSchema event={event}/>}/>
<Route path={"e/questions"} element={<EditDefaultEntryQuestionSchema event={event}/>}/>
</>}
<Route path={"overview"} element={<EventListsOverview event={event}/>}/> <Route path={"overview"} element={<EventListsOverview event={event}/>}/>
<Route path={"overview/:listId/*"} element={<EventListRouter event={event}/>}/> <Route path={"overview/:listId/*"} element={<EventListRouter event={event}/>}/>
<Route path={"*"} element={<NotFound/>}/>
</Routes> </Routes>
</> </>
} }

View File

@ -1,4 +1,4 @@
import {EventListSlotEntriesWithUserModel} from "@/models/EventTypes.ts"; import {EventListSlotEntriesWithUserModel, EventModel} from "@/models/EventTypes.ts";
import classes from "./EventEntries.module.css"; import classes from "./EventEntries.module.css";
import {ActionIcon, Code, Collapse, ThemeIcon, Tooltip} from "@mantine/core"; import {ActionIcon, Code, Collapse, ThemeIcon, Tooltip} from "@mantine/core";
import {IconEye, IconEyeOff, IconList, IconUser} from "@tabler/icons-react"; import {IconEye, IconEyeOff, IconList, IconUser} from "@tabler/icons-react";
@ -16,9 +16,10 @@ import {RenderDateRange} from "@/pages/events/e/:eventId/EventLists/EventListCom
import EditSlotEntryMenu from "@/pages/events/e/:eventId/EventLists/EventListComponents/EditSlotEntryMenu.tsx"; import EditSlotEntryMenu from "@/pages/events/e/:eventId/EventLists/EventListComponents/EditSlotEntryMenu.tsx";
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
function EventEntry({entry, refetch}: { function EventEntry({entry, refetch, event}: {
entry: EventListSlotEntriesWithUserModel, entry: EventListSlotEntriesWithUserModel,
refetch: () => void refetch: () => void,
event: EventModel
}) { }) {
const [expanded, expandedHandler] = useDisclosure(false) const [expanded, expandedHandler] = useDisclosure(false)
@ -55,7 +56,7 @@ function EventEntry({entry, refetch}: {
{expanded ? <IconEyeOff/> : <IconEye/>} {expanded ? <IconEyeOff/> : <IconEye/>}
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
<EditSlotEntryMenu entry={entry} refetch={refetch}/> <EditSlotEntryMenu event={event} entry={entry} refetch={refetch}/>
</div> </div>
</div> </div>
@ -81,11 +82,12 @@ function EventEntry({entry, refetch}: {
</> </>
} }
export default function EventEntries({entries, refetch}: { export default function EventEntries({entries, refetch, event}: {
entries: EventListSlotEntriesWithUserModel[], entries: EventListSlotEntriesWithUserModel[],
refetch: () => void refetch: () => void,
event: EventModel
}) { }) {
return <div className={classes.mainGrid}> return <div className={classes.mainGrid}>
{entries.map(entry => <EventEntry key={entry.id} entry={entry} refetch={refetch}/>)} {entries.map(entry => <EventEntry event={event} key={entry.id} entry={entry} refetch={refetch}/>)}
</div> </div>
} }

View File

@ -12,7 +12,15 @@ import {
Title, Title,
Tooltip Tooltip
} from "@mantine/core"; } from "@mantine/core";
import {IconArrowDown, IconFilter, IconFilterEdit, IconFilterOff, IconSend, IconUserSearch} from "@tabler/icons-react"; import {
IconArrowDown,
IconCsv,
IconFilter,
IconFilterEdit,
IconFilterOff,
IconSend,
IconUserSearch
} from "@tabler/icons-react";
import {useInfiniteQuery} from "@tanstack/react-query"; import {useInfiniteQuery} from "@tanstack/react-query";
import {useDebouncedValue, useDisclosure} from "@mantine/hooks"; import {useDebouncedValue, useDisclosure} from "@mantine/hooks";
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx"; import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
@ -188,6 +196,10 @@ export default function ListSearch({event}: { event: EventModel }) {
<Button size={"xs"} leftSection={<IconSend size={16}/>} disabled> <Button size={"xs"} leftSection={<IconSend size={16}/>} disabled>
{entriesCount} Personen benachrichtigen {entriesCount} Personen benachrichtigen
</Button> </Button>
<Button size={"xs"} leftSection={<IconCsv size={16}/>} disabled>
Daten exportieren
</Button>
</Group> </Group>
</div> </div>
</Collapse> </Collapse>
@ -206,7 +218,7 @@ export default function ListSearch({event}: { event: EventModel }) {
<PocketBaseErrorAlert error={query.error}/> <PocketBaseErrorAlert error={query.error}/>
<EventEntries entries={entries} refetch={() => query.refetch()}/> <EventEntries event={event} entries={entries} refetch={() => query.refetch()}/>
{query.hasNextPage && ( {query.hasNextPage && (
<Center p={"xs"}> <Center p={"xs"}>

View File

@ -41,7 +41,7 @@ export default function SharedEvent() {
enabled: !!eventId enabled: !!eventId
}) })
const {canEditEventList, canEditEvent} = useEventRights(eventQuery.data) const {isPrivilegedUser, canEditEvent} = useEventRights(eventQuery.data)
const {handler: loginHandler} = useLogin() const {handler: loginHandler} = useLogin()
@ -112,7 +112,7 @@ export default function SharedEvent() {
</Alert> </Alert>
</div>} </div>}
{canEditEventList && <div className={"section-transparent"}> {isPrivilegedUser && <div className={"section-transparent"}>
<Alert color={"green"} title={"Du kannst dieses Event und alle Teilnehmenden ansehen"}> <Alert color={"green"} title={"Du kannst dieses Event und alle Teilnehmenden ansehen"}>
<Button <Button
component={"a"} component={"a"}

View File

@ -1,5 +1,7 @@
import { import {
EventListSlotEntriesWithUserModel, EventListSlotEntriesWithUserModel,
EventListSlotEntryModel,
EventListSlotModel,
EventListSlotsWithEntriesCountModel, EventListSlotsWithEntriesCountModel,
EventModel EventModel
} from "@/models/EventTypes.ts"; } from "@/models/EventTypes.ts";
@ -20,9 +22,15 @@ export const useEventRights = (event?: EventModel) => {
) )
const isEventAdmin = !!(user && event && event.eventAdmins.includes(user.id)) const isEventAdmin = !!(user && event && event.eventAdmins.includes(user.id))
// for this to work the query of the event must include this backrelation expand:
// 'privilegedLists.eventListSlots_via_eventList.eventListSlotEntries_via_eventListsSlot'
const privilegedUsers = event?.expand?.privilegedLists?.flatMap(pl =>
pl?.expand?.eventListSlots_via_eventList.flatMap((els: EventListSlotModel) =>
els?.expand?.eventListSlotEntries_via_eventListsSlot.flatMap((e: EventListSlotEntryModel) => e.user))) ?? []
return { return {
canEditEvent: isEventAdmin || isStex, canEditEvent: isEventAdmin || isStex,
canEditEventList: false isPrivilegedUser: privilegedUsers.includes(user?.id),
} }
} }

View File

@ -1,5 +1,5 @@
import {useShowDebug} from "@/components/ShowDebug.tsx"; import {useShowDebug} from "@/components/ShowDebug.tsx";
import {ActionIcon, Alert, Group, LoadingOverlay, Pagination, Text, TextInput, Title} from "@mantine/core"; import {ActionIcon, Alert, Divider, Group, LoadingOverlay, Pagination, Text, TextInput, Title} from "@mantine/core";
import {useForm} from "@mantine/form"; import {useForm} from "@mantine/form";
import {useQuery} from "@tanstack/react-query"; import {useQuery} from "@tanstack/react-query";
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx"; import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
@ -23,13 +23,13 @@ export default function DebugPage() {
filter: "", filter: "",
sort: "", sort: "",
expand: "", expand: "",
select: ""
} }
}) })
const debugQuery = useQuery({ const debugQuery = useQuery({
queryKey: ["debug", formValues.values.collectionName, formValues.values.filter, formValues.values.sort, formValues.values.expand], queryKey: ["debug", formValues.values.collectionName, formValues.values.filter, formValues.values.sort, formValues.values.expand],
queryFn: async () => { queryFn: async () => {
const options: RecordListOptions = {} const options: RecordListOptions = {}
if (formValues.values.filter) options["filter"] = formValues.values.filter if (formValues.values.filter) options["filter"] = formValues.values.filter
@ -50,6 +50,8 @@ export default function DebugPage() {
</Alert> </Alert>
</div>) </div>)
const result = formValues.values.select ? debugQuery.data?.items.map(i => i[formValues.values.select]) : debugQuery.data?.items
return <> return <>
<div className={"section"}> <div className={"section"}>
<Title c={"orange"} order={1}>Debug</Title> <Title c={"orange"} order={1}>Debug</Title>
@ -76,6 +78,14 @@ export default function DebugPage() {
placeholder={"expand"} placeholder={"expand"}
{...formValues.getInputProps("expand")} {...formValues.getInputProps("expand")}
/> />
<Divider label={"client only"}/>
<TextInput
label={"Select"}
placeholder={"select"}
{...formValues.getInputProps("select")}
/>
</div> </div>
<div className={"section stack"} style={{position: "relative"}}> <div className={"section stack"} style={{position: "relative"}}>
@ -102,7 +112,7 @@ export default function DebugPage() {
</ActionIcon> </ActionIcon>
</Group> </Group>
<CodeHighlight code={JSON.stringify(debugQuery.data.items, null, 2)} language="json"/> <CodeHighlight code={JSON.stringify(result, null, 2)} language="json"/>
</div>} </div>}
</div> </div>