Skip to content

Commit

Permalink
Player and team search (#85)
Browse files Browse the repository at this point in the history
  • Loading branch information
wweitzel committed Dec 13, 2023
1 parent 3fa1f29 commit c410f7a
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 41 deletions.
36 changes: 29 additions & 7 deletions src/components/Select.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import 'bootstrap/js/dist/dropdown';
import {useState} from 'react';
import {useEffect, useState} from 'react';
import {usePrevious} from '../hooks/usePrevious';

interface Option {
displayName: string;
value: number | string;
key: any;
value: any;
image?: string;
}

interface SelectProps {
label?: string;
options?: Option[];
value?: number | string;
value?: any;
onChange: (value: any) => void;
showSearchInput?: boolean;
onSearchChange?: (value: string) => void;
showAllOption?: boolean;
onFirstInteraction?: () => void;
}

function Select({
Expand All @@ -21,13 +26,32 @@ function Select({
value,
onChange,
showSearchInput = false,
onSearchChange = () => {},
showAllOption = true,
onFirstInteraction = () => {},
}: SelectProps) {
const currentOption = options?.find((option) => option.value == value);
const [searchText, setSearchText] = useState('');
const prevSearchText = usePrevious(searchText);
const [hadFirstInteraction, setHadFirstInteraction] = useState(false);

const dropdownId = `dropdown-${Math.random().toString(36).substring(7)}`;

useEffect(() => {
const id = setTimeout(() => {
if (searchText || (prevSearchText && prevSearchText.length > 0)) {
onSearchChange(searchText);
}
}, 250);
return () => clearTimeout(id);
}, [searchText]);

useEffect(() => {
if (hadFirstInteraction) {
onFirstInteraction();
}
}, [hadFirstInteraction]);

return (
<div className="form-group text-muted" style={{minWidth: '0px', width: '100%'}}>
<label className="form-label" htmlFor={dropdownId}>
Expand All @@ -36,6 +60,7 @@ function Select({
<div className="d-flex">
<div className="dropdown mr-1 w-100">
<button
onClick={() => setHadFirstInteraction(true)}
type="button"
className="btn btn-secondary dropdown-toggle w-100 text-left shadow-sm rounded-pill border-none text-start text-muted overflow-hidden"
id={dropdownId}
Expand Down Expand Up @@ -84,9 +109,6 @@ function Select({
)}
{options &&
options
.filter((option) =>
searchText ? new RegExp(`${searchText}*`, 'i').test(option.displayName) : option
)
.sort((option1, option2) =>
option1.displayName < option2.displayName
? -1
Expand All @@ -99,7 +121,7 @@ function Select({
className={`dropdown-item ${
currentOption && currentOption.value == option.value ? 'active' : ''
}`}
key={option.value}
key={option.key}
onClick={() => onChange(option.value)}
aria-selected={currentOption && currentOption.value === option.value}
role="option"
Expand Down
4 changes: 2 additions & 2 deletions src/components/ThemeSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ function ThemeSelect({onChange}: ThemeSelectProps) {
<Select
label="Theme"
options={[
{value: 'dark', displayName: 'Dark'},
{value: 'light', displayName: 'Light'},
{key: 'dark', value: 'dark', displayName: 'Dark'},
{key: 'light', value: 'light', displayName: 'Light'},
]}
value={preferredTheme}
onChange={onChange}
Expand Down
9 changes: 9 additions & 0 deletions src/hooks/usePrevious.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {useEffect, useRef} from 'react';

export function usePrevious<Type>(value: Type) {
const ref = useRef<Type>();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
1 change: 1 addition & 0 deletions src/lib/api/goals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface GetGoalsFilter {
season?: number;
teamId?: number;
fixtureId?: number;
playerId?: number;
}

export interface GetGoalsRequest {
Expand Down
33 changes: 33 additions & 0 deletions src/lib/api/players.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {API_BASE_URL} from './core';

import axios from 'axios';

export interface Player {
id: number;
name: string;
firstName: string;
lastName: string;
age: number;
nationality: string;
height: string;
weight: string;
photo: string;
createdAt: string;
}

export interface SearchPlayersRequest {
searchTerm: string;
}

export interface SearchPlayersResponse {
players: Player[];
}

export async function searchPlayers(searchTerm: string) {
const request: SearchPlayersRequest = {searchTerm};
const json = encodeURIComponent(JSON.stringify(request));
const url = `${API_BASE_URL}/players?json=${json}`;

const response = await axios.get<SearchPlayersResponse>(url);
return response.data;
}
1 change: 1 addition & 0 deletions src/lib/api/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface Teams {
export interface GetTeamsFilter {
leagueId?: number;
season?: number;
searchTerm?: string;
}

export interface GetTeamsResponse {
Expand Down
80 changes: 48 additions & 32 deletions src/pages/Goals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {Pagination} from '../lib/api/core';
import {getFixtures, GetFixturesResponse} from '../lib/api/fixtures';
import {getGoals, GetGoalsFilter, GetGoalsResponse} from '../lib/api/goals';
import {getLeagues, GetLeaguesResponse} from '../lib/api/leagues';
import {Player, searchPlayers, SearchPlayersResponse} from '../lib/api/players';
import {getTeams, GetTeamsResponse} from '../lib/api/teams';
import {getPreferredTheme, setTheme} from '../lib/utils';

Expand All @@ -21,13 +22,14 @@ function Goals() {
const [currentPage, setCurrentPage] = useState(0);
const [selectedLeagueId, setSelectedLeagueId] = useState<number>();
const [selectedTeamId, setSelectedTeamId] = useState<number>();
const [selectedSeason, setSelectedSeason] = useState<number>();
const [selectedPlayer, setSelectedPlayer] = useState<Player>();
const [searchInput, setSearchInput] = useState('');
const [selectedTheme, setSelectedTheme] = useState(getPreferredTheme());
const [getGoalsResponse, setGetGoalsResponse] = useState<GetGoalsResponse>();
const [getTeamsResponse, setGetTeamsResponse] = useState<GetTeamsResponse>();
const [getFixturesResponse, setGetFixturesResponse] = useState<GetFixturesResponse>();
const [getLeaguesResponse, setGetLeaguesResponse] = useState<GetLeaguesResponse>();
const [searchPlayesResponse, setSearchPlayersResponse] = useState<SearchPlayersResponse>();

const pageCount = Math.ceil(
(getGoalsResponse ? getGoalsResponse.total : 0) / (pagination.limit || defaultPagination.limit)
Expand All @@ -43,9 +45,6 @@ function Goals() {
getGoals().then((data) => {
setGetGoalsResponse(data);
});
getTeams().then((data) => {
setGetTeamsResponse(data);
});
}, []);

function handlePageClick(selectedItem: {selected: number}) {
Expand All @@ -54,7 +53,6 @@ function Goals() {
const getGoalsFilter: GetGoalsFilter = {
searchTerm: searchInput,
leagueId: selectedLeagueId,
season: selectedSeason,
teamId: selectedTeamId,
};
setGetGoalsResponse(undefined);
Expand All @@ -70,8 +68,8 @@ function Goals() {
const getGoalsFilter: GetGoalsFilter = {
searchTerm: searchInput,
leagueId: selectedLeagueId,
season: selectedSeason,
teamId: selectedTeamId,
playerId: selectedPlayer?.id,
};
setGetGoalsResponse(undefined);
getGoals(defaultPagination, getGoalsFilter).then((data) => setGetGoalsResponse(data));
Expand All @@ -85,16 +83,13 @@ function Goals() {
const getGoalsFilter: GetGoalsFilter = {
searchTerm: searchInput,
leagueId: parseInt(selectedLeagueId),
season: selectedSeason,
teamId: selectedTeamId,
playerId: selectedPlayer?.id,
};
setGetGoalsResponse(undefined);
getGoals(defaultPagination, getGoalsFilter).then((data) => setGetGoalsResponse(data));
getTeams({leagueId: parseInt(selectedLeagueId), season: selectedSeason}).then((data) =>
setGetTeamsResponse(data)
);

setSelectedLeagueId(parseInt(selectedLeagueId));
setSelectedTeamId(undefined);
setPagination(defaultPagination);
setCurrentPage(0);
}
Expand All @@ -104,8 +99,8 @@ function Goals() {
const getGoalsFilter: GetGoalsFilter = {
searchTerm: searchInput,
leagueId: selectedLeagueId,
season: selectedSeason,
teamId: parseInt(selectedTeamId),
playerId: selectedPlayer?.id,
};
setGetGoalsResponse(undefined);
getGoals(defaultPagination, getGoalsFilter).then((data) => setGetGoalsResponse(data));
Expand All @@ -115,30 +110,39 @@ function Goals() {
setCurrentPage(0);
}

function handleSelectedSeasonChange(value: string) {
const selectedSeason = value;
function handleSelectedPlayerChange(player: Player) {
const getGoalsFilter: GetGoalsFilter = {
searchTerm: searchInput,
leagueId: selectedLeagueId,
season: parseInt(selectedSeason),
teamId: selectedTeamId,
playerId: player.id,
};

setGetGoalsResponse(undefined);
getGoals(defaultPagination, getGoalsFilter).then((data) => setGetGoalsResponse(data));

setSelectedSeason(parseInt(selectedSeason));
setSelectedPlayer(player);
setPagination(defaultPagination);
setCurrentPage(0);
}

function handleTeamSearchInputChange(value: string) {
getTeams({searchTerm: value}).then((data) => {
setGetTeamsResponse(data);
});
}

function handlePlayerSearchInputChange(value: string) {
searchPlayers(value).then((data) => {
setSearchPlayersResponse(data);
});
}

function reset() {
setGetGoalsResponse(undefined);
getGoals().then((data) => setGetGoalsResponse(data));
getTeams().then((data) => setGetTeamsResponse(data));

setSelectedLeagueId(undefined);
setSelectedTeamId(undefined);
setSelectedSeason(undefined);
setSearchInput('');
setCurrentPage(0);
setPagination(defaultPagination);
Expand Down Expand Up @@ -210,11 +214,11 @@ function Goals() {
<Select
label={'League'}
options={[
{value: 1, displayName: 'World Cup'},
{value: 2, displayName: 'Champions League'},
{value: 3, displayName: 'Europa League'},
{value: 39, displayName: 'Premier League'},
{value: 253, displayName: 'Major League Soccer'},
{key: 1, value: 1, displayName: 'World Cup'},
{key: 2, value: 2, displayName: 'Champions League'},
{key: 3, value: 3, displayName: 'Europa League'},
{key: 39, value: 39, displayName: 'Premier League'},
{key: 253, value: 253, displayName: 'Major League Soccer'},
]}
value={selectedLeagueId}
onChange={handleSelectedLeagueChange}
Expand All @@ -225,23 +229,35 @@ function Goals() {
getTeamsResponse &&
getTeamsResponse.teams &&
getTeamsResponse.teams.map((team) => ({
key: team.id,
value: team.id,
displayName: team.name,
}))
}
value={selectedTeamId}
onChange={handleSelectedTeamChange}
onSearchChange={handleTeamSearchInputChange}
onFirstInteraction={() => getTeams().then((data) => setGetTeamsResponse(data))}
showSearchInput
></Select>
<Select
label={'Season'}
options={[
{value: '2023', displayName: '2023'},
{value: '2022', displayName: '2022'},
{value: '2021', displayName: '2021'},
]}
value={selectedSeason}
onChange={handleSelectedSeasonChange}
label={'Player'}
options={
searchPlayesResponse &&
searchPlayesResponse.players &&
searchPlayesResponse.players.map((player) => ({
key: player.id,
value: player,
displayName: player.name,
}))
}
value={selectedPlayer}
onChange={handleSelectedPlayerChange}
onSearchChange={handlePlayerSearchInputChange}
onFirstInteraction={() =>
searchPlayers('').then((data) => setSearchPlayersResponse(data))
}
showSearchInput
></Select>
</div>

Expand Down

0 comments on commit c410f7a

Please sign in to comment.