diff --git a/src/components/ShowDebug.tsx b/src/components/ShowDebug.tsx index 51aa83b..2706be9 100644 --- a/src/components/ShowDebug.tsx +++ b/src/components/ShowDebug.tsx @@ -1,4 +1,4 @@ -import {Alert, Tooltip} from "@mantine/core"; +import {Alert, AlertProps, Tooltip} from "@mantine/core"; import {IconBug} from "@tabler/icons-react"; import {useLocalStorage} from "@mantine/hooks"; import {ReactNode} from "react"; @@ -34,8 +34,9 @@ export const useShowDebug = (): { * Shows a help dialog if the user has not disabled it * @see useShowHelp * @param children - the content of the help dialog + * @param props - the props of the alert component */ -export default function ShowDebug({children}: { children: ReactNode }) { +export default function ShowDebug({children, ...props}: { children: ReactNode } & Omit) { const {showDebug} = useShowDebug() @@ -54,7 +55,9 @@ export default function ShowDebug({children}: { children: ReactNode }) { > - }> + } + {...props} + > {children} } \ No newline at end of file diff --git a/src/components/layout/TextWithIcon/index.tsx b/src/components/layout/TextWithIcon/index.tsx index 5c5c1d7..16327f1 100644 --- a/src/components/layout/TextWithIcon/index.tsx +++ b/src/components/layout/TextWithIcon/index.tsx @@ -1,12 +1,22 @@ import classes from "./index.module.css"; import {ReactNode} from "react"; +import {ThemeIcon, ThemeIconProps} from "@mantine/core"; -export default function TextWithIcon({icon, children}: { +export default function TextWithIcon({icon, children, className, themeIconProps}: { icon: ReactNode, - children: ReactNode + children: ReactNode, + className?: string, + themeIconProps?: ThemeIconProps }) { - return
- {icon} + return
+ + {icon} + {children}
} \ No newline at end of file diff --git a/src/lib/datetime.ts b/src/lib/datetime.ts index 95e0eab..2532fc3 100644 --- a/src/lib/datetime.ts +++ b/src/lib/datetime.ts @@ -134,12 +134,18 @@ export function formatDateForExcel(date: string | Date | Dayjs): string { * Example: "3 Wo, 1 Tag, 2 Std, 53 Min" * @param date1 * @param date2 + * @param includeSeconds - If true, include seconds in the output. */ -export function formatDuration(date1: string | Date | Dayjs, date2: string | Date | Dayjs) { +export function formatDuration(date1: string | Date | Dayjs, date2: string | Date | Dayjs, includeSeconds = false) { - // ignore seconds and milliseconds - date1 = dayjs(date1).startOf('minute'); - date2 = dayjs(date2).startOf('minute'); + if (!includeSeconds) { + // ignore seconds and milliseconds + date1 = dayjs(date1).startOf('minute'); + date2 = dayjs(date2).startOf('minute'); + } else { + date1 = dayjs(date1); + date2 = dayjs(date2); + } // get the difference in milliseconds const diff = dayjs(date2).diff(dayjs(date1)); @@ -150,6 +156,7 @@ export function formatDuration(date1: string | Date | Dayjs, date2: string | Dat const days = durationObj.days(); const hours = durationObj.hours(); const minutes = durationObj.minutes(); + const seconds = durationObj.seconds(); // create a string array with the duration parts const result: string[] = []; @@ -157,6 +164,8 @@ export function formatDuration(date1: string | Date | Dayjs, date2: string | Dat if (days > 0) result.push(`${days} Tag${days > 1 ? 'e' : ''}`); if (hours > 0) result.push(`${hours} Std`); if (minutes > 0) result.push(`${minutes} Min`); + if (includeSeconds && seconds > 0) result.push(`${seconds} Sek`); - return result.join(', '); + // join the parts with a comma and space + return result.length ? result.join(', ') : `0 ${includeSeconds ? 'Sek' : 'Min'}`; } \ No newline at end of file diff --git a/src/models/AnalyticsTypes.ts b/src/models/AnalyticsTypes.ts index 81a7bed..7d76970 100644 --- a/src/models/AnalyticsTypes.ts +++ b/src/models/AnalyticsTypes.ts @@ -17,7 +17,6 @@ export type AnalyticsSessionModel = { preferred_language?: string expand?: { visitor?: AnalyticsVisitorsModel, - analyticsErrors_via_session?: AnalyticsErrorModel[], analyticsPageViews_via_session?: AnalyticsPageViewModel[] } } & RecordModel @@ -38,18 +37,6 @@ export type AnalyticsPageViewModel = { } } & RecordModel -export type AnalyticsErrorModel = { - session: string - error_message?: string - error_type?: string - stack_trace?: string - path?: string - - expand?: { - session?: AnalyticsSessionModel - } -} & RecordModel - export type AnalyticsIpApiResult = { ip: string country_code: string diff --git a/src/pages/admin/AnalyticsDashboard/AnalyticsSessions/AnalyticsSessionDetail/index.module.css b/src/pages/admin/AnalyticsDashboard/AnalyticsSessions/AnalyticsSessionDetail/index.module.css new file mode 100644 index 0000000..0ee4fc2 --- /dev/null +++ b/src/pages/admin/AnalyticsDashboard/AnalyticsSessions/AnalyticsSessionDetail/index.module.css @@ -0,0 +1,48 @@ +.mainGrid { + display: grid; + grid-template-columns: auto auto auto auto auto auto; + background-color: var(--mantine-color-body); + border: var(--border); + border-radius: var(--border-radius); +} + +.subGrid { + display: grid; + grid-template-columns: subgrid; + grid-column: span 6; + gap: var(--gap); + + font-size: var(--mantine-font-size-sm); + padding: var(--padding); + + border-bottom: var(--border); + + &:last-of-type { + border-bottom: none; + } + + @media (max-width: $mantine-breakpoint-sm) { + font-size: var(--mantine-font-size-sm); + display: flex; + flex-direction: column; + gap: var(--gap); + } +} + +.gridCell { + display: flex; + justify-content: start; + align-items: center; + text-align: start; + gap: var(--gap); + + word-wrap: break-word; /* Ensures text wraps within the cell */ + overflow-wrap: break-word; /* Ensures text wraps within the cell */ + hyphens: auto; + @media (max-width: $mantine-breakpoint-sm) { + } +} + +.debugContainer { + grid-column: span 6; /* Spans all columns of the main grid */ +} \ No newline at end of file diff --git a/src/pages/admin/AnalyticsDashboard/AnalyticsSessions/AnalyticsSessionDetail/index.tsx b/src/pages/admin/AnalyticsDashboard/AnalyticsSessions/AnalyticsSessionDetail/index.tsx new file mode 100644 index 0000000..513f748 --- /dev/null +++ b/src/pages/admin/AnalyticsDashboard/AnalyticsSessions/AnalyticsSessionDetail/index.tsx @@ -0,0 +1,104 @@ +import {AnalyticsPageViewModel, AnalyticsSessionModel} from "@/models/AnalyticsTypes.ts"; +import classes from "./index.module.css"; +import {formatDuration, pprintDateTime, pprintTime} from "@/lib/datetime.ts"; +import ShowDebug from "@/components/ShowDebug.tsx"; +import {Code} from "@mantine/core"; +import TextWithIcon from "@/components/layout/TextWithIcon"; +import {IconBug, IconClick, IconClock, IconHourglass, IconInfoCircle, IconStack3Filled} from "@tabler/icons-react"; + +export default function AnalyticsSessionDetail({session}: { session: AnalyticsSessionModel }) { + return
+ + Session Id - {session.id} +
+ created - {pprintDateTime(session.created)} +
+ updated - {pprintDateTime(session.updated)} +
+
+ IP - {session.ip_address} +
+ User Agent - {session.user_agent} +
+ Visitor Id - {session.visitor} +
+ { + session.expand?.analyticsPageViews_via_session?.sort((a, b) => { + return a.created < b.created ? -1 : 1 + }).map((pageView, index, array) => { + return ( + + ) + }) + + } +
+} + +function PageViewRow({pageView, nextPageView}: { + pageView: AnalyticsPageViewModel, + nextPageView?: AnalyticsPageViewModel +}) { + return ( +
+ } + > + {pprintTime(pageView.created)} + + + } + > + {nextPageView ? + formatDuration(pageView.created, nextPageView.created, true) + : + "End" + } + + + } + > + {pageView.path} + + + { + pageView.error && <> + } + > + {pageView.error.error_type} + + + } + > + {pageView.error.error_message} + + + } + > + + {pageView.error.stack_trace} + + + + } +
+ ) +} \ No newline at end of file diff --git a/src/pages/admin/AnalyticsDashboard/AnalyticsSessions/AnalyticsSessionRow.tsx b/src/pages/admin/AnalyticsDashboard/AnalyticsSessions/AnalyticsSessionRow.tsx index 876319b..93c3569 100644 --- a/src/pages/admin/AnalyticsDashboard/AnalyticsSessions/AnalyticsSessionRow.tsx +++ b/src/pages/admin/AnalyticsDashboard/AnalyticsSessions/AnalyticsSessionRow.tsx @@ -1,5 +1,5 @@ import classes from "./index.module.css"; -import {ActionIcon, Code, Collapse, ThemeIcon, Tooltip} from "@mantine/core"; +import {ActionIcon, Collapse, Tooltip} from "@mantine/core"; import { IconBrandAndroid, IconBrandApple, @@ -31,9 +31,9 @@ import TextWithIcon from "@/components/layout/TextWithIcon"; import {useDisclosure} from "@mantine/hooks"; -import ShowDebug from "@/components/ShowDebug.tsx"; import {pprintDateTime} from "@/lib/datetime.ts"; import {AnalyticsSessionModel} from "@/models/AnalyticsTypes.ts"; +import AnalyticsSessionDetail from "@/pages/admin/AnalyticsDashboard/AnalyticsSessions/AnalyticsSessionDetail"; export const DeviceTypeIcon = ({deviceType}: { deviceType: string }) => { if (deviceType === "mobile") { @@ -102,75 +102,54 @@ export default function AnalyticsSessionRow({session}: { return <>
-
- - - - }> - {pprintDateTime(session.created)} - -
+ } + > + {pprintDateTime(session.created)} + -
- - - - }> - {session.browser_name} {session.browser_version} - -
+ } + > + {session.browser_name} {session.browser_version} + -
- - - - }> - {session.operating_system} {session.operating_system_version} - -
+ } + > + {session.operating_system} {session.operating_system_version} + -
- - - - }> - {session.geo_country_code || "--"} - -
+ } + > + {session.geo_country_code || "--"} + -
- - - - }> - {session.preferred_language || "--"} - -
+ } + > + {session.preferred_language || "--"} + -
- - - - }> - {pageViewCount ?? '--'} - -
+ } + > + {pageViewCount ?? '--'} + -
- - - - }> - {errorCount ?? '--'} - -
+ } + > + {errorCount ?? '--'} +
@@ -182,21 +161,7 @@ export default function AnalyticsSessionRow({session}: {
- - Session Id - {session.id} -
- created - {pprintDateTime(session.created)} -
- updated - {pprintDateTime(session.updated)} -
-
- IP - {session.ip_address} -
- User Agent - {session.user_agent} -
- Visitor Id - {session.visitor} -
- {/**/} +
} \ No newline at end of file diff --git a/src/pages/admin/AnalyticsDashboard/AnalyticsSessions/index.tsx b/src/pages/admin/AnalyticsDashboard/AnalyticsSessions/index.tsx index b305a51..054537f 100644 --- a/src/pages/admin/AnalyticsDashboard/AnalyticsSessions/index.tsx +++ b/src/pages/admin/AnalyticsDashboard/AnalyticsSessions/index.tsx @@ -52,7 +52,7 @@ export default function AnalyticsSessions({lastNDays}: { lastNDays?: number }) { filter.push('analyticsPageViews_via_session.session=id&&analyticsPageViews_via_session.error?!=null') } - return await pb.collection("analyticsSessions").getList(pageParam, 500, { + return await pb.collection("analyticsSessions").getList(pageParam, 100, { filter: filter.join("&&"), expand: 'analyticsErrors_via_session,analyticsPageViews_via_session', sort: '-created' @@ -67,6 +67,7 @@ export default function AnalyticsSessions({lastNDays}: { lastNDays?: number }) { const sessionAnalytics = useMemo(() => { return countByAllKeys(sessions) + // eslint-disable-next-line react-hooks/exhaustive-deps }, [query.data]) return <> @@ -102,7 +103,8 @@ export default function AnalyticsSessions({lastNDays}: { lastNDays?: number }) {
- Datenvisualisierung der letzten {sessions.length} Sessions ({query.data?.pages[0].totalItems} insgesamt) + Datenvisualisierung der neuesten {sessions.length} Sessions + ({query.data?.pages[0].totalItems} insgesamt) {query.hasNextPage && <> {" • "} diff --git a/src/pages/events/e/:eventId/EventLists/:listId/EventListRouter.tsx b/src/pages/events/e/:eventId/EventLists/:listId/EventListRouter.tsx index 77e9096..dfdedec 100644 --- a/src/pages/events/e/:eventId/EventLists/:listId/EventListRouter.tsx +++ b/src/pages/events/e/:eventId/EventLists/:listId/EventListRouter.tsx @@ -100,16 +100,16 @@ export default function EventListRouter({event}: { event: EventModel }) { - : }> + : }> Liste ist für Anmeldungen {list.open ? "geöffnet" : "geschlossen"}
- }> + }> Anmeldung für Personen mit {list.onlyStuVeAccounts ? <>StuVe Account : <>StuVe und Gast Account}
- }> + }> Überlappende Einträge sind {!list.allowOverlappingEntries && nicht} erlaubt
diff --git a/src/pages/events/e/:eventId/EventLists/EventListComponents/RenderDateRange.tsx b/src/pages/events/e/:eventId/EventLists/EventListComponents/RenderDateRange.tsx index 902a1a4..3950e90 100644 --- a/src/pages/events/e/:eventId/EventLists/EventListComponents/RenderDateRange.tsx +++ b/src/pages/events/e/:eventId/EventLists/EventListComponents/RenderDateRange.tsx @@ -1,7 +1,7 @@ import {areDatesSame, formatDuration} from "@/lib/datetime.ts"; -import {Group, ThemeIcon} from "@mantine/core"; +import {Group} from "@mantine/core"; import {IconCalendar, IconCalendarClock, IconClock, IconHourglass} from "@tabler/icons-react"; -import dayjs from "dayjs"; +import dayjs, {Dayjs} from "dayjs"; import TextWithIcon from "@/components/layout/TextWithIcon"; /** @@ -11,68 +11,47 @@ import TextWithIcon from "@/components/layout/TextWithIcon"; * @param start - start date * @param end - end date */ -export const RenderDateRange = ({start, end}: { start: Date, end: Date }) => { +export const RenderDateRange = ({start, end}: { start: string | Date | Dayjs, end: string | Date | Dayjs }) => { const duration = formatDuration(start, end) + start = dayjs(start) + end = dayjs(end) + // case for same date if (areDatesSame(start, end)) { return - - - - }> + }> {dayjs(start).format("HH:mm")} {"-"} {dayjs(end).format("HH:mm")} - - - - }> + }> {dayjs(start).format("DD.MM.YY")} - - - - }> + }> {duration} } // case both dates start at 00:00:00 - if (start.getHours() === 0 && start.getMinutes() === 0 && start.getSeconds() === 0 && - end.getHours() === 0 && end.getMinutes() === 0 && end.getSeconds() === 0) { + if (start.hour() === 0 && start.minute() === 0 && start.second() === 0 && + end.hour() === 0 && end.minute() === 0 && end.second() === 0) { return - - - - }> + }> {dayjs(start).format("DD.MM.YY")} {"bis"} - - - - }> + }> {dayjs(end).format("DD.MM.YY")} - - - - }> + }> {duration} @@ -80,29 +59,17 @@ export const RenderDateRange = ({start, end}: { start: Date, end: Date }) => { // case different dates and times return - - - - }> + }> {dayjs(start).format("HH:mm DD.MM.YY")} {"bis"} - - - - }> + }> {dayjs(end).format("HH:mm DD.MM.YY")} - - - - }> + }> {duration} diff --git a/src/pages/events/e/:eventId/EventLists/Search/EventEntries.tsx b/src/pages/events/e/:eventId/EventLists/Search/EventEntries.tsx index a22e255..9a9e6aa 100644 --- a/src/pages/events/e/:eventId/EventLists/Search/EventEntries.tsx +++ b/src/pages/events/e/:eventId/EventLists/Search/EventEntries.tsx @@ -1,6 +1,6 @@ import {EventListSlotEntriesWithUserModel, EventModel} from "@/models/EventTypes.ts"; import classes from "./EventEntries.module.css"; -import {ActionIcon, Code, Collapse, ThemeIcon, Tooltip} from "@mantine/core"; +import {ActionIcon, Code, Collapse, Tooltip} from "@mantine/core"; import {IconEye, IconEyeOff, IconList, IconUser} from "@tabler/icons-react"; import TextWithIcon from "@/components/layout/TextWithIcon"; @@ -26,22 +26,15 @@ function EventEntry({entry, refetch, event}: { return <>
-
- - - - }> - - -
+ } + > + + - - - - }> + }> {entry.listName} diff --git a/src/pages/events/entries/UserEntryRow.tsx b/src/pages/events/entries/UserEntryRow.tsx index 9861ae0..066557c 100644 --- a/src/pages/events/entries/UserEntryRow.tsx +++ b/src/pages/events/entries/UserEntryRow.tsx @@ -1,15 +1,5 @@ import {EventListSlotEntriesWithUserModel} from "@/models/EventTypes.ts"; -import { - ActionIcon, - Collapse, - Group, - Modal, - Text, - ThemeIcon, - Tooltip, - useMantineColorScheme, - useMantineTheme -} from "@mantine/core"; +import {ActionIcon, Collapse, Group, Modal, Text, Tooltip, useMantineColorScheme, useMantineTheme} from "@mantine/core"; import { IconChevronDown, IconChevronRight, @@ -107,19 +97,11 @@ export default function UserEntryRow({entry, refetch}: {
- - - - }> + }> {entry.eventName} - - - - }> + }> {entry.listName} diff --git a/src/pages/util/whatWeKnowAboutYou/index.module.css b/src/pages/util/whatWeKnowAboutYou/index.module.css index e6b5aca..f556aa3 100644 --- a/src/pages/util/whatWeKnowAboutYou/index.module.css +++ b/src/pages/util/whatWeKnowAboutYou/index.module.css @@ -1,24 +1,4 @@ -.wrapper { - padding-top: calc(var(--mantine-spacing-xl) * 4); - padding-bottom: calc(var(--mantine-spacing-xl) * 4); -} - -.title { - font-family: var(--mantine-font-family), sans-serif; - font-weight: 900; - margin-bottom: var(--mantine-spacing-md); - text-align: center; - - @media (max-width: $mantine-breakpoint-sm) { - font-size: var(--mantine-font-size-lg); - text-align: left; - } -} - -.description { - text-align: center; - - @media (max-width: $mantine-breakpoint-sm) { - text-align: left; - } +.svg { + max-width: 70vw; + max-height: 20vh; } \ No newline at end of file diff --git a/src/pages/util/whatWeKnowAboutYou/index.tsx b/src/pages/util/whatWeKnowAboutYou/index.tsx index bcfbfcf..4206b09 100644 --- a/src/pages/util/whatWeKnowAboutYou/index.tsx +++ b/src/pages/util/whatWeKnowAboutYou/index.tsx @@ -3,6 +3,8 @@ import {Link} from "react-router-dom"; import CollectedAnalyticsData from "@/pages/util/whatWeKnowAboutYou/CollectedAnalyticsData.tsx"; import CollectedUserData from "@/pages/util/whatWeKnowAboutYou/CollectedUserData.tsx"; +import SVG from "@/illustrations/chart-circle.svg?react" +import classes from "./index.module.css"; export default function WhatWeKnowAboutYou() { @@ -10,6 +12,8 @@ export default function WhatWeKnowAboutYou() {
+ + Welche Daten werden gesammelt?