diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts index ee3ec3af..0b24d09b 100644 --- a/convex/_generated/api.d.ts +++ b/convex/_generated/api.d.ts @@ -200,6 +200,7 @@ import type * as _model_tournamentRegistrations_queries_getTournamentRegistratio import type * as _model_tournamentRegistrations_queries_getTournamentRegistrationsByTournament from "../_model/tournamentRegistrations/queries/getTournamentRegistrationsByTournament.js"; import type * as _model_tournamentRegistrations_queries_getTournamentRegistrationsByUser from "../_model/tournamentRegistrations/queries/getTournamentRegistrationsByUser.js"; import type * as _model_tournamentRegistrations_table from "../_model/tournamentRegistrations/table.js"; +import type * as _model_tournamentRegistrations_triggers from "../_model/tournamentRegistrations/triggers.js"; import type * as _model_tournamentRegistrations_types from "../_model/tournamentRegistrations/types.js"; import type * as _model_tournamentResults__helpers_aggregateTournamentData from "../_model/tournamentResults/_helpers/aggregateTournamentData.js"; import type * as _model_tournamentResults__helpers_applyScoreAdjustments from "../_model/tournamentResults/_helpers/applyScoreAdjustments.js"; @@ -529,6 +530,7 @@ declare const fullApi: ApiFromModules<{ "_model/tournamentRegistrations/queries/getTournamentRegistrationsByTournament": typeof _model_tournamentRegistrations_queries_getTournamentRegistrationsByTournament; "_model/tournamentRegistrations/queries/getTournamentRegistrationsByUser": typeof _model_tournamentRegistrations_queries_getTournamentRegistrationsByUser; "_model/tournamentRegistrations/table": typeof _model_tournamentRegistrations_table; + "_model/tournamentRegistrations/triggers": typeof _model_tournamentRegistrations_triggers; "_model/tournamentRegistrations/types": typeof _model_tournamentRegistrations_types; "_model/tournamentResults/_helpers/aggregateTournamentData": typeof _model_tournamentResults__helpers_aggregateTournamentData; "_model/tournamentResults/_helpers/applyScoreAdjustments": typeof _model_tournamentResults__helpers_applyScoreAdjustments; diff --git a/convex/_model/tournamentCompetitors/triggers.ts b/convex/_model/tournamentCompetitors/triggers.ts index d5a2e067..43a0624f 100644 --- a/convex/_model/tournamentCompetitors/triggers.ts +++ b/convex/_model/tournamentCompetitors/triggers.ts @@ -1,41 +1,39 @@ -import { ConvexError } from 'convex/values'; +import isEqual from 'fast-deep-equal/es6'; import { MutationCtx } from '../../_generated/server'; -import { getErrorMessage } from '../common/errors'; +import { getDocStrict } from '../common/_helpers/getDocStrict'; import { TriggerChange } from '../common/types'; import { refreshTournamentResult } from '../tournamentResults'; /** * Trigger refresh of tournament results if a tournament competitor is modified. - * - * Several types of changes could cause this to be necessary: - * - Score adjustments altered; - * - Roster (registrations) changed; */ export const refreshTournamentResults = async ( ctx: MutationCtx, change: TriggerChange<'tournamentCompetitors'>, ): Promise => { - const { newDoc } = change; + const { newDoc, oldDoc } = change; - // Ignore if competitor was deleted: - if (!newDoc) { - return; + // For updates, skip if no ranking-relevant fields changed: + if (newDoc && oldDoc) { + if (isEqual(newDoc.scoreAdjustments, oldDoc.scoreAdjustments)) { + return; + } } // DON'T ALTER RESULTS OF ARCHIVED TOURNAMENTS! - // Get the relevant tournament pairing to identify round: - const tournament = await ctx.db.get(newDoc.tournamentId); - if (!tournament) { - throw new ConvexError(getErrorMessage('TOURNAMENT_NOT_FOUND')); + const doc = newDoc ?? oldDoc; + if (!doc) { + return; // In theory either the new or old doc will exist, otherwise how did this trigger? } + const tournament = await getDocStrict(ctx, doc.tournamentId); if (tournament.status === 'archived') { return; // No need to throw an error but also don't change anything. } // Refresh all existing tournament results: const tournamentResults = await ctx.db.query('tournamentResults') - .withIndex('by_tournament_round', (q) => q.eq('tournamentId', newDoc.tournamentId)) + .withIndex('by_tournament_round', (q) => q.eq('tournamentId', doc.tournamentId)) .collect(); for (const tournamentResult of tournamentResults) { diff --git a/convex/_model/tournamentRegistrations/index.ts b/convex/_model/tournamentRegistrations/index.ts index a6208b40..01b352db 100644 --- a/convex/_model/tournamentRegistrations/index.ts +++ b/convex/_model/tournamentRegistrations/index.ts @@ -33,6 +33,10 @@ export { updateTournamentRegistrationArgs, } from './mutations/updateTournamentRegistration'; +// Triggers +// (Grouped/namespaced so they can more easily be merged in functions.ts with other models.) +export * as tournamentRegistrationTriggers from './triggers'; + // Queries export { getTournamentRegistrationByTournamentUser, diff --git a/convex/_model/tournamentRegistrations/mutations/updateTournamentRegistration.ts b/convex/_model/tournamentRegistrations/mutations/updateTournamentRegistration.ts index e485d58c..99e8a062 100644 --- a/convex/_model/tournamentRegistrations/mutations/updateTournamentRegistration.ts +++ b/convex/_model/tournamentRegistrations/mutations/updateTournamentRegistration.ts @@ -5,13 +5,16 @@ import { } from 'convex/values'; import { MutationCtx } from '../../../_generated/server'; +import { getDocStrict } from '../../common/_helpers/getDocStrict'; import { getErrorMessage } from '../../common/errors'; +import { scoreAdjustment } from '../../common/scoreAdjustment'; import { getAvailableActions, TournamentRegistrationActionKey } from '../_helpers/getAvailableActions'; import { detailFields } from '../table'; export const updateTournamentRegistrationArgs = v.object({ _id: v.id('tournamentRegistrations'), details: v.optional(detailFields), + scoreAdjustments: v.optional(v.array(scoreAdjustment)), }); export const updateTournamentRegistration = async ( @@ -19,13 +22,10 @@ export const updateTournamentRegistration = async ( args: Infer, ): Promise => { const { _id, ...updated } = args; - + // ---- AUTH & VALIDATION CHECK ---- - const doc = await ctx.db.get(_id); - if (!doc) { - throw new ConvexError(getErrorMessage('TOURNAMENT_REGISTRATION_NOT_FOUND')); - } - const availableActions = await getAvailableActions(ctx, doc); + const existingDoc = await getDocStrict(ctx, _id); + const availableActions = await getAvailableActions(ctx, existingDoc); if (!availableActions.includes(TournamentRegistrationActionKey.Edit)) { throw new ConvexError(getErrorMessage('USER_DOES_NOT_HAVE_PERMISSION')); } diff --git a/convex/_model/tournamentRegistrations/table.ts b/convex/_model/tournamentRegistrations/table.ts index 5cae739f..3b8f6746 100644 --- a/convex/_model/tournamentRegistrations/table.ts +++ b/convex/_model/tournamentRegistrations/table.ts @@ -3,6 +3,7 @@ import { v } from 'convex/values'; import { alignment } from '../common/alignment'; import { faction } from '../common/faction'; +import { scoreAdjustment } from '../common/scoreAdjustment'; export const detailFields = v.object({ alignment: v.optional(alignment), @@ -14,6 +15,7 @@ export default defineTable({ tournamentId: v.id('tournaments'), tournamentCompetitorId: v.id('tournamentCompetitors'), details: v.optional(detailFields), + scoreAdjustments: v.optional(v.array(scoreAdjustment)), active: v.optional(v.boolean()), confirmed: v.optional(v.boolean()), modifiedAt: v.optional(v.number()), diff --git a/convex/_model/tournamentRegistrations/triggers.ts b/convex/_model/tournamentRegistrations/triggers.ts new file mode 100644 index 00000000..6e77e9a8 --- /dev/null +++ b/convex/_model/tournamentRegistrations/triggers.ts @@ -0,0 +1,52 @@ +import isEqual from 'fast-deep-equal/es6'; + +import { MutationCtx } from '../../_generated/server'; +import { getDocStrict } from '../common/_helpers/getDocStrict'; +import { TriggerChange } from '../common/types'; +import { refreshTournamentResult } from '../tournamentResults'; + +/** + * Trigger refresh of tournament results if a tournament registration is modified. + * + * Only refreshes when fields that affect ranking change: + * - scoreAdjustments: directly affects ranking factors + * - tournamentCompetitorId: affects which competitor's aggregated score this feeds into + */ +export const refreshTournamentResults = async ( + ctx: MutationCtx, + change: TriggerChange<'tournamentRegistrations'>, +): Promise => { + const { newDoc, oldDoc } = change; + + // For updates, skip if no ranking-relevant fields changed: + if (newDoc && oldDoc) { + if (isEqual({ + scoreAdjustments: newDoc.scoreAdjustments, + tournamentCompetitorId: newDoc.tournamentCompetitorId, + }, { + scoreAdjustments: oldDoc.scoreAdjustments, + tournamentCompetitorId: oldDoc.tournamentCompetitorId, + })) { + return; + } + } + + // DON'T ALTER RESULTS OF ARCHIVED TOURNAMENTS! + const doc = newDoc ?? oldDoc; + if (!doc) { + return; // In theory either the new or old doc will exist, otherwise how did this trigger? + } + const tournament = await getDocStrict(ctx, doc.tournamentId); + if (tournament.status === 'archived') { + return; // No need to throw an error but also don't change anything. + } + + // Refresh all existing tournament results: + const tournamentResults = await ctx.db.query('tournamentResults') + .withIndex('by_tournament_round', (q) => q.eq('tournamentId', doc.tournamentId)) + .collect(); + + for (const tournamentResult of tournamentResults) { + await refreshTournamentResult(ctx, tournamentResult); + } +}; diff --git a/convex/_model/tournamentResults/mutations/refreshTournamentResult.ts b/convex/_model/tournamentResults/mutations/refreshTournamentResult.ts index aa0d7321..2e96e9a8 100644 --- a/convex/_model/tournamentResults/mutations/refreshTournamentResult.ts +++ b/convex/_model/tournamentResults/mutations/refreshTournamentResult.ts @@ -8,6 +8,7 @@ import { Id } from '../../../_generated/dataModel'; import { MutationCtx } from '../../../_generated/server'; import { createSortByRanking } from '../../common/_helpers/gameSystem/createSortByRanking'; import { getErrorMessage } from '../../common/errors'; +import { ScoreAdjustment } from '../../common/types'; import { getTournamentShallow } from '../../tournaments'; import { aggregateTournamentData } from '../_helpers/aggregateTournamentData'; import { applyScoreAdjustments } from '../_helpers/applyScoreAdjustments'; @@ -30,28 +31,47 @@ export const refreshTournamentResult = async ( const tournamentCompetitors = await ctx.db.query('tournamentCompetitors') .withIndex('by_tournament_id', (q) => q.eq('tournamentId', args.tournamentId)) .collect(); + const tournamentRegistrations = await ctx.db.query('tournamentRegistrations') + .withIndex('by_tournament', (q) => q.eq('tournamentId', args.tournamentId)) + .collect(); // ---- AGGREGATE RANKING DATA ---- const { registrations, competitors } = await aggregateTournamentData(ctx, tournament, args.round); // ---- APPLY SCORE ADJUSTMENTS ---- - const adjustedCompetitors = competitors.map((c) => { - const competitor = tournamentCompetitors.find((w) => w._id === c.id); - const adjustedRankingFactors = applyScoreAdjustments( - c.rankingFactors, - competitor?.scoreAdjustments ?? [], - args.round, - ); - return { - ...c, - rankingFactors: adjustedRankingFactors, - }; - }); + + // Precompute lookup maps for O(1) access during the adjustment passes: + const regById = new Map(tournamentRegistrations.map((r) => [r._id, r])); + const compById = new Map(tournamentCompetitors.map((c) => [c._id, c])); + const registrationsByCompetitorId = new Map(); + for (const reg of tournamentRegistrations) { + const list = registrationsByCompetitorId.get(reg.tournamentCompetitorId) ?? []; + list.push(reg); + registrationsByCompetitorId.set(reg.tournamentCompetitorId, list); + } + + // Registrations: + const adjustedRegistrations = registrations.map((r) => ({ + ...r, + rankingFactors: applyScoreAdjustments(r.rankingFactors, regById.get(r.id)?.scoreAdjustments ?? [], args.round), + })); + + // Competitors (all player adjustments plus competitor adjustments): + const adjustedCompetitors = competitors.map((c) => ({ + ...c, + rankingFactors: applyScoreAdjustments(c.rankingFactors, [ + ...(registrationsByCompetitorId.get(c.id) ?? []).reduce((acc, r) => [ + ...acc, + ...(r.scoreAdjustments ?? []), + ], [] as ScoreAdjustment[]), + ...(compById.get(c.id)?.scoreAdjustments ?? []), + ], args.round), + })); // ---- SORT USING RANKING FACTORS ---- const sortByRanking = createSortByRanking(tournament.gameSystem, tournament.rankingFactors); const result = { - registrations: registrations.sort(sortByRanking).map((data, i) => ({ + registrations: adjustedRegistrations.sort(sortByRanking).map((data, i) => ({ ...data, rank: i, })), diff --git a/convex/functions.ts b/convex/functions.ts index 8aef2288..6a31d4e2 100644 --- a/convex/functions.ts +++ b/convex/functions.ts @@ -6,6 +6,7 @@ import { DataModel, Doc } from './_generated/dataModel'; import { mutation as convexMutation } from './_generated/server'; import { matchResultTriggers } from './_model/matchResults'; import { tournamentCompetitorTriggers } from './_model/tournamentCompetitors'; +import { tournamentRegistrationTriggers } from './_model/tournamentRegistrations'; import { tournamentResultTriggers } from './_model/tournamentResults'; import { extractSearchTokens as extractTournamentSearchTokens } from './_model/tournaments/_helpers/extractSearchTokens'; import { extractSearchTokens as extractUserSearchTokens } from './_model/users/_helpers/extractSearchTokens'; @@ -69,4 +70,5 @@ triggers.register('tournaments', async (ctx, change) => { triggers.register('matchResults', matchResultTriggers.refreshTournamentResults); triggers.register('tournamentCompetitors', tournamentCompetitorTriggers.refreshTournamentResults); +triggers.register('tournamentRegistrations', tournamentRegistrationTriggers.refreshTournamentResults); triggers.register('tournamentResults', tournamentResultTriggers.refreshLeagueRankings); diff --git a/convex/migrations.ts b/convex/migrations.ts index 1874157c..f5d61a4e 100644 --- a/convex/migrations.ts +++ b/convex/migrations.ts @@ -64,3 +64,66 @@ export const migrateTournamentResults = migrations.define({ } }, }); + +/** + * Moves score adjustments from solo tournament competitors to their single registration. + * + * Prior to this migration, score adjustments could be set on tournament competitors + * regardless of tournament type. This migration moves those adjustments to the + * registration level (where they now belong for solo tournaments) and refreshes results + * once per tournament (not once per competitor document). + */ +export const moveSoloCompetitorScoreAdjustments = migrations.define({ + table: 'tournaments', + migrateOne: async (ctx, doc) => { + // Only applies to solo (non-team) tournaments: + if (doc.competitorSize > 1) { + return; + } + + // Find all competitors for this tournament that have adjustments to migrate: + const competitors = await ctx.db.query('tournamentCompetitors') + .withIndex('by_tournament_id', (q) => q.eq('tournamentId', doc._id)) + .collect(); + + let anyMoved = false; + for (const competitor of competitors) { + if (!competitor.scoreAdjustments?.length) { + continue; + } + + // Find the single registration for this competitor: + const registration = await ctx.db.query('tournamentRegistrations') + .withIndex('by_tournament_competitor', (q) => q.eq('tournamentCompetitorId', competitor._id)) + .first(); + if (!registration) { + continue; + } + + // Move adjustments to the registration, merging with any that may already exist: + await ctx.db.patch(registration._id, { + scoreAdjustments: [ + ...(registration.scoreAdjustments ?? []), + ...competitor.scoreAdjustments, + ], + }); + + // Clear from the competitor: + await ctx.db.patch(competitor._id, { scoreAdjustments: [] }); + anyMoved = true; + } + + // Only refresh results if anything was actually moved: + if (!anyMoved) { + return; + } + + // Refresh all existing tournament results once for the whole tournament: + const tournamentResults = await ctx.db.query('tournamentResults') + .withIndex('by_tournament_round', (q) => q.eq('tournamentId', doc._id)) + .collect(); + for (const result of tournamentResults) { + await refreshTournamentResult(ctx, result); + } + }, +}); diff --git a/convex/tournamentRegistrations.ts b/convex/tournamentRegistrations.ts index 22fc95c9..e4fe6aae 100644 --- a/convex/tournamentRegistrations.ts +++ b/convex/tournamentRegistrations.ts @@ -1,5 +1,6 @@ -import { mutation, query } from './_generated/server'; +import { query } from './_generated/server'; import * as model from './_model/tournamentRegistrations'; +import { mutationWithTrigger } from './functions'; export const getTournamentRegistrationByTournamentUser = query({ args: model.getTournamentRegistrationByTournamentUserArgs, @@ -22,23 +23,23 @@ export const getTournamentRegistrationsByTournament = query({ }); // CRUD Operations -export const createTournamentRegistration = mutation({ +export const createTournamentRegistration = mutationWithTrigger({ args: model.createTournamentRegistrationArgs, handler: model.createTournamentRegistration, }); -export const updateTournamentRegistration = mutation({ +export const updateTournamentRegistration = mutationWithTrigger({ args: model.updateTournamentRegistrationArgs, handler: model.updateTournamentRegistration, }); -export const deleteTournamentRegistration = mutation({ +export const deleteTournamentRegistration = mutationWithTrigger({ args: model.deleteTournamentRegistrationArgs, handler: model.deleteTournamentRegistration, }); // Actions -export const toggleTournamentRegistrationActive = mutation({ +export const toggleTournamentRegistrationActive = mutationWithTrigger({ args: model.toggleTournamentRegistrationActiveArgs, handler: model.toggleTournamentRegistrationActive, }); diff --git a/src/components/PageWrapper/PageWrapper.module.scss b/src/components/PageWrapper/PageWrapper.module.scss index bb67a763..155b3167 100644 --- a/src/components/PageWrapper/PageWrapper.module.scss +++ b/src/components/PageWrapper/PageWrapper.module.scss @@ -62,9 +62,20 @@ $header-height: 2.5rem; } &_Header { - @include flex.row; + display: grid; + grid-template-columns: 2.5rem minmax(0, 1fr) 2.5rem; + align-items: center; + justify-items: center; min-height: $header-height; + + h1 { + grid-column: 2; + } + } + + &_ContextMenu { + grid-column: 3; } &_Body { diff --git a/src/components/PageWrapper/PageWrapper.tsx b/src/components/PageWrapper/PageWrapper.tsx index 60e054e5..a93eb6a0 100644 --- a/src/components/PageWrapper/PageWrapper.tsx +++ b/src/components/PageWrapper/PageWrapper.tsx @@ -1,4 +1,9 @@ -import { ReactNode, useEffect } from 'react'; +import { + cloneElement, + ReactElement, + ReactNode, + useEffect, +} from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import clsx from 'clsx'; import { ArrowLeft } from 'lucide-react'; @@ -19,6 +24,7 @@ export interface PageWrapperProps { title?: string; hideTitle?: boolean; banner?: ReactNode; + contextMenu?: ReactElement; } export const PageWrapper = ({ @@ -30,6 +36,7 @@ export const PageWrapper = ({ title, hideTitle = false, banner, + contextMenu, }: PageWrapperProps): JSX.Element => { const navigate = useNavigate(); const { pathname } = useLocation(); @@ -61,6 +68,9 @@ export const PageWrapper = ({ {title && (

{title}

)} + {contextMenu && cloneElement(contextMenu, { + className: clsx(contextMenu.props.className, styles.PageWrapper_ContextMenu), + })} )}
diff --git a/src/components/TournamentCompetitorForm/components/ScoreAdjustmentFormItem/ScoreAdjustmentFormItem.module.scss b/src/components/ScoreAdjustmentFields/ScoreAdjustmentFields.module.scss similarity index 95% rename from src/components/TournamentCompetitorForm/components/ScoreAdjustmentFormItem/ScoreAdjustmentFormItem.module.scss rename to src/components/ScoreAdjustmentFields/ScoreAdjustmentFields.module.scss index 0ad2c612..5d18621e 100644 --- a/src/components/TournamentCompetitorForm/components/ScoreAdjustmentFormItem/ScoreAdjustmentFormItem.module.scss +++ b/src/components/ScoreAdjustmentFields/ScoreAdjustmentFields.module.scss @@ -3,7 +3,7 @@ @use "/src/style/borders"; @use "/src/style/text"; -.ScoreAdjustmentFormItem { +.ScoreAdjustmentFields { @include corners.normal; @include borders.normal; diff --git a/src/components/TournamentCompetitorForm/components/ScoreAdjustmentFormItem/ScoreAdjustmentFormItem.schema.ts b/src/components/ScoreAdjustmentFields/ScoreAdjustmentFields.schema.ts similarity index 100% rename from src/components/TournamentCompetitorForm/components/ScoreAdjustmentFormItem/ScoreAdjustmentFormItem.schema.ts rename to src/components/ScoreAdjustmentFields/ScoreAdjustmentFields.schema.ts diff --git a/src/components/TournamentCompetitorForm/components/ScoreAdjustmentFormItem/ScoreAdjustmentFormItem.tsx b/src/components/ScoreAdjustmentFields/ScoreAdjustmentFields.tsx similarity index 71% rename from src/components/TournamentCompetitorForm/components/ScoreAdjustmentFormItem/ScoreAdjustmentFormItem.tsx rename to src/components/ScoreAdjustmentFields/ScoreAdjustmentFields.tsx index 7e76206d..dbb5a966 100644 --- a/src/components/TournamentCompetitorForm/components/ScoreAdjustmentFormItem/ScoreAdjustmentFormItem.tsx +++ b/src/components/ScoreAdjustmentFields/ScoreAdjustmentFields.tsx @@ -10,26 +10,28 @@ import { FormField } from '~/components/generic/Form'; import { InputSelect } from '~/components/generic/InputSelect'; import { InputText } from '~/components/generic/InputText'; import { getRoundOptions } from '~/utils/common/getRoundOptions'; -import { scoreAdjustmentSchema } from './ScoreAdjustmentFormItem.schema'; -import { formatScoreAdjustment } from './ScoreAdjustmentFormItem.utils'; +import { scoreAdjustmentSchema } from './ScoreAdjustmentFields.schema'; +import { formatScoreAdjustment } from './ScoreAdjustmentFields.utils'; -import styles from './ScoreAdjustmentFormItem.module.scss'; +import styles from './ScoreAdjustmentFields.module.scss'; -export interface ScoreAdjustmentFormItemProps { +export interface ScoreAdjustmentFieldsProps { className?: string; disabled?: boolean; index: number; onRemove: (index: number) => void; tournament: Tournament; + subjectDisplayName: string; } -export const ScoreAdjustmentFormItem = ({ +export const ScoreAdjustmentFields = ({ className, disabled = false, index, onRemove, tournament, -}: ScoreAdjustmentFormItemProps): JSX.Element => { + subjectDisplayName, +}: ScoreAdjustmentFieldsProps): JSX.Element => { const watched = useWatch({ name: `scoreAdjustments.${index}` }); const result = scoreAdjustmentSchema.safeParse(watched); @@ -48,29 +50,29 @@ export const ScoreAdjustmentFormItem = ({ }; return ( -
+
- + - + -
+
{(safeValue && safeValue.amount !== 0) ? ( - {formatScoreAdjustment(tournament.gameSystem, safeValue, tournament.useTeams)} + {formatScoreAdjustment(tournament.gameSystem, safeValue, subjectDisplayName)} ) : ( Please add an amount. )}
+ {fields.map((field, index) => ( + + ))} + + )} ); }; diff --git a/src/components/TournamentRegistrationProvider/TournamentRegistrationContextMenu.tsx b/src/components/TournamentRegistrationProvider/TournamentRegistrationContextMenu.tsx index 32d7441a..fb6a6514 100644 --- a/src/components/TournamentRegistrationProvider/TournamentRegistrationContextMenu.tsx +++ b/src/components/TournamentRegistrationProvider/TournamentRegistrationContextMenu.tsx @@ -1,19 +1,18 @@ import { TournamentRegistration, TournamentRegistrationActionKey } from '~/api'; -import { ContextMenu } from '~/components/ContextMenu'; +import { ContextMenu, ContextMenuProps } from '~/components/ContextMenu'; import { useActions } from './TournamentRegistrationProvider.hooks'; -export interface TournamentRegistrationContextMenuProps { - className?: string; +export interface TournamentRegistrationContextMenuProps extends Omit { tournamentRegistration: TournamentRegistration; } export const TournamentRegistrationContextMenu = ({ - className, tournamentRegistration, + ...restProps }: TournamentRegistrationContextMenuProps): JSX.Element => { const actions = useActions(tournamentRegistration); return ( - - + + ) : ( + + )} + >
diff --git a/src/pages/TournamentCompetitorDetailPage/components/Header/Header.module.scss b/src/pages/TournamentCompetitorDetailPage/components/Header/Header.module.scss index d30e7909..874d6d4e 100644 --- a/src/pages/TournamentCompetitorDetailPage/components/Header/Header.module.scss +++ b/src/pages/TournamentCompetitorDetailPage/components/Header/Header.module.scss @@ -6,22 +6,24 @@ @use "/src/style/variants"; .Header { + --avatar-size: 8rem; + display: grid; grid-template-areas: - "avatar . actions" - "avatar name actions" - "avatar tournament actions" - "avatar standing actions"; - grid-template-columns: 8rem 1fr auto; + "avatar . ." + "avatar name name" + "avatar tournament tournament" + "avatar standing standing"; + grid-template-columns: var(--avatar-size) minmax(0, 1fr) auto; grid-template-rows: 1fr auto auto 1fr; row-gap: 0.25rem; column-gap: 1rem; - box-sizing: content-box; - height: 8rem; + min-width: 0; &_Avatar { grid-area: avatar; + align-self: center; } &_Badge { @@ -68,10 +70,4 @@ align-self: center; justify-self: start; } - - &_Actions { - grid-area: actions; - align-self: start; - justify-self: end; - } } diff --git a/src/pages/TournamentCompetitorDetailPage/components/Header/Header.tsx b/src/pages/TournamentCompetitorDetailPage/components/Header/Header.tsx index d8af2702..dd786ddb 100644 --- a/src/pages/TournamentCompetitorDetailPage/components/Header/Header.tsx +++ b/src/pages/TournamentCompetitorDetailPage/components/Header/Header.tsx @@ -1,11 +1,10 @@ import { generatePath, useNavigate } from 'react-router-dom'; +import { Button } from '@ianpaschal/combat-command-components'; import clsx from 'clsx'; import { ChevronRight, Users } from 'lucide-react'; -import { Button } from '~/components/generic/Button'; import { Tag } from '~/components/generic/Tag'; import { TournamentCompetitorAvatar } from '~/components/IdentityBadge'; -import { TournamentCompetitorContextMenu } from '~/components/TournamentCompetitorProvider'; import { useTournamentCompetitor } from '~/components/TournamentCompetitorProvider/TournamentCompetitorProvider.hooks'; import { useTournament } from '~/components/TournamentProvider'; import { PATHS } from '~/settings'; @@ -44,9 +43,11 @@ export const Header = ({ )}

{tournamentCompetitor.displayName}

- - {`${tournamentCompetitor?.activeRegistrationCount ?? 0}/${tournament.competitorSize}`} - + {tournament.useTeams && ( + + {`${tournamentCompetitor?.activeRegistrationCount ?? 0}/${tournament.competitorSize}`} + + )}
{tournament.displayName} @@ -61,10 +62,6 @@ export const Header = ({ text="View All Rankings" variant="ghost" /> -
); };