From b3d81916538ae47edfff88cfdf85ab670ae6fab3 Mon Sep 17 00:00:00 2001 From: Afifah Hadi Date: Wed, 15 Apr 2026 23:15:10 -0700 Subject: [PATCH 1/7] added admin only missing team restore logic and button --- app/(api)/_actions/teams/reportMissingTeam.ts | 122 +++++++++++- .../_components/Teams/TeamCard.module.scss | 20 +- .../admin/_components/Teams/TeamCard.tsx | 13 ++ app/(pages)/admin/teams/page.tsx | 21 ++ package-lock.json | 186 +++++------------- 5 files changed, 222 insertions(+), 140 deletions(-) diff --git a/app/(api)/_actions/teams/reportMissingTeam.ts b/app/(api)/_actions/teams/reportMissingTeam.ts index dca6d069d..51994807c 100644 --- a/app/(api)/_actions/teams/reportMissingTeam.ts +++ b/app/(api)/_actions/teams/reportMissingTeam.ts @@ -1,10 +1,13 @@ 'use server'; -import { HttpError } from '@utils/response/Errors'; +import { auth } from '@/auth'; +import { HttpError, NotAuthenticatedError } from '@utils/response/Errors'; import { GetManySubmissions } from '@datalib/submissions/getSubmissions'; import { UpdateSubmission } from '@datalib/submissions/updateSubmission'; import { UpdateTeam } from '@datalib/teams/updateTeam'; import Submission from '@typeDefs/submission'; +import Team from '@typeDefs/team'; +import { GetTeam } from '@datalib/teams/getTeam'; export async function reportMissingProject(judge_id: string, team_id: string) { try { @@ -90,3 +93,120 @@ export async function reportMissingProject(judge_id: string, team_id: string) { return { ok: false, body: null, error: error.message }; } } + +export async function restoreMissingTeamForAllJudges(team_id: string) { + try { + const session = await auth(); + if (!session || session.user.role !== 'admin') { + throw new NotAuthenticatedError('Access Denied.'); + } + + const teamRes = JSON.parse(JSON.stringify(await GetTeam(team_id))); + if (!teamRes.ok || !teamRes.body) { + throw new Error(teamRes.error ?? `Team with id: ${team_id} not found.`); + } + + const team = teamRes.body as Team; + const reports = team.reports ?? []; + const reporterJudgeIds = [ + ...new Set( + reports + .map((report) => String(report.judge_id ?? '')) + .filter((judge_id) => judge_id.length > 0) + ), + ]; + const requeueResults: { judge_id: string; reorderResCount: number }[] = []; + + for (const judge_id of reporterJudgeIds) { + const submissionsRes = JSON.parse( + JSON.stringify( + await GetManySubmissions({ + judge_id: { + '*convertId': { + id: judge_id, + }, + }, + }) + ) + ); + + if (!submissionsRes.ok) { + throw new Error(submissionsRes.error ?? ''); + } + + const submissions: Submission[] = (submissionsRes.body ?? []).map( + (sub: Submission) => ({ + ...sub, + judge_id: String(sub.judge_id), + team_id: String(sub.team_id), + }) + ); + + const targetSubmission = submissions.find( + (sub: Submission) => sub.team_id === team_id + ); + + // Only restore queue for judges where this team is effectively "missing": + // reported + has an existing submission + unscored. + if (!targetSubmission || targetSubmission.is_scored) { + continue; + } + + targetSubmission.queuePosition = + Math.max( + ...submissions.map((sub: Submission) => sub.queuePosition ?? 0) + ) + 1; + + const reorderedSubmissions = submissions + .sort( + (a: Submission, b: Submission) => + (a.queuePosition ?? 0) - (b.queuePosition ?? 0) + ) + .map((sub: Submission, index: number) => ({ + ...sub, + queuePosition: index, + })); + + const reorderResList = await Promise.all( + reorderedSubmissions.map((sub: Submission) => + UpdateSubmission(sub.judge_id, sub.team_id, { + $set: { queuePosition: sub.queuePosition }, + }) + ) + ); + + if (!reorderResList.every((res: any) => res.ok)) { + throw new Error( + `Not all submission order updates succeeded\n${JSON.stringify( + reorderResList + )}` + ); + } + + requeueResults.push({ + judge_id, + reorderResCount: reorderResList.length, + }); + } + + const updateTeamRes = await UpdateTeam(team_id, { + $set: { + active: true, + reports: [], + }, + }); + + if (!updateTeamRes.ok) { + throw new Error(updateTeamRes.error ?? ''); + } + + return { + ok: true, + body: { updateTeamRes, requeueResults }, + error: null, + }; + } catch (e) { + const error = e as HttpError; + return { ok: false, body: null, error: error.message }; + } +} diff --git a/app/(pages)/admin/_components/Teams/TeamCard.module.scss b/app/(pages)/admin/_components/Teams/TeamCard.module.scss index 28f453992..720256e51 100644 --- a/app/(pages)/admin/_components/Teams/TeamCard.module.scss +++ b/app/(pages)/admin/_components/Teams/TeamCard.module.scss @@ -81,4 +81,22 @@ border-radius: 8px; background: var(--cow, linear-gradient(180deg, var(--text-error) 0%, var(--text-error) 100%)); box-shadow: 0px 4px 8px 4px rgba(195, 194, 194, 0.08); -} \ No newline at end of file +} + +.restore_button { + width: fit-content; + border: none; + border-radius: 999px; + padding: 8px 14px; + background: #123041; + color: white; + font-size: 0.85rem; + font-weight: 700; + margin-top: 4px; + cursor: pointer; +} + +.restore_button:disabled { + opacity: 0.65; + cursor: not-allowed; +} diff --git a/app/(pages)/admin/_components/Teams/TeamCard.tsx b/app/(pages)/admin/_components/Teams/TeamCard.tsx index ad7de7928..24e824e8f 100644 --- a/app/(pages)/admin/_components/Teams/TeamCard.tsx +++ b/app/(pages)/admin/_components/Teams/TeamCard.tsx @@ -16,12 +16,16 @@ interface TeamCardProps { team: TeamWithJudges; onEditClick?: () => void; editable?: boolean; + onRestoreMissingTeam?: (team_id: string) => void | Promise; + restoringMissingTeam?: boolean; } export default function TeamCard({ team, onEditClick = () => {}, editable = true, + onRestoreMissingTeam = () => {}, + restoringMissingTeam = false, }: TeamCardProps) { const reports = team.reports ?? []; return ( @@ -63,6 +67,15 @@ export default function TeamCard({ ))} + {editable && reports.length > 0 && ( + + )} ); diff --git a/app/(pages)/admin/teams/page.tsx b/app/(pages)/admin/teams/page.tsx index 31ebfbe4c..ad75924e3 100644 --- a/app/(pages)/admin/teams/page.tsx +++ b/app/(pages)/admin/teams/page.tsx @@ -11,6 +11,7 @@ import User from '@typeDefs/user'; import BarChart from '../_components/BarChart/BarChart'; import TeamForm from '../_components/Teams/TeamForm'; import useFormContext from '../_hooks/useFormContext'; +import { restoreMissingTeamForAllJudges } from '@actions/teams/reportMissingTeam'; import styles from './page.module.scss'; interface TeamWithJudges extends Team { @@ -23,6 +24,7 @@ export default function Teams() { const { data, setData } = useFormContext(); const isEditing = Boolean(data._id); const [reportedTeamsDisplay, setReportedTeamsDisplay] = useState(false); + const [restoringTeamId, setRestoringTeamId] = useState(null); if (loading) { return 'loading...'; @@ -65,6 +67,23 @@ export default function Teams() { listContainer?.scrollTo({ top: 0, behavior: 'smooth' }); }; + const handleRestoreMissingTeam = async (team_id: string) => { + if (!team_id) return; + try { + setRestoringTeamId(team_id); + const restoreRes = await restoreMissingTeamForAllJudges(team_id); + if (!restoreRes.ok) { + throw new Error(restoreRes.error ?? 'Failed to restore missing team.'); + } + await getTeams(); + } catch (e) { + const error = e as Error; + alert(error.message); + } finally { + setRestoringTeamId(null); + } + }; + return (

Team Manager

@@ -140,6 +159,8 @@ export default function Teams() { handleEditTeam(team)} + onRestoreMissingTeam={handleRestoreMissingTeam} + restoringMissingTeam={restoringTeamId === team._id} />
))} diff --git a/package-lock.json b/package-lock.json index 83d1639f4..c35d425f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -211,6 +211,7 @@ "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -909,7 +910,6 @@ "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -1036,7 +1036,6 @@ "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -2086,8 +2085,7 @@ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@colors/colors": { "version": "1.5.0", @@ -2192,6 +2190,7 @@ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -2235,6 +2234,7 @@ "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -3167,7 +3167,6 @@ "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -3186,7 +3185,6 @@ "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", @@ -3235,7 +3233,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -3255,7 +3252,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -3266,7 +3262,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -3281,8 +3276,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@jest/environment": { "version": "29.7.0", @@ -3290,7 +3284,6 @@ "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", @@ -3307,7 +3300,6 @@ "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" @@ -3335,7 +3327,6 @@ "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", @@ -3354,7 +3345,6 @@ "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -3371,7 +3361,6 @@ "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", @@ -3416,7 +3405,6 @@ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3429,7 +3417,6 @@ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3451,7 +3438,6 @@ "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", @@ -3469,7 +3455,6 @@ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3496,7 +3481,6 @@ "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", @@ -3512,7 +3496,6 @@ "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", @@ -3529,7 +3512,6 @@ "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", @@ -3696,6 +3678,7 @@ "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.16.14.tgz", "integrity": "sha512-eSXQVCMKU2xc7EcTxe/X/rC9QsV2jUe8eLM3MUCPYbo6V52eCE436akRIvELq/AqZpxx2bwkq7HC0cRhLB+yaw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.23.9", "@mui/core-downloads-tracker": "^5.16.14", @@ -5037,7 +5020,6 @@ "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "type-detect": "4.0.8" } @@ -5048,7 +5030,6 @@ "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@sinonjs/commons": "^3.0.0" } @@ -5310,6 +5291,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.19.tgz", "integrity": "sha512-LEwC7o1ifqg/6r2gn9Dns0f1rhK+fPFDoMiceTJ6kWmVk6bgXBI/9IOWfVan4WiAavK9pIVWdX0/e3J+eEUh5A==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.19.2" } @@ -5358,6 +5340,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -5369,6 +5352,7 @@ "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -5466,6 +5450,7 @@ "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", @@ -5763,6 +5748,7 @@ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "devOptional": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5853,7 +5839,6 @@ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "type-fest": "^0.21.3" }, @@ -5870,7 +5855,6 @@ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=10" }, @@ -6478,6 +6462,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -6543,8 +6528,7 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/builtin-modules": { "version": "3.3.0", @@ -6739,7 +6723,6 @@ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" } @@ -6749,6 +6732,7 @@ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz", "integrity": "sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg==", "license": "MIT", + "peer": true, "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -6856,8 +6840,7 @@ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/class-variance-authority": { "version": "0.7.1", @@ -6944,7 +6927,6 @@ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -6959,8 +6941,7 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/cliui/node_modules/string-width": { "version": "4.2.3", @@ -6968,7 +6949,6 @@ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -6984,7 +6964,6 @@ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -7034,7 +7013,6 @@ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "iojs": ">= 1.0.0", "node": ">= 0.12.0" @@ -7045,8 +7023,7 @@ "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/color": { "version": "4.2.3", @@ -7174,7 +7151,6 @@ "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -7396,7 +7372,6 @@ "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, @@ -7419,7 +7394,6 @@ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -7485,7 +7459,6 @@ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -7707,7 +7680,8 @@ "version": "8.5.2", "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.5.2.tgz", "integrity": "sha512-xQ9oVLrun/eCG/7ru3R+I5bJ7shsD8fFwLEY7yPe27/+fDHCNj0OT5EoG5ZbFyOxOcG6yTwW8oTz/dWyFnyGpg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/embla-carousel-auto-height": { "version": "8.5.2", @@ -7761,7 +7735,6 @@ "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -8036,6 +8009,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -8295,6 +8269,7 @@ "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -8393,6 +8368,7 @@ "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -9254,7 +9230,6 @@ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -9279,7 +9254,6 @@ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -9292,15 +9266,13 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true, - "peer": true, "engines": { "node": ">= 0.8.0" } @@ -9727,7 +9699,6 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "license": "ISC", - "peer": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -10074,8 +10045,7 @@ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/htmlparser2": { "version": "8.0.2", @@ -10121,7 +10091,6 @@ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=10.17.0" } @@ -10164,7 +10133,6 @@ "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" @@ -10478,7 +10446,6 @@ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -10617,7 +10584,6 @@ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" }, @@ -10787,7 +10753,6 @@ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", @@ -10803,7 +10768,6 @@ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "semver": "^7.5.3" }, @@ -10820,7 +10784,6 @@ "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -10836,7 +10799,6 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -10847,7 +10809,6 @@ "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -10969,7 +10930,6 @@ "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", @@ -10985,7 +10945,6 @@ "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -11018,7 +10977,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -11032,7 +10990,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -11047,8 +11004,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-cli": { "version": "29.7.0", @@ -11056,7 +11012,6 @@ "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", @@ -11091,7 +11046,6 @@ "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", @@ -11138,7 +11092,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -11152,7 +11105,6 @@ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -11170,7 +11122,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -11182,7 +11133,6 @@ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -11204,7 +11154,6 @@ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -11218,7 +11167,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -11233,8 +11181,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-diff": { "version": "29.7.0", @@ -11293,7 +11240,6 @@ "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "detect-newline": "^3.0.0" }, @@ -11307,7 +11253,6 @@ "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -11325,7 +11270,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -11339,7 +11283,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -11354,8 +11297,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-environment-node": { "version": "29.7.0", @@ -11418,7 +11360,6 @@ "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" @@ -11433,7 +11374,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -11447,7 +11387,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -11462,8 +11401,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-matcher-utils": { "version": "29.7.0", @@ -11578,7 +11516,6 @@ "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -11594,7 +11531,6 @@ "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" }, @@ -11623,7 +11559,6 @@ "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", @@ -11645,7 +11580,6 @@ "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.7.0" @@ -11660,7 +11594,6 @@ "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/environment": "^29.7.0", @@ -11694,7 +11627,6 @@ "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -11729,7 +11661,6 @@ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -11742,7 +11673,6 @@ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -11764,7 +11694,6 @@ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -11778,7 +11707,6 @@ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -11789,7 +11717,6 @@ "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", @@ -11822,7 +11749,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -11836,7 +11762,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -11851,8 +11776,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-util": { "version": "29.7.0", @@ -11894,7 +11818,6 @@ "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", @@ -11913,7 +11836,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -11927,7 +11849,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -11942,8 +11863,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-watcher": { "version": "29.7.0", @@ -11951,7 +11871,6 @@ "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", @@ -12196,7 +12115,6 @@ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -12227,7 +12145,6 @@ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -12523,7 +12440,6 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -12595,6 +12511,7 @@ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.13.1.tgz", "integrity": "sha512-gdq40tX8StmhP6akMp1pPoEVv+9jTYFSrga/g23JxajPAQhH39ysZrHGzQCSd9PEOnuEQEdjIWqxO7ZSwC0w7Q==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@mongodb-js/saslprep": "^1.1.9", "bson": "^6.10.3", @@ -12837,6 +12754,7 @@ "resolved": "https://registry.npmjs.org/next/-/next-15.5.12.tgz", "integrity": "sha512-Fi/wQ4Etlrn60rz78bebG1i1SR20QxvV8tVp6iJspjLUSHcZoeUXCt+vmWoEcza85ElZzExK/jJ/F6SvtGktjA==", "license": "MIT", + "peer": true, "dependencies": { "@next/env": "15.5.12", "@swc/helpers": "0.5.15", @@ -13410,6 +13328,7 @@ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz", "integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==", "license": "MIT-0", + "peer": true, "engines": { "node": ">=6.0.0" } @@ -13471,7 +13390,6 @@ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "path-key": "^3.0.0" }, @@ -13657,7 +13575,6 @@ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mimic-fn": "^2.1.0" }, @@ -14024,6 +13941,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14165,6 +14083,7 @@ "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -14207,6 +14126,7 @@ "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -14252,7 +14172,6 @@ "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" @@ -14312,8 +14231,7 @@ "url": "https://opencollective.com/fast-check" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/querystring": { "version": "0.2.0", @@ -14349,6 +14267,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -14368,6 +14287,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -14734,7 +14654,6 @@ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -14785,7 +14704,6 @@ "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "resolve-from": "^5.0.0" }, @@ -14799,7 +14717,6 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -14829,7 +14746,6 @@ "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" } @@ -15030,6 +14946,7 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.85.1.tgz", "integrity": "sha512-Uk8WpxM5v+0cMR0XjX9KfRIacmSG86RH4DCCZjLU2rFh5tyutt9siAXJ7G+YfxQ99Q6wrRMbMlVl6KqUms71ag==", "license": "MIT", + "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -15289,8 +15206,7 @@ "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/slash": { "version": "3.0.0", @@ -15368,7 +15284,6 @@ "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -15380,7 +15295,6 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -15485,7 +15399,6 @@ "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" @@ -15713,7 +15626,6 @@ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -15924,6 +15836,7 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", "license": "MIT", + "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -16173,6 +16086,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -16321,6 +16235,7 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -16404,7 +16319,6 @@ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -16503,6 +16417,7 @@ "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16685,7 +16600,6 @@ "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", @@ -16700,8 +16614,7 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/validate-npm-package-license": { "version": "3.0.4", @@ -16995,7 +16908,6 @@ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "license": "ISC", - "peer": true, "engines": { "node": ">=10" } @@ -17088,7 +17000,6 @@ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -17117,8 +17028,7 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/yargs/node_modules/string-width": { "version": "4.2.3", @@ -17126,7 +17036,6 @@ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -17174,6 +17083,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } From ac46caf8b6c2f175e1438af6d2c59a2b94671189 Mon Sep 17 00:00:00 2001 From: Afifah Hadi Date: Wed, 15 Apr 2026 23:19:28 -0700 Subject: [PATCH 2/7] changed button typography and added safety guard --- .../_components/Teams/TeamCard.module.scss | 10 ++++++++- .../admin/_components/Teams/TeamCard.tsx | 22 +++++++++++++------ app/(pages)/admin/teams/page.tsx | 6 +++++ 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/app/(pages)/admin/_components/Teams/TeamCard.module.scss b/app/(pages)/admin/_components/Teams/TeamCard.module.scss index 720256e51..8a876b6e2 100644 --- a/app/(pages)/admin/_components/Teams/TeamCard.module.scss +++ b/app/(pages)/admin/_components/Teams/TeamCard.module.scss @@ -88,7 +88,7 @@ border: none; border-radius: 999px; padding: 8px 14px; - background: #123041; + background: #7a1b20; color: white; font-size: 0.85rem; font-weight: 700; @@ -100,3 +100,11 @@ opacity: 0.65; cursor: not-allowed; } + +.restore_helper_text { + max-width: 380px; + color: #5f5f66; + font-size: 0.8rem; + line-height: 1.25rem; + margin-top: 2px; +} diff --git a/app/(pages)/admin/_components/Teams/TeamCard.tsx b/app/(pages)/admin/_components/Teams/TeamCard.tsx index 24e824e8f..ef1403de7 100644 --- a/app/(pages)/admin/_components/Teams/TeamCard.tsx +++ b/app/(pages)/admin/_components/Teams/TeamCard.tsx @@ -68,13 +68,21 @@ export default function TeamCard({ ))} {editable && reports.length > 0 && ( - + <> + +

+ Restores this team to all missing judges' queues and clears + missing reports. +

+ )} diff --git a/app/(pages)/admin/teams/page.tsx b/app/(pages)/admin/teams/page.tsx index ad75924e3..587c68c2d 100644 --- a/app/(pages)/admin/teams/page.tsx +++ b/app/(pages)/admin/teams/page.tsx @@ -69,6 +69,12 @@ export default function Teams() { const handleRestoreMissingTeam = async (team_id: string) => { if (!team_id) return; + + const confirmed = window.confirm( + "Are you sure this team is present now? This will restore the team to all missing judges' queues and clear missing reports." + ); + if (!confirmed) return; + try { setRestoringTeamId(team_id); const restoreRes = await restoreMissingTeamForAllJudges(team_id); From 5e24ef0754bb21462ab2d8a3479e23b4bb38f2e5 Mon Sep 17 00:00:00 2001 From: Afifah Hadi Date: Wed, 15 Apr 2026 23:33:52 -0700 Subject: [PATCH 3/7] added comments and changed function name for readability --- app/(api)/_actions/teams/reportMissingTeam.ts | 14 +++++++++++--- app/(pages)/admin/teams/page.tsx | 4 ++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/(api)/_actions/teams/reportMissingTeam.ts b/app/(api)/_actions/teams/reportMissingTeam.ts index 51994807c..10dfc611a 100644 --- a/app/(api)/_actions/teams/reportMissingTeam.ts +++ b/app/(api)/_actions/teams/reportMissingTeam.ts @@ -94,13 +94,15 @@ export async function reportMissingProject(judge_id: string, team_id: string) { } } -export async function restoreMissingTeamForAllJudges(team_id: string) { +export async function restoreMissingTeam(team_id: string) { try { + // This action mutates queue order for multiple judges, so keep it admin-only. const session = await auth(); if (!session || session.user.role !== 'admin') { throw new NotAuthenticatedError('Access Denied.'); } + // Pull current team state so we can restore based on authoritative reports. const teamRes = JSON.parse(JSON.stringify(await GetTeam(team_id))); if (!teamRes.ok || !teamRes.body) { throw new Error(teamRes.error ?? `Team with id: ${team_id} not found.`); @@ -108,6 +110,7 @@ export async function restoreMissingTeamForAllJudges(team_id: string) { const team = teamRes.body as Team; const reports = team.reports ?? []; + // Reports can contain duplicates; restore logic should touch each judge once. const reporterJudgeIds = [ ...new Set( reports @@ -118,6 +121,7 @@ export async function restoreMissingTeamForAllJudges(team_id: string) { const requeueResults: { judge_id: string; reorderResCount: number }[] = []; for (const judge_id of reporterJudgeIds) { + // Rebuild this judge's queue from submissions so we can safely reindex. const submissionsRes = JSON.parse( JSON.stringify( await GetManySubmissions({ @@ -134,6 +138,7 @@ export async function restoreMissingTeamForAllJudges(team_id: string) { throw new Error(submissionsRes.error ?? ''); } + // Convert ids to strings for reliable equality checks and update calls. const submissions: Submission[] = (submissionsRes.body ?? []).map( (sub: Submission) => ({ ...sub, @@ -146,12 +151,14 @@ export async function restoreMissingTeamForAllJudges(team_id: string) { (sub: Submission) => sub.team_id === team_id ); - // Only restore queue for judges where this team is effectively "missing": - // reported + has an existing submission + unscored. + // Only move queue position if this judge actually has an unscored target + // submission. This preserves existing judged work and avoids requeueing + // judges where the team is not actionable anymore. if (!targetSubmission || targetSubmission.is_scored) { continue; } + // "Restore" policy: put team at end, then normalize all queue positions. targetSubmission.queuePosition = Math.max( ...submissions.map((sub: Submission) => sub.queuePosition ?? 0) @@ -189,6 +196,7 @@ export async function restoreMissingTeamForAllJudges(team_id: string) { }); } + // Once queues are restored, mark team present and clear all missing reports. const updateTeamRes = await UpdateTeam(team_id, { $set: { active: true, diff --git a/app/(pages)/admin/teams/page.tsx b/app/(pages)/admin/teams/page.tsx index 587c68c2d..cf2a0de05 100644 --- a/app/(pages)/admin/teams/page.tsx +++ b/app/(pages)/admin/teams/page.tsx @@ -11,7 +11,7 @@ import User from '@typeDefs/user'; import BarChart from '../_components/BarChart/BarChart'; import TeamForm from '../_components/Teams/TeamForm'; import useFormContext from '../_hooks/useFormContext'; -import { restoreMissingTeamForAllJudges } from '@actions/teams/reportMissingTeam'; +import { restoreMissingTeam } from '@actions/teams/reportMissingTeam'; import styles from './page.module.scss'; interface TeamWithJudges extends Team { @@ -77,7 +77,7 @@ export default function Teams() { try { setRestoringTeamId(team_id); - const restoreRes = await restoreMissingTeamForAllJudges(team_id); + const restoreRes = await restoreMissingTeam(team_id); if (!restoreRes.ok) { throw new Error(restoreRes.error ?? 'Failed to restore missing team.'); } From 495413895fdff3d0221386438d7853517880aee4 Mon Sep 17 00:00:00 2001 From: Afifah Hadi Date: Thu, 23 Apr 2026 17:26:18 -0700 Subject: [PATCH 4/7] added functionality to button to set active true --- app/(api)/_actions/teams/reportMissingTeam.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/app/(api)/_actions/teams/reportMissingTeam.ts b/app/(api)/_actions/teams/reportMissingTeam.ts index 10dfc611a..fc8946e7e 100644 --- a/app/(api)/_actions/teams/reportMissingTeam.ts +++ b/app/(api)/_actions/teams/reportMissingTeam.ts @@ -196,13 +196,12 @@ export async function restoreMissingTeam(team_id: string) { }); } - // Once queues are restored, mark team present and clear all missing reports. - const updateTeamRes = await UpdateTeam(team_id, { - $set: { - active: true, - reports: [], - }, - }); + // Once queues are restored, clear reports and only set active when needed. + const teamUpdate = team.active + ? { $set: { reports: [] } } + : { $set: { reports: [], active: true } }; + + const updateTeamRes = await UpdateTeam(team_id, teamUpdate); if (!updateTeamRes.ok) { throw new Error(updateTeamRes.error ?? ''); From e12c357e476430a4259faf202a2666c3a2984230 Mon Sep 17 00:00:00 2001 From: Afifah Hadi Date: Wed, 29 Apr 2026 13:06:32 -0700 Subject: [PATCH 5/7] changed active behavior and added tests --- __tests__/actions/reportMissingTeam.test.ts | 289 ++++++++++++++++++ app/(api)/_actions/teams/reportMissingTeam.ts | 13 +- 2 files changed, 296 insertions(+), 6 deletions(-) create mode 100644 __tests__/actions/reportMissingTeam.test.ts diff --git a/__tests__/actions/reportMissingTeam.test.ts b/__tests__/actions/reportMissingTeam.test.ts new file mode 100644 index 000000000..22da61cef --- /dev/null +++ b/__tests__/actions/reportMissingTeam.test.ts @@ -0,0 +1,289 @@ +/** @jest-environment node */ + +import { auth } from '@/auth'; +import { GetManySubmissions } from '@datalib/submissions/getSubmissions'; +import { UpdateSubmission } from '@datalib/submissions/updateSubmission'; +import { GetTeam } from '@datalib/teams/getTeam'; +import { UpdateTeam } from '@datalib/teams/updateTeam'; +import { + reportMissingProject, + restoreMissingTeam, +} from '@actions/teams/reportMissingTeam'; +import Submission from '@typeDefs/submission'; + +jest.mock('@/auth', () => ({ + auth: jest.fn(), +})); + +jest.mock('@datalib/submissions/getSubmissions', () => ({ + GetManySubmissions: jest.fn(), +})); + +jest.mock('@datalib/submissions/updateSubmission', () => ({ + UpdateSubmission: jest.fn(), +})); + +jest.mock('@datalib/teams/getTeam', () => ({ + GetTeam: jest.fn(), +})); + +jest.mock('@datalib/teams/updateTeam', () => ({ + UpdateTeam: jest.fn(), +})); + +const mockAuth = auth as jest.MockedFunction; +const mockGetManySubmissions = GetManySubmissions as jest.MockedFunction< + typeof GetManySubmissions +>; +const mockUpdateSubmission = UpdateSubmission as jest.MockedFunction< + typeof UpdateSubmission +>; +const mockGetTeam = GetTeam as jest.MockedFunction; +const mockUpdateTeam = UpdateTeam as jest.MockedFunction; + +const makeSubmission = ( + judge_id: string, + team_id: string, + queuePosition: number, + is_scored = false +): Submission => ({ + judge_id, + team_id, + queuePosition, + is_scored, + social_good: null, + creativity: null, + presentation: null, + scores: [], +}); + +describe('reportMissingTeam flow', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('reportMissingProject', () => { + it('pushes a report and moves the reported team to the end of the judge queue', async () => { + mockUpdateTeam.mockResolvedValue({ + ok: true, + body: {}, + error: null, + } as any); + mockGetManySubmissions.mockResolvedValue({ + ok: true, + body: [ + makeSubmission('judge-1', 'team-a', 0), + makeSubmission('judge-1', 'team-b', 1), + makeSubmission('judge-1', 'team-c', 2), + ], + error: null, + } as any); + mockUpdateSubmission.mockResolvedValue({ + ok: true, + body: {}, + error: null, + } as any); + + const res = await reportMissingProject('judge-1', 'team-b'); + + expect(res.ok).toBe(true); + expect(mockUpdateTeam).toHaveBeenCalledWith('team-b', { + $push: { + reports: { + timestamp: expect.any(Number), + judge_id: 'judge-1', + }, + }, + }); + + expect(mockUpdateSubmission).toHaveBeenCalledTimes(3); + expect(mockUpdateSubmission).toHaveBeenNthCalledWith( + 1, + 'judge-1', + 'team-a', + { + $set: { queuePosition: 0 }, + } + ); + expect(mockUpdateSubmission).toHaveBeenNthCalledWith( + 2, + 'judge-1', + 'team-c', + { + $set: { queuePosition: 1 }, + } + ); + expect(mockUpdateSubmission).toHaveBeenNthCalledWith( + 3, + 'judge-1', + 'team-b', + { + $set: { queuePosition: 2 }, + } + ); + }); + + it('returns an error when the reported team is not in that judge submission list', async () => { + mockUpdateTeam.mockResolvedValue({ + ok: true, + body: {}, + error: null, + } as any); + mockGetManySubmissions.mockResolvedValue({ + ok: true, + body: [makeSubmission('judge-1', 'team-x', 0)], + error: null, + } as any); + + const res = await reportMissingProject('judge-1', 'team-b'); + + expect(res.ok).toBe(false); + expect(res.error).toContain( + 'Submission from judge: judge-1 and team: team-b not found.' + ); + expect(mockUpdateSubmission).not.toHaveBeenCalled(); + }); + }); + + describe('restoreMissingTeam', () => { + it('requeues each unique reporting judge, clears reports, and reactivates inactive team', async () => { + mockAuth.mockResolvedValue({ user: { role: 'admin' } } as any); + mockGetTeam.mockResolvedValue({ + ok: true, + body: { + _id: 'team-target', + teamNumber: 5, + tableNumber: 'A1', + name: 'Target Team', + tracks: ['General'], + active: false, + reports: [ + { timestamp: 1, judge_id: 'judge-a' }, + { timestamp: 2, judge_id: 'judge-a' }, + { timestamp: 3, judge_id: 'judge-b' }, + { timestamp: 4, judge_id: '' }, + ], + }, + error: null, + } as any); + + mockGetManySubmissions + .mockResolvedValueOnce({ + ok: true, + body: [ + makeSubmission('judge-a', 'team-x', 0), + makeSubmission('judge-a', 'team-target', 1), + makeSubmission('judge-a', 'team-y', 2), + ], + error: null, + } as any) + .mockResolvedValueOnce({ + ok: true, + body: [ + makeSubmission('judge-b', 'team-target', 0, true), + makeSubmission('judge-b', 'team-z', 1), + ], + error: null, + } as any); + + mockUpdateSubmission.mockResolvedValue({ + ok: true, + body: {}, + error: null, + } as any); + mockUpdateTeam.mockResolvedValue({ + ok: true, + body: {}, + error: null, + } as any); + + const res = await restoreMissingTeam('team-target'); + + expect(res.ok).toBe(true); + expect(mockGetManySubmissions).toHaveBeenCalledTimes(2); + expect(mockGetManySubmissions).toHaveBeenNthCalledWith(1, { + judge_id: { '*convertId': { id: 'judge-a' } }, + }); + expect(mockGetManySubmissions).toHaveBeenNthCalledWith(2, { + judge_id: { '*convertId': { id: 'judge-b' } }, + }); + + // judge-a queue should be reordered, judge-b is skipped because target is already scored + expect(mockUpdateSubmission).toHaveBeenCalledTimes(3); + expect(mockUpdateSubmission).toHaveBeenNthCalledWith( + 1, + 'judge-a', + 'team-x', + { + $set: { queuePosition: 0 }, + } + ); + expect(mockUpdateSubmission).toHaveBeenNthCalledWith( + 2, + 'judge-a', + 'team-y', + { + $set: { queuePosition: 1 }, + } + ); + expect(mockUpdateSubmission).toHaveBeenNthCalledWith( + 3, + 'judge-a', + 'team-target', + { + $set: { queuePosition: 2 }, + } + ); + + expect(mockUpdateTeam).toHaveBeenCalledWith('team-target', { + $set: { reports: [], active: true }, + }); + expect(res.body?.requeueResults).toEqual([ + { judge_id: 'judge-a', reorderResCount: 3 }, + ]); + }); + + it('clears reports and keeps the team active when the team is already active', async () => { + mockAuth.mockResolvedValue({ user: { role: 'admin' } } as any); + mockGetTeam.mockResolvedValue({ + ok: true, + body: { + _id: 'team-target', + teamNumber: 10, + tableNumber: 'B2', + name: 'Already Active Team', + tracks: ['General'], + active: true, + reports: [], + }, + error: null, + } as any); + mockUpdateTeam.mockResolvedValue({ + ok: true, + body: {}, + error: null, + } as any); + + const res = await restoreMissingTeam('team-target'); + + expect(res.ok).toBe(true); + expect(mockGetManySubmissions).not.toHaveBeenCalled(); + expect(mockUpdateTeam).toHaveBeenCalledWith('team-target', { + $set: { reports: [], active: true }, + }); + }); + + it('rejects non-admin users', async () => { + mockAuth.mockResolvedValue({ user: { role: 'judge' } } as any); + + const res = await restoreMissingTeam('team-target'); + + expect(res.ok).toBe(false); + expect(res.error).toBe('Access Denied.'); + expect(mockGetTeam).not.toHaveBeenCalled(); + expect(mockUpdateTeam).not.toHaveBeenCalled(); + expect(mockGetManySubmissions).not.toHaveBeenCalled(); + expect(mockUpdateSubmission).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/app/(api)/_actions/teams/reportMissingTeam.ts b/app/(api)/_actions/teams/reportMissingTeam.ts index fc8946e7e..10dfc611a 100644 --- a/app/(api)/_actions/teams/reportMissingTeam.ts +++ b/app/(api)/_actions/teams/reportMissingTeam.ts @@ -196,12 +196,13 @@ export async function restoreMissingTeam(team_id: string) { }); } - // Once queues are restored, clear reports and only set active when needed. - const teamUpdate = team.active - ? { $set: { reports: [] } } - : { $set: { reports: [], active: true } }; - - const updateTeamRes = await UpdateTeam(team_id, teamUpdate); + // Once queues are restored, mark team present and clear all missing reports. + const updateTeamRes = await UpdateTeam(team_id, { + $set: { + active: true, + reports: [], + }, + }); if (!updateTeamRes.ok) { throw new Error(updateTeamRes.error ?? ''); From dac8279ca265b40c1f95cc495ff08fb788741094 Mon Sep 17 00:00:00 2001 From: michelleyeoh <74385095+michelleyeoh@users.noreply.github.com> Date: Fri, 1 May 2026 11:26:38 -0700 Subject: [PATCH 6/7] Add generic error message Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- app/(api)/_actions/teams/reportMissingTeam.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/(api)/_actions/teams/reportMissingTeam.ts b/app/(api)/_actions/teams/reportMissingTeam.ts index 10dfc611a..a4338a554 100644 --- a/app/(api)/_actions/teams/reportMissingTeam.ts +++ b/app/(api)/_actions/teams/reportMissingTeam.ts @@ -205,7 +205,9 @@ export async function restoreMissingTeam(team_id: string) { }); if (!updateTeamRes.ok) { - throw new Error(updateTeamRes.error ?? ''); + throw new Error( + updateTeamRes.error ?? `Failed to restore missing team with id: ${team_id}.` + ); } return { From d9100113bd44860f05f979b4bd231ac74a1871de Mon Sep 17 00:00:00 2001 From: michelleyeoh Date: Fri, 1 May 2026 11:30:38 -0700 Subject: [PATCH 7/7] lint and error message fix --- app/(api)/_actions/teams/reportMissingTeam.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/(api)/_actions/teams/reportMissingTeam.ts b/app/(api)/_actions/teams/reportMissingTeam.ts index a4338a554..43df178c7 100644 --- a/app/(api)/_actions/teams/reportMissingTeam.ts +++ b/app/(api)/_actions/teams/reportMissingTeam.ts @@ -37,7 +37,10 @@ export async function reportMissingProject(judge_id: string, team_id: string) { ); if (!submissionsRes.ok) { - throw new Error(submissionsRes.error ?? ''); + throw new Error( + submissionsRes.error ?? + `Failed to fetch submissions for judge: ${judge_id}` + ); } const submissions: Submission[] = submissionsRes.body ?? []; @@ -206,7 +209,8 @@ export async function restoreMissingTeam(team_id: string) { if (!updateTeamRes.ok) { throw new Error( - updateTeamRes.error ?? `Failed to restore missing team with id: ${team_id}.` + updateTeamRes.error ?? + `Failed to restore missing team with id: ${team_id}.` ); }