Neuere deploy version

This commit is contained in:
Marc Wieland 2026-01-23 10:41:19 +01:00
parent da018a070d
commit fc75c19611
9 changed files with 1784 additions and 542 deletions

17
.dockerignore Normal file
View File

@ -0,0 +1,17 @@
node_modules
dist
.git
.github
.vscode
*.md
.env
.env.local
.DS_Store
Thumbs.db
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.eslintcache
coverage
.vitest

104
.github/copilot-instructions.md vendored Normal file
View File

@ -0,0 +1,104 @@
# NVJ Turnierplaner - AI Coding Agent Instructions
## Project Overview
Volleyball tournament organizer (NVJ = Netzroller Volleyball Jugend) for managing teams, rounds, and scoring across two league types: Bundesliga and Champions League. Built with React/TypeScript.
## Architecture
### State Management Pattern
Single global context provider in [src/context/TournamentContext.tsx](src/context/TournamentContext.tsx):
- **TournamentProvider** wraps entire app in [App.tsx](src/App.tsx)
- Access via `useTournament()` hook - throws error if used outside provider
- State includes: teams, rounds, field count, match results
- **Critical**: Use `crypto.randomUUID()` for all new IDs (teams, matches, rounds)
### Data Flow
1. Teams created on Index page → stored in context
2. `generateMatches()` utility distributes teams across fields/leagues
3. Rounds track match results and waiting teams
4. Scores calculated via point differential: `pointsA = -(scoreB - scoreA)`, `pointsB = (scoreB - scoreA)`
### Type System ([src/types/tournament.ts](src/types/tournament.ts))
- **League**: `'bundesliga' | 'champions'` - string literal types only
- **Match** includes `result?: MatchResult` - undefined until scores entered
- **Round** has `completed: boolean` - only one active round allowed at a time
- **TeamScore** maintains `pointsHistory: number[]` for per-round tracking
## Development Workflow
### Running the App
```bash
npm run dev # Start development server on port 8080
npm run build # Production build
npm run build:dev # Development mode build
npm run test # Run Vitest once
npm run test:watch # Watch mode for tests
```
### Vite Configuration
- **Path alias**: `@/` maps to `src/` (configured in [vite.config.ts](vite.config.ts))
- **React deduplication**: Critical for preventing hook errors - never remove from config
## UI Component Conventions
### shadcn/ui Pattern
- All UI primitives in [src/components/ui/](src/components/ui/) - **do not edit** these directly
- Import from `@/components/ui/<component>`
- Customization via Tailwind classes and variants, not component modification
### Design System ([src/index.css](src/index.css))
**Custom CSS tokens** (use these instead of arbitrary values):
- `card-apple` - Standard card styling (rounded-2xl, shadow, border)
- `card-apple-hover` - Interactive card with hover effects
- `bg-bundesliga` / `bg-champions` - League-specific colors with matching foregrounds
- `bg-waiting` / `bg-field` - Status colors for team/field states
**Color semantics**:
- Bundesliga: Red theme (`--bundesliga: 0 72% 51%`)
- Champions: Gold/yellow theme (`--champions: 45 93% 47%`)
- Field: Green (`--field: 142 71% 45%`)
- All have HSL definitions in CSS custom properties
### Component Structure Pattern
See [src/components/TournamentView.tsx](src/components/TournamentView.tsx) for reference:
- Extract sub-components within file for local use (`WaitingTeam`, `LeagueSection`)
- Use typed icon props: `icon: typeof Trophy` instead of React.ComponentType
- Filter round data at component level, not in context
## Key Files & Responsibilities
- [src/utils/tournamentUtils.ts](src/utils/tournamentUtils.ts) - Match generation with field distribution logic
- [src/context/TournamentContext.tsx](src/context/TournamentContext.tsx) - All tournament state mutations
- [src/pages/Index.tsx](src/pages/Index.tsx) - Team setup interface
- [src/pages/Tournament.tsx](src/pages/Tournament.tsx) - Active tournament management
- [src/components/MatchScoreInput.tsx](src/components/MatchScoreInput.tsx) - Score entry with point calculation
## Project-Specific Rules
1. **Never modify completed rounds** - `completed: true` rounds are immutable
2. **Field allocation**: Bundesliga and Champions always share fields evenly (±1) when both have teams
3. **Waiting teams**: Odd-numbered teams in a league → last team waits (see `createMatchesForLeague`)
4. **Score point mapping**: Differential-based, not win/loss binary
5. **Component naming**: PascalCase files match export name exactly
6. **Routing**: Custom routes MUST be above `path="*"` catch-all in [App.tsx](App.tsx)
## Dependencies Notes
- **Bun runtime**: Package manager is Bun (uses bun.lockb), but npm scripts work
- **React Router**: v6 pattern with BrowserRouter
- **React Query**: QueryClient setup in App.tsx but minimal usage currently
- **Lucide React**: Icon library of choice (see imports in components)
- **Sonner + shadcn Toast**: Dual toast systems - prefer Sonner for new toasts
## Testing
- Vitest + Testing Library configured ([vitest.config.ts](vitest.config.ts))
- Test setup in [src/test/setup.ts](src/test/setup.ts)
- Run with `npm run test:watch` for development
## Common Pitfalls
- Don't destructure `useTournament()` before checking context existence
- TypeScript paths require build tool awareness - always use `@/` imports
- Custom CSS utilities won't work without `@layer utilities` wrapper
- Dark mode is class-based - test both light/dark themes

34
Dockerfile Normal file
View File

@ -0,0 +1,34 @@
# Multi-stage build für minimales Image
# Build Stage
FROM node:18-alpine AS builder
WORKDIR /app
# Kopiere package files
COPY package*.json ./
COPY bun.lockb* ./
# Installiere Dependencies
RUN npm ci --only=production=false
# Kopiere Source Code
COPY . .
# Build der Anwendung
RUN npm run build
# Production Stage
FROM nginx:alpine
# Kopiere Build-Artefakte vom Builder
COPY --from=builder /app/dist /usr/share/nginx/html
# Kopiere nginx Konfiguration
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Exponiere Port 80
EXPOSE 80
# nginx läuft automatisch im Vordergrund
CMD ["nginx", "-g", "daemon off;"]

View File

@ -1,73 +1,65 @@
# Welcome to your Lovable project # NVJ Turnierplaner
## Project info Volleyball tournament organizer for Netzroller Volleyball Jugend (NVJ). Manage teams, rounds, and scoring across Bundesliga and Champions League formats.
**URL**: https://lovable.dev/projects/REPLACE_WITH_PROJECT_ID ## Getting Started
## How can I edit this code? ### Prerequisites
There are several ways of editing your application. - Node.js (v18 or higher) - [install with nvm](https://github.com/nvm-sh/nvm#installing-and-updating)
- Bun (optional, for package management)
**Use Lovable** ### Installation
Simply visit the [Lovable Project](https://lovable.dev/projects/REPLACE_WITH_PROJECT_ID) and start prompting.
Changes made via Lovable will be committed automatically to this repo.
**Use your preferred IDE**
If you want to work locally using your own IDE, you can clone this repo and push changes. Pushed changes will also be reflected in Lovable.
The only requirement is having Node.js & npm installed - [install with nvm](https://github.com/nvm-sh/nvm#installing-and-updating)
Follow these steps:
```sh ```sh
# Step 1: Clone the repository using the project's Git URL. # Clone the repository
git clone <YOUR_GIT_URL> git clone <YOUR_GIT_URL>
# Step 2: Navigate to the project directory. # Navigate to the project directory
cd <YOUR_PROJECT_NAME> cd nvj-turnierplaner2
# Step 3: Install the necessary dependencies. # Install dependencies
npm i npm install
# or with bun
bun install
# Step 4: Start the development server with auto-reloading and an instant preview. # Start the development server
npm run dev npm run dev
``` ```
**Edit a file directly in GitHub** The app will be available at `http://localhost:8080`
- Navigate to the desired file(s). ## Tech Stack
- Click the "Edit" button (pencil icon) at the top right of the file view.
- Make your changes and commit the changes.
**Use GitHub Codespaces** - **Vite** - Build tool and dev server
- **React 18** - UI framework
- **TypeScript** - Type safety
- **shadcn/ui** - Component library
- **Tailwind CSS** - Styling
- **React Router** - Navigation
- **Vitest** - Testing framework
- Navigate to the main page of your repository. ## Available Scripts
- Click on the "Code" button (green button) near the top right.
- Select the "Codespaces" tab.
- Click on "New codespace" to launch a new Codespace environment.
- Edit files directly within the Codespace and commit and push your changes once you're done.
## What technologies are used for this project? ```sh
npm run dev # Start development server (port 8080)
npm run build # Production build
npm run build:dev # Development mode build
npm run test # Run tests once
npm run test:watch # Run tests in watch mode
npm run lint # Lint code
npm run preview # Preview production build
```
This project is built with: ## Project Structure
- Vite - `src/context/` - Global state management (TournamentContext)
- TypeScript - `src/pages/` - Page components (Index, Tournament, NotFound)
- React - `src/components/` - Reusable components
- shadcn-ui - `src/components/ui/` - shadcn/ui primitives (do not edit directly)
- Tailwind CSS - `src/utils/` - Utility functions (match generation logic)
- `src/types/` - TypeScript type definitions
## How can I deploy this project? ## Development Guide
Simply open [Lovable](https://lovable.dev/projects/REPLACE_WITH_PROJECT_ID) and click on Share -> Publish. See [.github/copilot-instructions.md](.github/copilot-instructions.md) for detailed architecture documentation and coding conventions.
## Can I connect a custom domain to my Lovable project?
Yes, you can!
To connect a domain, navigate to Project > Settings > Domains and click Connect Domain.
Read more here: [Setting up a custom domain](https://docs.lovable.dev/features/custom-domain#custom-domain)

17
docker-compose.yml Normal file
View File

@ -0,0 +1,17 @@
version: '3.8'
services:
turnierplaner:
build:
context: .
dockerfile: Dockerfile
container_name: turnierplaner
ports:
- "18080:80"
restart: unless-stopped
networks:
- turnierplaner-network
networks:
turnierplaner-network:
driver: bridge

38
nginx.conf Normal file
View File

@ -0,0 +1,38 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Gzip Kompression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json application/javascript;
# Security Headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Cache statische Assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# React Router - alle Anfragen zu index.html weiterleiten
location / {
try_files $uri $uri/ /index.html;
}
# Keine Cache für index.html
location = /index.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
}
# Fehlerseiten
error_page 404 /index.html;
}

2014
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -65,9 +65,9 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.32.0", "@eslint/js": "^9.32.0",
"@tailwindcss/typography": "^0.5.16",
"@testing-library/jest-dom": "^6.6.0", "@testing-library/jest-dom": "^6.6.0",
"@testing-library/react": "^16.0.0", "@testing-library/react": "^16.0.0",
"@tailwindcss/typography": "^0.5.16",
"@types/node": "^22.16.5", "@types/node": "^22.16.5",
"@types/react": "^18.3.23", "@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7", "@types/react-dom": "^18.3.7",
@ -78,7 +78,6 @@
"eslint-plugin-react-refresh": "^0.4.20", "eslint-plugin-react-refresh": "^0.4.20",
"globals": "^15.15.0", "globals": "^15.15.0",
"jsdom": "^20.0.3", "jsdom": "^20.0.3",
"lovable-tagger": "^1.1.13",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"typescript": "^5.8.3", "typescript": "^5.8.3",

View File

@ -1,10 +1,9 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc"; import react from "@vitejs/plugin-react-swc";
import path from "path"; import path from "path";
import { componentTagger } from "lovable-tagger";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig(({ mode }) => ({ export default defineConfig(() => ({
server: { server: {
host: "::", host: "::",
port: 8080, port: 8080,
@ -12,7 +11,7 @@ export default defineConfig(({ mode }) => ({
overlay: false, overlay: false,
}, },
}, },
plugins: [react(), mode === "development" && componentTagger()].filter(Boolean), plugins: [react()],
resolve: { resolve: {
alias: { alias: {
"@": path.resolve(__dirname, "./src"), "@": path.resolve(__dirname, "./src"),