feat(analyticsDashboard): finished sessions view dashboard
Build and Push Docker image / build-and-push (push) Failing after 3m41s
Details
Build and Push Docker image / build-and-push (push) Failing after 3m41s
Details
This commit is contained in:
parent
d605d17d06
commit
8a7dc4869b
|
@ -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<AlertProps, "children">) {
|
||||
|
||||
const {showDebug} = useShowDebug()
|
||||
|
||||
|
@ -54,7 +55,9 @@ export default function ShowDebug({children}: { children: ReactNode }) {
|
|||
>
|
||||
<IconBug/>
|
||||
</Tooltip>
|
||||
}>
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Alert>
|
||||
}
|
|
@ -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 <div className={classes.container}>
|
||||
{icon}
|
||||
return <div className={`${classes.container} ${className}`}>
|
||||
<ThemeIcon
|
||||
variant={"transparent"}
|
||||
size={"xs"}
|
||||
color={"gray"}
|
||||
{...themeIconProps}
|
||||
>
|
||||
{icon}
|
||||
</ThemeIcon>
|
||||
<span>{children}</span>
|
||||
</div>
|
||||
}
|
|
@ -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'}`;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
}
|
|
@ -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 <div className={classes.mainGrid}>
|
||||
<ShowDebug className={classes.debugContainer}>
|
||||
Session Id - <Code>{session.id}</Code>
|
||||
<br/>
|
||||
created - <Code>{pprintDateTime(session.created)}</Code>
|
||||
<br/>
|
||||
updated - <Code>{pprintDateTime(session.updated)}</Code>
|
||||
<br/>
|
||||
<br/>
|
||||
IP - <Code>{session.ip_address}</Code>
|
||||
<br/>
|
||||
User Agent - <Code>{session.user_agent}</Code>
|
||||
<br/>
|
||||
Visitor Id - <Code>{session.visitor}</Code>
|
||||
</ShowDebug>
|
||||
{
|
||||
session.expand?.analyticsPageViews_via_session?.sort((a, b) => {
|
||||
return a.created < b.created ? -1 : 1
|
||||
}).map((pageView, index, array) => {
|
||||
return (
|
||||
<PageViewRow
|
||||
key={index}
|
||||
pageView={pageView}
|
||||
nextPageView={array[index + 1]}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
function PageViewRow({pageView, nextPageView}: {
|
||||
pageView: AnalyticsPageViewModel,
|
||||
nextPageView?: AnalyticsPageViewModel
|
||||
}) {
|
||||
return (
|
||||
<div className={classes.subGrid}>
|
||||
<TextWithIcon
|
||||
className={classes.gridCell}
|
||||
icon={<IconClock/>}
|
||||
>
|
||||
{pprintTime(pageView.created)}
|
||||
</TextWithIcon>
|
||||
|
||||
<TextWithIcon
|
||||
className={classes.gridCell}
|
||||
icon={<IconHourglass/>}
|
||||
>
|
||||
{nextPageView ?
|
||||
formatDuration(pageView.created, nextPageView.created, true)
|
||||
:
|
||||
"End"
|
||||
}
|
||||
</TextWithIcon>
|
||||
|
||||
<TextWithIcon
|
||||
className={classes.gridCell}
|
||||
icon={<IconClick/>}
|
||||
>
|
||||
{pageView.path}
|
||||
</TextWithIcon>
|
||||
|
||||
{
|
||||
pageView.error && <>
|
||||
<TextWithIcon
|
||||
className={classes.gridCell}
|
||||
themeIconProps={{color: "red"}}
|
||||
icon={<IconBug/>}
|
||||
>
|
||||
{pageView.error.error_type}
|
||||
</TextWithIcon>
|
||||
|
||||
<TextWithIcon
|
||||
className={classes.gridCell}
|
||||
themeIconProps={{color: "red"}}
|
||||
icon={<IconInfoCircle/>}
|
||||
>
|
||||
{pageView.error.error_message}
|
||||
</TextWithIcon>
|
||||
|
||||
<TextWithIcon
|
||||
className={classes.gridCell}
|
||||
themeIconProps={{color: "red"}}
|
||||
icon={<IconStack3Filled/>}
|
||||
>
|
||||
<Code>
|
||||
{pageView.error.stack_trace}
|
||||
</Code>
|
||||
</TextWithIcon>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -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 <>
|
||||
<div className={classes.subgrid} data-error={!!errorCount}>
|
||||
<div className={classes.child}>
|
||||
<TextWithIcon icon={
|
||||
<ThemeIcon variant={"transparent"} size={"xs"} color={"gray"}>
|
||||
<IconCalendarClock/>
|
||||
</ThemeIcon>
|
||||
}>
|
||||
{pprintDateTime(session.created)}
|
||||
</TextWithIcon>
|
||||
</div>
|
||||
<TextWithIcon
|
||||
className={classes.child}
|
||||
icon={<IconCalendarClock/>}
|
||||
>
|
||||
{pprintDateTime(session.created)}
|
||||
</TextWithIcon>
|
||||
|
||||
<div className={classes.child}>
|
||||
<TextWithIcon icon={
|
||||
<ThemeIcon variant={"transparent"} size={"xs"} color={"gray"}>
|
||||
<BrowserIcon browser={session.browser_name ?? ""}/>
|
||||
</ThemeIcon>
|
||||
}>
|
||||
{session.browser_name} {session.browser_version}
|
||||
</TextWithIcon>
|
||||
</div>
|
||||
<TextWithIcon
|
||||
className={classes.child}
|
||||
icon={<BrowserIcon browser={session.browser_name ?? ""}/>}
|
||||
>
|
||||
{session.browser_name} {session.browser_version}
|
||||
</TextWithIcon>
|
||||
|
||||
<div className={classes.child}>
|
||||
<TextWithIcon icon={
|
||||
<ThemeIcon variant={"transparent"} size={"xs"} color={"gray"}>
|
||||
<OsIcon os={session.operating_system ?? ""}/>
|
||||
</ThemeIcon>
|
||||
}>
|
||||
{session.operating_system} {session.operating_system_version}
|
||||
</TextWithIcon>
|
||||
</div>
|
||||
<TextWithIcon
|
||||
className={classes.child}
|
||||
icon={<OsIcon os={session.operating_system ?? ""}/>}
|
||||
>
|
||||
{session.operating_system} {session.operating_system_version}
|
||||
</TextWithIcon>
|
||||
|
||||
<div className={classes.child}>
|
||||
<TextWithIcon icon={
|
||||
<ThemeIcon variant={"transparent"} size={"xs"} color={"gray"}>
|
||||
<IconGlobe/>
|
||||
</ThemeIcon>
|
||||
}>
|
||||
{session.geo_country_code || "--"}
|
||||
</TextWithIcon>
|
||||
</div>
|
||||
<TextWithIcon
|
||||
className={classes.child}
|
||||
icon={<IconGlobe/>}
|
||||
>
|
||||
{session.geo_country_code || "--"}
|
||||
</TextWithIcon>
|
||||
|
||||
<div className={classes.child}>
|
||||
<TextWithIcon icon={
|
||||
<ThemeIcon variant={"transparent"} size={"xs"} color={"gray"}>
|
||||
<IconLanguage/>
|
||||
</ThemeIcon>
|
||||
}>
|
||||
{session.preferred_language || "--"}
|
||||
</TextWithIcon>
|
||||
</div>
|
||||
<TextWithIcon
|
||||
className={classes.child}
|
||||
icon={<IconLanguage/>}
|
||||
>
|
||||
{session.preferred_language || "--"}
|
||||
</TextWithIcon>
|
||||
|
||||
<div className={classes.child}>
|
||||
<TextWithIcon icon={
|
||||
<ThemeIcon variant={"transparent"} size={"xs"} color={"gray"}>
|
||||
<IconClick/>
|
||||
</ThemeIcon>
|
||||
}>
|
||||
{pageViewCount ?? '--'}
|
||||
</TextWithIcon>
|
||||
</div>
|
||||
<TextWithIcon
|
||||
className={classes.child}
|
||||
icon={<IconClick/>}
|
||||
>
|
||||
{pageViewCount ?? '--'}
|
||||
</TextWithIcon>
|
||||
|
||||
<div className={classes.child}>
|
||||
<TextWithIcon icon={
|
||||
<ThemeIcon variant={"transparent"} size={"xs"} color={errorCount ? "red" : "gray"}>
|
||||
<IconBug/>
|
||||
</ThemeIcon>
|
||||
}>
|
||||
{errorCount ?? '--'}
|
||||
</TextWithIcon>
|
||||
</div>
|
||||
<TextWithIcon
|
||||
className={classes.child}
|
||||
icon={<IconBug/>}
|
||||
>
|
||||
{errorCount ?? '--'}
|
||||
</TextWithIcon>
|
||||
|
||||
<div className={`${classes.child} ${classes.alignEnd}`}>
|
||||
<Tooltip label={expanded ? "Details verbergen" : "Details anzeigen"} withArrow>
|
||||
|
@ -182,21 +161,7 @@ export default function AnalyticsSessionRow({session}: {
|
|||
</div>
|
||||
|
||||
<Collapse in={expanded} className={classes.detailsContainer}>
|
||||
<ShowDebug>
|
||||
Session Id - <Code>{session.id}</Code>
|
||||
<br/>
|
||||
created - <Code>{pprintDateTime(session.created)}</Code>
|
||||
<br/>
|
||||
updated - <Code>{pprintDateTime(session.updated)}</Code>
|
||||
<br/>
|
||||
<br/>
|
||||
IP - <Code>{session.ip_address}</Code>
|
||||
<br/>
|
||||
User Agent - <Code>{session.user_agent}</Code>
|
||||
<br/>
|
||||
Visitor Id - <Code>{session.visitor}</Code>
|
||||
</ShowDebug>
|
||||
{/*<EntryQuestionAndStatusData entry={entry}/>*/}
|
||||
<AnalyticsSessionDetail session={session}/>
|
||||
</Collapse>
|
||||
</>
|
||||
}
|
|
@ -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 }) {
|
|||
|
||||
<div className={"section-transparent center"}>
|
||||
<Text size={"xs"} c={"dimmed"}>
|
||||
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 && <>
|
||||
{" • "}
|
||||
|
|
|
@ -100,16 +100,16 @@ export default function EventListRouter({event}: { event: EventModel }) {
|
|||
</ShowDebug>
|
||||
|
||||
<Alert color={list.open ? "green" : "red"}>
|
||||
<TextWithIcon icon={list.open ? <IconLockOpen size={16}/> : <IconLock size={16}/>}>
|
||||
<TextWithIcon icon={list.open ? <IconLockOpen/> : <IconLock/>}>
|
||||
Liste ist für Anmeldungen <b>{list.open ? "geöffnet" : "geschlossen"}</b>
|
||||
</TextWithIcon>
|
||||
<br/>
|
||||
<TextWithIcon icon={<IconUserCog size={16}/>}>
|
||||
<TextWithIcon icon={<IconUserCog/>}>
|
||||
Anmeldung für Personen mit {list.onlyStuVeAccounts ?
|
||||
<><b>StuVe</b> Account</> : <>StuVe <b>und</b> Gast Account</>}
|
||||
</TextWithIcon>
|
||||
<br/>
|
||||
<TextWithIcon icon={<IconClockCog size={16}/>}>
|
||||
<TextWithIcon icon={<IconClockCog/>}>
|
||||
Überlappende Einträge sind {!list.allowOverlappingEntries && <b>nicht</b>} erlaubt
|
||||
</TextWithIcon>
|
||||
</Alert>
|
||||
|
|
|
@ -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 <Group gap={"xs"}>
|
||||
<TextWithIcon icon={
|
||||
<ThemeIcon variant={"transparent"} size={"xs"} color={"gray"}>
|
||||
<IconClock/>
|
||||
</ThemeIcon>
|
||||
}>
|
||||
<TextWithIcon icon={<IconClock/>}>
|
||||
{dayjs(start).format("HH:mm")}
|
||||
{"-"}
|
||||
{dayjs(end).format("HH:mm")}
|
||||
</TextWithIcon>
|
||||
|
||||
<TextWithIcon icon={
|
||||
<ThemeIcon variant={"transparent"} size={"xs"} color={"gray"}>
|
||||
<IconCalendar/>
|
||||
</ThemeIcon>
|
||||
}>
|
||||
<TextWithIcon icon={<IconCalendar/>}>
|
||||
{dayjs(start).format("DD.MM.YY")}
|
||||
</TextWithIcon>
|
||||
|
||||
<TextWithIcon icon={
|
||||
<ThemeIcon variant={"transparent"} size={"xs"} color={"gray"}>
|
||||
<IconHourglass/>
|
||||
</ThemeIcon>
|
||||
}>
|
||||
<TextWithIcon icon={<IconHourglass/>}>
|
||||
{duration}
|
||||
</TextWithIcon>
|
||||
</Group>
|
||||
}
|
||||
|
||||
// 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 <Group gap={'xs'}>
|
||||
<TextWithIcon icon={
|
||||
<ThemeIcon variant={"transparent"} size={"xs"} color={"gray"}>
|
||||
<IconCalendar/>
|
||||
</ThemeIcon>
|
||||
}>
|
||||
<TextWithIcon icon={<IconCalendar/>}>
|
||||
{dayjs(start).format("DD.MM.YY")}
|
||||
</TextWithIcon>
|
||||
|
||||
{"bis"}
|
||||
|
||||
<TextWithIcon icon={
|
||||
<ThemeIcon variant={"transparent"} size={"xs"} color={"gray"}>
|
||||
<IconCalendar/>
|
||||
</ThemeIcon>
|
||||
}>
|
||||
<TextWithIcon icon={<IconCalendar/>}>
|
||||
{dayjs(end).format("DD.MM.YY")}
|
||||
</TextWithIcon>
|
||||
|
||||
<TextWithIcon icon={
|
||||
<ThemeIcon variant={"transparent"} size={"xs"} color={"gray"}>
|
||||
<IconHourglass/>
|
||||
</ThemeIcon>
|
||||
}>
|
||||
<TextWithIcon icon={<IconHourglass/>}>
|
||||
{duration}
|
||||
</TextWithIcon>
|
||||
</Group>
|
||||
|
@ -80,29 +59,17 @@ export const RenderDateRange = ({start, end}: { start: Date, end: Date }) => {
|
|||
|
||||
// case different dates and times
|
||||
return <Group gap={'xs'}>
|
||||
<TextWithIcon icon={
|
||||
<ThemeIcon variant={"transparent"} size={"xs"} color={"gray"}>
|
||||
<IconCalendarClock/>
|
||||
</ThemeIcon>
|
||||
}>
|
||||
<TextWithIcon icon={<IconCalendarClock/>}>
|
||||
{dayjs(start).format("HH:mm DD.MM.YY")}
|
||||
</TextWithIcon>
|
||||
|
||||
{"bis"}
|
||||
|
||||
<TextWithIcon icon={
|
||||
<ThemeIcon variant={"transparent"} size={"xs"} color={"gray"}>
|
||||
<IconCalendarClock/>
|
||||
</ThemeIcon>
|
||||
}>
|
||||
<TextWithIcon icon={<IconCalendarClock/>}>
|
||||
{dayjs(end).format("HH:mm DD.MM.YY")}
|
||||
</TextWithIcon>
|
||||
|
||||
<TextWithIcon icon={
|
||||
<ThemeIcon variant={"transparent"} size={"xs"} color={"gray"}>
|
||||
<IconHourglass/>
|
||||
</ThemeIcon>
|
||||
}>
|
||||
<TextWithIcon icon={<IconHourglass/>}>
|
||||
{duration}
|
||||
</TextWithIcon>
|
||||
</Group>
|
||||
|
|
|
@ -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 <>
|
||||
<div className={classes.subgrid}>
|
||||
<div className={classes.child}>
|
||||
<TextWithIcon icon={
|
||||
<ThemeIcon variant={"transparent"} size={"xs"} color={"gray"}>
|
||||
<IconUser/>
|
||||
</ThemeIcon>
|
||||
}>
|
||||
<RenderUserName user={entry.expand?.user}/>
|
||||
</TextWithIcon>
|
||||
</div>
|
||||
<TextWithIcon
|
||||
className={classes.child}
|
||||
icon={<IconUser/>}
|
||||
>
|
||||
<RenderUserName user={entry.expand?.user}/>
|
||||
</TextWithIcon>
|
||||
|
||||
<Link to={`/events/e/${entry.event}/lists/overview/${entry.eventList}`} className={classes.child}>
|
||||
<TextWithIcon icon={
|
||||
<ThemeIcon variant={"transparent"} size={"xs"} color={"gray"}>
|
||||
<IconList/>
|
||||
</ThemeIcon>
|
||||
}>
|
||||
<TextWithIcon icon={<IconList/>}>
|
||||
{entry.listName}
|
||||
</TextWithIcon>
|
||||
</Link>
|
||||
|
|
|
@ -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}: {
|
|||
</Tooltip>
|
||||
|
||||
<div className={classes.entryInfo}>
|
||||
<TextWithIcon icon={
|
||||
<ThemeIcon variant={"transparent"} size={"xs"} color={"gray"}>
|
||||
<IconConfetti/>
|
||||
</ThemeIcon>
|
||||
}>
|
||||
<TextWithIcon icon={<IconConfetti/>}>
|
||||
{entry.eventName}
|
||||
</TextWithIcon>
|
||||
|
||||
<TextWithIcon icon={
|
||||
<ThemeIcon variant={"transparent"} size={"xs"} color={"gray"}>
|
||||
<IconList/>
|
||||
</ThemeIcon>
|
||||
}>
|
||||
<TextWithIcon icon={<IconList/>}>
|
||||
<Link to={`/events/s/${entry.event}?lists=${entry.eventList}`}>
|
||||
{entry.listName}
|
||||
</Link>
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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() {
|
|||
<div className={"section-transparent stack"}>
|
||||
<Center mt={"xl"} className={"stack"}>
|
||||
|
||||
<SVG className={classes.svg} aria-label={"data analysis image"}/>
|
||||
|
||||
<Title order={1} c={"blue"}>Welche Daten werden gesammelt?</Title>
|
||||
|
||||
<Anchor
|
||||
|
|
Loading…
Reference in New Issue