feat: enhance draft reset functionality to include deletion of draft timers
This commit is contained in:
parent
4aafd5853b
commit
e7baea319e
2 changed files with 97 additions and 29 deletions
|
|
@ -13,6 +13,7 @@ import { findAllSportsSeasons } from "~/models/sports-season";
|
||||||
import { findDraftSlotsBySeasonId, setDraftOrder, randomizeDraftOrder } from "~/models/draft-slot";
|
import { findDraftSlotsBySeasonId, setDraftOrder, randomizeDraftOrder } from "~/models/draft-slot";
|
||||||
import { deleteAllDraftPicks } from "~/models/draft-pick";
|
import { deleteAllDraftPicks } from "~/models/draft-pick";
|
||||||
import { clearAllQueuesForSeason } from "~/models/draft-queue";
|
import { clearAllQueuesForSeason } from "~/models/draft-queue";
|
||||||
|
import { deleteSeasonTimers } from "~/models/draft-timer";
|
||||||
import type { Route } from "./+types/$leagueId.settings";
|
import type { Route } from "./+types/$leagueId.settings";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Input } from "~/components/ui/input";
|
import { Input } from "~/components/ui/input";
|
||||||
|
|
@ -284,21 +285,24 @@ export async function action(args: Route.ActionArgs) {
|
||||||
return { error: "Only admins can reset the draft" };
|
return { error: "Only admins can reset the draft" };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if season status is draft or completed
|
|
||||||
if (season.status !== "draft" && season.status !== "completed" && season.status !== "active") {
|
|
||||||
return { error: "Draft can only be reset after it has started" };
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Delete all draft picks
|
// Delete all draft picks
|
||||||
await deleteAllDraftPicks(season.id);
|
await deleteAllDraftPicks(season.id);
|
||||||
|
|
||||||
// Clear all draft queues
|
// Clear all draft queues
|
||||||
await clearAllQueuesForSeason(season.id);
|
await clearAllQueuesForSeason(season.id);
|
||||||
|
|
||||||
// Set season status back to pre_draft (keeps draft order intact)
|
// Delete all draft timers
|
||||||
await updateSeason(season.id, { status: "pre_draft" });
|
await deleteSeasonTimers(season.id);
|
||||||
|
|
||||||
|
// Set season status back to pre_draft and reset draft state (keeps draft order intact)
|
||||||
|
await updateSeason(season.id, {
|
||||||
|
status: "pre_draft",
|
||||||
|
currentPickNumber: null,
|
||||||
|
draftStartedAt: null,
|
||||||
|
draftPaused: false,
|
||||||
|
});
|
||||||
|
|
||||||
return { success: true, message: "Draft has been reset successfully. Draft order preserved." };
|
return { success: true, message: "Draft has been reset successfully. Draft order preserved." };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error resetting draft:", error);
|
console.error("Error resetting draft:", error);
|
||||||
|
|
@ -676,7 +680,6 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone
|
||||||
mode="single"
|
mode="single"
|
||||||
selected={draftDate}
|
selected={draftDate}
|
||||||
onSelect={setDraftDate}
|
onSelect={setDraftDate}
|
||||||
initialFocus
|
|
||||||
disabled={(date) => date < new Date(new Date().setHours(0, 0, 0, 0))}
|
disabled={(date) => date < new Date(new Date().setHours(0, 0, 0, 0))}
|
||||||
/>
|
/>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
|
|
@ -1017,11 +1020,11 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
{/* Reset Draft - Admin Only */}
|
{/* Reset Draft - Admin Only */}
|
||||||
{isAdmin && season && (season.status === "draft" || season.status === "completed" || season.status === "active") && (
|
{isAdmin && season && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h3 className="font-medium">Reset Draft</h3>
|
<h3 className="font-medium">Reset Draft</h3>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Delete all draft picks and reset the season to pre-draft status. The draft order will be preserved.
|
Delete all draft picks, queues, and timers, and reset the season to pre-draft status. The draft order will be preserved.
|
||||||
</p>
|
</p>
|
||||||
<AlertDialog>
|
<AlertDialog>
|
||||||
<AlertDialogTrigger asChild>
|
<AlertDialogTrigger asChild>
|
||||||
|
|
@ -1033,7 +1036,7 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>Reset the draft?</AlertDialogTitle>
|
<AlertDialogTitle>Reset the draft?</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
This will delete all draft picks and draft queues, and set the season back to pre-draft status.
|
This will delete all draft picks, draft queues, and draft timers, and set the season back to pre-draft status.
|
||||||
The draft order will remain intact. This action cannot be undone.
|
The draft order will remain intact. This action cannot be undone.
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
import { deleteAllDraftPicks } from '~/models/draft-pick';
|
import { deleteAllDraftPicks } from '~/models/draft-pick';
|
||||||
import { clearAllQueuesForSeason } from '~/models/draft-queue';
|
import { clearAllQueuesForSeason } from '~/models/draft-queue';
|
||||||
|
import { deleteSeasonTimers } from '~/models/draft-timer';
|
||||||
import { updateSeason } from '~/models/season';
|
import { updateSeason } from '~/models/season';
|
||||||
import { isUserAdminByClerkId } from '~/models/user';
|
import { isUserAdminByClerkId } from '~/models/user';
|
||||||
|
|
||||||
|
|
@ -38,6 +39,10 @@ vi.mock('~/models/draft-queue', () => ({
|
||||||
clearAllQueuesForSeason: vi.fn(),
|
clearAllQueuesForSeason: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('~/models/draft-timer', () => ({
|
||||||
|
deleteSeasonTimers: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock('~/models/season', () => ({
|
vi.mock('~/models/season', () => ({
|
||||||
updateSeason: vi.fn(),
|
updateSeason: vi.fn(),
|
||||||
findCurrentSeasonWithSports: vi.fn(),
|
findCurrentSeasonWithSports: vi.fn(),
|
||||||
|
|
@ -119,6 +124,17 @@ describe('Draft Reset - Delete Operations', () => {
|
||||||
expect(clearAllQueuesForSeason).toHaveBeenCalledTimes(1);
|
expect(clearAllQueuesForSeason).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should delete all draft timers for a season', async () => {
|
||||||
|
const seasonId = 'season-1';
|
||||||
|
|
||||||
|
vi.mocked(deleteSeasonTimers).mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
await deleteSeasonTimers(seasonId);
|
||||||
|
|
||||||
|
expect(deleteSeasonTimers).toHaveBeenCalledWith(seasonId);
|
||||||
|
expect(deleteSeasonTimers).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
it('should handle errors when deleting draft picks fails', async () => {
|
it('should handle errors when deleting draft picks fails', async () => {
|
||||||
const seasonId = 'season-1';
|
const seasonId = 'season-1';
|
||||||
const error = new Error('Database error');
|
const error = new Error('Database error');
|
||||||
|
|
@ -136,6 +152,15 @@ describe('Draft Reset - Delete Operations', () => {
|
||||||
|
|
||||||
await expect(clearAllQueuesForSeason(seasonId)).rejects.toThrow('Database error');
|
await expect(clearAllQueuesForSeason(seasonId)).rejects.toThrow('Database error');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle errors when deleting timers fails', async () => {
|
||||||
|
const seasonId = 'season-1';
|
||||||
|
const error = new Error('Database error');
|
||||||
|
|
||||||
|
vi.mocked(deleteSeasonTimers).mockRejectedValue(error);
|
||||||
|
|
||||||
|
await expect(deleteSeasonTimers(seasonId)).rejects.toThrow('Database error');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Draft Reset - Season Status Update', () => {
|
describe('Draft Reset - Season Status Update', () => {
|
||||||
|
|
@ -172,30 +197,30 @@ describe('Draft Reset - Status Validation', () => {
|
||||||
|
|
||||||
it('should allow reset when season status is draft', () => {
|
it('should allow reset when season status is draft', () => {
|
||||||
const seasonStatus = 'draft';
|
const seasonStatus = 'draft';
|
||||||
const allowedStatuses = ['draft', 'completed', 'active'];
|
const validStatuses = ['pre_draft', 'draft', 'completed', 'active'];
|
||||||
|
|
||||||
expect(allowedStatuses.includes(seasonStatus)).toBe(true);
|
expect(validStatuses.includes(seasonStatus)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow reset when season status is completed', () => {
|
it('should allow reset when season status is completed', () => {
|
||||||
const seasonStatus = 'completed';
|
const seasonStatus = 'completed';
|
||||||
const allowedStatuses = ['draft', 'completed', 'active'];
|
const validStatuses = ['pre_draft', 'draft', 'completed', 'active'];
|
||||||
|
|
||||||
expect(allowedStatuses.includes(seasonStatus)).toBe(true);
|
expect(validStatuses.includes(seasonStatus)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow reset when season status is active', () => {
|
it('should allow reset when season status is active', () => {
|
||||||
const seasonStatus = 'active';
|
const seasonStatus = 'active';
|
||||||
const allowedStatuses = ['draft', 'completed', 'active'];
|
const validStatuses = ['pre_draft', 'draft', 'completed', 'active'];
|
||||||
|
|
||||||
expect(allowedStatuses.includes(seasonStatus)).toBe(true);
|
expect(validStatuses.includes(seasonStatus)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not allow reset when season status is pre_draft', () => {
|
it('should allow reset when season status is pre_draft', () => {
|
||||||
const seasonStatus = 'pre_draft';
|
const seasonStatus = 'pre_draft';
|
||||||
const allowedStatuses = ['draft', 'completed', 'active'];
|
const validStatuses = ['pre_draft', 'draft', 'completed', 'active'];
|
||||||
|
|
||||||
expect(allowedStatuses.includes(seasonStatus)).toBe(false);
|
expect(validStatuses.includes(seasonStatus)).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -223,14 +248,29 @@ describe('Draft Reset - Complete Workflow', () => {
|
||||||
await clearAllQueuesForSeason(seasonId);
|
await clearAllQueuesForSeason(seasonId);
|
||||||
expect(clearAllQueuesForSeason).toHaveBeenCalledWith(seasonId);
|
expect(clearAllQueuesForSeason).toHaveBeenCalledWith(seasonId);
|
||||||
|
|
||||||
// Step 4: Update season status
|
// Step 4: Delete draft timers
|
||||||
|
vi.mocked(deleteSeasonTimers).mockResolvedValue(undefined);
|
||||||
|
await deleteSeasonTimers(seasonId);
|
||||||
|
expect(deleteSeasonTimers).toHaveBeenCalledWith(seasonId);
|
||||||
|
|
||||||
|
// Step 5: Update season status and reset draft state
|
||||||
const mockUpdatedSeason = createMockSeason({ id: seasonId, status: 'pre_draft' });
|
const mockUpdatedSeason = createMockSeason({ id: seasonId, status: 'pre_draft' });
|
||||||
|
|
||||||
vi.mocked(updateSeason).mockResolvedValue(mockUpdatedSeason);
|
vi.mocked(updateSeason).mockResolvedValue(mockUpdatedSeason);
|
||||||
const result = await updateSeason(seasonId, { status: 'pre_draft' });
|
const result = await updateSeason(seasonId, {
|
||||||
|
status: 'pre_draft',
|
||||||
|
currentPickNumber: null,
|
||||||
|
draftStartedAt: null,
|
||||||
|
draftPaused: false,
|
||||||
|
});
|
||||||
|
|
||||||
expect(result.status).toBe('pre_draft');
|
expect(result.status).toBe('pre_draft');
|
||||||
expect(updateSeason).toHaveBeenCalledWith(seasonId, { status: 'pre_draft' });
|
expect(updateSeason).toHaveBeenCalledWith(seasonId, {
|
||||||
|
status: 'pre_draft',
|
||||||
|
currentPickNumber: null,
|
||||||
|
draftStartedAt: null,
|
||||||
|
draftPaused: false,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not proceed with reset if admin check fails', async () => {
|
it('should not proceed with reset if admin check fails', async () => {
|
||||||
|
|
@ -244,6 +284,7 @@ describe('Draft Reset - Complete Workflow', () => {
|
||||||
// Should not call any reset functions
|
// Should not call any reset functions
|
||||||
expect(deleteAllDraftPicks).not.toHaveBeenCalled();
|
expect(deleteAllDraftPicks).not.toHaveBeenCalled();
|
||||||
expect(clearAllQueuesForSeason).not.toHaveBeenCalled();
|
expect(clearAllQueuesForSeason).not.toHaveBeenCalled();
|
||||||
|
expect(deleteSeasonTimers).not.toHaveBeenCalled();
|
||||||
expect(updateSeason).not.toHaveBeenCalled();
|
expect(updateSeason).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -385,6 +426,10 @@ describe('Draft Reset - Data Integrity', () => {
|
||||||
callOrder.push('clearAllQueuesForSeason');
|
callOrder.push('clearAllQueuesForSeason');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
vi.mocked(deleteSeasonTimers).mockImplementation(async () => {
|
||||||
|
callOrder.push('deleteSeasonTimers');
|
||||||
|
});
|
||||||
|
|
||||||
vi.mocked(updateSeason).mockImplementation(async () => {
|
vi.mocked(updateSeason).mockImplementation(async () => {
|
||||||
callOrder.push('updateSeason');
|
callOrder.push('updateSeason');
|
||||||
return createMockSeason({ id: seasonId, status: 'pre_draft' });
|
return createMockSeason({ id: seasonId, status: 'pre_draft' });
|
||||||
|
|
@ -393,12 +438,19 @@ describe('Draft Reset - Data Integrity', () => {
|
||||||
// Execute in correct order
|
// Execute in correct order
|
||||||
await deleteAllDraftPicks(seasonId);
|
await deleteAllDraftPicks(seasonId);
|
||||||
await clearAllQueuesForSeason(seasonId);
|
await clearAllQueuesForSeason(seasonId);
|
||||||
await updateSeason(seasonId, { status: 'pre_draft' });
|
await deleteSeasonTimers(seasonId);
|
||||||
|
await updateSeason(seasonId, {
|
||||||
|
status: 'pre_draft',
|
||||||
|
currentPickNumber: null,
|
||||||
|
draftStartedAt: null,
|
||||||
|
draftPaused: false,
|
||||||
|
});
|
||||||
|
|
||||||
// Verify order
|
// Verify order
|
||||||
expect(callOrder).toEqual([
|
expect(callOrder).toEqual([
|
||||||
'deleteAllDraftPicks',
|
'deleteAllDraftPicks',
|
||||||
'clearAllQueuesForSeason',
|
'clearAllQueuesForSeason',
|
||||||
|
'deleteSeasonTimers',
|
||||||
'updateSeason',
|
'updateSeason',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
@ -409,17 +461,30 @@ describe('Draft Reset - Data Integrity', () => {
|
||||||
// All operations reference the same season
|
// All operations reference the same season
|
||||||
vi.mocked(deleteAllDraftPicks).mockResolvedValue(undefined);
|
vi.mocked(deleteAllDraftPicks).mockResolvedValue(undefined);
|
||||||
vi.mocked(clearAllQueuesForSeason).mockResolvedValue(undefined);
|
vi.mocked(clearAllQueuesForSeason).mockResolvedValue(undefined);
|
||||||
|
vi.mocked(deleteSeasonTimers).mockResolvedValue(undefined);
|
||||||
vi.mocked(updateSeason).mockResolvedValue(
|
vi.mocked(updateSeason).mockResolvedValue(
|
||||||
createMockSeason({ id: seasonId, status: 'pre_draft' })
|
createMockSeason({ id: seasonId, status: 'pre_draft' })
|
||||||
);
|
);
|
||||||
|
|
||||||
await deleteAllDraftPicks(seasonId);
|
await deleteAllDraftPicks(seasonId);
|
||||||
await clearAllQueuesForSeason(seasonId);
|
await clearAllQueuesForSeason(seasonId);
|
||||||
await updateSeason(seasonId, { status: 'pre_draft' });
|
await deleteSeasonTimers(seasonId);
|
||||||
|
await updateSeason(seasonId, {
|
||||||
|
status: 'pre_draft',
|
||||||
|
currentPickNumber: null,
|
||||||
|
draftStartedAt: null,
|
||||||
|
draftPaused: false,
|
||||||
|
});
|
||||||
|
|
||||||
// All operations used the same season ID
|
// All operations used the same season ID
|
||||||
expect(deleteAllDraftPicks).toHaveBeenCalledWith(seasonId);
|
expect(deleteAllDraftPicks).toHaveBeenCalledWith(seasonId);
|
||||||
expect(clearAllQueuesForSeason).toHaveBeenCalledWith(seasonId);
|
expect(clearAllQueuesForSeason).toHaveBeenCalledWith(seasonId);
|
||||||
expect(updateSeason).toHaveBeenCalledWith(seasonId, { status: 'pre_draft' });
|
expect(deleteSeasonTimers).toHaveBeenCalledWith(seasonId);
|
||||||
|
expect(updateSeason).toHaveBeenCalledWith(seasonId, {
|
||||||
|
status: 'pre_draft',
|
||||||
|
currentPickNumber: null,
|
||||||
|
draftStartedAt: null,
|
||||||
|
draftPaused: false,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue