Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 16 additions & 13 deletions convex/_model/fowV4/aggregateFowV4TournamentData.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { Id } from '../../_generated/dataModel';
import { QueryCtx } from '../../_generated/server';
import { getRange, Range } from '../common/_helpers/getRange';
import { getMatchResultsByTournament } from '../matchResults/queries/getMatchResultsByTournament';
import { getTournamentCompetitorsByTournament } from '../tournamentCompetitors';
import { getTournamentPairings } from '../tournamentPairings';
import { createFowV4TournamentExtendedStatMap } from './createFowV4TournamentExtendedStatMap';
import { createTournamentCompetitorMetaMap } from './createTournamentCompetitorMetaMap';
import { divideFowV4BaseStats } from './divideFowV4BaseStats';
Expand All @@ -30,17 +27,23 @@ export const aggregateFowV4TournamentData = async (
range?: Range | number,
) => {
// ---- 1. Gather base data ----
const tournamentCompetitors = await getTournamentCompetitorsByTournament(ctx, { tournamentId }); // TODO: No reason to get them not-by-tournament
const tournamentPairings = await getTournamentPairings(ctx, { tournamentId });
const matchResults = await getMatchResultsByTournament(ctx, { tournamentId });
const tournamentCompetitors = await ctx.db.query('tournamentCompetitors')
.withIndex('by_tournament_id', (q) => q.eq('tournamentId', tournamentId))
.collect();
const tournamentPairings = await ctx.db.query('tournamentPairings')
.withIndex('by_tournament_id', (q) => q.eq('tournamentId', tournamentId))
.collect();
const matchResults = await ctx.db.query('matchResults')
.withIndex('by_tournament_id', (q) => q.eq('tournamentId', tournamentId))
.collect();

// ---- End of async portion ----

// ---- 2. Set-up containers to store all stats ----
const tournamentCompetitorIds = tournamentCompetitors.map((c) => c._id);
const tournamentPlayerIds = Array.from(new Set(tournamentCompetitors.reduce((acc, c) => [
...acc,
...c.players.map((p) => p.user._id),
...c.players.map((p) => p.userId),
], [] as Id<'users'>[])));
// TODO: Replace the above with a re-usable function

Expand Down Expand Up @@ -112,14 +115,14 @@ export const aggregateFowV4TournamentData = async (
// ---- 7. Compute stats for each competitor ----
for (const tournamentCompetitor of tournamentCompetitors) {
const id = tournamentCompetitor._id;
const gamesPlayed = tournamentCompetitor.players.reduce((acc, { user }) => (
acc + playerStats[user._id].gamesPlayed
const gamesPlayed = tournamentCompetitor.players.reduce((acc, { userId }) => (
acc + playerStats[userId].gamesPlayed
), 0);
const total = sumFowV4BaseStats(tournamentCompetitor.players.map(({ user }) => (
playerStats[user._id].total
const total = sumFowV4BaseStats(tournamentCompetitor.players.map(({ userId }) => (
playerStats[userId].total
)));
const total_opponent = sumFowV4BaseStats(tournamentCompetitor.players.map(({ user }) => (
playerStats[user._id].total_opponent
const total_opponent = sumFowV4BaseStats(tournamentCompetitor.players.map(({ userId }) => (
playerStats[userId].total_opponent
)));
competitorStats[id] = {
total,
Expand Down
3 changes: 1 addition & 2 deletions convex/_model/fowV4/extractFowV4MatchResultBaseStats.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Doc } from '../../_generated/dataModel';
import { DeepMatchResult } from '../matchResults';
import { calculateFowV4MatchResultScore } from './calculateFowV4MatchResultScore';
import { FowV4BaseStats } from './types';

Expand All @@ -10,7 +9,7 @@ import { FowV4BaseStats } from './types';
* @returns
*/

export const extractFowV4MatchResultBaseStats = (matchResult: Doc<'matchResults'> | DeepMatchResult): [FowV4BaseStats, FowV4BaseStats] => {
export const extractFowV4MatchResultBaseStats = (matchResult: Doc<'matchResults'>): [FowV4BaseStats, FowV4BaseStats] => {
const score = calculateFowV4MatchResultScore(matchResult);
return [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import { getAuthUserId } from '@convex-dev/auth/server';

import { Doc } from '../../../_generated/dataModel';
import { QueryCtx } from '../../../_generated/server';
import { getTournamentShallow } from '../../../_model/tournaments';
import { deepenTournamentPairing } from '../../tournamentPairings';

/**
* Checks if a match result's battle plans should be visible or not.
Expand All @@ -23,17 +21,15 @@ export const checkMatchResultBattlePlanVisibility = async (
return true;
}

const tournamentPairing = await ctx.db.get(matchResult.tournamentPairingId);

// If the match result's pairing has gone missing, treat it the same as a single match:
const tournamentPairing = await ctx.db.get(matchResult.tournamentPairingId);
if (!tournamentPairing) {
return true;
}
const deepTournamentPairing = await deepenTournamentPairing(ctx, tournamentPairing);
const tournament = await getTournamentShallow(ctx, deepTournamentPairing.tournamentId);

// If the match result is not from an on-going tournament, battle plans should be visible:
if (tournament?.status !== 'active') {
const tournament = await ctx.db.get(tournamentPairing.tournamentId);
if (!tournament || tournament?.status !== 'active') {
return true;
}

Expand All @@ -45,7 +41,7 @@ export const checkMatchResultBattlePlanVisibility = async (
}

// If the requesting user is a player within that pairing, battle plans should be visible:
if (deepTournamentPairing.playerUserIds.includes(userId)) {
if (matchResult.player0UserId === userId || matchResult.player1UserId === userId) {
return true;
}
}
Expand Down
1 change: 1 addition & 0 deletions convex/_model/matchResults/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export {
} from './queries/getMatchResult';
export {
getMatchResults,
getMatchResultsArgs,
} from './queries/getMatchResults';
export {
getMatchResultsByTournament,
Expand Down
21 changes: 16 additions & 5 deletions convex/_model/matchResults/queries/getMatchResults.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
import { paginationOptsValidator, PaginationResult } from 'convex/server';
import { Infer, v } from 'convex/values';

import { QueryCtx } from '../../../_generated/server';
import { deepenMatchResult, DeepMatchResult } from '../_helpers/deepenMatchResult';

export const getMatchResultsArgs = v.object({
paginationOpts: paginationOptsValidator,
});

export const getMatchResults = async (
ctx: QueryCtx,
): Promise<DeepMatchResult[]> => {
const matchResults = await ctx.db.query('matchResults').order('desc').collect();
return await Promise.all(matchResults.map(
async (item) => await deepenMatchResult(ctx, item),
));
args: Infer<typeof getMatchResultsArgs>,
): Promise<PaginationResult<DeepMatchResult>> => {
const results = await ctx.db.query('matchResults').order('desc').paginate(args.paginationOpts);
return {
...results,
page: await Promise.all(results.page.map(
async (item) => await deepenMatchResult(ctx, item),
)),
};
};
1 change: 1 addition & 0 deletions convex/_model/tournaments/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export enum TournamentActionKey {
StartRound = 'startRound',
EndRound = 'endRound',
End = 'end',
SubmitMatchResult = 'submitMatchResult',
}

// Helpers
Expand Down
2 changes: 1 addition & 1 deletion convex/matchResults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const getMatchResult = query({
});

export const getMatchResults = query({
args: {},
args: model.getMatchResultsArgs,
handler: model.getMatchResults,
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { useModal } from '~/modals';

export const useMatchResultCreateDialog = () => useModal('match-result-create-dialog');
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@use "/src/style/flex";

.MatchResultCreateDialog {
@include flex.column($gap: 0);
}

.Form {
padding: 1rem var(--modal-inner-gutter);
}
34 changes: 34 additions & 0 deletions src/components/MatchResultCreateDialog/MatchResultCreateDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { FowV4MatchResultForm } from '~/components/FowV4MatchResultForm';
import { Button } from '~/components/generic/Button';
import {
ControlledDialog,
DialogActions,
DialogHeader,
} from '~/components/generic/Dialog';
import { ScrollArea } from '~/components/generic/ScrollArea';
import { Separator } from '~/components/generic/Separator';
import { useMatchResultCreateDialog } from './MatchResultCreateDialog.hooks';

import styles from './MatchResultCreateDialog.module.scss';

export const MatchResultCreateDialog = (): JSX.Element => {
const { id, close } = useMatchResultCreateDialog();
return (
<ControlledDialog id={id} className={styles.MatchResultCreateDialog}>
<DialogHeader title="Create Match Result" onCancel={close} />
<Separator />
<ScrollArea type="scroll" indicatorBorders={['top', 'bottom']}>
<FowV4MatchResultForm
id="fow-v4-match-result-form"
className={styles.Form}
onSuccess={close}
/>
</ScrollArea>
<Separator />
<DialogActions>
<Button variant="secondary" onClick={close}>Cancel</Button>
<Button type="submit" form="fow-v4-match-result-form">Save</Button>
</DialogActions>
</ControlledDialog>
);
};
2 changes: 2 additions & 0 deletions src/components/MatchResultCreateDialog/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { MatchResultCreateDialog } from './MatchResultCreateDialog';
export { useMatchResultCreateDialog } from './MatchResultCreateDialog.hooks';
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { TournamentActionKey } from '~/api';
import { useAuth } from '~/components/AuthProvider';
import { ConfirmationDialogData } from '~/components/ConfirmationDialog';
import { Warning } from '~/components/generic/Warning';
import { useMatchResultCreateDialog } from '~/components/MatchResultCreateDialog';
import { toast } from '~/components/ToastProvider';
import { useTournament } from '~/components/TournamentProvider';
import { useGetTournamentCompetitorsByTournament } from '~/services/tournamentCompetitors';
Expand Down Expand Up @@ -48,6 +49,7 @@ export const useActions = (openDialog: (data?: ConfirmationDialogData) => void):
const configureTournamentRound = (): void => {
navigate(generatePath(PATHS.tournamentPairings, { id: tournament._id }));
};
const { open: openMatchResultCreateDialog } = useMatchResultCreateDialog();
const { mutation: deleteTournament } = useDeleteTournament({
onSuccess: (): void => {
toast.success(`${tournament.title} deleted!`);
Expand Down Expand Up @@ -171,6 +173,12 @@ export const useActions = (openDialog: (data?: ConfirmationDialogData) => void):
available: isOrganizer && isBetweenRounds && hasNextRound && (nextRoundPairings ?? []).length > 0,
handler: () => startTournamentRound({ id: tournament._id }),
},
{
key: TournamentActionKey.SubmitMatchResult,
label: 'Submit Match Result',
available: !!openRound,
handler: () => openMatchResultCreateDialog(),
},
{
key: TournamentActionKey.EndRound,
label: `End Round ${currentRoundLabel}`,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ReactNode } from 'react';

import { ConfirmationDialog, useConfirmationDialog } from '~/components/ConfirmationDialog';
import { MatchResultCreateDialog } from '~/components/MatchResultCreateDialog';
import { TournamentActionsContext } from './TournamentActionsProvider.context';
import { useActions } from './TournamentActionsProvider.hooks';

Expand All @@ -17,6 +18,7 @@ export const TournamentActionsProvider = ({
<TournamentActionsContext.Provider value={actions}>
{children}
<ConfirmationDialog id={id} />
<MatchResultCreateDialog />
</TournamentActionsContext.Provider>
);
};
4 changes: 2 additions & 2 deletions src/pages/DashboardPage/DashboardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ export const DashboardPage = (): JSX.Element => {
<PageWrapper title="Dashboard" fitToWindow footer={!isDesktop ? (
<div className={styles.DashboardPage_Tabs}>
{Object.entries(tabs).map(([key, icon]) => (
<div className={styles.DashboardPage_TabTrigger}>
<Button key={key} variant="ghost" size="large" onClick={() => setView(key as TabKey)}>
<div key={key} className={styles.DashboardPage_TabTrigger}>
<Button variant="ghost" size="large" onClick={() => setView(key as TabKey)}>
{icon}
</Button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,12 @@ export const MatchResultsCard = ({
{(matchResults ?? []).length ? (
<ScrollArea className={styles.MatchResultsCard_ScrollArea}>
<div className={styles.MatchResultsCard_List}>
{(matchResults ?? []).slice(0, 5).map((matchResult) => (
<MatchResultCard matchResult={matchResult} />
{(matchResults ?? []).map((matchResult) => (
<MatchResultCard key={matchResult._id} matchResult={matchResult} />
))}
{/* {(tournaments ?? []).length > 5 && ( */}
<div className={styles.MatchResultsCard_List_ViewAllButton} onClick={handleViewMore}>
<Button>View All<ChevronRight /></Button>
</div>
{/* )} */}
</div>
</ScrollArea>
) : (
Expand Down
6 changes: 6 additions & 0 deletions src/pages/MatchResultsPage/MatchResultsPage.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,10 @@
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
grid-template-rows: min-content;
gap: 1rem;

&_LoadMoreButton {
@include flex.centered;

padding: 2rem;
}
}
8 changes: 7 additions & 1 deletion src/pages/MatchResultsPage/MatchResultsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ export const MatchResultsPage = (): JSX.Element => {
const showFilters = false;
const showAddMatchResultButton = !!user;
const showButtonText = useWindowWidth() > MIN_WIDTH_TABLET;
const { data: matchResults } = useGetMatchResults({});
const { data: matchResults, loadMore } = useGetMatchResults({});
const handleLoadMore = (): void => {
loadMore(10);
};
return (
<PageWrapper title="Match Results">
{showFilters && (
Expand Down Expand Up @@ -51,6 +54,9 @@ export const MatchResultsPage = (): JSX.Element => {
<MatchResultCard key={matchResult._id} matchResult={matchResult} />
))}
</div>
<div className={styles.List_LoadMoreButton} onClick={handleLoadMore}>
<Button>Load More</Button>
</div>
{showAddMatchResultButton && (
<CheckInMatchDialog>
<FloatingActionButton>
Expand Down
8 changes: 6 additions & 2 deletions src/services/matchResults.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { api } from '~/api';
import { createMutationHook, createQueryHook } from '~/services/utils';
import {
createMutationHook,
createPaginatedQueryHook,
createQueryHook,
} from '~/services/utils';

// Basic Queries
export const useGetMatchResult = createQueryHook(api.matchResults.getMatchResult);
export const useGetMatchResults = createQueryHook(api.matchResults.getMatchResults);
export const useGetMatchResults = createPaginatedQueryHook(api.matchResults.getMatchResults);

// Special Queries
export const useGetMatchResultsByTournament = createQueryHook(api.matchResults.getMatchResultsByTournament);
Expand Down
35 changes: 35 additions & 0 deletions src/services/utils/createPaginatedQueryHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useRef } from 'react';
import { usePaginatedQuery } from 'convex/react';
import {
BetterOmit,
Expand,
FunctionArgs,
FunctionReference,
} from 'convex/server';

type QueryFn = FunctionReference<'query'>;

export const createPaginatedQueryHook = <T extends QueryFn>(queryFn: T) => {
function isArgs(args: unknown): args is 'skip' | Expand<BetterOmit<FunctionArgs<T>, 'paginationOpts'>> {
return args !== 'skip' && args !== undefined && args !== null;
}
return (args: Omit<T['_args'], 'paginationOpts'> | 'skip') => {
if (!isArgs(args)) {
return {
data: undefined,
loading: false,
loadMore: (_n: number) => undefined,
};
}
const { results: data, isLoading, loadMore } = usePaginatedQuery(queryFn, args, { initialNumItems: 10 });
const stored = useRef(data);
if (data !== undefined) {
stored.current = data;
}
return {
data: stored.current,
loading: isLoading || stored.current === undefined,
loadMore,
};
};
};
1 change: 1 addition & 0 deletions src/services/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export {
type MutationFn,
type MutationHookConfig,
} from './createMutationHook';
export { createPaginatedQueryHook } from './createPaginatedQueryHook';
export { createQueryHook } from './createQueryHook';