diff --git a/main_controller.py b/main_controller.py index c4de4e7..997a692 100644 --- a/main_controller.py +++ b/main_controller.py @@ -38,27 +38,62 @@ def search_teams(): return jsonify([json.loads(x[0])['name'] for x in result]) #currently all we need is the name but that can change +MAX_SUBLEAGUE_DIVISION_TOTAL = 22; +MAX_TEAMS_PER_DIVISION = 12; @app.route('/api/leagues', methods=['POST']) def create_league(): config = json.loads(request.data) - if (league_exists(config['name'])): - abort(400, "A league by that name already exists") + if league_exists(config['name']): + return jsonify({'status':'err_league_exists'}), 400 - print(config) - league_dic = { - subleague['name'] : { - division['name'] : [games.get_team(team_name) for team_name in division['teams']] - for division in subleague['divisions'] - } - for subleague in config['structure']['subleagues'] - } + num_subleagues = len(config['structure']['subleagues']) + if num_subleagues < 1 or num_subleagues % 2 != 0: + return jsonify({'status':'err_invalid_subleague_count'}), 400 + + num_divisions = len(config['structure']['subleagues'][0]['divisions']) + if num_subleagues * (num_divisions + 1) > MAX_SUBLEAGUE_DIVISION_TOTAL: + return jsonify({'status':'err_invalid_subleague_division_total'}), 400 + + league_dic = {} + err_teams = [] + for subleague in config['structure']['subleagues']: + if subleague['name'] in league_dic: + return jsonify({'status':'err_duplicate_name', 'cause':subleague['name']}) + + subleague_dic = {} + for division in subleague['divisions']: + if division['name'] in subleague_dic: + return jsonify({'status':'err_duplicate_name', 'cause':f"{subleague['name']}/{division['name']}"}), 400 + elif len(division['teams']) > MAX_TEAMS_PER_DIVISION: + return jsonify({'status':'err_too_many_teams', 'cause':f"{subleague['name']}/{division['name']}"}) + + teams = [] + for team_name in division['teams']: + team = games.get_team(team_name) + if team is None: + err_teams.append(team_name) + else: + teams.append(team) + subleague_dic[division['name']] = teams + league_dic[subleague['name']] = subleague_dic + + if len(err_teams) > 0: + return jsonify({'status':'err_no_such_team', 'cause': err_teams}), 400 + + for (key, min_val) in [ + ('division_series', 1), + ('inter_division_series', 1), + ('inter_league_series', 1) + ]: + if config[key] < min_val: + return jsonify({'status':'err_invalid_optiion_value', 'cause':key}), 400 new_league = league_structure(config['name']) new_league.setup( league_dic, - division_games=config['division_series'], + division_games=config['division_series'], # need to add a check that makes sure these values are ok inter_division_games=config['inter_division_series'], inter_league_games=config['inter_league_series'], ) @@ -67,7 +102,7 @@ def create_league(): new_league.generate_schedule() leagues.save_league(new_league) - return "League created successfully" + return jsonify({'status':'success_league_created'}) diff --git a/simmadome/.eslintcache b/simmadome/.eslintcache index 9f96e6b..32ab91c 100644 --- a/simmadome/.eslintcache +++ b/simmadome/.eslintcache @@ -1 +1 @@ -[{"M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\index.tsx":"1","M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\reportWebVitals.ts":"2","M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\GamesPage.tsx":"3","M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\GamePage.tsx":"4","M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\GamesUtil.tsx":"5","M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\Game.tsx":"6","M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\CreateLeague.tsx":"7","M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\util.tsx":"8"},{"size":2425,"mtime":1610685584594,"results":"9","hashOfConfig":"10"},{"size":440,"mtime":1610521673158,"results":"11","hashOfConfig":"10"},{"size":4866,"mtime":1610685584593,"results":"12","hashOfConfig":"10"},{"size":1897,"mtime":1610685584589,"results":"13","hashOfConfig":"10"},{"size":1157,"mtime":1610685584594,"results":"14","hashOfConfig":"10"},{"size":3173,"mtime":1610583643836,"results":"15","hashOfConfig":"10"},{"size":17241,"mtime":1610685584587,"results":"16","hashOfConfig":"10"},{"size":1029,"mtime":1610685584594,"results":"17","hashOfConfig":"10"},{"filePath":"18","messages":"19","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"1jg8ts7",{"filePath":"20","messages":"21","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"22"},{"filePath":"23","messages":"24","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"25","messages":"26","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"27","messages":"28","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"29","messages":"30","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"22"},{"filePath":"31","messages":"32","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"33","messages":"34","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},"M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\index.tsx",[],"M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\reportWebVitals.ts",[],["35","36"],"M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\GamesPage.tsx",[],"M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\GamePage.tsx",[],"M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\GamesUtil.tsx",[],"M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\Game.tsx",[],"M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\CreateLeague.tsx",[],"M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\util.tsx",["37"],{"ruleId":"38","replacedBy":"39"},{"ruleId":"40","replacedBy":"41"},{"ruleId":"42","severity":1,"message":"43","line":1,"column":9,"nodeType":"44","messageId":"45","endLine":1,"endColumn":15},"no-native-reassign",["46"],"no-negated-in-lhs",["47"],"@typescript-eslint/no-unused-vars","'useRef' is defined but never used.","Identifier","unusedVar","no-global-assign","no-unsafe-negation"] \ No newline at end of file +[{"/Users/elijah/Documents/Projects/matteo/simmadome/src/index.tsx":"1","/Users/elijah/Documents/Projects/matteo/simmadome/src/reportWebVitals.ts":"2","/Users/elijah/Documents/Projects/matteo/simmadome/src/GamesPage.tsx":"3","/Users/elijah/Documents/Projects/matteo/simmadome/src/GamePage.tsx":"4","/Users/elijah/Documents/Projects/matteo/simmadome/src/CreateLeague.tsx":"5","/Users/elijah/Documents/Projects/matteo/simmadome/src/GamesUtil.tsx":"6","/Users/elijah/Documents/Projects/matteo/simmadome/src/util.tsx":"7","/Users/elijah/Documents/Projects/matteo/simmadome/src/Game.tsx":"8"},{"size":2368,"mtime":1610663769654,"results":"9","hashOfConfig":"10"},{"size":425,"mtime":1610566206674,"results":"11","hashOfConfig":"10"},{"size":4725,"mtime":1610664926203,"results":"12","hashOfConfig":"10"},{"size":1836,"mtime":1610677519051,"results":"13","hashOfConfig":"10"},{"size":18825,"mtime":1610778204901,"results":"14","hashOfConfig":"10"},{"size":1116,"mtime":1610677473305,"results":"15","hashOfConfig":"10"},{"size":961,"mtime":1610694553519,"results":"16","hashOfConfig":"10"},{"size":3089,"mtime":1610572714752,"results":"17","hashOfConfig":"10"},{"filePath":"18","messages":"19","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"1bvn6qu",{"filePath":"20","messages":"21","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"22","messages":"23","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"24","messages":"25","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"26","messages":"27","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"28","messages":"29","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"30","messages":"31","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"32","messages":"33","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/elijah/Documents/Projects/matteo/simmadome/src/index.tsx",[],"/Users/elijah/Documents/Projects/matteo/simmadome/src/reportWebVitals.ts",[],"/Users/elijah/Documents/Projects/matteo/simmadome/src/GamesPage.tsx",[],"/Users/elijah/Documents/Projects/matteo/simmadome/src/GamePage.tsx",[],"/Users/elijah/Documents/Projects/matteo/simmadome/src/CreateLeague.tsx",[],"/Users/elijah/Documents/Projects/matteo/simmadome/src/GamesUtil.tsx",[],"/Users/elijah/Documents/Projects/matteo/simmadome/src/util.tsx",[],"/Users/elijah/Documents/Projects/matteo/simmadome/src/Game.tsx",[]] \ No newline at end of file diff --git a/simmadome/src/CreateLeague.css b/simmadome/src/CreateLeague.css index 90c680b..9aadf3f 100644 --- a/simmadome/src/CreateLeague.css +++ b/simmadome/src/CreateLeague.css @@ -240,7 +240,7 @@ input[type=number]::-webkit-outer-spin-button { .cl_option_err { min-height: 1.5rem; margin-bottom: -0.5rem; - margin-top: 0.5rem; + margin-top: 0.25rem; } .cl_structure_err { @@ -248,14 +248,29 @@ input[type=number]::-webkit-outer-spin-button { } .cl_structure_err_div { + margin-top: -0.25rem; + margin-bottom: 0; +} + +.cl_structure_err_team { margin-top: -0.5rem; margin-bottom: 0; + width: 85%; +} + +.cl_team_name_err { + color: var(--accent-red); } .cl_structure_err_teams { width: 98%; } +.cl_subleague_add_filler, .cl_division_add_filler { + width: 2rem; + height: 2rem; +} + /* button styles */ button > .emoji { @@ -283,12 +298,12 @@ button > .emoji { background: var(--accent-green); } -.cl_subleague_add { +.cl_subleague_add, .cl_subleague_add_filler { position: relative; - top: 1.6rem; + top: 1.5rem; } -.cl_division_add { +.cl_division_add, .cl_division_add_filler { margin-top: 1.25rem; margin-bottom: 1.25rem; } diff --git a/simmadome/src/CreateLeague.tsx b/simmadome/src/CreateLeague.tsx index 29d0d6c..7f2d530 100644 --- a/simmadome/src/CreateLeague.tsx +++ b/simmadome/src/CreateLeague.tsx @@ -3,6 +3,11 @@ import {removeIndex, replaceIndex, append, arrayOf, shallowClone, getUID, Distri import './CreateLeague.css'; import twemoji from 'twemoji'; +// CONSTS + +const MAX_SUBLEAGUE_DIVISION_TOTAL = 22; +const MAX_TEAMS_PER_DIVISION = 12; + // STATE CLASSES class LeagueStructureState { @@ -178,6 +183,7 @@ function CreateLeague() { let [name, setName] = useState(""); let [showError, setShowError] = useState(false); let [nameExists, setNameExists] = useState(false); + let [deletedTeams, setDeletedTeams] = useState([]); let [createSuccess, setCreateSuccess] = useState(false); let [structure, structureDispatch] = useReducer(leagueStructureReducer, initLeagueStructure); let [options, optionsDispatch] = useReducer(LeagueOptionsReducer, new LeagueOptionsState()); @@ -211,7 +217,7 @@ function CreateLeague() { nameExists && showError ? "A league by that name already exists" : "" } - +
@@ -229,13 +235,20 @@ function CreateLeague() { setCreateSuccess(true); } if (req.status === 400) { - setNameExists(true); + let err = JSON.parse(req.response); + switch (err.status) { + case 'err_league_exists': + setNameExists(true); + break; + case 'err_no_such_team': + setDeletedTeams(err.cause); + break; + } setShowError(true); } } } req.send(data); - } }}>Submit
{ @@ -270,45 +283,61 @@ function makeRequest(name:string, structure: LeagueStructureState, options:Leagu } function validRequest(name:string, structure: LeagueStructureState, options:LeagueOptionsState) { + + return ( name !== "" && + validNumber(options.games_series) && validNumber(options.intra_division_series) && validNumber(options.inter_division_series) && validNumber(options.inter_league_series) && validNumber(options.top_postseason) && validNumber(options.wildcards, 0) && + structure.subleagues.length % 2 === 0 && - structure.subleagues.every(subleague => + + structure.subleagues.every((subleague, si) => subleague.name !== "" && - subleague.divisions.every(division => + !structure.subleagues.slice(0, si).some(val => val.name === subleague.name) && + subleague.divisions.every((division, di) => division.name !== "" && - division.teams.length >= 2 + division.teams.length >= 2 && + division.teams.length <= MAX_TEAMS_PER_DIVISION && + !subleague.divisions.slice(0, di).some(val => val.name === division.name) ) ) ) } function validNumber(value: string, min = 1) { - return !isNaN(Number(value)) && Number(value) >= min + return !isNaN(Number(value)) && Number(value) >= min; } // LEAGUE STRUCUTRE -function LeagueStructre(props: {state: LeagueStructureState, dispatch: React.Dispatch, showError: boolean}) { +function LeagueStructre(props: {state: LeagueStructureState, dispatch: React.Dispatch, deletedTeams: string[], showError: boolean}) { + let nSubleagues = props.state.subleagues.length; + let nDivisions = props.state.subleagues[0].divisions.length; return (
- +
- + { (nSubleagues+1) * (nDivisions+1) < MAX_SUBLEAGUE_DIVISION_TOTAL ? + : +
+ }
{props.state.subleagues.length % 2 !== 0 && props.showError ? "Must have an even number of subleagues." : ""}
- + { nSubleagues * (nDivisions+2) < MAX_SUBLEAGUE_DIVISION_TOTAL ? + : +
+ }
); } @@ -317,16 +346,25 @@ function SubleagueHeaders(props: {subleagues: SubleagueState[], dispatch: React. return (
- {props.subleagues.map((subleague, i) => ( -
-
- 1} dispatch={action => - props.dispatch(Object.assign({subleague_index: i}, action)) - }/> -
{subleague.name === "" && props.showError ? "A name is required." : ""}
+ {props.subleagues.map((subleague, i) => { + let err = + subleague.name === "" ? + "A name is required." : + props.subleagues.slice(0, i).some(val => val.name === subleague.name) ? + "Each subleague must have a different name." : + ""; + + return ( +
+
+ 1} dispatch={action => + props.dispatch(Object.assign({subleague_index: i}, action)) + }/> +
{props.showError ? err : ""}
+
-
- ))} + ) + })}
); } @@ -342,7 +380,7 @@ function SubleageHeader(props: {state: SubleagueState, canDelete: boolean, dispa ); } -function Divisions(props: {subleagues: SubleagueState[], dispatch: React.Dispatch, showError: boolean}) { +function Divisions(props: {subleagues: SubleagueState[], dispatch: React.Dispatch, deletedTeams: string[], showError: boolean}) { return (<> {props.subleagues[0].divisions.map((val, di) => (
@@ -357,7 +395,9 @@ function Divisions(props: {subleagues: SubleagueState[], dispatch: React.Dispatc
props.dispatch(Object.assign({subleague_index: si, division_index: di}, action)) - } showError={props.showError}/> + } + isDuplicate={subleague.divisions.slice(0, di).some(val => val.name === subleague.divisions[di].name)} + deletedTeams={props.deletedTeams} showError={props.showError} />
))} @@ -366,7 +406,14 @@ function Divisions(props: {subleagues: SubleagueState[], dispatch: React.Dispatc ); } -function Division(props: {state: DivisionState, dispatch:(action: DistributiveOmit) => void, showError:boolean}) { +function Division(props: { + state: DivisionState, + dispatch: (action: DistributiveOmit) => void, + isDuplicate: boolean, + deletedTeams: string[], + showError: boolean + }) { + let [newName, setNewName] = useState(""); let [searchResults, setSearchResults] = useState([]); let newNameInput = useRef(null); @@ -378,45 +425,62 @@ function Division(props: {state: DivisionState, dispatch:(action: DistributiveOm } }) + let divisionErr = + props.state.name === "" ? + "A name is required." : + props.isDuplicate ? + "Each division in a subleague must have a different name." : + "" + + let teamsErr = props.state.teams.length < 2 ? "Must have at least 2 teams." : ""; + return (
props.dispatch({type: 'rename_division', name: e.target.value}) }/> -
{props.state.name === "" && props.showError ? "A name is required." : ""}
+
{props.showError ? divisionErr : ""}
- {props.state.teams.map((team, i) => ( -
-
{team.name}
- + {props.state.teams.map((team, i) => { + let showDeleted = props.showError && props.deletedTeams.includes(team.name) + return (<> +
+
{team.name}
+ +
+
{showDeleted ? "This team was deleted" : ""}
+ ) + })} + { props.state.teams.length < MAX_TEAMS_PER_DIVISION ? <> +
+ { + let params = new URLSearchParams({query: e.target.value, page_len: '5', page_num: '0'}); + fetch("/api/teams/search?" + params.toString()) + .then(response => response.json()) + .then(data => setSearchResults(data)); + setNewName(e.target.value); + }}/>
- ))} -
- { - let params = new URLSearchParams({query: e.target.value, page_len: '5', page_num: '0'}); - fetch("/api/teams/search?" + params.toString()) - .then(response => response.json()) - .then(data => setSearchResults(data)); - setNewName(e.target.value); - }}/> -
- {searchResults.length > 0 && newName.length > 0 ? - (
- {searchResults.map(result => -
{ - props.dispatch({type:'add_team', name: result}); - setNewName(""); - if (newNameInput.current) { - newNameInput.current.focus(); - } - }}>{result}
- )} -
): -
+ {searchResults.length > 0 && newName.length > 0 ? + (
+ {searchResults.map(result => +
{ + props.dispatch({type:'add_team', name: result}); + setNewName(""); + if (newNameInput.current) { + newNameInput.current.focus(); + } + }}>{result}
+ )} +
): + null + } : + null } -
{props.state.teams.length < 2 && props.showError ? "Must have at least 2 teams." : ""}
+ +
{props.showError ? teamsErr : ""}
); } @@ -446,7 +510,7 @@ function LeagueOptions(props: {state: LeagueOptionsState, dispatch: React.Dispat ); } -function NumberInput(props: {title: string, value: string, setValue: (newVal: string) => void, showError: boolean, minValue?:number}) { +function NumberInput(props: {title: string, value: string, setValue: (newVal: string) => void, showError: boolean, minValue? : number}) { let minValue = 1; if (props.minValue !== undefined) { minValue = props.minValue @@ -455,7 +519,7 @@ function NumberInput(props: {title: string, value: string, setValue: (newVal: st
{props.title}
props.setValue(e.target.value)}/> -
{(!isNaN(Number(props.value)) || Number(props.value) < minValue) && props.showError ? "Must be a number greater than "+minValue : ""}
+
{(isNaN(Number(props.value)) || Number(props.value) < minValue) && props.showError ? "Must be a number greater than " + minValue : ""}
); } diff --git a/simmadome/src/util.tsx b/simmadome/src/util.tsx index 41fb20e..b1f840c 100644 --- a/simmadome/src/util.tsx +++ b/simmadome/src/util.tsx @@ -1,6 +1,3 @@ -import {useRef} from 'react'; - - function removeIndex(arr: any[], index: number) { return arr.slice(0, index).concat(arr.slice(index+1)); }