refactor: move filter bar to GameList and fix button styling
- Moved filter button bar from App.jsx to GameList.jsx for better separation of concerns. - Updated GameList to accept filter/setFilter props and render the filter bar internally. - Moved .new-game-button styles to global CSS for consistent styling. - Ensured filter button styles remain in GameList.module.css. - Improves modularity and UI consistency.
This commit is contained in:
@@ -19,6 +19,7 @@ export default function App() {
|
|||||||
const [modal, setModal] = useState({ open: false, gameId: null });
|
const [modal, setModal] = useState({ open: false, gameId: null });
|
||||||
const [validation, setValidation] = useState({ open: false, message: '' });
|
const [validation, setValidation] = useState({ open: false, message: '' });
|
||||||
const [completionModal, setCompletionModal] = useState({ open: false, game: null });
|
const [completionModal, setCompletionModal] = useState({ open: false, game: null });
|
||||||
|
const [filter, setFilter] = useState('all');
|
||||||
|
|
||||||
// Load games from localStorage on mount
|
// Load games from localStorage on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -137,11 +138,13 @@ export default function App() {
|
|||||||
{screen === 'game-list' && (
|
{screen === 'game-list' && (
|
||||||
<div className="screen active">
|
<div className="screen active">
|
||||||
<div className="screen-content">
|
<div className="screen-content">
|
||||||
<button className="nav-button" onClick={showNewGame}>Neues Spiel</button>
|
<button className="nav-button new-game-button" onClick={showNewGame}>Neues Spiel</button>
|
||||||
<GameList
|
<GameList
|
||||||
games={games}
|
games={games}
|
||||||
|
filter={filter}
|
||||||
onShowGameDetail={showGameDetail}
|
onShowGameDetail={showGameDetail}
|
||||||
onDeleteGame={handleDeleteGame}
|
onDeleteGame={handleDeleteGame}
|
||||||
|
setFilter={setFilter}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { h } from 'preact';
|
import { h } from 'preact';
|
||||||
import styles from './GameList.module.css';
|
import styles from './GameList.module.css';
|
||||||
|
|
||||||
export default function GameList({ games, filter = 'all', onShowGameDetail, onDeleteGame }) {
|
export default function GameList({ games, filter = 'all', setFilter, onShowGameDetail, onDeleteGame }) {
|
||||||
const filteredGames = games
|
const filteredGames = games
|
||||||
.filter(game => {
|
.filter(game => {
|
||||||
if (filter === 'active') return game.status === 'active';
|
if (filter === 'active') return game.status === 'active';
|
||||||
@@ -10,32 +10,37 @@ export default function GameList({ games, filter = 'all', onShowGameDetail, onDe
|
|||||||
})
|
})
|
||||||
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
||||||
|
|
||||||
if (filteredGames.length === 0) {
|
|
||||||
return <div className={styles['empty-state']}>Keine Spiele vorhanden</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles['game-list'] + ' ' + styles['games-container']}>
|
<div className={styles['game-list'] + ' ' + styles['games-container']}>
|
||||||
{filteredGames.map(game => {
|
<div className={styles['filter-buttons']}>
|
||||||
const playerNames = game.player3
|
<button className={styles['filter-button'] + (filter === 'all' ? ' ' + styles['active'] : '')} onClick={() => setFilter('all')}>Alle</button>
|
||||||
? `${game.player1} vs ${game.player2} vs ${game.player3}`
|
<button className={styles['filter-button'] + (filter === 'active' ? ' ' + styles['active'] : '')} onClick={() => setFilter('active')}>Aktiv</button>
|
||||||
: `${game.player1} vs ${game.player2}`;
|
<button className={styles['filter-button'] + (filter === 'completed' ? ' ' + styles['active'] : '')} onClick={() => setFilter('completed')}>Abgeschlossen</button>
|
||||||
const scores = game.player3
|
</div>
|
||||||
? `${game.score1} - ${game.score2} - ${game.score3}`
|
{filteredGames.length === 0 ? (
|
||||||
: `${game.score1} - ${game.score2}`;
|
<div className={styles['empty-state']}>Keine Spiele vorhanden</div>
|
||||||
return (
|
) : (
|
||||||
<div className={
|
filteredGames.map(game => {
|
||||||
styles['game-item'] + ' ' + (game.status === 'completed' ? styles['completed'] : styles['active'])
|
const playerNames = game.player3
|
||||||
} key={game.id}>
|
? `${game.player1} vs ${game.player2} vs ${game.player3}`
|
||||||
<div className={styles['game-info']} onClick={() => onShowGameDetail(game.id)}>
|
: `${game.player1} vs ${game.player2}`;
|
||||||
<div className={styles['game-type']}>{game.gameType}{game.raceTo ? ` | ${game.raceTo}` : ''}</div>
|
const scores = game.player3
|
||||||
<div className={styles['player-names']}>{playerNames}</div>
|
? `${game.score1} - ${game.score2} - ${game.score3}`
|
||||||
<div className={styles['game-scores']}>{scores}</div>
|
: `${game.score1} - ${game.score2}`;
|
||||||
|
return (
|
||||||
|
<div className={
|
||||||
|
styles['game-item'] + ' ' + (game.status === 'completed' ? styles['completed'] : styles['active'])
|
||||||
|
} key={game.id}>
|
||||||
|
<div className={styles['game-info']} onClick={() => onShowGameDetail(game.id)}>
|
||||||
|
<div className={styles['game-type']}>{game.gameType}{game.raceTo ? ` | ${game.raceTo}` : ''}</div>
|
||||||
|
<div className={styles['player-names']}>{playerNames}</div>
|
||||||
|
<div className={styles['game-scores']}>{scores}</div>
|
||||||
|
</div>
|
||||||
|
<button className={styles['delete-button']} onClick={() => onDeleteGame(game.id)}></button>
|
||||||
</div>
|
</div>
|
||||||
<button className={styles['delete-button']} onClick={() => onDeleteGame(game.id)}></button>
|
);
|
||||||
</div>
|
})
|
||||||
);
|
)}
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -38,9 +38,114 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
touch-action: manipulation;
|
touch-action: manipulation;
|
||||||
}
|
}
|
||||||
|
.filter-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 24px 0 16px 0;
|
||||||
|
}
|
||||||
.filter-button {
|
.filter-button {
|
||||||
|
flex: 1;
|
||||||
background: #333;
|
background: #333;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
padding: 18px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: background 0.2s, color 0.2s;
|
||||||
}
|
}
|
||||||
.filter-button.active {
|
.filter-button.active {
|
||||||
background: #4CAF50;
|
background: #4CAF50;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.games-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
.game-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
transition: transform 0.1s ease, background-color 0.2s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.game-item.active {
|
||||||
|
background: #1e4620;
|
||||||
|
}
|
||||||
|
.game-item.completed {
|
||||||
|
background: #333;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
.game-info {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
.game-type {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
min-width: 8rem;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.player-names {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
min-width: 16rem;
|
||||||
|
}
|
||||||
|
.game-scores {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
min-width: 6rem;
|
||||||
|
text-align: center;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.delete-button {
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
border: none;
|
||||||
|
background: #ff4444;
|
||||||
|
color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-left: 1rem;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.delete-button::before {
|
||||||
|
content: '\1F5D1'; /* 🗑️ */
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
.delete-button:hover {
|
||||||
|
background: #cc0000;
|
||||||
|
}
|
||||||
|
.delete-button:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
color: #666;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
.page-header {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
background: #181818;
|
||||||
|
padding: 24px 0 16px 0;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
}
|
}
|
||||||
@@ -56,4 +56,24 @@ input, select {
|
|||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-game-button {
|
||||||
|
width: 100%;
|
||||||
|
background: #222;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 20px 0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s, color 0.2s;
|
||||||
|
text-align: center;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-game-button:hover {
|
||||||
|
background: #333;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user