Skip to content

Commit 59f2f7b

Browse files
author
chris0chris
committed
feat: add officials and kickoff page with components #163
1 parent 02db076 commit 59f2f7b

File tree

10 files changed

+519
-6
lines changed

10 files changed

+519
-6
lines changed

scorecard2/app/src/App.tsx

+2-5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import SelectPasscheckOrScorecard from "./components/SelectPasscheckOrScorecard"
77
import SelectGame from "./components/SelectGame/SelectGame";
88
import NotificationProvider from "./components/provider/NotificationProvider";
99
import Notification from "./components/Notification";
10+
import Officials from "./components/Officials/Officials";
1011

1112
function App() {
1213
const [isUserStatusChecked, setIsUserStatusChecked] = useState(false);
@@ -31,13 +32,9 @@ function App() {
3132
<Routes>
3233
<Route path={LOGIN_URL} element={<Login />} />
3334
<Route path="/*" element={<SelectGame />} />
35+
<Route path={OFFICIALS_URL} element={<Officials />} />
3436
{/* <Route
3537
exact
36-
path={OFFICIALS_URL}
37-
element={<PrivateRoute component={Officials} />}
38-
/>
39-
<Route
40-
exact
4138
path={DETAILS_URL}
4239
element={<PrivateRoute component={Details} />}
4340
/>

scorecard2/app/src/common/api/axiosApi.ts

-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ export const axiosGet = async (url: string): Promise<any> => {
6767
console.error("api ERROR", error);
6868
if (error.response && error.response.status === 401) {
6969
window.location.href = "#/login";
70-
// throwApiError("Bitte erst anmelden.");
7170
}
7271
throwApiError(error);
7372
});
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { GameSetup } from "../../types/gameSetup.types";
2+
import { axiosGet } from "./axiosApi";
3+
4+
export const loadGameSetup = async (gameId: number): Promise<GameSetup> => {
5+
return axiosGet(`api/scorecard/game/${gameId}/setup`);
6+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { InputDropdownItem } from "../../types";
2+
import { Official } from "../../types/gameSetup.types";
3+
4+
export class InputDropdownItemFactory {
5+
static createFromOfficial(official: Official): InputDropdownItem {
6+
return {
7+
id: official.id,
8+
text: `${official.first_name} ${official.last_name}`,
9+
subtext: official.team,
10+
};
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { useState } from "react";
2+
import { Row } from "react-bootstrap";
3+
import {
4+
ScorecardCategory,
5+
SelectedCategory,
6+
} from "../../types/gameSetup.types";
7+
import RadioButton from "../shared/RadioButton";
8+
9+
type Props = {
10+
value: ScorecardCategory;
11+
home: string;
12+
away: string;
13+
onCategoryChange: (categories: SelectedCategory) => void;
14+
};
15+
16+
const Category: React.FC<Props> = ({
17+
value: category,
18+
home = "home",
19+
away = "away",
20+
onCategoryChange,
21+
}) => {
22+
const [selectedCategory, setSelectedCategory] = useState<SelectedCategory>({
23+
id: -1,
24+
valueId: -1,
25+
});
26+
27+
const addOrUpdateCategory = (newCategory: SelectedCategory) => {
28+
console.log("addOrUpdateCategory newCategory.id", newCategory.id);
29+
if (newCategory.valueId !== selectedCategory.valueId) {
30+
setSelectedCategory(newCategory);
31+
onCategoryChange(newCategory);
32+
}
33+
};
34+
return (
35+
<>
36+
<Row className="mt-3">
37+
<div>
38+
{category.name}
39+
{`${category.is_required ? "* " : ""}`}
40+
{category.team_option !== "none" && (
41+
<span className="fw-bold" data-testid="ctTeam">
42+
{`${category.team_option === "home" ? home : away}`}
43+
</span>
44+
)}
45+
</div>
46+
</Row>
47+
<Row className="mt-1">
48+
{category.values.map((currentValue, index) => (
49+
<RadioButton
50+
className="mt-2"
51+
key={index}
52+
id={currentValue.value + index}
53+
name={category.name}
54+
required={category.is_required}
55+
color="secondary"
56+
onChange={(valueId: number) =>
57+
addOrUpdateCategory({
58+
id: category.id,
59+
valueId: valueId,
60+
})
61+
}
62+
text={currentValue.value}
63+
value={currentValue.id}
64+
checked={selectedCategory.valueId === currentValue.id}
65+
/>
66+
))}
67+
</Row>
68+
</>
69+
);
70+
};
71+
export default Category;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import { useEffect, useState } from "react";
2+
import { useSearchParams } from "react-router-dom";
3+
import useNotification from "../../hooks/useNotification";
4+
import { loadGameSetup } from "../../common/api/setup";
5+
import {
6+
GameInfo,
7+
GameSetup,
8+
Official,
9+
ScorecardConfig,
10+
SelectedCategory,
11+
SelectedOfficial,
12+
} from "../../types/gameSetup.types";
13+
import { Button, Col, Form, Row } from "react-bootstrap";
14+
import InputDropdown from "../shared/InputDropdown";
15+
import { InputDropdownItemFactory } from "../../common/factories/input-dropdown.factory";
16+
import Category from "./Category";
17+
18+
const Officials: React.FC = () => {
19+
const { setNotification } = useNotification();
20+
const [searchParams, setSearchParams] = useSearchParams();
21+
const [scorecardConfig, setScorecardConfig] = useState<ScorecardConfig>({
22+
categories: [],
23+
officials: [],
24+
});
25+
const [teamOfficials, setTeamOfficials] = useState<Official[]>([]);
26+
const [selectedOfficials, setSelectedOfficials] = useState<
27+
SelectedOfficial[]
28+
>([]);
29+
const [selectedCategories, setSelectedCategories] = useState<
30+
SelectedCategory[]
31+
>([]);
32+
const [gameInfo, setGameInfo] = useState<GameInfo>({
33+
away: "away",
34+
field: 0,
35+
home: "home",
36+
scheduled: "00:00",
37+
stage: "Runde ?",
38+
standing: "Gruppe ?",
39+
});
40+
useEffect(() => {
41+
const fetchData = async (gameId: number) => {
42+
try {
43+
const gameSetup: GameSetup = await loadGameSetup(gameId);
44+
setScorecardConfig(gameSetup.scorecard);
45+
setTeamOfficials(gameSetup.teamOfficials);
46+
setGameInfo(gameSetup.gameInfo);
47+
} catch (error: any) {
48+
console.log("error", error);
49+
setNotification({ text: error.message });
50+
}
51+
};
52+
const gameId = searchParams.get("gameId");
53+
if (gameId) {
54+
fetchData(parseInt(gameId));
55+
}
56+
}, [searchParams]);
57+
const addOrUpdateOfficial = (newOfficial: SelectedOfficial) => {
58+
setSelectedOfficials((prevOfficials) => {
59+
const existingPosition = prevOfficials.some(
60+
(official) => official.position === newOfficial.position,
61+
);
62+
if (existingPosition) {
63+
return prevOfficials.map((official) =>
64+
official.id === newOfficial.id
65+
? { ...official, position: newOfficial.position }
66+
: official,
67+
);
68+
} else {
69+
return [...prevOfficials, newOfficial];
70+
}
71+
});
72+
};
73+
const addOrUpdateCategory = (newCategory: SelectedCategory) => {
74+
setSelectedCategories((prevCategories) => {
75+
const existingCategory = prevCategories.some(
76+
(category) => category.id === newCategory.id,
77+
);
78+
if (existingCategory) {
79+
return prevCategories.map((category) =>
80+
category.id === newCategory.id
81+
? { ...category, valueId: newCategory.valueId }
82+
: category,
83+
);
84+
} else {
85+
return [...prevCategories, newCategory];
86+
}
87+
});
88+
};
89+
const resetSelectedOfficials = (position: string) => {
90+
setSelectedOfficials((prevOfficials) =>
91+
prevOfficials.filter((official) => official.position !== position),
92+
);
93+
};
94+
// const allOfficials = [...searchOffi, ...teamOffi];
95+
const filteredOfficials = teamOfficials.filter(
96+
(item) =>
97+
!selectedOfficials.some(
98+
(selectedOfficial) => selectedOfficial.id === item.id,
99+
),
100+
);
101+
// setTeamOfficials(filteredOfficials);
102+
const handleSubmit = (event: any) => {
103+
event.preventDefault();
104+
};
105+
const officialsAsDropdownItem = filteredOfficials.map(
106+
InputDropdownItemFactory.createFromOfficial,
107+
);
108+
console.log("selectedObjects", selectedCategories, selectedOfficials);
109+
return (
110+
<>
111+
<div className="text-muted fs6">
112+
Feld {gameInfo.field} - {gameInfo.scheduled} Uhr / {gameInfo.stage}:{" "}
113+
{gameInfo.standing}
114+
</div>
115+
<h4 className="mt-2">
116+
{gameInfo.home} vs {gameInfo.away}
117+
</h4>
118+
<Form id="formId" onSubmit={handleSubmit}>
119+
{scorecardConfig.officials.map((currentOfficialPosition, index) => (
120+
<InputDropdown
121+
key={index}
122+
id={currentOfficialPosition.position_name + index}
123+
label={`${currentOfficialPosition.position_name} (Vorname Nachname)`}
124+
isRequired={!currentOfficialPosition.is_optional}
125+
focus={index === 0}
126+
onSelected={(id, text) =>
127+
addOrUpdateOfficial({
128+
name: text,
129+
id: id,
130+
position: currentOfficialPosition.position_name,
131+
})
132+
}
133+
onReset={() =>
134+
resetSelectedOfficials(currentOfficialPosition.position_name)
135+
}
136+
// initValues={scJudgeInit}
137+
// searchForText={searchForOfficials}
138+
items={officialsAsDropdownItem}
139+
/>
140+
))}
141+
{scorecardConfig.categories.map((currentCategory, index) => (
142+
<Category
143+
value={currentCategory}
144+
key={index}
145+
home={gameInfo.home}
146+
away={gameInfo.away}
147+
onCategoryChange={addOrUpdateCategory}
148+
/>
149+
))}
150+
<Row className="mt-3">
151+
<Col>
152+
<Button className="w-100" variant="primary" type="submit">
153+
Spiel starten
154+
</Button>
155+
</Col>
156+
</Row>
157+
</Form>
158+
</>
159+
);
160+
};
161+
export default Officials;

0 commit comments

Comments
 (0)