diff --git a/src/components/sheet/slide-panel/slide-panel.tsx b/src/components/sheet/slide-panel/slide-panel.tsx
index c2f1f06..2bf5f83 100644
--- a/src/components/sheet/slide-panel/slide-panel.tsx
+++ b/src/components/sheet/slide-panel/slide-panel.tsx
@@ -172,6 +172,8 @@ const SlidePanel = ({
onChange={setTempValue}
{...(panelConfig.componentProps || {})}
/>
+
+
>
)}
diff --git a/src/features/activities/components/activities-table.tsx b/src/features/activities/components/activities-table.tsx
index 8cb8529..2b07a88 100644
--- a/src/features/activities/components/activities-table.tsx
+++ b/src/features/activities/components/activities-table.tsx
@@ -8,9 +8,10 @@ import {
Container,
Divider,
UnstyledButton,
- Badge,
Select,
Pagination,
+ Code,
+ Alert,
} from "@mantine/core";
import {
MagnifyingGlassIcon,
@@ -18,15 +19,19 @@ import {
CaretDownIcon,
CheckIcon,
XIcon,
+ ChecksIcon,
} from "@phosphor-icons/react";
import { Activity, ActivitySearchParams } from "../types";
import { useActivities } from "../queries";
+import Sheet from "@/components/sheet/sheet";
+import { useSheet } from "@/hooks/use-sheet";
interface ActivityListItemProps {
activity: Activity;
+ onClick: () => void;
}
-const ActivityListItem = memo(({ activity }: ActivityListItemProps) => {
+const ActivityListItem = memo(({ activity, onClick }: ActivityListItemProps) => {
const playerName = typeof activity.player === "object" && activity.player
? `${activity.player.first_name} ${activity.player.last_name}`
: "System";
@@ -37,7 +42,22 @@ const ActivityListItem = memo(({ activity }: ActivityListItemProps) => {
};
return (
-
+
@@ -68,59 +88,183 @@ const ActivityListItem = memo(({ activity }: ActivityListItemProps) => {
)}
-
+
);
});
-const ActivitiesResults = ({ searchParams, page, setPage }: any) => {
+ActivityListItem.displayName = "ActivityListItem";
+
+interface ActivityDetailsSheetProps {
+ activity: Activity | null;
+ isOpen: boolean;
+ onClose: () => void;
+}
+
+const ActivityDetailsSheet = memo(({ activity, isOpen, onClose }: ActivityDetailsSheetProps) => {
+ if (!activity) return null;
+
+ const playerName = typeof activity.player === "object" && activity.player
+ ? `${activity.player.first_name} ${activity.player.last_name}`
+ : "System";
+
+ const formatDate = (dateStr: string) => {
+ const date = new Date(dateStr);
+ return date.toLocaleString();
+ };
+
+ return (
+
+
+
+
+ Function Name
+
+ {activity.name}
+
+
+
+
+ Status
+
+
+ {activity.success ? (
+ <>
+
+
+ Success
+
+ >
+ ) : (
+ <>
+
+
+ Failed
+
+ >
+ )}
+
+
+
+
+
+ Player
+
+ {playerName}
+
+
+
+
+ Duration
+
+ {activity.duration}ms
+
+
+
+
+ Created
+
+ {formatDate(activity.created)}
+
+
+ {activity.user_agent && (
+
+
+ User Agent
+
+
+ {activity.user_agent}
+
+
+ )}
+
+ {activity.error && (
+
+
+ Error Message
+
+
+
+ {activity.error}
+
+
+
+ )}
+
+ {activity.arguments && (
+
+
+ Arguments
+
+
+ {JSON.stringify(activity.arguments, null, 2)}
+
+
+ )}
+
+
+ );
+});
+
+ActivityDetailsSheet.displayName = "ActivityDetailsSheet";
+
+const ActivitiesResults = ({ searchParams, page, setPage, onActivityClick }: any) => {
const { data: result } = useActivities(searchParams);
return (
<>
- {result.items.map((activity: Activity, index: number) => (
-
-
- {index < result.items.length - 1 && }
-
- ))}
-
-
- {result.items.length === 0 && (
-
- No activities found
-
- )}
-
- {result.totalPages > 1 && (
-
- (
+
+ onActivityClick(activity)}
/>
-
- )}
+ {index < result.items.length - 1 && }
+
+ ))}
+
+
+ {result.items.length === 0 && (
+
+ No activities found
+
+ )}
+
+ {result.totalPages > 1 && (
+
+
+
+ )}
>
- )
+ );
};
export const ActivitiesTable = () => {
const [page, setPage] = useState(1);
- const [perPage, setPerPage] = useState(100);
const [search, setSearch] = useState("");
const [successFilter, setSuccessFilter] = useState(null);
const [sortBy, setSortBy] = useState("-created");
+ const [selectedActivity, setSelectedActivity] = useState(null);
+
+ const {
+ isOpen: detailsOpened,
+ open: openDetails,
+ close: closeDetails,
+ } = useSheet();
const searchParams: ActivitySearchParams = useMemo(
() => ({
page,
- perPage,
+ perPage: 100,
name: search || undefined,
success: successFilter === "success" ? true : successFilter === "failure" ? false : undefined,
sortBy,
}),
- [page, perPage, search, successFilter, sortBy]
+ [page, search, successFilter, sortBy]
);
const { data: result } = useActivities(searchParams);
@@ -139,6 +283,16 @@ export const ActivitiesTable = () => {
return null;
};
+ const handleActivityClick = (activity: Activity) => {
+ setSelectedActivity(activity);
+ openDetails();
+ };
+
+ const handleCloseDetails = () => {
+ setSelectedActivity(null);
+ closeDetails();
+ };
+
return (
@@ -214,9 +368,19 @@ export const ActivitiesTable = () => {
-
-
+
+
+
);
};
diff --git a/src/features/core/components/layout.tsx b/src/features/core/components/layout.tsx
index 6e293b5..87fdb87 100644
--- a/src/features/core/components/layout.tsx
+++ b/src/features/core/components/layout.tsx
@@ -42,7 +42,7 @@ const Layout: React.FC = ({ children }) => {
pos='relative'
h='100%'
mah='100%'
- pb={{ base: 65, md: 0 }}
+ pb={{ base: 65, sm: 0 }}
px={{ base: 0.01, sm: 100, md: 200, lg: 300 }}
maw='100dvw'
style={{ transition: 'none', overflow: 'hidden' }}
diff --git a/src/features/matches/components/match-list.tsx b/src/features/matches/components/match-list.tsx
index e883303..d441557 100644
--- a/src/features/matches/components/match-list.tsx
+++ b/src/features/matches/components/match-list.tsx
@@ -9,7 +9,7 @@ interface MatchListProps {
const MatchList = ({ matches }: MatchListProps) => {
const filteredMatches = matches?.filter(match =>
match.home && match.away && !match.bye && match.status != "tbd"
- ) || [];
+ ).sort((a, b) => a.start_time < b.start_time ? 1 : -1) || [];
if (!filteredMatches.length) {
return undefined;
diff --git a/src/features/tournaments/components/podium.tsx b/src/features/tournaments/components/podium.tsx
new file mode 100644
index 0000000..4fa5254
--- /dev/null
+++ b/src/features/tournaments/components/podium.tsx
@@ -0,0 +1,112 @@
+import { Stack, Group, Text, ThemeIcon, Box, Center } from "@mantine/core";
+import { CrownIcon, MedalIcon } from "@phosphor-icons/react";
+import { Tournament } from "../types";
+
+interface PodiumProps {
+ tournament: Tournament;
+}
+
+export const Podium = ({ tournament }: PodiumProps) => {
+ if (!tournament.first_place) {
+ return (
+
+
+
+ Podium will appear here when the tournament is over
+
+
+
+ );
+ }
+
+ return (
+
+ {tournament.first_place && (
+
+
+
+
+
+
+ {tournament.first_place.name}
+
+
+ {tournament.first_place.players?.map((player) => (
+
+ {player.first_name} {player.last_name}
+
+ ))}
+
+
+
+ )}
+
+ {tournament.second_place && (
+
+
+
+
+
+
+ {tournament.second_place.name}
+
+
+ {tournament.second_place.players?.map((player) => (
+
+ {player.first_name} {player.last_name}
+
+ ))}
+
+
+
+ )}
+
+ {tournament.third_place && (
+
+
+
+
+
+
+ {tournament.third_place.name}
+
+
+ {tournament.third_place.players?.map((player) => (
+
+ {player.first_name} {player.last_name}
+
+ ))}
+
+
+
+ )}
+
+ );
+};
diff --git a/src/features/tournaments/components/started-tournament/index.tsx b/src/features/tournaments/components/started-tournament/index.tsx
index 768b0e7..6e23dc6 100644
--- a/src/features/tournaments/components/started-tournament/index.tsx
+++ b/src/features/tournaments/components/started-tournament/index.tsx
@@ -4,11 +4,12 @@ import { useAuth } from "@/contexts/auth-context";
import { Box, Divider, Stack, Text, Card, Center } from "@mantine/core";
import { Carousel } from "@mantine/carousel";
import ListLink from "@/components/list-link";
-import { TreeStructureIcon, UsersIcon, ClockIcon } from "@phosphor-icons/react";
+import { TreeStructureIcon, UsersIcon, ClockIcon, TrophyIcon } from "@phosphor-icons/react";
import TeamListButton from "../upcoming-tournament/team-list-button";
import RulesListButton from "../upcoming-tournament/rules-list-button";
import MatchCard from "@/features/matches/components/match-card";
import Header from "./header";
+import { Podium } from "../podium";
const StartedTournament: React.FC<{ tournament: Tournament }> = ({
tournament,
@@ -22,6 +23,20 @@ const StartedTournament: React.FC<{ tournament: Tournament }> = ({
[tournament.matches]
);
+ const isTournamentOver = useMemo(() => {
+ const matches = tournament.matches || [];
+ if (matches.length === 0) return false;
+
+ const nonByeMatches = matches.filter((match) => !(match.status === 'tbd' && match.bye === true));
+ if (nonByeMatches.length === 0) return false;
+
+ const finalsMatch = nonByeMatches.reduce((highest, current) =>
+ (!highest || current.lid > highest.lid) ? current : highest
+ );
+
+ return finalsMatch?.status === 'ended';
+ }, [tournament.matches]);
+
return (
@@ -42,6 +57,10 @@ const StartedTournament: React.FC<{ tournament: Tournament }> = ({
))}
+ ) : isTournamentOver ? (
+
+
+
) : (
diff --git a/src/features/tournaments/components/tournament-stats.tsx b/src/features/tournaments/components/tournament-stats.tsx
index 3681bc4..fa59141 100644
--- a/src/features/tournaments/components/tournament-stats.tsx
+++ b/src/features/tournaments/components/tournament-stats.tsx
@@ -146,12 +146,11 @@ export const TournamentStats = memo(({ tournament }: TournamentStatsProps) => {
};
const teamStatsWithCalculations = useMemo(() => {
- return sortedTeamStats.map((stat, index) => ({
+ return sortedTeamStats.map((stat) => ({
...stat,
- index,
winPercentage: stat.matches > 0 ? (stat.wins / stat.matches) * 100 : 0,
avgCupsPerMatch: stat.matches > 0 ? stat.total_cups_made / stat.matches : 0,
- }));
+ })).sort((a, b) => b.winPercentage - a.winPercentage);;
}, [sortedTeamStats]);
const renderTeamStatsTable = () => {
@@ -170,23 +169,14 @@ export const TournamentStats = memo(({ tournament }: TournamentStatsProps) => {
return (
Results
- {teamStatsWithCalculations.map((stat) => {
+ Sorted by win percentage
+ {teamStatsWithCalculations.map((stat, index) => {
return (
@@ -194,12 +184,12 @@ export const TournamentStats = memo(({ tournament }: TournamentStatsProps) => {
- #{stat.index + 1}
+ #{index + 1}
{stat.team_name}
- {stat.index === 0 && isComplete && (
+ {index === 0 && isComplete && (
@@ -259,7 +249,7 @@ export const TournamentStats = memo(({ tournament }: TournamentStatsProps) => {
- {stat.index < teamStatsWithCalculations.length - 1 && }
+ {index < teamStatsWithCalculations.length - 1 && }
);
})}
diff --git a/src/features/tournaments/server.ts b/src/features/tournaments/server.ts
index 2b9a9fd..5ec212c 100644
--- a/src/features/tournaments/server.ts
+++ b/src/features/tournaments/server.ts
@@ -6,6 +6,7 @@ import { logger } from ".";
import { z } from "zod";
import { toServerResult } from "@/lib/tanstack-query/utils/to-server-result";
import { serverFnLoggingMiddleware } from "@/utils/activities";
+import { fa } from "zod/v4/locales";
export const listTournaments = createServerFn()
.middleware([superTokensFunctionMiddleware])
@@ -64,6 +65,14 @@ export const enrollTeam = createServerFn()
// throw new Error('You do not have permission to enroll this team');
//}
+ const freeAgents = await pbAdmin.getFreeAgents(tournamentId);
+ for (const player of team.players || []) {
+ const isFreeAgent = freeAgents.some(fa => fa.player?.id === player.id);
+ if (isFreeAgent) {
+ await pbAdmin.unenrollFreeAgent(player.id, tournamentId);
+ }
+ }
+
logger.info('Enrolling team in tournament', { tournamentId, teamId, userId });
const tournament = await pbAdmin.enrollTeam(tournamentId, teamId);
return tournament;
diff --git a/src/lib/mantine/mantine-provider.tsx b/src/lib/mantine/mantine-provider.tsx
index f71c344..2c5c382 100644
--- a/src/lib/mantine/mantine-provider.tsx
+++ b/src/lib/mantine/mantine-provider.tsx
@@ -57,7 +57,7 @@ const MantineProvider = ({ children }: { children: React.ReactNode }) => {
setIsHydrated(true);
}, []);
- const colorScheme = isHydrated ? metadata.colorScheme || "dark" : "dark";
+ const colorScheme = isHydrated ? metadata.colorScheme || "auto" : "auto";
const primaryColor = isHydrated ? metadata.accentColor || "blue" : "blue";
return (
diff --git a/src/lib/mantine/themes/default.ts b/src/lib/mantine/themes/default.ts
deleted file mode 100644
index 3052857..0000000
--- a/src/lib/mantine/themes/default.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import { Card, Container, createTheme, Paper, rem, Select } from "@mantine/core";
-import type { MantineThemeOverride } from "@mantine/core";
-
-const CONTAINER_SIZES: Record = {
- xxs: rem("200px"),
- xs: rem("300px"),
- sm: rem("400px"),
- md: rem("500px"),
- lg: rem("600px"),
- xl: rem("1400px"),
- xxl: rem("1600px"),
-};
-
-export const defaultTheme: MantineThemeOverride = createTheme({
- scale: 1.1,
- autoContrast: true,
- fontSizes: {
- xs: rem("12px"),
- sm: rem("14px"),
- md: rem("16px"),
- lg: rem("18px"),
- xl: rem("20px"),
- "2xl": rem("24px"),
- "3xl": rem("30px"),
- "4xl": rem("36px"),
- "5xl": rem("48px"),
- },
- spacing: {
- "3xs": rem("4px"),
- "2xs": rem("8px"),
- xs: rem("10px"),
- sm: rem("12px"),
- md: rem("16px"),
- lg: rem("20px"),
- xl: rem("24px"),
- "2xl": rem("28px"),
- "3xl": rem("32px"),
- },
- primaryColor: "red",
- components: {
- Container: Container.extend({
- vars: (_, { size, fluid }) => ({
- root: {
- "--container-size": fluid
- ? "100%"
- : size !== undefined && size in CONTAINER_SIZES
- ? CONTAINER_SIZES[size]
- : rem(size),
- },
- }),
- }),
- Paper: Paper.extend({
- defaultProps: {
- p: "md",
- shadow: "xl",
- radius: "md",
- withBorder: true,
- },
- }),
-
- Card: Card.extend({
- defaultProps: {
- p: "xl",
- shadow: "xl",
- radius: "var(--mantine-radius-default)",
- withBorder: true,
- },
- }),
- Select: Select.extend({
- defaultProps: {
- checkIconPosition: "right",
- },
- }),
- },
- other: {
- style: "mantine",
- },
-});
diff --git a/src/lib/pocketbase/services/badges.ts b/src/lib/pocketbase/services/badges.ts
index 3ae8546..3fc7dec 100644
--- a/src/lib/pocketbase/services/badges.ts
+++ b/src/lib/pocketbase/services/badges.ts
@@ -63,7 +63,9 @@ export function createBadgesService(pb: PocketBase) {
},
async clearAllBadgeProgress(): Promise {
- const existingProgress = await pb.collection("badge_progress").getFullList();
+ const existingProgress = await pb.collection("badge_progress").getFullList({
+ filter: 'badge.type != "manual"',
+ });
for (const progress of existingProgress) {
await pb.collection("badge_progress").delete(progress.id);
}