mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-09 04:02:40 -05:00
Tournament weapon report
This commit is contained in:
parent
b788839215
commit
c802faf151
|
|
@ -90,6 +90,12 @@
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.rootStandalone {
|
||||
margin-block-start: calc(-1 * var(--s-6));
|
||||
min-height: 200px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.collapseButton {
|
||||
position: absolute;
|
||||
top: var(--s-2);
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ export interface WeaponReporterProps {
|
|||
onSubmit: (weaponSplId: MainWeaponId) => void;
|
||||
onUndo: () => void;
|
||||
isSubmitting?: boolean;
|
||||
standalone?: boolean;
|
||||
}
|
||||
|
||||
// xxx: on sendouq all weapons report different / component tab..? or not? check usage
|
||||
|
|
@ -37,6 +38,7 @@ export function WeaponReporter({
|
|||
onSubmit,
|
||||
onUndo,
|
||||
isSubmitting,
|
||||
standalone,
|
||||
}: WeaponReporterProps) {
|
||||
const { t } = useTranslation(["q", "game-misc", "common"]);
|
||||
const user = useUser();
|
||||
|
|
@ -62,7 +64,7 @@ export function WeaponReporter({
|
|||
);
|
||||
};
|
||||
|
||||
if (!isOpen) {
|
||||
if (!isOpen && !standalone) {
|
||||
return (
|
||||
<div className={styles.rootCollapsed}>
|
||||
<SendouButton
|
||||
|
|
@ -78,15 +80,21 @@ export function WeaponReporter({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={clsx(styles.root, styles.rootExpanded)}>
|
||||
<SendouButton
|
||||
variant="minimal"
|
||||
size="miniscule"
|
||||
icon={<ChevronUp size={22} />}
|
||||
onPress={() => handleToggle(false)}
|
||||
className={styles.collapseButton}
|
||||
aria-label={t("q:match.actions.reportWeapons")}
|
||||
/>
|
||||
<div
|
||||
className={clsx(styles.root, styles.rootExpanded, {
|
||||
[styles.rootStandalone]: standalone,
|
||||
})}
|
||||
>
|
||||
{standalone ? null : (
|
||||
<SendouButton
|
||||
variant="minimal"
|
||||
size="miniscule"
|
||||
icon={<ChevronUp size={22} />}
|
||||
onPress={() => handleToggle(false)}
|
||||
className={styles.collapseButton}
|
||||
aria-label={t("q:match.actions.reportWeapons")}
|
||||
/>
|
||||
)}
|
||||
{pastReported.length > 0 ? (
|
||||
<div className={styles.pastRow}>
|
||||
{pastReported.map((weaponId, i) => (
|
||||
|
|
|
|||
|
|
@ -453,8 +453,14 @@ export interface PlusVotingResult {
|
|||
passedVoting: DBBoolean;
|
||||
}
|
||||
|
||||
// xxx: unify SendouQ side to also key on (matchId, mapIndex). Replace
|
||||
// groupMatchMapId with groupMatchId, make mapIndex non-null, and have a
|
||||
// single CHECK that exactly one of groupMatchId / tournamentMatchId is set.
|
||||
// Backfill (groupMatchId, mapIndex) from GroupMatchMap.matchId/index.
|
||||
export interface ReportedWeapon {
|
||||
groupMatchMapId: number | null;
|
||||
tournamentMatchId: number | null;
|
||||
mapIndex: number | null;
|
||||
userId: number;
|
||||
weaponSplId: MainWeaponId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import type { NotNull, Transaction } from "kysely";
|
||||
import { db } from "~/db/sql";
|
||||
import type { DB, TablesInsertable } from "~/db/tables";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import type { MainWeaponId } from "~/modules/in-game-lists/types";
|
||||
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
|
||||
export function createMany(
|
||||
weapons: TablesInsertable["ReportedWeapon"][],
|
||||
|
|
@ -106,3 +109,143 @@ export async function findByMatchId(matchId: number) {
|
|||
|
||||
return rows;
|
||||
}
|
||||
|
||||
export async function upsertOneTournament({
|
||||
tournamentMatchId,
|
||||
mapIndex,
|
||||
userId,
|
||||
weaponSplId,
|
||||
}: TablesInsertable["ReportedWeapon"] & {
|
||||
tournamentMatchId: number;
|
||||
mapIndex: number;
|
||||
}) {
|
||||
await db
|
||||
.deleteFrom("ReportedWeapon")
|
||||
.where("tournamentMatchId", "=", tournamentMatchId)
|
||||
.where("mapIndex", "=", mapIndex)
|
||||
.where("userId", "=", userId)
|
||||
.execute();
|
||||
|
||||
await db
|
||||
.insertInto("ReportedWeapon")
|
||||
.values({ tournamentMatchId, mapIndex, userId, weaponSplId })
|
||||
.execute();
|
||||
}
|
||||
|
||||
export async function deleteByUserMapIndexTournament({
|
||||
tournamentMatchId,
|
||||
userId,
|
||||
mapIndex,
|
||||
}: {
|
||||
tournamentMatchId: number;
|
||||
userId: number;
|
||||
mapIndex: number;
|
||||
}) {
|
||||
await db
|
||||
.deleteFrom("ReportedWeapon")
|
||||
.where("tournamentMatchId", "=", tournamentMatchId)
|
||||
.where("mapIndex", "=", mapIndex)
|
||||
.where("userId", "=", userId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
export async function findByTournamentMatchId(matchId: number) {
|
||||
const rows = await db
|
||||
.selectFrom("ReportedWeapon")
|
||||
.select([
|
||||
"ReportedWeapon.tournamentMatchId",
|
||||
"ReportedWeapon.mapIndex",
|
||||
"ReportedWeapon.weaponSplId",
|
||||
"ReportedWeapon.userId",
|
||||
])
|
||||
.where("ReportedWeapon.tournamentMatchId", "=", matchId)
|
||||
.orderBy("ReportedWeapon.mapIndex", "asc")
|
||||
.orderBy("ReportedWeapon.userId", "asc")
|
||||
.$narrowType<{ tournamentMatchId: NotNull; mapIndex: NotNull }>()
|
||||
.execute();
|
||||
|
||||
if (rows.length === 0) return null;
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregates a user's reported weapons across both SendouQ matches and
|
||||
* finalized tournaments that fall within the given season's date range.
|
||||
*/
|
||||
export async function seasonReportedWeaponsByUserId({
|
||||
userId,
|
||||
season,
|
||||
}: {
|
||||
userId: number;
|
||||
season: number;
|
||||
}): Promise<Array<{ weaponSplId: MainWeaponId; count: number }>> {
|
||||
const { starts, ends } = Seasons.nthToDateRange(season);
|
||||
const startsTs = dateToDatabaseTimestamp(starts);
|
||||
const endsTs = dateToDatabaseTimestamp(ends);
|
||||
|
||||
const sendouqWeapons = db
|
||||
.selectFrom("ReportedWeapon")
|
||||
.innerJoin(
|
||||
"GroupMatchMap",
|
||||
"GroupMatchMap.id",
|
||||
"ReportedWeapon.groupMatchMapId",
|
||||
)
|
||||
.innerJoin("GroupMatch", "GroupMatch.id", "GroupMatchMap.matchId")
|
||||
.select(({ fn }) => [
|
||||
"ReportedWeapon.weaponSplId",
|
||||
fn.countAll<number>().as("count"),
|
||||
])
|
||||
.where("ReportedWeapon.userId", "=", userId)
|
||||
.where("GroupMatch.createdAt", ">=", startsTs)
|
||||
.where("GroupMatch.createdAt", "<=", endsTs)
|
||||
.groupBy("ReportedWeapon.weaponSplId");
|
||||
|
||||
const tournamentWeapons = db
|
||||
.selectFrom("ReportedWeapon")
|
||||
.innerJoin(
|
||||
"TournamentMatch",
|
||||
"TournamentMatch.id",
|
||||
"ReportedWeapon.tournamentMatchId",
|
||||
)
|
||||
.innerJoin(
|
||||
"TournamentStage",
|
||||
"TournamentStage.id",
|
||||
"TournamentMatch.stageId",
|
||||
)
|
||||
.innerJoin("Tournament", "Tournament.id", "TournamentStage.tournamentId")
|
||||
.innerJoin("CalendarEvent", "CalendarEvent.tournamentId", "Tournament.id")
|
||||
.innerJoin(
|
||||
(eb) =>
|
||||
eb
|
||||
.selectFrom("CalendarEventDate")
|
||||
.select(({ fn }) => [
|
||||
"CalendarEventDate.eventId",
|
||||
fn.min("CalendarEventDate.startTime").as("startTime"),
|
||||
])
|
||||
.groupBy("CalendarEventDate.eventId")
|
||||
.as("EventStartTime"),
|
||||
(join) => join.onRef("EventStartTime.eventId", "=", "CalendarEvent.id"),
|
||||
)
|
||||
.select(({ fn }) => [
|
||||
"ReportedWeapon.weaponSplId",
|
||||
fn.countAll<number>().as("count"),
|
||||
])
|
||||
.where("ReportedWeapon.userId", "=", userId)
|
||||
.where("Tournament.isFinalized", "=", 1)
|
||||
.where("EventStartTime.startTime", ">=", startsTs)
|
||||
.where("EventStartTime.startTime", "<=", endsTs)
|
||||
.groupBy("ReportedWeapon.weaponSplId");
|
||||
|
||||
const rows = await db
|
||||
.selectFrom(sendouqWeapons.unionAll(tournamentWeapons).as("merged"))
|
||||
.select(({ fn }) => [
|
||||
"merged.weaponSplId",
|
||||
fn.sum<number>("merged.count").as("count"),
|
||||
])
|
||||
.groupBy("merged.weaponSplId")
|
||||
.orderBy("count", "desc")
|
||||
.execute();
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
import { sql } from "~/db/sql";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import type { MainWeaponId } from "~/modules/in-game-lists/types";
|
||||
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
|
||||
const stm = sql.prepare(/* sql */ `
|
||||
select
|
||||
"ReportedWeapon"."weaponSplId",
|
||||
count(*) as "count"
|
||||
from
|
||||
"ReportedWeapon"
|
||||
left join "GroupMatchMap" on "GroupMatchMap"."id" = "ReportedWeapon"."groupMatchMapId"
|
||||
left join "GroupMatch" on "GroupMatch"."id" = "GroupMatchMap"."matchId"
|
||||
where
|
||||
"ReportedWeapon"."userId" = @userId
|
||||
and "GroupMatch"."createdAt" between @starts and @ends
|
||||
group by "ReportedWeapon"."weaponSplId"
|
||||
order by "count" desc
|
||||
`);
|
||||
|
||||
export function seasonReportedWeaponsByUserId({
|
||||
userId,
|
||||
season,
|
||||
}: {
|
||||
userId: number;
|
||||
season: number;
|
||||
}) {
|
||||
const { starts, ends } = Seasons.nthToDateRange(season);
|
||||
|
||||
return stm.all({
|
||||
userId,
|
||||
starts: dateToDatabaseTimestamp(starts),
|
||||
ends: dateToDatabaseTimestamp(ends),
|
||||
}) as Array<{ weaponSplId: MainWeaponId; count: number }>;
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import {
|
|||
numericEnum,
|
||||
safeJSONParse,
|
||||
stageId,
|
||||
weaponSplId,
|
||||
} from "~/utils/zod";
|
||||
import { TOURNAMENT } from "../tournament/tournament-constants";
|
||||
import * as PickBan from "./core/PickBan";
|
||||
|
|
@ -102,6 +103,15 @@ export const matchSchema = z.union([
|
|||
_action: _action("END_SET"),
|
||||
winnerTeamId: z.preprocess(nullLiteraltoNull, id.nullable()),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("REPORT_WEAPON"),
|
||||
weaponSplId,
|
||||
mapIndex: z.coerce.number().int().nonnegative(),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("UNDO_WEAPON_REPORT"),
|
||||
mapIndex: z.coerce.number().int().nonnegative(),
|
||||
}),
|
||||
]);
|
||||
|
||||
export const bracketIdx = z.coerce.number().int().min(0).max(100);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { sql } from "~/db/sql";
|
|||
import { TournamentMatchStatus } from "~/db/tables";
|
||||
import { requireUser } from "~/features/auth/core/user.server";
|
||||
import * as ChatSystemMessage from "~/features/chat/ChatSystemMessage.server";
|
||||
import * as ReportedWeaponRepository from "~/features/sendouq-match/ReportedWeaponRepository.server";
|
||||
import * as TournamentRepository from "~/features/tournament/TournamentRepository.server";
|
||||
import * as TournamentTeamRepository from "~/features/tournament/TournamentTeamRepository.server";
|
||||
import { endDroppedTeamMatches } from "~/features/tournament/tournament-utils.server";
|
||||
|
|
@ -736,6 +737,37 @@ export const action: ActionFunction = async ({ params, request }) => {
|
|||
|
||||
break;
|
||||
}
|
||||
case "REPORT_WEAPON": {
|
||||
const isMemberOfATeamInTheMatch = match.players.some(
|
||||
(p) => p.id === user.id,
|
||||
);
|
||||
errorToastIfFalsy(isMemberOfATeamInTheMatch, "Unauthorized");
|
||||
errorToastIfFalsy(!tournament.ctx.isFinalized, "Tournament is finalized");
|
||||
|
||||
await ReportedWeaponRepository.upsertOneTournament({
|
||||
tournamentMatchId: matchId,
|
||||
mapIndex: data.mapIndex,
|
||||
userId: user.id,
|
||||
weaponSplId: data.weaponSplId,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case "UNDO_WEAPON_REPORT": {
|
||||
const isMemberOfATeamInTheMatch = match.players.some(
|
||||
(p) => p.id === user.id,
|
||||
);
|
||||
errorToastIfFalsy(isMemberOfATeamInTheMatch, "Unauthorized");
|
||||
errorToastIfFalsy(!tournament.ctx.isFinalized, "Tournament is finalized");
|
||||
|
||||
await ReportedWeaponRepository.deleteByUserMapIndexTournament({
|
||||
tournamentMatchId: matchId,
|
||||
userId: user.id,
|
||||
mapIndex: data.mapIndex,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertUnreachable(data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,21 @@ import { Undo2 } from "lucide-react";
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { useFetcher } from "react-router";
|
||||
import { SendouButton } from "~/components/elements/Button";
|
||||
import { SendouTabPanel } from "~/components/elements/Tabs";
|
||||
import { MatchActionTab } from "~/components/match-page/MatchActionTab";
|
||||
import { TAB_KEYS } from "~/components/match-page/MatchTabs";
|
||||
import {
|
||||
WeaponReporter,
|
||||
type WeaponReporterProps,
|
||||
} from "~/components/match-page/WeaponReporter";
|
||||
import { useUser } from "~/features/auth/core/user";
|
||||
import { useRecentlyReportedWeapons } from "~/features/sendouq/q-hooks";
|
||||
import { useTournament } from "~/features/tournament/routes/to.$id";
|
||||
import type { ModeShort, StageId } from "~/modules/in-game-lists/types";
|
||||
import type {
|
||||
MainWeaponId,
|
||||
ModeShort,
|
||||
StageId,
|
||||
} from "~/modules/in-game-lists/types";
|
||||
import { databaseTimestampToJavascriptTimestamp } from "~/utils/dates";
|
||||
import type { CommonUser } from "~/utils/kysely.server";
|
||||
import type { TournamentMatchLoaderData } from "../loaders/to.$id.matches.$mid.server";
|
||||
|
|
@ -17,14 +29,28 @@ export function TournamentMatchActionTab({
|
|||
ownTeamId,
|
||||
}: {
|
||||
data: TournamentMatchLoaderData;
|
||||
currentMap: { stageId: StageId; mode: ModeShort };
|
||||
currentMap?: { stageId: StageId; mode: ModeShort };
|
||||
ownTeamId: number;
|
||||
}) {
|
||||
const { t } = useTranslation(["q"]);
|
||||
const tournament = useTournament();
|
||||
const user = useUser();
|
||||
const reportFetcher = useFetcher();
|
||||
const undoFetcher = useFetcher();
|
||||
|
||||
const weaponReport = useTournamentWeaponReport({
|
||||
data,
|
||||
viewerUserId: user?.id,
|
||||
});
|
||||
|
||||
if (!currentMap) {
|
||||
return (
|
||||
<SendouTabPanel id={TAB_KEYS.ACTION}>
|
||||
{weaponReport ? <WeaponReporter {...weaponReport} standalone /> : null}
|
||||
</SendouTabPanel>
|
||||
);
|
||||
}
|
||||
|
||||
const opponentOneId = data.match.opponentOne!.id!;
|
||||
const opponentTwoId = data.match.opponentTwo!.id!;
|
||||
|
||||
|
|
@ -129,10 +155,74 @@ export function TournamentMatchActionTab({
|
|||
{t("q:match.undoReport")}
|
||||
</SendouButton>
|
||||
}
|
||||
weaponReport={weaponReport ?? undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function useTournamentWeaponReport({
|
||||
data,
|
||||
viewerUserId,
|
||||
}: {
|
||||
data: TournamentMatchLoaderData;
|
||||
viewerUserId: number | undefined;
|
||||
}): WeaponReporterProps | null {
|
||||
const weaponFetcher = useFetcher();
|
||||
const { recentlyReportedWeapons, addRecentlyReportedWeapon } =
|
||||
useRecentlyReportedWeapons();
|
||||
|
||||
if (viewerUserId === undefined) return null;
|
||||
|
||||
const isParticipant = data.match.players.some((p) => p.id === viewerUserId);
|
||||
if (!isParticipant) return null;
|
||||
|
||||
const playOrderMaps = (data.mapList ?? []).filter(
|
||||
(m) => !m.bannedByTournamentTeamId,
|
||||
);
|
||||
const reportedCount = data.results.length;
|
||||
const weaponReportMaps = playOrderMaps
|
||||
.slice(0, reportedCount + 1)
|
||||
.map((m) => ({ stageId: m.stageId, mode: m.mode }));
|
||||
|
||||
if (weaponReportMaps.length === 0) return null;
|
||||
|
||||
const pastReported: MainWeaponId[] = data.reportedWeapons
|
||||
? data.reportedWeapons
|
||||
.filter((w) => w.userId === viewerUserId)
|
||||
.map((w) => w.weaponSplId)
|
||||
: [];
|
||||
|
||||
return {
|
||||
maps: weaponReportMaps,
|
||||
pastReported,
|
||||
quickSelectWeaponIds: recentlyReportedWeapons,
|
||||
isSubmitting: weaponFetcher.state !== "idle",
|
||||
onSubmit: (weaponSplId) => {
|
||||
addRecentlyReportedWeapon(weaponSplId);
|
||||
const mapIndex = pastReported.length;
|
||||
weaponFetcher.submit(
|
||||
{
|
||||
_action: "REPORT_WEAPON",
|
||||
weaponSplId: String(weaponSplId),
|
||||
mapIndex: String(mapIndex),
|
||||
},
|
||||
{ method: "post" },
|
||||
);
|
||||
},
|
||||
onUndo: () => {
|
||||
const mapIndex = pastReported.length - 1;
|
||||
if (mapIndex < 0) return;
|
||||
weaponFetcher.submit(
|
||||
{
|
||||
_action: "UNDO_WEAPON_REPORT",
|
||||
mapIndex: String(mapIndex),
|
||||
},
|
||||
{ method: "post" },
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function buildSetEndingData({
|
||||
teams,
|
||||
scores,
|
||||
|
|
|
|||
|
|
@ -86,9 +86,14 @@ export function TournamentMatchTabs({
|
|||
const hasReportedMaps = data.results.length > 0;
|
||||
const hasPickBanEvents = data.pickBanEventCount > 0;
|
||||
|
||||
const isParticipant = data.match.players.some((p) => p.id === user?.id);
|
||||
const canReportWeapons =
|
||||
isParticipant && !tournament.ctx.isFinalized && hasReportedMaps;
|
||||
|
||||
const tabs = resolveVisibleTabs({
|
||||
matchIsOver: data.matchIsOver,
|
||||
canReportScore,
|
||||
canReportWeapons,
|
||||
canJoin: data.canJoin,
|
||||
hasCurrentMap: Boolean(currentMap),
|
||||
hasMissingActiveRoster,
|
||||
|
|
@ -136,13 +141,13 @@ export function TournamentMatchTabs({
|
|||
teams={pickBanTeams}
|
||||
turnOfResult={turnOfResult}
|
||||
/>
|
||||
) : currentMap ? (
|
||||
) : (
|
||||
<TournamentMatchActionTab
|
||||
data={data}
|
||||
currentMap={currentMap}
|
||||
ownTeamId={userTeamId ?? opponentOneId}
|
||||
/>
|
||||
) : null
|
||||
)
|
||||
) : null}
|
||||
{tabs.includes("admin") ? <TournamentMatchAdminTab data={data} /> : null}
|
||||
</MatchTabs>
|
||||
|
|
@ -192,10 +197,24 @@ function resolveTimelineMaps(
|
|||
customUrl: u.customUrl,
|
||||
}));
|
||||
|
||||
return data.results.map((result) => {
|
||||
return data.results.map((result, mapIndex) => {
|
||||
const hasPoints =
|
||||
result.opponentOnePoints !== null && result.opponentTwoPoints !== null;
|
||||
|
||||
const alphaRoster = resolveRoster(result.participants, opponentOneId);
|
||||
const bravoRoster = resolveRoster(result.participants, opponentTwoId);
|
||||
|
||||
const weaponFor = (userId: number) =>
|
||||
data.reportedWeapons?.find(
|
||||
(w) => w.mapIndex === mapIndex && w.userId === userId,
|
||||
)?.weaponSplId ?? null;
|
||||
|
||||
const alphaWeapons = alphaRoster.map((u) => weaponFor(u.id));
|
||||
const bravoWeapons = bravoRoster.map((u) => weaponFor(u.id));
|
||||
const hasAnyWeapon =
|
||||
alphaWeapons.some((w) => w !== null) ||
|
||||
bravoWeapons.some((w) => w !== null);
|
||||
|
||||
return {
|
||||
stageId: result.stageId,
|
||||
mode: result.mode,
|
||||
|
|
@ -205,9 +224,12 @@ function resolveTimelineMaps(
|
|||
? ("ALPHA" as const)
|
||||
: ("BRAVO" as const),
|
||||
rosters: {
|
||||
alpha: resolveRoster(result.participants, opponentOneId),
|
||||
bravo: resolveRoster(result.participants, opponentTwoId),
|
||||
alpha: alphaRoster,
|
||||
bravo: bravoRoster,
|
||||
},
|
||||
weapons: hasAnyWeapon
|
||||
? { alpha: alphaWeapons, bravo: bravoWeapons }
|
||||
: undefined,
|
||||
points: hasPoints
|
||||
? ([result.opponentOnePoints, result.opponentTwoPoints] as [
|
||||
number,
|
||||
|
|
@ -485,6 +507,7 @@ function TournamentMatchRosterTab({
|
|||
function resolveVisibleTabs({
|
||||
matchIsOver,
|
||||
canReportScore,
|
||||
canReportWeapons,
|
||||
canJoin,
|
||||
hasCurrentMap,
|
||||
hasMissingActiveRoster,
|
||||
|
|
@ -496,6 +519,7 @@ function resolveVisibleTabs({
|
|||
}: {
|
||||
matchIsOver: boolean;
|
||||
canReportScore: boolean;
|
||||
canReportWeapons: boolean;
|
||||
canJoin: boolean;
|
||||
hasCurrentMap: boolean;
|
||||
hasMissingActiveRoster: boolean;
|
||||
|
|
@ -517,7 +541,8 @@ function resolveVisibleTabs({
|
|||
if (
|
||||
!leagueRoundLocked &&
|
||||
(isPickBanStep ||
|
||||
(canReportScore && hasCurrentMap && !hasMissingActiveRoster))
|
||||
(canReportScore && hasCurrentMap && !hasMissingActiveRoster) ||
|
||||
canReportWeapons)
|
||||
) {
|
||||
tabs.push("action");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { getUser } from "~/features/auth/core/user.server";
|
|||
import * as ChatSystemMessage from "~/features/chat/ChatSystemMessage.server";
|
||||
import { chatAccessible } from "~/features/chat/chat-utils";
|
||||
import * as RoomLinkRepository from "~/features/chat/RoomLinkRepository.server";
|
||||
import * as ReportedWeaponRepository from "~/features/sendouq-match/ReportedWeaponRepository.server";
|
||||
import * as TournamentRepository from "~/features/tournament/TournamentRepository.server";
|
||||
import * as TournamentTeamRepository from "~/features/tournament/TournamentTeamRepository.server";
|
||||
import { isLeagueRoundLocked } from "~/features/tournament/tournament-utils";
|
||||
|
|
@ -53,6 +54,9 @@ export const loader = async ({ params }: LoaderFunctionArgs) => {
|
|||
|
||||
const results = findResultsByMatchId(matchId);
|
||||
|
||||
const reportedWeapons =
|
||||
await ReportedWeaponRepository.findByTournamentMatchId(matchId);
|
||||
|
||||
const matchIsOver =
|
||||
match.opponentOne?.result === "win" || match.opponentTwo?.result === "win";
|
||||
|
||||
|
|
@ -219,6 +223,7 @@ export const loader = async ({ params }: LoaderFunctionArgs) => {
|
|||
return {
|
||||
match: hasPermsToSeeChat ? match : { ...match, chatCode: undefined },
|
||||
results,
|
||||
reportedWeapons,
|
||||
mapList,
|
||||
matchIsOver,
|
||||
endedEarly,
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import * as LeaderboardRepository from "~/features/leaderboards/LeaderboardRepos
|
|||
import * as SkillRepository from "~/features/mmr/SkillRepository.server";
|
||||
import { userSkills as _userSkills } from "~/features/mmr/tiered.server";
|
||||
import { seasonMapWinrateByUserId } from "~/features/sendouq/queries/seasonMapWinrateByUserId.server";
|
||||
import { seasonReportedWeaponsByUserId } from "~/features/sendouq/queries/seasonReportedWeaponsByUserId.server";
|
||||
import { seasonSetWinrateByUserId } from "~/features/sendouq/queries/seasonSetWinrateByUserId.server";
|
||||
import { seasonStagesByUserId } from "~/features/sendouq/queries/seasonStagesByUserId.server";
|
||||
import { seasonsMatesEnemiesByUserId } from "~/features/sendouq/queries/seasonsMatesEnemiesByUserId.server";
|
||||
import * as ReportedWeaponRepository from "~/features/sendouq-match/ReportedWeaponRepository.server";
|
||||
import * as SQMatchRepository from "~/features/sendouq-match/SQMatchRepository.server";
|
||||
import * as UserRepository from "~/features/user-page/UserRepository.server";
|
||||
import type { SerializeFrom } from "~/utils/remix";
|
||||
|
|
@ -91,7 +91,7 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
|
|||
: null,
|
||||
weapons:
|
||||
info === "weapons"
|
||||
? seasonReportedWeaponsByUserId({ season, userId: user.id })
|
||||
? await ReportedWeaponRepository.seasonReportedWeaponsByUserId({ season, userId: user.id })
|
||||
: null,
|
||||
players:
|
||||
info === "enemies" || info === "mates"
|
||||
|
|
|
|||
BIN
db-test.sqlite3
BIN
db-test.sqlite3
Binary file not shown.
|
|
@ -135,6 +135,53 @@ export function up(db) {
|
|||
/* sql */ `create index group_match_continue_vote_group_id on "GroupMatchContinueVote"("groupId")`,
|
||||
).run();
|
||||
|
||||
db.prepare(
|
||||
/* sql */ `
|
||||
create table "ReportedWeapon_new" (
|
||||
"groupMatchMapId" integer,
|
||||
"tournamentMatchId" integer,
|
||||
"mapIndex" integer,
|
||||
"weaponSplId" integer not null,
|
||||
"userId" integer not null,
|
||||
foreign key ("groupMatchMapId") references "GroupMatchMap"("id") on delete restrict,
|
||||
foreign key ("tournamentMatchId") references "TournamentMatch"("id") on delete cascade,
|
||||
foreign key ("userId") references "User"("id") on delete restrict,
|
||||
unique("groupMatchMapId", "userId") on conflict rollback,
|
||||
unique("tournamentMatchId", "mapIndex", "userId") on conflict rollback,
|
||||
check (
|
||||
("groupMatchMapId" is not null and "tournamentMatchId" is null and "mapIndex" is null)
|
||||
or
|
||||
("groupMatchMapId" is null and "tournamentMatchId" is not null and "mapIndex" is not null)
|
||||
)
|
||||
) strict
|
||||
`,
|
||||
).run();
|
||||
|
||||
db.prepare(
|
||||
/* sql */ `
|
||||
insert into "ReportedWeapon_new" (
|
||||
"groupMatchMapId", "tournamentMatchId", "mapIndex", "weaponSplId", "userId"
|
||||
)
|
||||
select "groupMatchMapId", null, null, "weaponSplId", "userId"
|
||||
from "ReportedWeapon"
|
||||
`,
|
||||
).run();
|
||||
|
||||
db.prepare(/* sql */ `drop table "ReportedWeapon"`).run();
|
||||
db.prepare(
|
||||
/* sql */ `alter table "ReportedWeapon_new" rename to "ReportedWeapon"`,
|
||||
).run();
|
||||
|
||||
db.prepare(
|
||||
/* sql */ `create index reported_weapon_group_match_map_id on "ReportedWeapon"("groupMatchMapId")`,
|
||||
).run();
|
||||
db.prepare(
|
||||
/* sql */ `create index reported_weapon_tournament_match_id on "ReportedWeapon"("tournamentMatchId")`,
|
||||
).run();
|
||||
db.prepare(
|
||||
/* sql */ `create index reported_weapon_user_id on "ReportedWeapon"("userId")`,
|
||||
).run();
|
||||
|
||||
db.pragma("foreign_key_check");
|
||||
})();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user