feat: add draft rounds configuration to league settings
This commit is contained in:
parent
0e68bd2037
commit
501c5b183f
7 changed files with 1356 additions and 130 deletions
|
|
@ -3,7 +3,7 @@ import { Form, Link, redirect, useNavigation } from "react-router";
|
|||
import { getAuth } from "@clerk/react-router/server";
|
||||
import { findLeagueById, updateLeague, deleteLeague } from "~/models/league";
|
||||
import { isCommissioner } from "~/models/commissioner";
|
||||
import { findCurrentSeasonWithSports } from "~/models/season";
|
||||
import { findCurrentSeasonWithSports, updateSeason } from "~/models/season";
|
||||
import { findTeamsBySeasonId, createManyTeams, deleteTeam } from "~/models/team";
|
||||
import { unlinkSportFromSeason, linkMultipleSportsToSeason } from "~/models/season-sport";
|
||||
import { findAllSportsSeasons } from "~/models/sports-season";
|
||||
|
|
@ -117,6 +117,31 @@ export async function action(args: Route.ActionArgs) {
|
|||
return { error: "No active season found" };
|
||||
}
|
||||
|
||||
if (intent === "update-draft-rounds") {
|
||||
if (season.status !== "pre_draft") {
|
||||
return { error: "Cannot modify draft rounds after draft has started" };
|
||||
}
|
||||
|
||||
const draftRounds = formData.get("draftRounds");
|
||||
const draftRoundsNum = typeof draftRounds === "string" ? parseInt(draftRounds, 10) : null;
|
||||
|
||||
if (draftRoundsNum === null || isNaN(draftRoundsNum)) {
|
||||
return { error: "Draft rounds must be a valid number" };
|
||||
}
|
||||
|
||||
const sportsCount = season.seasonSports?.length || 0;
|
||||
if (draftRoundsNum < sportsCount) {
|
||||
return { error: `Draft rounds must be at least ${sportsCount} (number of sports selected)` };
|
||||
}
|
||||
|
||||
if (draftRoundsNum < 1 || draftRoundsNum > 50) {
|
||||
return { error: "Draft rounds must be between 1 and 50" };
|
||||
}
|
||||
|
||||
await updateSeason(season.id, { draftRounds: draftRoundsNum });
|
||||
return { success: true, message: "Draft rounds updated successfully" };
|
||||
}
|
||||
|
||||
if (intent === "update-sports") {
|
||||
if (season.status !== "pre_draft") {
|
||||
return { error: "Cannot modify sports after draft has started" };
|
||||
|
|
@ -281,6 +306,7 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone
|
|||
? draftSlots.map((slot: any) => slot.teamId)
|
||||
: teams.map((team: any) => team.id)
|
||||
);
|
||||
const [draftRounds, setDraftRounds] = useState<number>(season?.draftRounds || 20);
|
||||
|
||||
// Update draft order when loader data changes (after randomization)
|
||||
useEffect(() => {
|
||||
|
|
@ -293,8 +319,15 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone
|
|||
const canEditSports = season && season.status === "pre_draft";
|
||||
const canEditTeamCount = season && season.status === "pre_draft";
|
||||
const canEditDraftOrder = season && season.status === "pre_draft";
|
||||
const canEditDraftRounds = season && season.status === "pre_draft";
|
||||
const minTeamCount = Math.max(6, teamsWithOwners);
|
||||
|
||||
// Calculate flex spots and minimum rounds
|
||||
const sportsCount = selectedSports.size;
|
||||
const minRounds = sportsCount;
|
||||
const recommendedRounds = Math.ceil(sportsCount * 1.25);
|
||||
const flexSpots = Math.max(0, draftRounds - sportsCount);
|
||||
|
||||
const handleSportToggle = (sportId: string) => {
|
||||
const newSelected = new Set(selectedSports);
|
||||
if (newSelected.has(sportId)) {
|
||||
|
|
@ -333,8 +366,10 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone
|
|||
<p className="text-muted-foreground">{league.name}</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* Update League Form */}
|
||||
<Form method="post" className="space-y-6">
|
||||
<input type="hidden" name="intent" value="update" />
|
||||
|
||||
{/* General Settings */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>General Settings</CardTitle>
|
||||
|
|
@ -342,68 +377,111 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone
|
|||
Update your league's basic information
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form method="post" className="space-y-4">
|
||||
<input type="hidden" name="intent" value="update" />
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">League Name</Label>
|
||||
<Input
|
||||
id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
defaultValue={league.name}
|
||||
placeholder="Enter league name"
|
||||
required
|
||||
minLength={3}
|
||||
maxLength={50}
|
||||
/>
|
||||
</div>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">League Name</Label>
|
||||
<Input
|
||||
id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
defaultValue={league.name}
|
||||
placeholder="Enter league name"
|
||||
required
|
||||
minLength={3}
|
||||
maxLength={50}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="teamCount">Number of Teams</Label>
|
||||
<Select
|
||||
name="teamCount"
|
||||
defaultValue={teamCount.toString()}
|
||||
disabled={!canEditTeamCount}
|
||||
>
|
||||
<SelectTrigger id="teamCount">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Array.from({ length: 11 }, (_, i) => i + 6).map((num) => (
|
||||
<SelectItem
|
||||
key={num}
|
||||
value={num.toString()}
|
||||
disabled={num < minTeamCount}
|
||||
>
|
||||
{num} Teams
|
||||
{num < minTeamCount && ` (${teamsWithOwners} teams have owners)`}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{canEditTeamCount
|
||||
? `Can be changed before draft starts (minimum ${minTeamCount} based on registered teams)`
|
||||
: "Team count cannot be changed after draft has started"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{actionData?.error && (
|
||||
<div className="bg-destructive/15 text-destructive px-4 py-3 rounded-md text-sm">
|
||||
{actionData.error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button type="submit" className="w-full">
|
||||
Save Changes
|
||||
</Button>
|
||||
</Form>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="teamCount">Number of Teams</Label>
|
||||
<Select
|
||||
name="teamCount"
|
||||
defaultValue={teamCount.toString()}
|
||||
disabled={!canEditTeamCount}
|
||||
>
|
||||
<SelectTrigger id="teamCount">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Array.from({ length: 11 }, (_, i) => i + 6).map((num) => (
|
||||
<SelectItem
|
||||
key={num}
|
||||
value={num.toString()}
|
||||
disabled={num < minTeamCount}
|
||||
>
|
||||
{num} Teams
|
||||
{num < minTeamCount && ` (${teamsWithOwners} teams have owners)`}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{canEditTeamCount
|
||||
? `Can be changed before draft starts (minimum ${minTeamCount} based on registered teams)`
|
||||
: "Team count cannot be changed after draft has started"}
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Sports Seasons Management */}
|
||||
{/* Draft Rounds */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Draft Rounds</CardTitle>
|
||||
<CardDescription>
|
||||
{canEditDraftRounds
|
||||
? "Set the number of draft rounds for your league"
|
||||
: "Draft rounds cannot be modified after the draft has started"}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="draftRounds">Number of Draft Rounds</Label>
|
||||
<Select
|
||||
name="draftRounds"
|
||||
value={draftRounds.toString()}
|
||||
onValueChange={(value) => setDraftRounds(parseInt(value))}
|
||||
disabled={!canEditDraftRounds}
|
||||
required
|
||||
>
|
||||
<SelectTrigger id="draftRounds">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Array.from({ length: 50 }, (_, i) => i + 1)
|
||||
.filter(num => num >= minRounds)
|
||||
.map((num) => (
|
||||
<SelectItem
|
||||
key={num}
|
||||
value={num.toString()}
|
||||
>
|
||||
{num} Round{num !== 1 ? 's' : ''}
|
||||
{num === recommendedRounds && ' (recommended)'}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Minimum: {minRounds} (number of sports selected)
|
||||
{recommendedRounds > minRounds && ` • Recommended: ${recommendedRounds}`}
|
||||
</p>
|
||||
{sportsCount > 0 && draftRounds < sportsCount && (
|
||||
<p className="text-sm font-medium text-destructive">
|
||||
⚠️ You need at least {sportsCount} rounds for the {sportsCount} sports selected
|
||||
</p>
|
||||
)}
|
||||
{sportsCount > 0 && draftRounds >= sportsCount && (
|
||||
<p className="text-sm font-medium text-primary">
|
||||
Flex Spots: {flexSpots}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Sports Seasons */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Sports Seasons</CardTitle>
|
||||
|
|
@ -413,74 +491,72 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone
|
|||
: "Sports cannot be modified after the draft has started"}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form method="post" className="space-y-4">
|
||||
<input type="hidden" name="intent" value="update-sports" />
|
||||
<CardContent className="space-y-4">
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="bg-muted/50 border border-muted-foreground/20 rounded-md p-3">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<strong>Recommendation:</strong> Select 16-20 sports seasons for optimal league balance and variety.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium">
|
||||
Sports Seasons ({selectedSports.size} selected)
|
||||
</Label>
|
||||
<div className="max-h-96 overflow-y-auto border rounded-md p-3 space-y-2">
|
||||
{allSportsSeasons.length > 0 ? (
|
||||
allSportsSeasons.map((season) => (
|
||||
<div key={season.id} className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={season.id}
|
||||
name="sportsSeasons"
|
||||
value={season.id}
|
||||
checked={selectedSports.has(season.id)}
|
||||
onCheckedChange={() => handleSportToggle(season.id)}
|
||||
disabled={!canEditSports}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={season.id}
|
||||
className={`font-normal flex-1 ${canEditSports ? 'cursor-pointer' : 'cursor-not-allowed opacity-60'}`}
|
||||
>
|
||||
<span className="font-medium">{season.sport.name}</span> - {season.name} ({season.year})
|
||||
<Badge variant="outline" className="ml-2 text-xs">
|
||||
{season.scoringType.replace("_", " ")}
|
||||
</Badge>
|
||||
</Label>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No sports seasons available
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="bg-muted/50 border border-muted-foreground/20 rounded-md p-3">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<strong>Recommendation:</strong> Select 16-20 sports seasons for optimal league balance and variety.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium">
|
||||
Sports Seasons ({selectedSports.size} selected)
|
||||
</Label>
|
||||
<div className="max-h-96 overflow-y-auto border rounded-md p-3 space-y-2">
|
||||
{allSportsSeasons.length > 0 ? (
|
||||
allSportsSeasons.map((season) => (
|
||||
<div key={season.id} className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={season.id}
|
||||
name="sportsSeasons"
|
||||
value={season.id}
|
||||
checked={selectedSports.has(season.id)}
|
||||
onCheckedChange={() => handleSportToggle(season.id)}
|
||||
disabled={!canEditSports}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={season.id}
|
||||
className={`font-normal flex-1 ${canEditSports ? 'cursor-pointer' : 'cursor-not-allowed opacity-60'}`}
|
||||
>
|
||||
<span className="font-medium">{season.sport.name}</span> - {season.name} ({season.year})
|
||||
<Badge variant="outline" className="ml-2 text-xs">
|
||||
{season.scoringType.replace("_", " ")}
|
||||
</Badge>
|
||||
</Label>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No sports seasons available
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{canEditSports && (
|
||||
<Button type="submit" className="w-full">
|
||||
Save Sports Selection
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{actionData?.success && (
|
||||
<div className="bg-green-500/15 text-green-700 dark:text-green-400 px-4 py-3 rounded-md text-sm">
|
||||
Sports updated successfully!
|
||||
</div>
|
||||
)}
|
||||
|
||||
{actionData?.error && (
|
||||
<div className="bg-destructive/15 text-destructive px-4 py-3 rounded-md text-sm">
|
||||
{actionData.error}
|
||||
</div>
|
||||
)}
|
||||
</Form>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Save Button */}
|
||||
{actionData?.error && (
|
||||
<div className="bg-destructive/15 text-destructive px-4 py-3 rounded-md text-sm">
|
||||
{actionData.error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{actionData?.success && (
|
||||
<div className="bg-green-500/15 text-green-700 dark:text-green-400 px-4 py-3 rounded-md text-sm">
|
||||
Settings updated successfully!
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button type="submit" size="lg" className="w-full">
|
||||
Save All Settings
|
||||
</Button>
|
||||
</Form>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* Draft Order Management */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
findUserByClerkId,
|
||||
findDraftSlotsBySeasonId,
|
||||
} from "~/models";
|
||||
import { findSeasonWithSportsSeasons } from "~/models/season";
|
||||
import type { Route } from "./+types/$leagueId";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import {
|
||||
|
|
@ -34,8 +35,9 @@ export async function loader(args: Route.LoaderArgs) {
|
|||
throw new Response("League not found", { status: 404 });
|
||||
}
|
||||
|
||||
// Fetch current season
|
||||
// Fetch current season with sports
|
||||
const season = await findCurrentSeason(leagueId);
|
||||
const seasonWithSports = season ? await findSeasonWithSportsSeasons(season.id) : null;
|
||||
|
||||
// Fetch commissioners
|
||||
const commissioners = await findCommissionersByLeagueId(leagueId);
|
||||
|
|
@ -110,6 +112,9 @@ export async function loader(args: Route.LoaderArgs) {
|
|||
// Count available teams
|
||||
const availableTeamCount = teams.filter((t) => !t.ownerId).length;
|
||||
|
||||
// Count sports in season
|
||||
const sportsCount = seasonWithSports?.seasonSports?.length || 0;
|
||||
|
||||
return {
|
||||
league,
|
||||
season,
|
||||
|
|
@ -121,6 +126,7 @@ export async function loader(args: Route.LoaderArgs) {
|
|||
commissionerMap: Object.fromEntries(commissionerMap),
|
||||
availableTeamCount,
|
||||
draftSlots,
|
||||
sportsCount,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -136,6 +142,7 @@ export default function LeagueHome({ loaderData }: Route.ComponentProps) {
|
|||
commissionerMap,
|
||||
availableTeamCount,
|
||||
draftSlots,
|
||||
sportsCount,
|
||||
} = loaderData;
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
|
@ -332,12 +339,28 @@ export default function LeagueHome({ loaderData }: Route.ComponentProps) {
|
|||
</p>
|
||||
</div>
|
||||
{season && (
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Season Status</p>
|
||||
<p className="font-medium capitalize">
|
||||
{season.status.replace("_", " ")}
|
||||
</p>
|
||||
</div>
|
||||
<>
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Season Status</p>
|
||||
<p className="font-medium capitalize">
|
||||
{season.status.replace("_", " ")}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Draft Rounds</p>
|
||||
<p className="font-medium">{season.draftRounds}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Sports Selected</p>
|
||||
<p className="font-medium">{sportsCount}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Flex Spots</p>
|
||||
<p className="font-medium text-primary">
|
||||
{Math.max(0, season.draftRounds - sportsCount)}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ export async function action(args: Route.ActionArgs) {
|
|||
const name = formData.get("name");
|
||||
const teamCount = formData.get("teamCount");
|
||||
const templateId = formData.get("templateId");
|
||||
const draftRounds = formData.get("draftRounds");
|
||||
|
||||
// Validation
|
||||
if (typeof name !== "string" || !name.trim()) {
|
||||
|
|
@ -81,6 +82,11 @@ export async function action(args: Route.ActionArgs) {
|
|||
return { error: "Number of teams must be between 6 and 16" };
|
||||
}
|
||||
|
||||
const draftRoundsNum = typeof draftRounds === "string" ? parseInt(draftRounds, 10) : 20;
|
||||
if (isNaN(draftRoundsNum) || draftRoundsNum < 1 || draftRoundsNum > 50) {
|
||||
return { error: "Draft rounds must be between 1 and 50" };
|
||||
}
|
||||
|
||||
try {
|
||||
// Create league
|
||||
const league = await createLeague({
|
||||
|
|
@ -101,6 +107,7 @@ export async function action(args: Route.ActionArgs) {
|
|||
year: currentYear,
|
||||
status: "pre_draft",
|
||||
templateId: typeof templateId === "string" && templateId !== "" ? templateId : null,
|
||||
draftRounds: draftRoundsNum,
|
||||
});
|
||||
|
||||
// Set this as the current season for the league
|
||||
|
|
@ -108,6 +115,14 @@ export async function action(args: Route.ActionArgs) {
|
|||
|
||||
// Add selected sports to the season
|
||||
const selectedSports = formData.getAll("sportsSeasons");
|
||||
|
||||
// Validate that draft rounds is at least the number of sports
|
||||
if (selectedSports.length > draftRoundsNum) {
|
||||
return {
|
||||
error: `You need at least ${selectedSports.length} draft rounds for the ${selectedSports.length} sports selected. Currently set to ${draftRoundsNum} rounds.`
|
||||
};
|
||||
}
|
||||
|
||||
if (selectedSports.length > 0) {
|
||||
await linkMultipleSportsToSeason(
|
||||
selectedSports.map((id) => ({
|
||||
|
|
@ -139,6 +154,7 @@ export default function NewLeague({ loaderData, actionData }: Route.ComponentPro
|
|||
const { templates, allSportsSeasons } = loaderData;
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<string>("");
|
||||
const [selectedSports, setSelectedSports] = useState<Set<string>>(new Set());
|
||||
const [draftRounds, setDraftRounds] = useState<number>(20);
|
||||
|
||||
const handleSportToggle = (sportId: string) => {
|
||||
const newSelected = new Set(selectedSports);
|
||||
|
|
@ -154,6 +170,13 @@ export default function NewLeague({ loaderData, actionData }: Route.ComponentPro
|
|||
setSelectedTemplate(templateId);
|
||||
setSelectedSports(new Set(sportsSeasonIds));
|
||||
};
|
||||
|
||||
// Calculate flex spots and minimum rounds
|
||||
const sportsCount = selectedSports.size;
|
||||
const minRounds = sportsCount;
|
||||
const recommendedRounds = Math.ceil(sportsCount * 1.25);
|
||||
const flexSpots = Math.max(0, draftRounds - sportsCount);
|
||||
|
||||
return (
|
||||
<div className="container max-w-2xl mx-auto py-8 px-4">
|
||||
<Card>
|
||||
|
|
@ -203,6 +226,49 @@ export default function NewLeague({ loaderData, actionData }: Route.ComponentPro
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="draftRounds">Draft Rounds</Label>
|
||||
<Select
|
||||
name="draftRounds"
|
||||
value={draftRounds.toString()}
|
||||
onValueChange={(value) => setDraftRounds(parseInt(value))}
|
||||
required
|
||||
>
|
||||
<SelectTrigger id="draftRounds">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Array.from({ length: 50 }, (_, i) => i + 1)
|
||||
.filter(num => num >= minRounds)
|
||||
.map((num) => (
|
||||
<SelectItem
|
||||
key={num}
|
||||
value={num.toString()}
|
||||
>
|
||||
{num} Round{num !== 1 ? 's' : ''}
|
||||
{num === recommendedRounds && ' (recommended)'}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Minimum: {minRounds} (number of sports selected)
|
||||
{recommendedRounds > minRounds && ` • Recommended: ${recommendedRounds}`}
|
||||
</p>
|
||||
{sportsCount > 0 && draftRounds < sportsCount && (
|
||||
<p className="text-sm font-medium text-destructive">
|
||||
⚠️ You need at least {sportsCount} rounds for the {sportsCount} sports selected
|
||||
</p>
|
||||
)}
|
||||
{sportsCount > 0 && draftRounds >= sportsCount && (
|
||||
<p className="text-sm font-medium text-primary">
|
||||
Flex Spots: {flexSpots}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<Label>Sports Selection</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ export const seasons = pgTable("seasons", {
|
|||
status: seasonStatusEnum("status").notNull().default("pre_draft"),
|
||||
templateId: uuid("template_id")
|
||||
.references(() => seasonTemplates.id, { onDelete: "set null" }),
|
||||
draftRounds: integer("draft_rounds").notNull().default(20),
|
||||
flexSpots: integer("flex_spots").notNull().default(0),
|
||||
inviteCode: varchar("invite_code", { length: 20 }).notNull().unique(),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
|
|
|
|||
1
drizzle/0015_exotic_loners.sql
Normal file
1
drizzle/0015_exotic_loners.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "seasons" ADD COLUMN "draft_rounds" integer DEFAULT 20 NOT NULL;
|
||||
1052
drizzle/meta/0015_snapshot.json
Normal file
1052
drizzle/meta/0015_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -106,6 +106,13 @@
|
|||
"when": 1760588333462,
|
||||
"tag": "0014_lowly_gateway",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 15,
|
||||
"version": "7",
|
||||
"when": 1760589195551,
|
||||
"tag": "0015_exotic_loners",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue