criação do website

This commit is contained in:
2026-05-05 17:12:06 +01:00
parent 9c36b714f1
commit 732e7276b7
46 changed files with 9844 additions and 0 deletions

207
docs/03-architecture.md Normal file
View File

@@ -0,0 +1,207 @@
# 03 — Arquitetura do Sistema
## Diagrama de Arquitetura
```
┌─────────────────────────────────────────────────────────────┐
│ FIREBASE │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ Firestore │ │ Auth │ │ Hosting │ │
│ │ (Database) │ │ (Admin only)│ │ (Deploy) │ │
│ └──────┬──────┘ └──────────────┘ └───────────────┘ │
│ │ │
└──────────┼────────────────────────────────────────────────── ┘
│ Real-time listeners
┌──────┴──────────────────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────────┐
│ ADMIN DASHBOARD │ │ APLICAÇÃO CLIENTE │
│ (Este projeto) │ │ (Já desenvolvida) │
│ │ │ │
│ - Escreve dados │ │ - Lê dados │
│ - Autenticado │ │ - Sem autenticação │
│ - React SPA │ │ - Apenas leitura │
└──────────────────┘ └──────────────────────┘
▲ WRITE ▲ READ
│ │
└────────── Firebase ────────┘
(fonte única de verdade)
```
---
## Fluxo de Dados
### Atualização de Resultado em Tempo Real
```
Admin clica "+1 Golo" no Live Score Editor
React Hook Form / estado local atualiza
Firebase transaction (atómica):
- Atualiza game.score
- Cria evento no subcollection game.events
- Atualiza stats do jogador (se golo com autor)
- Recalcula standings
Firebase Firestore (source of truth)
├──▶ Admin Dashboard: listener onSnapshot atualiza UI
└──▶ App Cliente: listener onSnapshot recebe atualização
(< 2 segundos latência)
```
### Fluxo de Autenticação
```
Utilizador acede ao site
Firebase Auth verifica sessão
├── Não autenticado ──▶ Redireciona para /login
└── Autenticado ──▶ Verifica role em Firestore
├── role: "admin" ──▶ Acesso total
└── outros ──▶ Redireciona para /login
```
---
## Arquitetura de Componentes
```
App
├── AuthProvider (contexto de autenticação)
│ └── Router
│ ├── /login → LoginPage
│ └── ProtectedRoute (requer auth)
│ └── Layout
│ ├── Sidebar
│ ├── Header
│ └── Outlet (conteúdo da página)
│ ├── /dashboard → DashboardPage
│ ├── /games → GamesPage
│ │ └── /games/:id/live → LiveGamePage ⭐
│ ├── /clubs → ClubsPage
│ │ └── /clubs/:id → ClubDetailPage
│ ├── /players → PlayersPage
│ ├── /standings → StandingsPage
│ └── /scorers → ScorersPage
```
---
## Gestão de Estado
### Zustand Stores
```typescript
// authStore — autenticação
{
user: FirebaseUser | null,
isAdmin: boolean,
loading: boolean
}
// leagueStore — dados da liga (cache local)
{
clubs: Club[],
players: Player[],
currentSeason: string,
activeGameId: string | null // jogo a decorrer
}
```
### Firebase Hooks (React Query pattern)
Cada módulo tem um hook custom que:
1. Subscreve ao Firestore com `onSnapshot`
2. Guarda dados no estado local do hook
3. Expõe funções de mutação (create, update, delete)
4. Faz cleanup do listener no unmount
```typescript
// Exemplo
function useGames(jornadaId?: string) {
const [games, setGames] = useState<Game[]>([])
const [loading, setLoading] = useState(true)
useEffect(() => {
const q = jornadaId
? query(gamesRef, where("jornadaId", "==", jornadaId))
: gamesRef
const unsubscribe = onSnapshot(q, (snapshot) => {
setGames(snapshot.docs.map(d => ({ id: d.id, ...d.data() })))
setLoading(false)
})
return () => unsubscribe() // cleanup
}, [jornadaId])
return { games, loading }
}
```
---
## Firebase Security Rules
```javascript
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Função helper: verifica se é admin
function isAdmin() {
return request.auth != null &&
get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == "admin";
}
// Jogos: leitura pública, escrita apenas admin
match /games/{gameId} {
allow read: if true;
allow write: if isAdmin();
match /events/{eventId} {
allow read: if true;
allow write: if isAdmin();
}
}
// Clubes: leitura pública, escrita apenas admin
match /clubs/{clubId} {
allow read: if true;
allow write: if isAdmin();
}
// Jogadores: leitura pública, escrita apenas admin
match /players/{playerId} {
allow read: if true;
allow write: if isAdmin();
}
// Standings: leitura pública, escrita apenas admin
match /standings/{doc} {
allow read: if true;
allow write: if isAdmin();
}
// Utilizadores: apenas o próprio ou admin
match /users/{userId} {
allow read: if request.auth.uid == userId || isAdmin();
allow write: if isAdmin();
}
}
}
```