feat: add UI components for alert dialog, badge, checkbox, dialog, label, select, sheet, table, textarea
Some checks failed
CI / lint (push) Failing after 1m5s
CI / test-backend (push) Has been skipped
CI / test-frontend (push) Has been skipped
CI / test-e2e (push) Has been skipped

- Implemented AlertDialog component with overlay, content, header, footer, title, description, action, and cancel functionalities.
- Created Badge component with variant support for different styles.
- Developed Checkbox component with custom styling and indicator.
- Added Dialog component with trigger, close, overlay, content, header, footer, title, and description.
- Introduced Label component for form elements.
- Built Select component with trigger, content, group, item, label, separator, and scroll buttons.
- Created Sheet component with trigger, close, overlay, content, header, footer, title, and description.
- Implemented Table component with header, body, footer, row, head, cell, and caption.
- Added Textarea component with custom styling.
- Established API service for game management with CRUD operations and metadata search functionalities.
- Updated dependencies in package lock files.
This commit is contained in:
2026-03-18 19:21:36 +01:00
parent b92cc19137
commit a07096d7c7
95 changed files with 8176 additions and 615 deletions

189
frontend/src/lib/api.ts Normal file
View File

@@ -0,0 +1,189 @@
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000/api';
export interface Game {
id: string;
title: string;
slug: string;
description?: string;
releaseDate?: string;
genre?: string;
platform?: string;
year?: number;
cover?: string;
source: string;
sourceId?: string;
// Campos específicos de ROM (si source = "rom")
romPath?: string;
romFilename?: string;
romSize?: number;
romChecksum?: string;
romFormat?: string;
romHashes?: string;
// IDs de integraciones externas
igdbId?: number;
rawgId?: number;
thegamesdbId?: number;
// Metadatos adicionales
metadata?: string;
// Timestamps
addedAt?: string;
lastSeenAt?: string;
createdAt: string;
updatedAt: string;
// Relaciones
artworks?: any[];
purchases?: any[];
gamePlatforms?: any[];
tags?: any[];
}
export interface CreateGameInput {
title: string;
slug?: string;
description?: string;
releaseDate?: string;
genre?: string;
platform?: string;
year?: number;
cover?: string;
source?: string;
sourceId?: string;
platformId?: string;
priceCents?: number;
currency?: string;
store?: string;
date?: string;
condition?: 'Loose' | 'CIB' | 'New';
}
export interface UpdateGameInput {
title?: string;
slug?: string;
description?: string;
releaseDate?: string;
genre?: string;
platform?: string;
year?: number;
cover?: string;
source?: string;
sourceId?: string;
platformId?: string;
priceCents?: number;
currency?: string;
store?: string;
date?: string;
condition?: 'Loose' | 'CIB' | 'New';
}
export interface ImportRequest {
directory: string;
recursive?: boolean;
}
export interface ImportResult {
success: boolean;
message: string;
imported: number;
errors: string[];
}
export const gamesApi = {
getAll: async (): Promise<Game[]> => {
const response = await fetch(`${API_BASE}/games`);
if (!response.ok) {
throw new Error(`Error fetching games: ${response.statusText}`);
}
return response.json();
},
getById: async (id: string): Promise<Game> => {
const response = await fetch(`${API_BASE}/games/${id}`);
if (!response.ok) {
throw new Error(`Error fetching game: ${response.statusText}`);
}
return response.json();
},
getBySource: async (source: string): Promise<Game[]> => {
const response = await fetch(`${API_BASE}/games/source/${source}`);
if (!response.ok) {
throw new Error(`Error fetching games by source: ${response.statusText}`);
}
return response.json();
},
create: async (data: CreateGameInput): Promise<Game> => {
const response = await fetch(`${API_BASE}/games`, {
method: 'POST',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' },
});
if (!response.ok) {
throw new Error(`Error creating game: ${response.statusText}`);
}
return response.json();
},
update: async (id: string, data: UpdateGameInput): Promise<Game> => {
const response = await fetch(`${API_BASE}/games/${id}`, {
method: 'PUT',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' },
});
if (!response.ok) {
throw new Error(`Error updating game: ${response.statusText}`);
}
return response.json();
},
delete: async (id: string): Promise<void> => {
const response = await fetch(`${API_BASE}/games/${id}`, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error(`Error deleting game: ${response.statusText}`);
}
},
};
export const importApi = {
start: async (data: ImportRequest): Promise<ImportResult> => {
const response = await fetch(`${API_BASE}/import`, {
method: 'POST',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' },
});
if (!response.ok) {
throw new Error(`Error starting import: ${response.statusText}`);
}
return response.json();
},
};
export const metadataApi = {
searchIGDB: async (query: string): Promise<any[]> => {
const response = await fetch(`${API_BASE}/metadata/igdb/search?q=${encodeURIComponent(query)}`);
if (!response.ok) {
throw new Error(`Error searching IGDB: ${response.statusText}`);
}
return response.json();
},
searchRAWG: async (query: string): Promise<any[]> => {
const response = await fetch(`${API_BASE}/metadata/rawg/search?q=${encodeURIComponent(query)}`);
if (!response.ok) {
throw new Error(`Error searching RAWG: ${response.statusText}`);
}
return response.json();
},
searchTheGamesDB: async (query: string): Promise<any[]> => {
const response = await fetch(
`${API_BASE}/metadata/thegamesdb/search?q=${encodeURIComponent(query)}`
);
if (!response.ok) {
throw new Error(`Error searching TheGamesDB: ${response.statusText}`);
}
return response.json();
},
};