feat(app): users are now all stored in a users table and more list settings are available
Build and Push Docker image / build-and-push (push) Successful in 5m36s
Details
Build and Push Docker image / build-and-push (push) Successful in 5m36s
Details
This commit is contained in:
parent
0fc99bd187
commit
ad2a69e6fe
|
@ -52,8 +52,7 @@ Dieser Client ist ein React Hook und kann in jeder React Komponente verwendet we
|
||||||
import {usePB} from "@/lib/pocketbase"
|
import {usePB} from "@/lib/pocketbase"
|
||||||
import {useQuery} from "@tanstack/react-query";
|
import {useQuery} from "@tanstack/react-query";
|
||||||
|
|
||||||
const {pb} = usePB()
|
const {pb,user} = usePB()
|
||||||
const user = useUser()
|
|
||||||
|
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryKey: ["collection", id],
|
queryKey: ["collection", id],
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import {useMutation} from "@tanstack/react-query";
|
import {useMutation} from "@tanstack/react-query";
|
||||||
import {IconUsers} from "@tabler/icons-react";
|
import {IconUsers} from "@tabler/icons-react";
|
||||||
import {usePB} from "@/lib/pocketbase.tsx";
|
import {usePB} from "@/lib/pocketbase.tsx";
|
||||||
import {LdapUserModel} from "@/models/AuthTypes.ts";
|
import {UserModal} from "@/models/AuthTypes.ts";
|
||||||
import RecordSearchInput, {GenericRecordSearchInputProps} from "../input/RecordSearchInput.tsx";
|
import RecordSearchInput, {GenericRecordSearchInputProps} from "../input/RecordSearchInput.tsx";
|
||||||
|
|
||||||
|
|
||||||
export default function LdapUserInput(props: GenericRecordSearchInputProps<LdapUserModel>) {
|
export default function UserInput(props: GenericRecordSearchInputProps<UserModal>) {
|
||||||
|
|
||||||
const {pb} = usePB()
|
const {pb} = usePB()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecordSearchInput
|
<RecordSearchInput
|
||||||
<LdapUserModel>
|
<UserModal>
|
||||||
recordToString={(user) => ({displayName: `${user.givenName} ${user.sn}`})}
|
recordToString={(user) => ({displayName: `${user.givenName} ${user.sn}`})}
|
||||||
{...props}
|
{...props}
|
||||||
placeholder={props.placeholder || "Suche nach Personen..."}
|
placeholder={props.placeholder || "Suche nach Personen..."}
|
||||||
|
@ -23,7 +23,7 @@ export default function LdapUserInput(props: GenericRecordSearchInputProps<LdapU
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
return (await pb.collection('ldap_users').getList(1, 5, {
|
return (await pb.collection('users').getList(1, 5, {
|
||||||
filter: `(username ~ "${
|
filter: `(username ~ "${
|
||||||
search.trim().toLowerCase().split(" ").map(s => s.trim()).join(".")
|
search.trim().toLowerCase().split(" ").map(s => s.trim()).join(".")
|
||||||
}%")`,
|
}%")`,
|
|
@ -1,18 +1,18 @@
|
||||||
import {LdapUserModel} from "@/models/AuthTypes.ts";
|
import {UserModal} from "@/models/AuthTypes.ts";
|
||||||
import {List} from "@mantine/core";
|
import {List} from "@mantine/core";
|
||||||
import {IconUser} from "@tabler/icons-react";
|
import {IconUser} from "@tabler/icons-react";
|
||||||
import {usePB} from "@/lib/pocketbase.tsx";
|
import {usePB} from "@/lib/pocketbase.tsx";
|
||||||
|
|
||||||
export default function LdapUsersDisplay({users}: { users: LdapUserModel[] }) {
|
export default function UsersDisplay({users}: { users: UserModal[] }) {
|
||||||
|
|
||||||
const {ldapUser} = usePB()
|
const {user} = usePB()
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<List size={"sm"} icon={<IconUser size={16}/>}>
|
<List size={"sm"} icon={<IconUser size={16}/>}>
|
||||||
{
|
{
|
||||||
users.map((u) => (
|
users.map((u) => (
|
||||||
<List.Item key={u.id} fw={500} c={ldapUser && ldapUser.id == u.id ? "green" : ""}>
|
<List.Item key={u.id} fw={500} c={user && user.id == u.id ? "green" : ""}>
|
||||||
{u.givenName} {u.sn}
|
{u.username}
|
||||||
</List.Item>
|
</List.Item>
|
||||||
))
|
))
|
||||||
}
|
}
|
|
@ -8,7 +8,6 @@ import {showSuccessNotification} from "@/components/util.tsx";
|
||||||
import {useSearchParams} from "react-router-dom";
|
import {useSearchParams} from "react-router-dom";
|
||||||
|
|
||||||
import EmailSVG from "@/illustrations/email.svg?react"
|
import EmailSVG from "@/illustrations/email.svg?react"
|
||||||
import {useUser} from "@/lib/user.ts";
|
|
||||||
import PromptLoginModal from "@/components/auth/modals/PromptLoginModal.tsx";
|
import PromptLoginModal from "@/components/auth/modals/PromptLoginModal.tsx";
|
||||||
|
|
||||||
export const CHANGE_EMAIL_TOKEN_KEY = "changeEmailToken"
|
export const CHANGE_EMAIL_TOKEN_KEY = "changeEmailToken"
|
||||||
|
@ -31,7 +30,7 @@ const RequestEmailChangeModal = ({open, onClose}: {
|
||||||
|
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
await pb.collection("guest_users").requestEmailChange(formValues.values.email)
|
await pb.collection("users").requestEmailChange(formValues.values.email)
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
formValues.reset()
|
formValues.reset()
|
||||||
|
@ -96,7 +95,7 @@ const ConfirmEmailChangeModal = ({open, onClose, token}: {
|
||||||
|
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
await pb.collection("guest_users").confirmEmailChange(
|
await pb.collection("users").confirmEmailChange(
|
||||||
token,
|
token,
|
||||||
formValues.values.password,
|
formValues.values.password,
|
||||||
)
|
)
|
||||||
|
@ -115,7 +114,7 @@ const ConfirmEmailChangeModal = ({open, onClose, token}: {
|
||||||
<form className={"stack"} onSubmit={formValues.onSubmit(() => mutation.mutate())}>
|
<form className={"stack"} onSubmit={formValues.onSubmit(() => mutation.mutate())}>
|
||||||
|
|
||||||
<Center>
|
<Center>
|
||||||
<EmailSVG height={"200px"} width={"200px"}/>
|
<EmailSVG height={"200px"} width={"200px"}/>
|
||||||
</Center>
|
</Center>
|
||||||
|
|
||||||
<PocketBaseErrorAlert error={mutation.error}/>
|
<PocketBaseErrorAlert error={mutation.error}/>
|
||||||
|
@ -153,7 +152,7 @@ const ConfirmEmailChangeModal = ({open, onClose, token}: {
|
||||||
export default function ChangeEmailModal() {
|
export default function ChangeEmailModal() {
|
||||||
const {value, handler} = useChangeEmail()
|
const {value, handler} = useChangeEmail()
|
||||||
|
|
||||||
const user = useUser()
|
const {user} = usePB()
|
||||||
|
|
||||||
const [searchParams] = useSearchParams()
|
const [searchParams] = useSearchParams()
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import {showErrorNotification, showSuccessNotification} from "@/components/util.
|
||||||
import {Alert, Button, Group, Modal, TextInput, Title} from "@mantine/core";
|
import {Alert, Button, Group, Modal, TextInput, Title} from "@mantine/core";
|
||||||
import {IconAt} from "@tabler/icons-react";
|
import {IconAt} from "@tabler/icons-react";
|
||||||
import {useLogin} from "@/components/auth/modals/hooks.ts";
|
import {useLogin} from "@/components/auth/modals/hooks.ts";
|
||||||
import {useUser} from "@/lib/user.ts";
|
|
||||||
|
|
||||||
export const EMAIL_TOKEN_KEY = "emailVerificationToken"
|
export const EMAIL_TOKEN_KEY = "emailVerificationToken"
|
||||||
|
|
||||||
|
@ -29,13 +28,11 @@ export default function EmailTokenVerification() {
|
||||||
|
|
||||||
const [email, setEmail] = useState<string>("")
|
const [email, setEmail] = useState<string>("")
|
||||||
|
|
||||||
const {pb, refreshUser} = usePB()
|
const {pb, user, refreshUser} = usePB()
|
||||||
|
|
||||||
const user = useUser()
|
|
||||||
|
|
||||||
const verifyTokenMutation = useMutation({
|
const verifyTokenMutation = useMutation({
|
||||||
mutationFn: async (token: string) => {
|
mutationFn: async (token: string) => {
|
||||||
await pb.collection("guest_users").confirmVerification(token)
|
await pb.collection("users").confirmVerification(token)
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
showSuccessNotification("E-Mail erfolgreich verifiziert")
|
showSuccessNotification("E-Mail erfolgreich verifiziert")
|
||||||
|
@ -77,7 +74,7 @@ export default function EmailTokenVerification() {
|
||||||
<Button
|
<Button
|
||||||
disabled={email.length === 0}
|
disabled={email.length === 0}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
pb.collection("guest_users").requestVerification(email)
|
pb.collection("users").requestVerification(email)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Token erneut senden
|
Token erneut senden
|
||||||
|
|
|
@ -30,7 +30,7 @@ const RequestResetPasswordModal = ({open, onClose}: {
|
||||||
|
|
||||||
const requestResetPasswordMutation = useMutation({
|
const requestResetPasswordMutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
await pb.collection("guest_users").requestPasswordReset(formValues.values.email)
|
await pb.collection("users").requestPasswordReset(formValues.values.email)
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
formValues.reset()
|
formValues.reset()
|
||||||
|
@ -84,7 +84,7 @@ const ResetPasswordModal = ({open, onClose, token}: {
|
||||||
onClose: () => void,
|
onClose: () => void,
|
||||||
token: string
|
token: string
|
||||||
}) => {
|
}) => {
|
||||||
const {pb, userRecord} = usePB()
|
const {pb, user} = usePB()
|
||||||
|
|
||||||
const {handler: loginHandler} = useLogin()
|
const {handler: loginHandler} = useLogin()
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ const ResetPasswordModal = ({open, onClose, token}: {
|
||||||
|
|
||||||
const requestResetPasswordMutation = useMutation({
|
const requestResetPasswordMutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
await pb.collection("guest_users").confirmPasswordReset(
|
await pb.collection("users").confirmPasswordReset(
|
||||||
token,
|
token,
|
||||||
formValues.values.password,
|
formValues.values.password,
|
||||||
formValues.values.passwordConfirm
|
formValues.values.passwordConfirm
|
||||||
|
@ -111,7 +111,7 @@ const ResetPasswordModal = ({open, onClose, token}: {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
formValues.reset()
|
formValues.reset()
|
||||||
showSuccessNotification("Passwort erfolgreich zurückgesetzt")
|
showSuccessNotification("Passwort erfolgreich zurückgesetzt")
|
||||||
if (!userRecord) {
|
if (!user) {
|
||||||
loginHandler.open()
|
loginHandler.open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ export default function LoginModal() {
|
||||||
|
|
||||||
const {value, handler} = useLogin()
|
const {value, handler} = useLogin()
|
||||||
|
|
||||||
const {ldapLogin, guestLogin, userRecord} = usePB()
|
const {ldapLogin, guestLogin, user} = usePB()
|
||||||
|
|
||||||
const {handler: registerHandler} = useRegister()
|
const {handler: registerHandler} = useRegister()
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ export default function LoginModal() {
|
||||||
})
|
})
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<Modal opened={value && !userRecord} onClose={handler.close} withCloseButton={false} size={"sm"}>
|
<Modal opened={value && !user} onClose={handler.close} withCloseButton={false} size={"sm"}>
|
||||||
<form className={"stack"} onSubmit={formValues.onSubmit(() => loginMutation.mutate())}>
|
<form className={"stack"} onSubmit={formValues.onSubmit(() => loginMutation.mutate())}>
|
||||||
<Title ta={"center"} order={3}>StuVe IT Login</Title>
|
<Title ta={"center"} order={3}>StuVe IT Login</Title>
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {Link} from "react-router-dom";
|
||||||
export default function RegisterModal() {
|
export default function RegisterModal() {
|
||||||
const {value, handler} = useRegister()
|
const {value, handler} = useRegister()
|
||||||
|
|
||||||
const {userRecord, pb} = usePB()
|
const {user, pb} = usePB()
|
||||||
|
|
||||||
const formValues = useForm({
|
const formValues = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
|
@ -36,10 +36,11 @@ export default function RegisterModal() {
|
||||||
|
|
||||||
const registerMutation = useMutation({
|
const registerMutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
await pb.collection("guest_users").create({
|
await pb.collection("users").create({
|
||||||
...formValues.values,
|
...formValues.values,
|
||||||
|
REALM: "GUEST"
|
||||||
})
|
})
|
||||||
await pb.collection("guest_users").requestVerification(formValues.values.email)
|
await pb.collection("users").requestVerification(formValues.values.email)
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
handler.close()
|
handler.close()
|
||||||
|
@ -50,7 +51,7 @@ export default function RegisterModal() {
|
||||||
})
|
})
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<Modal size={"lg"} opened={value && !userRecord} onClose={handler.close}
|
<Modal size={"lg"} opened={value && !user} onClose={handler.close}
|
||||||
title={"Gast Account anlegen"}>
|
title={"Gast Account anlegen"}>
|
||||||
|
|
||||||
<form className={"stack"} onSubmit={formValues.onSubmit(() => registerMutation.mutate())}>
|
<form className={"stack"} onSubmit={formValues.onSubmit(() => registerMutation.mutate())}>
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
IconMailCog,
|
IconMailCog,
|
||||||
IconPassword,
|
IconPassword,
|
||||||
IconServer,
|
IconServer,
|
||||||
IconServerOff
|
IconServerOff, IconUser
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import LdapGroupsDisplay from "@/components/auth/LdapGroupsDisplay.tsx";
|
import LdapGroupsDisplay from "@/components/auth/LdapGroupsDisplay.tsx";
|
||||||
|
|
||||||
|
@ -22,26 +22,44 @@ export default function UserMenuModal() {
|
||||||
|
|
||||||
const {handler: changeEmailHandler} = useChangeEmail()
|
const {handler: changeEmailHandler} = useChangeEmail()
|
||||||
|
|
||||||
const {logout, apiIsHealthy, userRecord} = usePB()
|
const {logout, apiIsHealthy, user} = usePB()
|
||||||
|
|
||||||
const {showHelp, toggleShowHelp} = useShowHelp()
|
const {showHelp, toggleShowHelp} = useShowHelp()
|
||||||
|
|
||||||
const {showDebug, toggleShowDebug} = useShowDebug()
|
const {showDebug, toggleShowDebug} = useShowDebug()
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<Modal opened={value && !!userRecord} onClose={handler.close} withCloseButton={false} size={"md"}>
|
<Modal opened={value && !!user} onClose={handler.close} withCloseButton={false} size={"md"}>
|
||||||
<div className={classes.stack}>
|
<div className={classes.stack}>
|
||||||
<Title order={3}>Hallo {userRecord?.username}</Title>
|
<Title order={3}>Hallo {user?.username}</Title>
|
||||||
|
|
||||||
<ShowDebug>
|
<ShowDebug>
|
||||||
Datenbank ID: <Code>{userRecord?.id}</Code>
|
Datenbank ID: <Code>{user?.id}</Code>
|
||||||
|
|
||||||
{userRecord?.objectGUID && <>
|
<br/>
|
||||||
|
|
||||||
|
REALM: <Code>{user?.REALM}</Code>
|
||||||
|
|
||||||
|
{user?.objectGUID && <>
|
||||||
<br/>
|
<br/>
|
||||||
GUID: <Code>{userRecord?.objectGUID}</Code>
|
GUID: <Code>{user?.objectGUID}</Code>
|
||||||
</>}
|
</>}
|
||||||
</ShowDebug>
|
</ShowDebug>
|
||||||
|
|
||||||
|
<div className={classes.row}>
|
||||||
|
<ThemeIcon
|
||||||
|
variant={"transparent"}
|
||||||
|
size={"xl"}
|
||||||
|
>
|
||||||
|
<IconUser/>
|
||||||
|
</ThemeIcon>
|
||||||
|
|
||||||
|
|
||||||
|
<Text>
|
||||||
|
{user?.REALM === "LDAP" ? "StuVe IT Account" : "Gast Account"}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className={classes.row}>
|
<div className={classes.row}>
|
||||||
<ThemeIcon
|
<ThemeIcon
|
||||||
variant={"transparent"}
|
variant={"transparent"}
|
||||||
|
@ -51,9 +69,11 @@ export default function UserMenuModal() {
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
|
|
||||||
|
|
||||||
<Text>
|
<Tooltip label={`Dein Email ist ${user?.emailVisibility ? "sichtbar" : "nicht sichtbar"}`}>
|
||||||
{userRecord?.email}
|
<Text>
|
||||||
</Text>
|
{user?.email}
|
||||||
|
</Text>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={classes.row}>
|
<div className={classes.row}>
|
||||||
|
@ -65,10 +85,10 @@ export default function UserMenuModal() {
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
|
|
||||||
<Text>
|
<Text>
|
||||||
{userRecord?.accountExpires ? (
|
{user?.accountExpires ? (
|
||||||
|
|
||||||
new Date(userRecord?.accountExpires).getTime() > Date.now() ? (
|
new Date(user?.accountExpires).getTime() > Date.now() ? (
|
||||||
"Account ist aktiv und läuft am " + new Date(userRecord?.accountExpires).toLocaleDateString() + " ab"
|
"Account ist aktiv und läuft am " + new Date(user?.accountExpires).toLocaleDateString() + " ab"
|
||||||
) : (
|
) : (
|
||||||
"Account ist abgelaufen"
|
"Account ist abgelaufen"
|
||||||
)
|
)
|
||||||
|
@ -103,9 +123,9 @@ export default function UserMenuModal() {
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{userRecord?.memberOf?.length && <>
|
{(user?.memberOf?.length ?? 0) > 0 && <>
|
||||||
<Title order={5}>Deine Gruppen</Title>
|
<Title order={5}>Deine Gruppen</Title>
|
||||||
<LdapGroupsDisplay groups={userRecord?.expand?.memberOf}/>
|
<LdapGroupsDisplay groups={user?.expand?.memberOf}/>
|
||||||
</>}
|
</>}
|
||||||
|
|
||||||
<Divider label={"Einstellungen"}/>
|
<Divider label={"Einstellungen"}/>
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
import {UserModal} from "@/models/AuthTypes.ts";
|
||||||
|
import {Tooltip} from "@mantine/core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the username of a user
|
||||||
|
* If the user has a first and last name, it will return the full name
|
||||||
|
* @param user
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line react-refresh/only-export-components
|
||||||
|
export const getUserName = (user?: UserModal | null) => {
|
||||||
|
if (!user) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.sn && user.givenName) {
|
||||||
|
return `${user.givenName} ${user.sn}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.username
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the username with a tooltip indicating the account type
|
||||||
|
* @param user
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
export const RenderUserName = ({user}: { user?: UserModal | null }) => {
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<Tooltip label={user.REALM === "LDAP" ? "StuVe IT Account" : "Gast Account"} withArrow>
|
||||||
|
<span>
|
||||||
|
{getUserName(user)}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
</>
|
||||||
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
import {Menu} from "@mantine/core";
|
import {Menu} from "@mantine/core";
|
||||||
import {NavLink} from "react-router-dom";
|
import {NavLink} from "react-router-dom";
|
||||||
import {Fragment} from "react";
|
import {Fragment} from "react";
|
||||||
import {IconConfetti, IconHome, IconQrcode} from "@tabler/icons-react";
|
import {IconConfetti, IconHome, IconList, IconQrcode} from "@tabler/icons-react";
|
||||||
|
|
||||||
|
|
||||||
const NavItems = [
|
const NavItems = [
|
||||||
{
|
{
|
||||||
section: "Seiten",
|
section: "StuVe IT",
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
title: "Home",
|
title: "Home",
|
||||||
|
@ -14,12 +14,29 @@ const NavItems = [
|
||||||
description: "Home",
|
description: "Home",
|
||||||
link: "/"
|
link: "/"
|
||||||
},
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: "Events",
|
||||||
|
items: [
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Events",
|
title: "Übersicht",
|
||||||
icon: IconConfetti,
|
icon: IconConfetti,
|
||||||
description: "Administration für StuVe Events.",
|
description: "Übersicht über alle Events.",
|
||||||
link: "/events"
|
link: "/events"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Deine Anmeldungen",
|
||||||
|
icon: IconList,
|
||||||
|
description: "Deine Anmeldungen bei Events.",
|
||||||
|
link: "/events/entries"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: "Tools",
|
||||||
|
items: [
|
||||||
{
|
{
|
||||||
title: "QR Code Generator",
|
title: "QR Code Generator",
|
||||||
icon: IconQrcode,
|
icon: IconQrcode,
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {useLogin, useUserMenu} from "@/components/auth/modals/hooks.ts";
|
||||||
|
|
||||||
export default function NavBar() {
|
export default function NavBar() {
|
||||||
|
|
||||||
const {userRecord} = usePB()
|
const {user} = usePB()
|
||||||
|
|
||||||
const {colorScheme, toggleColorScheme} = useMantineColorScheme()
|
const {colorScheme, toggleColorScheme} = useMantineColorScheme()
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ export default function NavBar() {
|
||||||
}
|
}
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
|
|
||||||
{userRecord ?
|
{user ?
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant={"transparent"}
|
variant={"transparent"}
|
||||||
color={"gray"}
|
color={"gray"}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {useInterval} from "@mantine/hooks";
|
||||||
import {useQuery} from "@tanstack/react-query";
|
import {useQuery} from "@tanstack/react-query";
|
||||||
import {TypedPocketBase} from "@/models";
|
import {TypedPocketBase} from "@/models";
|
||||||
import {PB_BASE_URL, PB_STORAGE_KEY, PB_USER_COLLECTION} from "../../config.ts";
|
import {PB_BASE_URL, PB_STORAGE_KEY, PB_USER_COLLECTION} from "../../config.ts";
|
||||||
import {GuestUserModel, LdapUserModel} from "@/models/AuthTypes.ts";
|
import {UserModal} from "@/models/AuthTypes.ts";
|
||||||
import {Alert, List} from "@mantine/core";
|
import {Alert, List} from "@mantine/core";
|
||||||
import {IconAlertTriangle} from "@tabler/icons-react";
|
import {IconAlertTriangle} from "@tabler/icons-react";
|
||||||
import {showSuccessNotification} from "@/components/util.tsx";
|
import {showSuccessNotification} from "@/components/util.tsx";
|
||||||
|
@ -104,7 +104,7 @@ const PocketData = () => {
|
||||||
}, [pb])
|
}, [pb])
|
||||||
|
|
||||||
const guestLogin = useCallback(async (usernameOrEmail: string, password: string) => {
|
const guestLogin = useCallback(async (usernameOrEmail: string, password: string) => {
|
||||||
await pb.collection("guest_users").authWithPassword(usernameOrEmail, password).then(res => {
|
await pb.collection("users").authWithPassword(usernameOrEmail, password).then(res => {
|
||||||
pb.authStore.clear()
|
pb.authStore.clear()
|
||||||
pb.authStore.save(res.token, res.record)
|
pb.authStore.save(res.token, res.record)
|
||||||
console.log(res.record)
|
console.log(res.record)
|
||||||
|
@ -134,9 +134,7 @@ const PocketData = () => {
|
||||||
ldapLogin,
|
ldapLogin,
|
||||||
guestLogin,
|
guestLogin,
|
||||||
logout,
|
logout,
|
||||||
userRecord: user as LdapUserModel | GuestUserModel | null,
|
user: user as UserModal | null,
|
||||||
guestUser: user && !Object.keys(user).includes("objectGUID") ? user as GuestUserModel : null,
|
|
||||||
ldapUser: user && Object.keys(user).includes("objectGUID") ? user as LdapUserModel : null,
|
|
||||||
pb,
|
pb,
|
||||||
refreshUser,
|
refreshUser,
|
||||||
useSubscription,
|
useSubscription,
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
import {usePB} from "@/lib/pocketbase.tsx";
|
|
||||||
import {useQuery} from "@tanstack/react-query";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This hook returns the user record of the currently logged-in user.
|
|
||||||
*
|
|
||||||
* If the specific LDAP user model or guest user model is needed, use the usePB hook
|
|
||||||
* to get the specific user model.
|
|
||||||
*
|
|
||||||
* @see usePB
|
|
||||||
* @returns The user record of the currently logged-in user.
|
|
||||||
*/
|
|
||||||
export const useUser = () => {
|
|
||||||
const {pb, userRecord} = usePB()
|
|
||||||
|
|
||||||
const query = useQuery({
|
|
||||||
queryKey: ["user", userRecord?.id ?? ""],
|
|
||||||
queryFn: async () => {
|
|
||||||
return await pb.collection("users").getOne(userRecord?.id ?? "", {
|
|
||||||
expand: "memberOf"
|
|
||||||
})
|
|
||||||
},
|
|
||||||
enabled: userRecord !== null
|
|
||||||
})
|
|
||||||
|
|
||||||
return query.data
|
|
||||||
}
|
|
|
@ -1,21 +1,27 @@
|
||||||
import {AuthModel, RecordModel} from "pocketbase";
|
import {RecordModel} from "pocketbase";
|
||||||
|
|
||||||
export type UserModal = {
|
export type UserModal = {
|
||||||
name: string;
|
|
||||||
verified: boolean;
|
|
||||||
realm: string;
|
|
||||||
email: string;
|
|
||||||
} & RecordModel
|
|
||||||
|
|
||||||
export type GuestUserModel = {
|
|
||||||
username: string;
|
username: string;
|
||||||
verified: boolean;
|
verified: boolean;
|
||||||
email: string;
|
email: string;
|
||||||
} & AuthModel & RecordModel
|
emailVisibility: boolean;
|
||||||
|
} & RecordModel & (GuestUser | LDAPUser)
|
||||||
|
|
||||||
|
type GuestUser = {
|
||||||
|
REALM: "GUEST",
|
||||||
|
|
||||||
|
cn: null;
|
||||||
|
dn: null;
|
||||||
|
sn: null;
|
||||||
|
givenName: null;
|
||||||
|
accountExpires: null;
|
||||||
|
objectGUID: null;
|
||||||
|
memberOf: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
type LDAPUser = {
|
||||||
|
REALM: "LDAP",
|
||||||
|
|
||||||
export type LdapUserModel = {
|
|
||||||
username: string;
|
|
||||||
email: string;
|
|
||||||
cn: string;
|
cn: string;
|
||||||
dn: string;
|
dn: string;
|
||||||
sn: string;
|
sn: string;
|
||||||
|
@ -27,7 +33,7 @@ export type LdapUserModel = {
|
||||||
expand: {
|
expand: {
|
||||||
memberOf: LdapGroupModel[]
|
memberOf: LdapGroupModel[]
|
||||||
}
|
}
|
||||||
} & AuthModel & RecordModel
|
}
|
||||||
|
|
||||||
export type LdapGroupModel = {
|
export type LdapGroupModel = {
|
||||||
description: string;
|
description: string;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {GuestUserModel, LdapUserModel} from "./AuthTypes.ts";
|
import {UserModal} from "./AuthTypes.ts";
|
||||||
import {RecordModel} from "pocketbase";
|
import {RecordModel} from "pocketbase";
|
||||||
import {FieldEntries} from "@/components/formUtil/FromInput/types.ts";
|
import {FieldEntries} from "@/components/formUtil/FromInput/types.ts";
|
||||||
import {FormSchema} from "@/components/formUtil/formBuilder/types.ts";
|
import {FormSchema} from "@/components/formUtil/formBuilder/types.ts";
|
||||||
|
@ -19,8 +19,8 @@ export type EventModel = {
|
||||||
defaultEntryQuestionSchema: FormSchema | null;
|
defaultEntryQuestionSchema: FormSchema | null;
|
||||||
defaultEntryStatusSchema: FormSchema | null;
|
defaultEntryStatusSchema: FormSchema | null;
|
||||||
expand?: {
|
expand?: {
|
||||||
eventAdmins: LdapUserModel[] | null | [];
|
eventAdmins: UserModal[] | null;
|
||||||
eventListAdmins: LdapUserModel[] | null | [];
|
eventListAdmins: UserModal[] | null;
|
||||||
}
|
}
|
||||||
} & RecordModel
|
} & RecordModel
|
||||||
|
|
||||||
|
@ -34,6 +34,8 @@ export type EventListModel = {
|
||||||
description: string | null;
|
description: string | null;
|
||||||
open: boolean | null;
|
open: boolean | null;
|
||||||
favourite: boolean | null;
|
favourite: boolean | null;
|
||||||
|
allowOverlappingEntries: boolean | null;
|
||||||
|
onlyStuVeAccounts: boolean | null;
|
||||||
event: string
|
event: string
|
||||||
entryQuestionSchema: FormSchema | null
|
entryQuestionSchema: FormSchema | null
|
||||||
entryStatusSchema: FormSchema | null;
|
entryStatusSchema: FormSchema | null;
|
||||||
|
@ -63,20 +65,17 @@ export type EventListSlotEntryModel = {
|
||||||
entryQuestionData: FieldEntries;
|
entryQuestionData: FieldEntries;
|
||||||
entryStatusData: FieldEntries | null;
|
entryStatusData: FieldEntries | null;
|
||||||
eventListsSlot: string;
|
eventListsSlot: string;
|
||||||
ldapUser: string | null
|
user: string | null
|
||||||
guestUser: string | null
|
|
||||||
expand?: {
|
expand?: {
|
||||||
eventListsSlot: EventListSlotModel;
|
eventListsSlot: EventListSlotModel;
|
||||||
ldapUser: LdapUserModel | null;
|
user: UserModal | null;
|
||||||
guestUser: GuestUserModel | null;
|
|
||||||
}
|
}
|
||||||
} & RecordModel
|
} & RecordModel
|
||||||
|
|
||||||
export type EventListSlotEntriesWithUserModel =
|
export type EventListSlotEntriesWithUserModel =
|
||||||
EventListSlotEntryModel
|
EventListSlotEntryModel
|
||||||
& {
|
& {
|
||||||
userId: string;
|
user: string;
|
||||||
userName: string;
|
|
||||||
listName: string,
|
listName: string,
|
||||||
slotStartDate: string,
|
slotStartDate: string,
|
||||||
slotEndDate: string,
|
slotEndDate: string,
|
||||||
|
@ -85,6 +84,11 @@ export type EventListSlotEntriesWithUserModel =
|
||||||
listDescription: string | null,
|
listDescription: string | null,
|
||||||
event: string,
|
event: string,
|
||||||
eventName: string,
|
eventName: string,
|
||||||
|
expand?: {
|
||||||
|
event: EventModel;
|
||||||
|
eventList: EventListModel;
|
||||||
|
user: UserModal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
& Pick<EventModel, "defaultEntryQuestionSchema" | "defaultEntryStatusSchema">
|
& Pick<EventModel, "defaultEntryQuestionSchema" | "defaultEntryStatusSchema">
|
||||||
& Pick<EventListModel, "entryQuestionSchema" | "entryStatusSchema">
|
& Pick<EventListModel, "entryQuestionSchema" | "entryStatusSchema">
|
|
@ -1,5 +1,5 @@
|
||||||
import PocketBase, {RecordModel, RecordService} from "pocketbase";
|
import PocketBase, {RecordModel, RecordService} from "pocketbase";
|
||||||
import {GuestUserModel, LdapGroupModel, LdapSyncLogModel, LdapUserModel, UserModal} from "./AuthTypes.ts";
|
import {LdapGroupModel, LdapSyncLogModel, UserModal} from "./AuthTypes.ts";
|
||||||
import {
|
import {
|
||||||
EventListModel,
|
EventListModel,
|
||||||
EventListSlotEntriesWithUserModel,
|
EventListSlotEntriesWithUserModel,
|
||||||
|
@ -32,10 +32,6 @@ export interface TypedPocketBase extends PocketBase {
|
||||||
|
|
||||||
collection(idOrName: 'users'): RecordService<UserModal>
|
collection(idOrName: 'users'): RecordService<UserModal>
|
||||||
|
|
||||||
collection(idOrName: 'guest_users'): RecordService<GuestUserModel>
|
|
||||||
|
|
||||||
collection(idOrName: 'ldap_users'): RecordService<LdapUserModel>
|
|
||||||
|
|
||||||
collection(idOrName: 'ldap_groups'): RecordService<LdapGroupModel>
|
collection(idOrName: 'ldap_groups'): RecordService<LdapGroupModel>
|
||||||
|
|
||||||
collection(idOrName: 'ldap_sync_logs'): RecordService<LdapSyncLogModel>
|
collection(idOrName: 'ldap_sync_logs'): RecordService<LdapSyncLogModel>
|
||||||
|
|
|
@ -4,9 +4,9 @@ import {DateTimePicker} from "@mantine/dates";
|
||||||
import {IconCheck, IconInfoCircle, IconX} from "@tabler/icons-react";
|
import {IconCheck, IconInfoCircle, IconX} from "@tabler/icons-react";
|
||||||
import {useMutation} from "@tanstack/react-query";
|
import {useMutation} from "@tanstack/react-query";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {LdapUserModel} from "@/models/AuthTypes.ts";
|
import {UserModal} from "@/models/AuthTypes.ts";
|
||||||
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
||||||
import LdapUserInput from "@/components/auth/LdapUserInput.tsx";
|
import UserInput from "@/components/auth/UserInput.tsx";
|
||||||
import {EventModel} from "@/models/EventTypes.ts";
|
import {EventModel} from "@/models/EventTypes.ts";
|
||||||
import ShowHelp from "@/components/ShowHelp.tsx";
|
import ShowHelp from "@/components/ShowHelp.tsx";
|
||||||
import {useSettings} from "@/lib/settings.ts";
|
import {useSettings} from "@/lib/settings.ts";
|
||||||
|
@ -23,7 +23,7 @@ export default function CreateEvent({onSuccess, onAbort}: {
|
||||||
onSuccess: (event: EventModel) => void,
|
onSuccess: (event: EventModel) => void,
|
||||||
onAbort?: () => void
|
onAbort?: () => void
|
||||||
}) {
|
}) {
|
||||||
const {ldapUser, pb} = usePB()
|
const {user, pb} = usePB()
|
||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
|
|
||||||
const stuveQuestions = JSON.parse(settings?.stuveEventQuestions?.value ?? `{ "fields":[] }`) as FormSchema
|
const stuveQuestions = JSON.parse(settings?.stuveEventQuestions?.value ?? `{ "fields":[] }`) as FormSchema
|
||||||
|
@ -33,7 +33,7 @@ export default function CreateEvent({onSuccess, onAbort}: {
|
||||||
name: "",
|
name: "",
|
||||||
startDate: null,
|
startDate: null,
|
||||||
endDate: null,
|
endDate: null,
|
||||||
eventAdmins: [ldapUser] as LdapUserModel[],
|
eventAdmins: [user] as UserModal[],
|
||||||
isStuveEvent: true,
|
isStuveEvent: true,
|
||||||
},
|
},
|
||||||
validate: {
|
validate: {
|
||||||
|
@ -46,7 +46,7 @@ export default function CreateEvent({onSuccess, onAbort}: {
|
||||||
|
|
||||||
const createEventMutation = useMutation({
|
const createEventMutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
if (!ldapUser) {
|
if (!user || user.REALM === "LDAP") {
|
||||||
throw new Error("Nur mit StuVe IT Account eingeloggte Personen können Events erstellen")
|
throw new Error("Nur mit StuVe IT Account eingeloggte Personen können Events erstellen")
|
||||||
}
|
}
|
||||||
return await pb.collection("events").create({
|
return await pb.collection("events").create({
|
||||||
|
@ -93,7 +93,7 @@ export default function CreateEvent({onSuccess, onAbort}: {
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
|
|
||||||
<Grid.Col span={{base: 12, sm: 6}}>
|
<Grid.Col span={{base: 12, sm: 6}}>
|
||||||
<LdapUserInput
|
<UserInput
|
||||||
required
|
required
|
||||||
label={"Event-Admins"}
|
label={"Event-Admins"}
|
||||||
description={"Die Event-Admins können das Event bearbeiten."}
|
description={"Die Event-Admins können das Event bearbeiten."}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {IconConfetti, IconPlus} from "@tabler/icons-react";
|
import {IconConfetti, IconPlus, IconUser} from "@tabler/icons-react";
|
||||||
import {EventCalendar} from "./EventCalendar.tsx";
|
import {EventCalendar} from "./EventCalendar.tsx";
|
||||||
import {EventList} from "./EventList.tsx";
|
import {EventList} from "./EventList.tsx";
|
||||||
import {Anchor, Breadcrumbs, Button, Collapse} from "@mantine/core";
|
import {Anchor, Breadcrumbs, Button, Collapse} from "@mantine/core";
|
||||||
|
@ -10,7 +10,7 @@ import {Link, useNavigate} from "react-router-dom";
|
||||||
|
|
||||||
export default function EventOverview() {
|
export default function EventOverview() {
|
||||||
|
|
||||||
const {ldapUser} = usePB()
|
const {user} = usePB()
|
||||||
const [showCreateEvent, showCreateEventHandler] = useDisclosure(false)
|
const [showCreateEvent, showCreateEventHandler] = useDisclosure(false)
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ export default function EventOverview() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
ldapUser && <>
|
(user && user.REALM === "LDAP") && <>
|
||||||
<Collapse in={showCreateEvent} className={"section"}>
|
<Collapse in={showCreateEvent} className={"section"}>
|
||||||
<CreateEvent
|
<CreateEvent
|
||||||
onAbort={showCreateEventHandler.close}
|
onAbort={showCreateEventHandler.close}
|
||||||
|
@ -39,6 +39,15 @@ export default function EventOverview() {
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
|
||||||
{!showCreateEvent && <div className={"section-transparent group"} style={{gap: 0}}>
|
{!showCreateEvent && <div className={"section-transparent group"} style={{gap: 0}}>
|
||||||
|
<Button
|
||||||
|
leftSection={<IconUser/>}
|
||||||
|
variant={"transparent"}
|
||||||
|
color={"green"}
|
||||||
|
component={Link}
|
||||||
|
to={"/events/entries"}
|
||||||
|
>
|
||||||
|
Deine Anmeldungen bei Events
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconPlus/>}
|
leftSection={<IconPlus/>}
|
||||||
variant={"transparent"}
|
variant={"transparent"}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {EventModel} from "@/models/EventTypes.ts";
|
||||||
import classes from "../EditEventRouter.module.css";
|
import classes from "../EditEventRouter.module.css";
|
||||||
import {IconAdjustments, IconCalendar, IconHourglass, IconList, IconMap, IconSparkles} from "@tabler/icons-react";
|
import {IconAdjustments, IconCalendar, IconHourglass, IconList, IconMap, IconSparkles} from "@tabler/icons-react";
|
||||||
import {areDatesSame, humanDeltaFromNow, pprintDate} from "@/lib/datetime.ts";
|
import {areDatesSame, humanDeltaFromNow, pprintDate} from "@/lib/datetime.ts";
|
||||||
import LdapUsersDisplay from "@/components/auth/LdapUsersDisplay.tsx";
|
import UsersDisplay from "@/components/auth/UsersDisplay.tsx";
|
||||||
import {Text, ThemeIcon, Title} from "@mantine/core";
|
import {Text, ThemeIcon, Title} from "@mantine/core";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -86,7 +86,7 @@ export default function EventData({event, hideHeader}: { event: EventModel, hide
|
||||||
<IconAdjustments size={16}/>
|
<IconAdjustments size={16}/>
|
||||||
Event Admins
|
Event Admins
|
||||||
</div>
|
</div>
|
||||||
<LdapUsersDisplay users={event.expand.eventAdmins}/>
|
<UsersDisplay users={event.expand.eventAdmins}/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ export default function EventData({event, hideHeader}: { event: EventModel, hide
|
||||||
<IconList size={16}/>
|
<IconList size={16}/>
|
||||||
Listen Admins
|
Listen Admins
|
||||||
</div>
|
</div>
|
||||||
<LdapUsersDisplay users={event.expand.eventListAdmins}/>
|
<UsersDisplay users={event.expand.eventListAdmins}/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import {EventModel} from "@/models/EventTypes.ts";
|
import {EventModel} from "@/models/EventTypes.ts";
|
||||||
import {useForm} from "@mantine/form";
|
import {useForm} from "@mantine/form";
|
||||||
import {LdapUserModel} from "@/models/AuthTypes.ts";
|
import {UserModal} from "@/models/AuthTypes.ts";
|
||||||
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
||||||
import {Button, Group, Title} from "@mantine/core";
|
import {Button, Group, Title} from "@mantine/core";
|
||||||
import LdapUserInput from "@/components/auth/LdapUserInput.tsx";
|
import UserInput from "@/components/auth/UserInput.tsx";
|
||||||
import {useMutation} from "@tanstack/react-query";
|
import {useMutation} from "@tanstack/react-query";
|
||||||
import {queryClient} from "@/main.tsx";
|
import {queryClient} from "@/main.tsx";
|
||||||
import {showSuccessNotification} from "@/components/util.tsx";
|
import {showSuccessNotification} from "@/components/util.tsx";
|
||||||
|
@ -15,12 +15,12 @@ import ShowHelp from "@/components/ShowHelp.tsx";
|
||||||
*/
|
*/
|
||||||
export default function EditEventMembers({event}: { event: EventModel }) {
|
export default function EditEventMembers({event}: { event: EventModel }) {
|
||||||
|
|
||||||
const {ldapUser, pb} = usePB()
|
const {user, pb} = usePB()
|
||||||
|
|
||||||
const formValues = useForm({
|
const formValues = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
eventAdmins: event?.expand?.eventAdmins ?? [ldapUser] as LdapUserModel[],
|
eventAdmins: event?.expand?.eventAdmins ?? [user] as UserModal[],
|
||||||
eventListAdmins: event?.expand?.eventListAdmins ?? [] as LdapUserModel[],
|
eventListAdmins: event?.expand?.eventListAdmins ?? [] as UserModal[],
|
||||||
},
|
},
|
||||||
validate: {
|
validate: {
|
||||||
eventAdmins: (value) => value.length > 0 ? null : "Es muss mindestens ein Admin ausgewählt werden.",
|
eventAdmins: (value) => value.length > 0 ? null : "Es muss mindestens ein Admin ausgewählt werden.",
|
||||||
|
@ -34,7 +34,7 @@ export default function EditEventMembers({event}: { event: EventModel }) {
|
||||||
const editMutation = useMutation({
|
const editMutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
return await pb.collection("events").update(event.id, {
|
return await pb.collection("events").update(event.id, {
|
||||||
eventAdmins: [...formValues.values.eventAdmins.map((member) => member.id), ldapUser?.id],
|
eventAdmins: [...formValues.values.eventAdmins.map((member) => member.id), user?.id],
|
||||||
eventListAdmins: formValues.values.eventListAdmins.map((member) => member.id)
|
eventListAdmins: formValues.values.eventListAdmins.map((member) => member.id)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -66,7 +66,7 @@ export default function EditEventMembers({event}: { event: EventModel }) {
|
||||||
<PocketBaseErrorAlert error={editMutation.error}/>
|
<PocketBaseErrorAlert error={editMutation.error}/>
|
||||||
|
|
||||||
|
|
||||||
<LdapUserInput
|
<UserInput
|
||||||
required
|
required
|
||||||
label={"Event Admins"}
|
label={"Event Admins"}
|
||||||
description={"Die Event-Admins können das ganze Event bearbeiten."}
|
description={"Die Event-Admins können das ganze Event bearbeiten."}
|
||||||
|
@ -75,7 +75,7 @@ export default function EditEventMembers({event}: { event: EventModel }) {
|
||||||
setSelectedRecords={(records) => formValues.setFieldValue("eventAdmins", records)}
|
setSelectedRecords={(records) => formValues.setFieldValue("eventAdmins", records)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LdapUserInput
|
<UserInput
|
||||||
label={"Listen Admins"}
|
label={"Listen Admins"}
|
||||||
description={"Die Listen Admins können Listen bearbeiten und verwalten."}
|
description={"Die Listen Admins können Listen bearbeiten und verwalten."}
|
||||||
error={formValues.errors.eventListAdmins}
|
error={formValues.errors.eventListAdmins}
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
import {useQuery} from "@tanstack/react-query";
|
import {useQuery} from "@tanstack/react-query";
|
||||||
import {Alert, Breadcrumbs, Button, Group, LoadingOverlay, Title} from "@mantine/core";
|
import {Alert, Breadcrumbs, Button, Group, LoadingOverlay, Title} from "@mantine/core";
|
||||||
import {IconCheckupList, IconClockCog, IconForms, IconLock, IconLockOpen, IconSettings} from "@tabler/icons-react";
|
import {
|
||||||
|
IconCheckupList,
|
||||||
|
IconClockCog,
|
||||||
|
IconForms,
|
||||||
|
IconLock,
|
||||||
|
IconLockOpen,
|
||||||
|
IconSettings,
|
||||||
|
IconUserCog
|
||||||
|
} from "@tabler/icons-react";
|
||||||
import {Link, Navigate, NavLink, Route, Routes, useParams} from "react-router-dom";
|
import {Link, Navigate, NavLink, Route, Routes, useParams} from "react-router-dom";
|
||||||
import InnerHtml from "@/components/InnerHtml";
|
import InnerHtml from "@/components/InnerHtml";
|
||||||
import {EventModel} from "@/models/EventTypes.ts";
|
import {EventModel} from "@/models/EventTypes.ts";
|
||||||
|
@ -11,6 +19,7 @@ import EventListEntryQuestionSettings
|
||||||
from "@/pages/events/e/:eventId/EventLists/:listId/EventListSettings/EventListEntryQuestionSettings.tsx";
|
from "@/pages/events/e/:eventId/EventLists/:listId/EventListSettings/EventListEntryQuestionSettings.tsx";
|
||||||
import EventListEntryStatusSettings
|
import EventListEntryStatusSettings
|
||||||
from "@/pages/events/e/:eventId/EventLists/:listId/EventListSettings/EventListEntryStatusSettings.tsx";
|
from "@/pages/events/e/:eventId/EventLists/:listId/EventListSettings/EventListEntryStatusSettings.tsx";
|
||||||
|
import TextWithIcon from "@/components/layout/TextWithIcon";
|
||||||
|
|
||||||
|
|
||||||
export default function EventListRouter({event}: { event: EventModel }) {
|
export default function EventListRouter({event}: { event: EventModel }) {
|
||||||
|
@ -48,10 +57,19 @@ export default function EventListRouter({event}: { event: EventModel }) {
|
||||||
</Link>
|
</Link>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
|
|
||||||
<Alert color={list.open ? "green" : "red"}
|
<Alert color={list.open ? "green" : "red"}>
|
||||||
icon={list.open ? <IconLockOpen/> : <IconLock/>}
|
<TextWithIcon icon={list.open ? <IconLockOpen size={16}/> : <IconLock size={16}/>}>
|
||||||
>
|
Liste ist für Anmeldungen <b>{list.open ? "geöffnet" : "geschlossen"}</b>
|
||||||
{list.open ? "Liste ist für Anmeldungen geöffnet" : "Liste ist für Anmeldungen geschlossen"}
|
</TextWithIcon>
|
||||||
|
<br/>
|
||||||
|
<TextWithIcon icon={<IconUserCog size={16}/>}>
|
||||||
|
Anmeldung für Personen mit {list.onlyStuVeAccounts ?
|
||||||
|
<><b>StuVe</b> Account</> : <>StuVe <b>und</b> Gast Account</>}
|
||||||
|
</TextWithIcon>
|
||||||
|
<br/>
|
||||||
|
<TextWithIcon icon={<IconClockCog size={16}/>}>
|
||||||
|
Überlappende Einträge sind {!list.allowOverlappingEntries && <b>nicht</b>} erlaubt
|
||||||
|
</TextWithIcon>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
<div className={"section"}>
|
<div className={"section"}>
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import {EventListModel, EventModel} from "@/models/EventTypes.ts";
|
import {EventListModel, EventModel} from "@/models/EventTypes.ts";
|
||||||
import {useForm} from "@mantine/form";
|
import {useForm} from "@mantine/form";
|
||||||
import {Button, Group, Switch, TextInput, Title} from "@mantine/core";
|
import {ActionIcon, Button, Group, TextInput, Title, Tooltip} from "@mantine/core";
|
||||||
import TextEditor from "@/components/input/Editor";
|
import TextEditor from "@/components/input/Editor";
|
||||||
import {useMutation} from "@tanstack/react-query";
|
import {useMutation} from "@tanstack/react-query";
|
||||||
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
||||||
import {queryClient} from "@/main.tsx";
|
import {queryClient} from "@/main.tsx";
|
||||||
import {showSuccessNotification} from "@/components/util.tsx";
|
import {showSuccessNotification} from "@/components/util.tsx";
|
||||||
import {IconLock, IconLockOpen, IconStarFilled, IconStarOff, IconTrash} from "@tabler/icons-react";
|
import {IconStarFilled, IconStarOff, IconTrash} from "@tabler/icons-react";
|
||||||
import {useNavigate} from "react-router-dom";
|
import {useNavigate} from "react-router-dom";
|
||||||
import {useConfirmModal} from "@/components/ConfirmModal.tsx";
|
import {useConfirmModal} from "@/components/ConfirmModal.tsx";
|
||||||
|
import {CheckboxCard} from "@/components/input/CheckboxCard";
|
||||||
|
|
||||||
export default function EventListSettings({list, event}: { list: EventListModel, event: EventModel }) {
|
export default function EventListSettings({list, event}: { list: EventListModel, event: EventModel }) {
|
||||||
const {pb} = usePB()
|
const {pb} = usePB()
|
||||||
|
@ -18,6 +19,8 @@ export default function EventListSettings({list, event}: { list: EventListModel,
|
||||||
initialValues: {
|
initialValues: {
|
||||||
name: list.name,
|
name: list.name,
|
||||||
open: list.open,
|
open: list.open,
|
||||||
|
allowOverlappingEntries: list.allowOverlappingEntries,
|
||||||
|
onlyStuVeAccounts: list.onlyStuVeAccounts,
|
||||||
favorite: list.favorite,
|
favorite: list.favorite,
|
||||||
description: list.description || ""
|
description: list.description || ""
|
||||||
}
|
}
|
||||||
|
@ -26,7 +29,7 @@ export default function EventListSettings({list, event}: { list: EventListModel,
|
||||||
const {ConfirmModal, toggleConfirmModal} = useConfirmModal({
|
const {ConfirmModal, toggleConfirmModal} = useConfirmModal({
|
||||||
onConfirm: () => deleteMutation.mutate(),
|
onConfirm: () => deleteMutation.mutate(),
|
||||||
description: "Bist du sicher, dass du diese Liste löschen möchtest? " +
|
description: "Bist du sicher, dass du diese Liste löschen möchtest? " +
|
||||||
"Alle Einträge werden ebenfalls gelöscht. " +
|
"Alle Anmeldungen werden ebenfalls gelöscht. " +
|
||||||
"Diese Aktion kann nicht rückgängig gemacht werden.",
|
"Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -68,23 +71,38 @@ export default function EventListSettings({list, event}: { list: EventListModel,
|
||||||
label={"Name"}
|
label={"Name"}
|
||||||
placeholder={"Name der Liste"}
|
placeholder={"Name der Liste"}
|
||||||
required
|
required
|
||||||
|
rightSection={<Tooltip label={"Liste im favoriten Menu anzeigen"}>
|
||||||
|
<ActionIcon
|
||||||
|
color={"blue"}
|
||||||
|
onClick={() => formValues.setFieldValue("favorite", !formValues.values.favorite)}
|
||||||
|
variant={"transparent"}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
formValues.values.favorite ? <IconStarFilled/> : <IconStarOff/>
|
||||||
|
}
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>}
|
||||||
{...formValues.getInputProps("name")}
|
{...formValues.getInputProps("name")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Switch
|
<CheckboxCard
|
||||||
onLabel={<IconStarFilled size={16}/>}
|
label={"Anmeldungen erlauben"}
|
||||||
offLabel={<IconStarOff size={16}/>}
|
description={"Ob sich Personen für Zeitslots dieser Liste anmelden können"}
|
||||||
label={"Liste favorisiert"}
|
{...formValues.getInputProps("open", {type: "checkbox"})}
|
||||||
color={"green"}
|
|
||||||
{...formValues.getInputProps("favorite", {type: "checkbox"})}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Switch
|
<CheckboxCard
|
||||||
onLabel={<IconLockOpen size={16}/>}
|
label={"Nur StuVe Accounts"}
|
||||||
offLabel={<IconLock size={16}/>}
|
description={"Nur Personen mit StuVe IT Account können sich für diese Liste Anmelden"}
|
||||||
label={"Anmeldungen erlauben"}
|
{...formValues.getInputProps("onlyStuVeAccounts", {type: "checkbox"})}
|
||||||
color={"green"}
|
/>
|
||||||
{...formValues.getInputProps("open", {type: "checkbox"})}
|
|
||||||
|
<CheckboxCard
|
||||||
|
label={"Überlappende Anmeldungen erlauben"}
|
||||||
|
description={" Wenn diese Option aktiviert ist, kann sich eine Person in überlappende Zeitslots anmelden. " +
|
||||||
|
"Dabei werden alle Zeitslots von diesem Event beachtet. " +
|
||||||
|
"Bei dem Verschieden von Anmeldungen wirds diese Regel nicht beachtet!"}
|
||||||
|
{...formValues.getInputProps("allowOverlappingEntries", {type: "checkbox"})}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextEditor
|
<TextEditor
|
||||||
|
@ -95,6 +113,7 @@ export default function EventListSettings({list, event}: { list: EventListModel,
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Group>
|
<Group>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant={"outline"}
|
variant={"outline"}
|
||||||
color={"red"}
|
color={"red"}
|
||||||
|
|
|
@ -26,6 +26,7 @@ export const EventListSlotEntriesTable = ({slot, list, event, refetch, visible}:
|
||||||
|
|
||||||
const res = await pb.collection("eventListSlotEntriesWithUser").getList(page, 50, {
|
const res = await pb.collection("eventListSlotEntriesWithUser").getList(page, 50, {
|
||||||
filter: `eventListsSlot='${slot.id}'`,
|
filter: `eventListsSlot='${slot.id}'`,
|
||||||
|
expand: "user"
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -43,7 +44,7 @@ export const EventListSlotEntriesTable = ({slot, list, event, refetch, visible}:
|
||||||
if (query.isLoading) {
|
if (query.isLoading) {
|
||||||
return <Stack align={"center"} gap={"xs"}>
|
return <Stack align={"center"} gap={"xs"}>
|
||||||
<Loader size={"sm"}/>
|
<Loader size={"sm"}/>
|
||||||
<Text size={"xs"} c={"dimmed"}>Einträge werden geladen</Text>
|
<Text size={"xs"} c={"dimmed"}>Anmeldungen werden geladen</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +53,7 @@ export const EventListSlotEntriesTable = ({slot, list, event, refetch, visible}:
|
||||||
<ThemeIcon variant={"transparent"} color={"gray"} size={"md"}>
|
<ThemeIcon variant={"transparent"} color={"gray"} size={"md"}>
|
||||||
<IconDatabaseOff/>
|
<IconDatabaseOff/>
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
<Text size={"xs"} c={"dimmed"}>Keine Einträge vorhanden</Text>
|
<Text size={"xs"} c={"dimmed"}>Keine Anmeldungen vorhanden</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +78,7 @@ export const EventListSlotEntriesTable = ({slot, list, event, refetch, visible}:
|
||||||
|
|
||||||
<div className={classes.bottomRow}>
|
<div className={classes.bottomRow}>
|
||||||
<Text size={"xs"} c={"dimmed"}>
|
<Text size={"xs"} c={"dimmed"}>
|
||||||
{query.data?.totalItems} Einträge
|
{query.data?.totalItems} Anmeldungen
|
||||||
</Text>
|
</Text>
|
||||||
<Pagination size={"xs"} value={page} onChange={setPage} total={query.data?.totalPages ?? 1}/>
|
<Pagination size={"xs"} value={page} onChange={setPage} total={query.data?.totalPages ?? 1}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {IconCheckupList, IconForms, IconUserMinus, IconUserPlus} from "@tabler/i
|
||||||
import {renderEntries} from "@/components/formUtil/formTable";
|
import {renderEntries} from "@/components/formUtil/formTable";
|
||||||
import RenderCell from "@/components/formUtil/formTable/RenderCell.tsx";
|
import RenderCell from "@/components/formUtil/formTable/RenderCell.tsx";
|
||||||
import EditSlotEntryMenu from "@/pages/events/e/:eventId/EventLists/components/EditSlotEntryMenu.tsx";
|
import EditSlotEntryMenu from "@/pages/events/e/:eventId/EventLists/components/EditSlotEntryMenu.tsx";
|
||||||
|
import {RenderUserName} from "@/components/auth/modals/util.tsx";
|
||||||
|
|
||||||
|
|
||||||
export const EventListSlotEntryDetails = ({entry}: {
|
export const EventListSlotEntryDetails = ({entry}: {
|
||||||
|
@ -77,9 +78,7 @@ export const EventListSlotEntryRow = ({entry, refetch}: {
|
||||||
event: EventModel,
|
event: EventModel,
|
||||||
refetch: () => void
|
refetch: () => void
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
const [expanded, expandedHandler] = useDisclosure(false)
|
const [expanded, expandedHandler] = useDisclosure(false)
|
||||||
|
|
||||||
return <div className={classes.entryContainer} aria-expanded={expanded}>
|
return <div className={classes.entryContainer} aria-expanded={expanded}>
|
||||||
<div className={classes.entryRow}>
|
<div className={classes.entryRow}>
|
||||||
<Tooltip label={"Details anzeigen"} withArrow>
|
<Tooltip label={"Details anzeigen"} withArrow>
|
||||||
|
@ -88,7 +87,7 @@ export const EventListSlotEntryRow = ({entry, refetch}: {
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<div>{`${entry.userName}`}</div>
|
<div><RenderUserName user={entry.expand?.user}/></div>
|
||||||
|
|
||||||
<Group gap={4} justify="right" wrap="nowrap">
|
<Group gap={4} justify="right" wrap="nowrap">
|
||||||
<EditSlotEntryMenu entry={entry} refetch={refetch}/>
|
<EditSlotEntryMenu entry={entry} refetch={refetch}/>
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
import {useDisclosure} from "@mantine/hooks";
|
import {useDisclosure} from "@mantine/hooks";
|
||||||
import {RenderDateRange} from "@/pages/events/e/:eventId/EventLists/components/RenderDateRange.tsx";
|
import {RenderDateRange} from "@/pages/events/e/:eventId/EventLists/components/RenderDateRange.tsx";
|
||||||
import EditSlotEntryMenu from "@/pages/events/e/:eventId/EventLists/components/EditSlotEntryMenu.tsx";
|
import EditSlotEntryMenu from "@/pages/events/e/:eventId/EventLists/components/EditSlotEntryMenu.tsx";
|
||||||
|
import {RenderUserName} from "@/components/auth/modals/util.tsx";
|
||||||
|
|
||||||
export default function EventListSearchResult({entry, refetch}: {
|
export default function EventListSearchResult({entry, refetch}: {
|
||||||
entry: EventListSlotEntriesWithUserModel,
|
entry: EventListSlotEntriesWithUserModel,
|
||||||
|
@ -32,7 +33,7 @@ export default function EventListSearchResult({entry, refetch}: {
|
||||||
<IconUser/>
|
<IconUser/>
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
}>
|
}>
|
||||||
{entry.userName}
|
<RenderUserName user={entry.expand?.user}/>
|
||||||
</TextWithIcon>
|
</TextWithIcon>
|
||||||
|
|
||||||
<TextWithIcon icon={
|
<TextWithIcon icon={
|
||||||
|
|
|
@ -41,7 +41,7 @@ export default function ListSearch({event}: { event: EventModel }) {
|
||||||
const filter: string[] = [`event='${event.id}'`]
|
const filter: string[] = [`event='${event.id}'`]
|
||||||
|
|
||||||
if (debouncedSearchQueryString) {
|
if (debouncedSearchQueryString) {
|
||||||
filter.push(`userName ~ '${debouncedSearchQueryString}'`)
|
filter.push(`user.username ~ '${debouncedSearchQueryString.trim().replace(" ", ".")}'`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedLists.length > 0) {
|
if (selectedLists.length > 0) {
|
||||||
|
@ -49,7 +49,8 @@ export default function ListSearch({event}: { event: EventModel }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return await pb.collection("eventListSlotEntriesWithUser").getList(1, 50, {
|
return await pb.collection("eventListSlotEntriesWithUser").getList(1, 50, {
|
||||||
filter: filter.join(" && ")
|
filter: filter.join(" && "),
|
||||||
|
expand: "user"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -61,7 +62,7 @@ export default function ListSearch({event}: { event: EventModel }) {
|
||||||
placeholder={"Nach Namen suchen ..."}
|
placeholder={"Nach Namen suchen ..."}
|
||||||
leftSection={<IconUserSearch/>}
|
leftSection={<IconUserSearch/>}
|
||||||
rightSection={searchQuery.isLoading ? <Loader size={"xs"}/> : (
|
rightSection={searchQuery.isLoading ? <Loader size={"xs"}/> : (
|
||||||
<Tooltip label={"Einträge Filtern"} withArrow>
|
<Tooltip label={"Anmeldungen Filtern"} withArrow>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={showFilterHandler.toggle}
|
onClick={showFilterHandler.toggle}
|
||||||
variant={"transparent"}
|
variant={"transparent"}
|
||||||
|
@ -75,7 +76,7 @@ export default function ListSearch({event}: { event: EventModel }) {
|
||||||
searchQuery
|
searchQuery
|
||||||
.data
|
.data
|
||||||
?.items
|
?.items
|
||||||
.map(e => e.userName)
|
.map(e => e.id)
|
||||||
.filter(onlyUnique) ?? []
|
.filter(onlyUnique) ?? []
|
||||||
}
|
}
|
||||||
value={searchQueryString} onChange={setSearchQueryString}
|
value={searchQueryString} onChange={setSearchQueryString}
|
||||||
|
@ -86,17 +87,17 @@ export default function ListSearch({event}: { event: EventModel }) {
|
||||||
event={event}
|
event={event}
|
||||||
selectedRecords={selectedLists}
|
selectedRecords={selectedLists}
|
||||||
setSelectedRecords={setSelectedLists}
|
setSelectedRecords={setSelectedLists}
|
||||||
placeholder={"Einträge nur für bestimmte Listen anzeigen"}
|
placeholder={"Anmeldungen nur für bestimmte Listen anzeigen"}
|
||||||
/>
|
/>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
|
||||||
<Group justify={"space-between"}>
|
<Group justify={"space-between"}>
|
||||||
<Text c={"dimmed"} size={"xs"}>
|
<Text c={"dimmed"} size={"xs"}>
|
||||||
{searchQueryString ? `Suche nach Person '${searchQueryString}'` : "Alle Einträge für dieses Event"}
|
{searchQueryString ? `Suche nach Person '${searchQueryString}'` : "Alle Anmeldungen für dieses Event"}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text c={"dimmed"} size={"xs"}>
|
<Text c={"dimmed"} size={"xs"}>
|
||||||
{searchQuery.data?.totalItems ?? 0} {searchQueryString ? "Ergebnisse" : "Einträge"}
|
{searchQuery.data?.totalItems ?? 0} {searchQueryString ? "Ergebnisse" : "Anmeldungen"}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
} from "@/pages/events/e/:eventId/EventLists/components/UpdateEventListSlotEntryFormModal.tsx";
|
} from "@/pages/events/e/:eventId/EventLists/components/UpdateEventListSlotEntryFormModal.tsx";
|
||||||
import {ActionIcon, Button, Menu} from "@mantine/core";
|
import {ActionIcon, Button, Menu} from "@mantine/core";
|
||||||
import {IconArrowsMove, IconCheckupList, IconForms, IconSettings, IconTrash} from "@tabler/icons-react";
|
import {IconArrowsMove, IconCheckupList, IconForms, IconSettings, IconTrash} from "@tabler/icons-react";
|
||||||
|
import {getUserName} from "@/components/auth/modals/util.tsx";
|
||||||
|
|
||||||
export default function EditSlotEntryMenu({entry, refetch}: {
|
export default function EditSlotEntryMenu({entry, refetch}: {
|
||||||
refetch: () => void,
|
refetch: () => void,
|
||||||
|
@ -41,7 +42,7 @@ export default function EditSlotEntryMenu({entry, refetch}: {
|
||||||
|
|
||||||
const {ConfirmModal, toggleConfirmModal} = useConfirmModal({
|
const {ConfirmModal, toggleConfirmModal} = useConfirmModal({
|
||||||
title: 'Eintrag löschen',
|
title: 'Eintrag löschen',
|
||||||
description: `Möchtest du den Eintrag von ${entry.userName} wirklich löschen?`,
|
description: `Möchtest du den Eintrag von ${getUserName(entry.expand?.user)} wirklich löschen?`,
|
||||||
onConfirm: () => deleteEntryMutation.mutate()
|
onConfirm: () => deleteEntryMutation.mutate()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {showSuccessNotification} from "@/components/util.tsx";
|
||||||
import {Alert, Button, Group, Modal, Select} from "@mantine/core";
|
import {Alert, Button, Group, Modal, Select} from "@mantine/core";
|
||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import {pprintDateTime} from "@/lib/datetime.ts";
|
import {pprintDateTime} from "@/lib/datetime.ts";
|
||||||
|
import {getUserName, RenderUserName} from "@/components/auth/modals/util.tsx";
|
||||||
|
|
||||||
export const MoveEventListSlotEntryModal = ({opened, close, refetch, entry}: {
|
export const MoveEventListSlotEntryModal = ({opened, close, refetch, entry}: {
|
||||||
opened: boolean,
|
opened: boolean,
|
||||||
|
@ -30,7 +31,7 @@ export const MoveEventListSlotEntryModal = ({opened, close, refetch, entry}: {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
showSuccessNotification(`Eintrag von ${entry.userName} erfolgreich verschoben`)
|
showSuccessNotification(`Eintrag von ${getUserName(entry.expand?.user)} erfolgreich verschoben`)
|
||||||
slotsQuery.refetch()
|
slotsQuery.refetch()
|
||||||
listsQuery.refetch()
|
listsQuery.refetch()
|
||||||
refetch()
|
refetch()
|
||||||
|
@ -66,7 +67,7 @@ export const MoveEventListSlotEntryModal = ({opened, close, refetch, entry}: {
|
||||||
size={"lg"}
|
size={"lg"}
|
||||||
opened={opened}
|
opened={opened}
|
||||||
onClose={close}
|
onClose={close}
|
||||||
title={<> Eintrag von <b>{entry.userName}</b> verschieben </>}
|
title={<> Eintrag von <b><RenderUserName user={entry.expand?.user}/></b> verschieben </>}
|
||||||
>
|
>
|
||||||
<form
|
<form
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {FieldEntries} from "@/components/formUtil/FromInput/types.ts";
|
||||||
import {showSuccessNotification} from "@/components/util.tsx";
|
import {showSuccessNotification} from "@/components/util.tsx";
|
||||||
import {Modal} from "@mantine/core";
|
import {Modal} from "@mantine/core";
|
||||||
import FormInput from "@/components/formUtil/FromInput";
|
import FormInput from "@/components/formUtil/FromInput";
|
||||||
|
import {getUserName, RenderUserName} from "@/components/auth/modals/util.tsx";
|
||||||
|
|
||||||
export const UpdateEventListSlotEntryFormModal = ({opened, close, refetch, entry}: {
|
export const UpdateEventListSlotEntryFormModal = ({opened, close, refetch, entry}: {
|
||||||
opened: boolean,
|
opened: boolean,
|
||||||
|
@ -23,7 +24,7 @@ export const UpdateEventListSlotEntryFormModal = ({opened, close, refetch, entry
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
showSuccessNotification(`Formular Daten von ${entry.userName} erfolgreich aktualisiert`)
|
showSuccessNotification(`Formular Daten von ${getUserName(entry.expand?.user)} erfolgreich aktualisiert`)
|
||||||
refetch()
|
refetch()
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
|
@ -38,7 +39,7 @@ export const UpdateEventListSlotEntryFormModal = ({opened, close, refetch, entry
|
||||||
size={"lg"}
|
size={"lg"}
|
||||||
opened={opened}
|
opened={opened}
|
||||||
onClose={close}
|
onClose={close}
|
||||||
title={<> Eintrag von <b>{entry.userName}</b> bearbeiten </>}
|
title={<> Eintrag von <b><RenderUserName user={entry.expand?.user}/></b> bearbeiten </>}
|
||||||
>
|
>
|
||||||
<PocketBaseErrorAlert error={mutation.error}/>
|
<PocketBaseErrorAlert error={mutation.error}/>
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {Alert, Modal} from "@mantine/core";
|
||||||
import InnerHtml from "@/components/InnerHtml";
|
import InnerHtml from "@/components/InnerHtml";
|
||||||
import {RenderDateRange} from "./RenderDateRange.tsx";
|
import {RenderDateRange} from "./RenderDateRange.tsx";
|
||||||
import FormInput from "@/components/formUtil/FromInput";
|
import FormInput from "@/components/formUtil/FromInput";
|
||||||
|
import {getUserName, RenderUserName} from "@/components/auth/modals/util.tsx";
|
||||||
|
|
||||||
export const UpdateEventListSlotEntryStatusModal = ({opened, close, refetch, entry}: {
|
export const UpdateEventListSlotEntryStatusModal = ({opened, close, refetch, entry}: {
|
||||||
opened: boolean,
|
opened: boolean,
|
||||||
|
@ -29,7 +30,7 @@ export const UpdateEventListSlotEntryStatusModal = ({opened, close, refetch, ent
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
showSuccessNotification(`Status von ${entry.userName} erfolgreich aktualisiert`)
|
showSuccessNotification(`Status von ${getUserName(entry.expand?.user)} erfolgreich aktualisiert`)
|
||||||
refetch()
|
refetch()
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
|
@ -39,7 +40,7 @@ export const UpdateEventListSlotEntryStatusModal = ({opened, close, refetch, ent
|
||||||
size={"lg"}
|
size={"lg"}
|
||||||
opened={opened}
|
opened={opened}
|
||||||
onClose={close}
|
onClose={close}
|
||||||
title={<> Eintrag von <b>{entry.userName}</b> bearbeiten </>}
|
title={<> Eintrag von <b><RenderUserName user={entry.expand?.user}/></b> bearbeiten </>}
|
||||||
>
|
>
|
||||||
<PocketBaseErrorAlert error={mutation.error}/>
|
<PocketBaseErrorAlert error={mutation.error}/>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {EventListModel, EventListSlotModel} from "@/models/EventTypes.ts";
|
import {EventListModel, EventListSlotModel} from "@/models/EventTypes.ts";
|
||||||
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
||||||
import {isNotEmpty, useForm} from "@mantine/form";
|
import {useForm} from "@mantine/form";
|
||||||
import {useMutation} from "@tanstack/react-query";
|
import {useMutation} from "@tanstack/react-query";
|
||||||
import {useConfirmModal} from "@/components/ConfirmModal.tsx";
|
import {useConfirmModal} from "@/components/ConfirmModal.tsx";
|
||||||
import {Button, Center, Group, NumberInput} from "@mantine/core";
|
import {Button, Center, Group, NumberInput} from "@mantine/core";
|
||||||
|
@ -26,7 +26,11 @@ export default function UpsertEventListSlot({list, slot, onSuccess, onAbort}: {
|
||||||
description: slot?.description ?? ""
|
description: slot?.description ?? ""
|
||||||
},
|
},
|
||||||
validate: {
|
validate: {
|
||||||
startDate: isNotEmpty("Startdatum muss angegeben werden"),
|
startDate: (val) => {
|
||||||
|
if (!val) {
|
||||||
|
return "Startdatum muss angegeben werden"
|
||||||
|
}
|
||||||
|
},
|
||||||
endDate: (val, values) => {
|
endDate: (val, values) => {
|
||||||
if (!val) return "Enddatum muss angegeben werden"
|
if (!val) return "Enddatum muss angegeben werden"
|
||||||
if (val < values.startDate) return "Enddatum muss nach dem Startdatum liegen"
|
if (val < values.startDate) return "Enddatum muss nach dem Startdatum liegen"
|
||||||
|
@ -61,14 +65,13 @@ export default function UpsertEventListSlot({list, slot, onSuccess, onAbort}: {
|
||||||
})
|
})
|
||||||
|
|
||||||
const {toggleConfirmModal, ConfirmModal} = useConfirmModal({
|
const {toggleConfirmModal, ConfirmModal} = useConfirmModal({
|
||||||
description: "Möchtest du diesen Slot wirklich löschen? Damit werden auch alle Einträge für diesen Slot gelöscht.",
|
description: "Möchtest du diesen Slot wirklich löschen? Damit werden auch alle Anmeldungen für diesen Slot gelöscht.",
|
||||||
onConfirm: deleteSlotMutation.mutate,
|
onConfirm: deleteSlotMutation.mutate,
|
||||||
})
|
})
|
||||||
|
|
||||||
const duration = formatDuration(formValues.values.startDate, formValues.values.endDate)
|
const duration = formatDuration(formValues.values.startDate, formValues.values.endDate)
|
||||||
|
|
||||||
return <form className={"stack"} onSubmit={formValues.onSubmit(() => upsertSlotMutation.mutate())}>
|
return <form className={"stack"} onSubmit={formValues.onSubmit(() => upsertSlotMutation.mutate())}>
|
||||||
|
|
||||||
<ConfirmModal/>
|
<ConfirmModal/>
|
||||||
|
|
||||||
<PocketBaseErrorAlert error={upsertSlotMutation.error}/>
|
<PocketBaseErrorAlert error={upsertSlotMutation.error}/>
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import {useUser} from "@/lib/user.ts";
|
|
||||||
import PromptLoginModal from "@/components/auth/modals/PromptLoginModal.tsx";
|
import PromptLoginModal from "@/components/auth/modals/PromptLoginModal.tsx";
|
||||||
import {Link, useNavigate} from "react-router-dom";
|
import {Link, useNavigate} from "react-router-dom";
|
||||||
import {useState} from "react";
|
import {useState} from "react";
|
||||||
|
@ -27,10 +26,9 @@ import UserEntryRow from "@/pages/events/entries/UserEntryRow.tsx";
|
||||||
|
|
||||||
export default function UserEntries() {
|
export default function UserEntries() {
|
||||||
|
|
||||||
const user = useUser()
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const {pb} = usePB()
|
const {pb, user} = usePB()
|
||||||
|
|
||||||
const [page, setPage] = useState(1)
|
const [page, setPage] = useState(1)
|
||||||
|
|
||||||
|
@ -44,8 +42,9 @@ export default function UserEntries() {
|
||||||
queryKey: ["userEntries", user?.id, page, debouncedSearch, sortDirection],
|
queryKey: ["userEntries", user?.id, page, debouncedSearch, sortDirection],
|
||||||
queryFn: async () => (
|
queryFn: async () => (
|
||||||
await pb.collection("eventListSlotEntriesWithUser").getList(page, 50, {
|
await pb.collection("eventListSlotEntriesWithUser").getList(page, 50, {
|
||||||
filter: `userId='${user?.id}'${debouncedSearch ? `&&event.name~'${debouncedSearch}'` : ""}`,
|
filter: `user='${user?.id}'${debouncedSearch ? `&&event.name~'${debouncedSearch}'` : ""}`,
|
||||||
sort: sortDirection === "ASC" ? "-event.startDate" : "event.startDate"
|
sort: sortDirection === "ASC" ? "-event.startDate" : "event.startDate",
|
||||||
|
expand: "user"
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
enabled: !!user
|
enabled: !!user
|
||||||
|
@ -62,7 +61,7 @@ export default function UserEntries() {
|
||||||
<Breadcrumbs>{[
|
<Breadcrumbs>{[
|
||||||
{title: "Home", to: "/"},
|
{title: "Home", to: "/"},
|
||||||
{title: "Events", to: "/events"},
|
{title: "Events", to: "/events"},
|
||||||
{title: "Meine Einträge", to: `/events/entries`},
|
{title: "Anmeldungen", to: `/events/entries`},
|
||||||
].map(({title, to}) => (
|
].map(({title, to}) => (
|
||||||
<Anchor component={Link} to={to} key={title}>
|
<Anchor component={Link} to={to} key={title}>
|
||||||
{title}
|
{title}
|
||||||
|
@ -70,11 +69,11 @@ export default function UserEntries() {
|
||||||
))}</Breadcrumbs>
|
))}</Breadcrumbs>
|
||||||
</div>
|
</div>
|
||||||
<div className={"section"}>
|
<div className={"section"}>
|
||||||
<Title order={1} c={"blue"}>Meine Einträge</Title>
|
<Title order={1} c={"blue"}>Anmeldungen</Title>
|
||||||
</div>
|
</div>
|
||||||
<div className={"section-transparent"}>
|
<div className={"section-transparent"}>
|
||||||
<ShowHelp>
|
<ShowHelp>
|
||||||
Auf dieser Seite findest du alle deine Einträge zu Events und kannst diese verwalten.
|
Auf dieser Seite findest du alle deine Anmeldungen zu Events und kannst diese verwalten.
|
||||||
</ShowHelp>
|
</ShowHelp>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -101,11 +100,11 @@ export default function UserEntries() {
|
||||||
|
|
||||||
<Group justify={"space-between"}>
|
<Group justify={"space-between"}>
|
||||||
<Text c={"dimmed"} size={"xs"}>
|
<Text c={"dimmed"} size={"xs"}>
|
||||||
{eventSearch ? `Suche nach Einträgen für Event '${eventSearch}'` : "Alle deine Einträge"}
|
{eventSearch ? `Suche nach Anmeldungen für Event '${eventSearch}'` : "Alle deine Anmeldungen"}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text c={"dimmed"} size={"xs"}>
|
<Text c={"dimmed"} size={"xs"}>
|
||||||
{entriesQuery.data?.totalItems ?? 0} {eventSearch ? "Ergebnisse" : "Einträge"}
|
{entriesQuery.data?.totalItems ?? 0} {eventSearch ? "Ergebnisse" : "Anmeldungen"}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
|
@ -116,7 +115,7 @@ export default function UserEntries() {
|
||||||
<IconDatabaseOff/>
|
<IconDatabaseOff/>
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
|
|
||||||
<Title order={4} c={"dimmed"}>Keine Einträge vorhanden</Title>
|
<Title order={4} c={"dimmed"}>Keine Anmeldungen vorhanden</Title>
|
||||||
</Stack>
|
</Stack>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {usePB} from "@/lib/pocketbase.tsx";
|
||||||
import {
|
import {
|
||||||
UpdateEventListSlotEntryFormModal
|
UpdateEventListSlotEntryFormModal
|
||||||
} from "@/pages/events/e/:eventId/EventLists/components/UpdateEventListSlotEntryFormModal.tsx";
|
} from "@/pages/events/e/:eventId/EventLists/components/UpdateEventListSlotEntryFormModal.tsx";
|
||||||
|
import {getUserName} from "@/components/auth/modals/util.tsx";
|
||||||
|
|
||||||
export default function UserEntryRow({entry, refetch}: {
|
export default function UserEntryRow({entry, refetch}: {
|
||||||
entry: EventListSlotEntriesWithUserModel,
|
entry: EventListSlotEntriesWithUserModel,
|
||||||
|
@ -42,7 +43,7 @@ export default function UserEntryRow({entry, refetch}: {
|
||||||
|
|
||||||
const {ConfirmModal, toggleConfirmModal} = useConfirmModal({
|
const {ConfirmModal, toggleConfirmModal} = useConfirmModal({
|
||||||
title: 'Eintrag löschen',
|
title: 'Eintrag löschen',
|
||||||
description: `Möchtest du den Eintrag von ${entry.userName} wirklich löschen?`,
|
description: `Möchtest du den Eintrag von ${getUserName(entry.expand?.user)} wirklich löschen?`,
|
||||||
onConfirm: () => deleteEntryMutation.mutate()
|
onConfirm: () => deleteEntryMutation.mutate()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -90,7 +91,7 @@ export default function UserEntryRow({entry, refetch}: {
|
||||||
|
|
||||||
<Group gap={"xs"}>
|
<Group gap={"xs"}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label={delta.delta === "PAST" ? "Vergangene Einträge können nicht bearbeiten werden" : "Eintrag bearbeiten"}
|
label={delta.delta === "PAST" ? "Vergangene Anmeldungen können nicht bearbeiten werden" : "Eintrag bearbeiten"}
|
||||||
withArrow>
|
withArrow>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant={"light"}
|
variant={"light"}
|
||||||
|
@ -104,7 +105,7 @@ export default function UserEntryRow({entry, refetch}: {
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label={delta.delta === "PAST" ? "Vergangene Einträge können nicht gelöscht werden" : "Eintrag löschen"}
|
label={delta.delta === "PAST" ? "Vergangene Anmeldungen können nicht gelöscht werden" : "Eintrag löschen"}
|
||||||
withArrow>
|
withArrow>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant={"light"}
|
variant={"light"}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {EventListSlotsWithEntriesCountModel} from "@/models/EventTypes.ts";
|
import {EventListModel, EventListSlotsWithEntriesCountModel} from "@/models/EventTypes.ts";
|
||||||
import classes from "./EventListSlotView.module.css";
|
import classes from "./EventListSlotView.module.css";
|
||||||
import {RenderDateRange} from "@/pages/events/e/:eventId/EventLists/components/RenderDateRange.tsx";
|
import {RenderDateRange} from "@/pages/events/e/:eventId/EventLists/components/RenderDateRange.tsx";
|
||||||
import EventListSlotProgress from "@/pages/events/e/:eventId/EventLists/components/EventListSlotProgress.tsx";
|
import EventListSlotProgress from "@/pages/events/e/:eventId/EventLists/components/EventListSlotProgress.tsx";
|
||||||
|
@ -11,9 +11,9 @@ import {FieldEntries} from "@/components/formUtil/FromInput/types.ts";
|
||||||
import {showSuccessNotification} from "@/components/util.tsx";
|
import {showSuccessNotification} from "@/components/util.tsx";
|
||||||
import InnerHtml from "@/components/InnerHtml";
|
import InnerHtml from "@/components/InnerHtml";
|
||||||
import FormInput from "@/components/formUtil/FromInput";
|
import FormInput from "@/components/formUtil/FromInput";
|
||||||
import {useUser} from "@/lib/user.ts";
|
|
||||||
|
|
||||||
export default function EventListSlotView({slot, refetch}: {
|
export default function EventListSlotView({slot, list, refetch}: {
|
||||||
|
list: EventListModel,
|
||||||
slot: EventListSlotsWithEntriesCountModel,
|
slot: EventListSlotsWithEntriesCountModel,
|
||||||
refetch: () => void
|
refetch: () => void
|
||||||
}) {
|
}) {
|
||||||
|
@ -31,17 +31,14 @@ export default function EventListSlotView({slot, refetch}: {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const {pb} = usePB()
|
const {pb, user} = usePB()
|
||||||
|
|
||||||
const user = useUser()
|
|
||||||
|
|
||||||
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,
|
entryQuestionData: data,
|
||||||
eventListsSlot: slot.id,
|
eventListsSlot: slot.id,
|
||||||
ldapUser: user?.realm === "STUVE" ? user.id : null,
|
user: user?.id
|
||||||
guestUser: user?.realm === "GUEST" ? user.id : null
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
@ -75,7 +72,12 @@ export default function EventListSlotView({slot, refetch}: {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
slotIsInPast ? <>
|
|
||||||
|
list.onlyStuVeAccounts && user?.REALM !== "LDAP" ? <>
|
||||||
|
<Alert color={"red"}>
|
||||||
|
Für diese Liste sind nur StuVe-Accounts zugelassen
|
||||||
|
</Alert>
|
||||||
|
</> : slotIsInPast ? <>
|
||||||
<Alert color={"red"}>
|
<Alert color={"red"}>
|
||||||
Dieser Zeitslot ist bereits vorbei
|
Dieser Zeitslot ist bereits vorbei
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
|
@ -48,7 +48,7 @@ export default function EventListView({event, listId}: { event: EventModel, list
|
||||||
|
|
||||||
{!list.open && (
|
{!list.open && (
|
||||||
<Alert icon={<IconLock/>}>
|
<Alert icon={<IconLock/>}>
|
||||||
Diese Liste ist nicht für Einträge geöffnet
|
Diese Liste ist nicht für Anmeldungen geöffnet
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -59,10 +59,25 @@ export default function EventListView({event, listId}: { event: EventModel, list
|
||||||
</Alert>
|
</Alert>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!list.allowOverlappingEntries &&
|
||||||
|
<Alert>
|
||||||
|
Diese Liste erlaubt Anmeldungen zu überschneidenden Zeiten mit anderen Listen von diesem Event
|
||||||
|
</Alert>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
list.onlyStuVeAccounts &&
|
||||||
|
<Alert>
|
||||||
|
Diese Liste erlaubt nur Anmeldungen von Personen mit einem StuVe Account
|
||||||
|
</Alert>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
(list.open && slots.length !== 0) && <>
|
(list.open && slots.length !== 0) && <>
|
||||||
{slots.map(slot => (
|
{slots.map(slot => (
|
||||||
<EventListSlotView key={slot.id} slot={slot} refetch={() => {
|
<EventListSlotView key={slot.id} list={list} slot={slot} refetch={() => {
|
||||||
listSlotsQuery.refetch()
|
listSlotsQuery.refetch()
|
||||||
}}/>
|
}}/>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -9,14 +9,12 @@ import {IconExternalLink, IconLogin, IconPencil, IconSectionSign} from "@tabler/
|
||||||
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";
|
||||||
import {useUser} from "@/lib/user.ts";
|
|
||||||
|
|
||||||
|
|
||||||
export default function SharedEvent() {
|
export default function SharedEvent() {
|
||||||
|
|
||||||
const {pb} = usePB()
|
const {pb,user} = usePB()
|
||||||
|
|
||||||
const user = useUser()
|
|
||||||
|
|
||||||
const {eventId} = useParams() as { eventId: string }
|
const {eventId} = useParams() as { eventId: string }
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
import {EventModel} from "@/models/EventTypes.ts";
|
import {EventModel} from "@/models/EventTypes.ts";
|
||||||
import {usePB} from "@/lib/pocketbase.tsx";
|
import {usePB} from "@/lib/pocketbase.tsx";
|
||||||
import {useSettings} from "@/lib/settings.ts";
|
import {useSettings} from "@/lib/settings.ts";
|
||||||
|
import {LdapGroupModel} from "@/models/AuthTypes.ts";
|
||||||
|
|
||||||
|
|
||||||
export const useEventRights = (event?: EventModel) => {
|
export const useEventRights = (event?: EventModel) => {
|
||||||
const {ldapUser} = usePB()
|
const {user} = usePB()
|
||||||
|
|
||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
|
|
||||||
const isStex = !!(
|
const isStex = !!(
|
||||||
ldapUser &&
|
user &&
|
||||||
settings?.stexGroupId?.value !== undefined &&
|
settings?.stexGroupId?.value !== undefined &&
|
||||||
ldapUser.expand?.memberOf?.map(g => g.cn).includes(settings.stexGroupId.value)
|
user.expand?.memberOf?.map((g: LdapGroupModel) => g.cn).includes(settings.stexGroupId.value)
|
||||||
)
|
)
|
||||||
const isEventAdmin = !!(ldapUser && event && event.eventAdmins.includes(ldapUser.id))
|
const isEventAdmin = !!(user && event && event.eventAdmins.includes(user.id))
|
||||||
const isEventListAdmin = !!(ldapUser && event && event.eventListAdmins.includes(ldapUser.id))
|
const isEventListAdmin = !!(user && event && event.eventListAdmins.includes(user.id))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
canEditEvent: isEventAdmin || isStex,
|
canEditEvent: isEventAdmin || isStex,
|
||||||
|
|
|
@ -3,14 +3,11 @@ import {Alert, Button, Group, ThemeIcon, Title} from "@mantine/core";
|
||||||
import {IconConfetti, IconHandLoveYou, IconQrcode} from "@tabler/icons-react";
|
import {IconConfetti, IconHandLoveYou, IconQrcode} from "@tabler/icons-react";
|
||||||
import {usePB} from "@/lib/pocketbase.tsx";
|
import {usePB} from "@/lib/pocketbase.tsx";
|
||||||
import {NavLink} from "react-router-dom";
|
import {NavLink} from "react-router-dom";
|
||||||
import {useUser} from "@/lib/user.ts";
|
|
||||||
|
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
|
|
||||||
const {userRecord} = usePB()
|
const {user} = usePB()
|
||||||
|
|
||||||
const user = useUser()
|
|
||||||
|
|
||||||
const nav = [
|
const nav = [
|
||||||
{
|
{
|
||||||
|
@ -34,10 +31,10 @@ export default function HomePage() {
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
Hallo
|
Hallo
|
||||||
|
|
||||||
{user && `, ${user.name}`}
|
{user && `, ${user.username}`}
|
||||||
</Title>
|
</Title>
|
||||||
|
|
||||||
{!userRecord && <>
|
{!user && <>
|
||||||
<Alert color={"blue"} title={"Willkommen bei der StuVe!"}>
|
<Alert color={"blue"} title={"Willkommen bei der StuVe!"}>
|
||||||
Um alle Funktionen nutzen zu können, logge dich bitte mit deinem StuVe IT Account ein.
|
Um alle Funktionen nutzen zu können, logge dich bitte mit deinem StuVe IT Account ein.
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
Loading…
Reference in New Issue