feat(events): privileged users can now see entries
Build and Push Docker image / build-and-push (push) Successful in 1m53s
Details
Build and Push Docker image / build-and-push (push) Successful in 1m53s
Details
This commit is contained in:
parent
594a7f42fd
commit
21edac4e43
|
@ -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>
|
||||||
|
|
|
@ -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/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
}
|
}
|
|
@ -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()
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 ..."}
|
||||||
|
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
|
@ -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>
|
||||||
}
|
}
|
|
@ -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"}>
|
||||||
|
|
|
@ -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"}
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue