feat: setup iniziale sistema controllo accessi Focolari

Struttura progetto:
- Backend mock Python (FastAPI) con API per gestione varchi
- Frontend React + TypeScript + Vite + Tailwind CSS
- Documentazione e piani di sviluppo

Backend (backend-mock/):
- API REST: /info-room, /login-validate, /anagrafica, /entry-request
- Dati mock: 7 utenti, validatore (999999/focolari)
- CORS abilitato, docs OpenAPI automatiche
- Configurazione pipenv per ambiente virtuale

Frontend (frontend/):
- State machine completa per flusso accesso varco
- Hook useRFIDScanner per lettura badge (pattern singolo)
- Componenti UI: Logo, Button, Input, Modal, UserCard, Timer
- Schermate: Loading, Login, ActiveGate, Success/Error Modal
- Design system con colori Focolari
- Ottimizzato per tablet touch

Documentazione (ai-prompts/):
- Welcome guide per futuri agenti
- Piano sviluppo backend e frontend con checklist

DA COMPLETARE:
- Hook RFID multi-pattern (US/IT/altri layout tastiera)
- Pagina /debug per diagnostica in loco
- Logging console strutturato
This commit is contained in:
2026-01-17 18:20:55 +01:00
commit 21b509c6ba
40 changed files with 7453 additions and 0 deletions

View File

@@ -0,0 +1,135 @@
/**
* Focolari Voting System - API Service
*/
import type {
RoomInfo,
User,
LoginRequest,
LoginResponse,
EntryRequest,
EntryResponse
} from '../types';
const API_BASE_URL = 'http://localhost:8000';
/**
* Custom error class for API errors
*/
export class ApiError extends Error {
constructor(
message: string,
public statusCode: number,
public detail?: string
) {
super(message);
this.name = 'ApiError';
}
}
/**
* Generic fetch wrapper with error handling
*/
async function apiFetch<T>(
endpoint: string,
options?: RequestInit
): Promise<T> {
try {
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers,
},
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new ApiError(
errorData.detail || `HTTP Error ${response.status}`,
response.status,
errorData.detail
);
}
return await response.json();
} catch (error) {
if (error instanceof ApiError) {
throw error;
}
throw new ApiError(
'Errore di connessione al server',
0,
'Verifica che il server sia attivo'
);
}
}
// ============================================
// API Endpoints
// ============================================
/**
* GET /info-room
* Ottiene le informazioni sulla sala e la riunione
*/
export async function getRoomInfo(): Promise<RoomInfo> {
return apiFetch<RoomInfo>('/info-room');
}
/**
* POST /login-validate
* Autentica il validatore con badge e password
*/
export async function loginValidator(
badge: string,
password: string
): Promise<LoginResponse> {
const payload: LoginRequest = { badge, password };
return apiFetch<LoginResponse>('/login-validate', {
method: 'POST',
body: JSON.stringify(payload),
});
}
/**
* GET /anagrafica/{badge_code}
* Ottiene i dati anagrafici di un utente tramite badge
*/
export async function getUserByBadge(badgeCode: string): Promise<User> {
return apiFetch<User>(`/anagrafica/${encodeURIComponent(badgeCode)}`);
}
/**
* POST /entry-request
* Registra l'ingresso di un utente
*/
export async function requestEntry(
userBadge: string,
validatorPassword: string
): Promise<EntryResponse> {
const payload: EntryRequest = {
user_badge: userBadge,
validator_password: validatorPassword,
};
return apiFetch<EntryResponse>('/entry-request', {
method: 'POST',
body: JSON.stringify(payload),
});
}
// ============================================
// Utility Functions
// ============================================
/**
* Check if the API server is reachable
*/
export async function checkServerHealth(): Promise<boolean> {
try {
const response = await fetch(`${API_BASE_URL}/`);
return response.ok;
} catch {
return false;
}
}