- 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.
173 lines
5.3 KiB
TypeScript
173 lines
5.3 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { ImportRequest, ImportResult, importApi } from '@/lib/api';
|
|
import {
|
|
Sheet,
|
|
SheetContent,
|
|
SheetDescription,
|
|
SheetHeader,
|
|
SheetTitle,
|
|
SheetTrigger,
|
|
} from '@/components/ui/sheet';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Checkbox } from '@/components/ui/checkbox';
|
|
import { UploadIcon, FolderOpenIcon, CheckCircleIcon, XCircleIcon, LoaderIcon } from 'lucide-react';
|
|
|
|
interface ImportSheetProps {
|
|
onSuccess: () => void;
|
|
}
|
|
|
|
export function ImportSheet({ onSuccess }: ImportSheetProps) {
|
|
const [open, setOpen] = useState(false);
|
|
const [directory, setDirectory] = useState('');
|
|
const [recursive, setRecursive] = useState(true);
|
|
const [isImporting, setIsImporting] = useState(false);
|
|
const [result, setResult] = useState<ImportResult | null>(null);
|
|
|
|
const handleImport = async () => {
|
|
if (!directory.trim()) return;
|
|
|
|
setIsImporting(true);
|
|
setResult(null);
|
|
|
|
try {
|
|
const importData: ImportRequest = {
|
|
directory: directory.trim(),
|
|
recursive,
|
|
};
|
|
|
|
const importResult = await importApi.start(importData);
|
|
setResult(importResult);
|
|
|
|
if (importResult.success) {
|
|
onSuccess();
|
|
}
|
|
} catch (err) {
|
|
setResult({
|
|
success: false,
|
|
message: err instanceof Error ? err.message : 'Error al importar',
|
|
imported: 0,
|
|
errors: [err instanceof Error ? err.message : 'Error desconocido'],
|
|
});
|
|
} finally {
|
|
setIsImporting(false);
|
|
}
|
|
};
|
|
|
|
const handleClose = () => {
|
|
setOpen(false);
|
|
setDirectory('');
|
|
setResult(null);
|
|
};
|
|
|
|
return (
|
|
<Sheet open={open} onOpenChange={setOpen}>
|
|
<SheetTrigger asChild>
|
|
<Button>
|
|
<UploadIcon data-icon="inline-start" />
|
|
Importar Juegos
|
|
</Button>
|
|
</SheetTrigger>
|
|
<SheetContent className="sm:max-w-md">
|
|
<SheetHeader>
|
|
<SheetTitle>Importar Juegos</SheetTitle>
|
|
<SheetDescription>
|
|
Importa juegos desde archivos ROM en un directorio local.
|
|
</SheetDescription>
|
|
</SheetHeader>
|
|
|
|
<div className="flex flex-col gap-4 py-4">
|
|
<div className="flex flex-col gap-2">
|
|
<Label htmlFor="directory">Directorio</Label>
|
|
<div className="flex gap-2">
|
|
<Input
|
|
id="directory"
|
|
value={directory}
|
|
onChange={(e) => setDirectory(e.target.value)}
|
|
placeholder="/path/to/roms"
|
|
disabled={isImporting}
|
|
/>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
size="icon"
|
|
disabled={isImporting}
|
|
title="Seleccionar directorio"
|
|
>
|
|
<FolderOpenIcon className="size-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2">
|
|
<Checkbox
|
|
id="recursive"
|
|
checked={recursive}
|
|
onCheckedChange={(checked) => setRecursive(checked === true)}
|
|
disabled={isImporting}
|
|
/>
|
|
<Label htmlFor="recursive" className="cursor-pointer">
|
|
Incluir subdirectorios
|
|
</Label>
|
|
</div>
|
|
|
|
{result && (
|
|
<div className="rounded-lg border border-border p-4">
|
|
<div className="flex items-center gap-2 mb-3">
|
|
{result.success ? (
|
|
<CheckCircleIcon className="size-5 text-emerald-500" />
|
|
) : (
|
|
<XCircleIcon className="size-5 text-destructive" />
|
|
)}
|
|
<span className="font-medium">
|
|
{result.success ? 'Importación completada' : 'Error en la importación'}
|
|
</span>
|
|
</div>
|
|
<p className="text-sm text-muted-foreground mb-2">{result.message}</p>
|
|
<p className="text-sm font-medium">ROMs importadas: {result.imported}</p>
|
|
{result.errors.length > 0 && (
|
|
<div className="mt-3">
|
|
<p className="text-sm font-medium mb-1">Errores:</p>
|
|
<ul className="text-sm text-destructive list-disc list-inside">
|
|
{result.errors.map((error, index) => (
|
|
<li key={index}>{error}</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{isImporting && (
|
|
<div className="flex items-center gap-2 text-muted-foreground">
|
|
<LoaderIcon className="size-4 animate-spin" />
|
|
<span>Importando ROMs...</span>
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex gap-2 mt-auto">
|
|
<Button
|
|
variant="outline"
|
|
className="flex-1"
|
|
onClick={handleClose}
|
|
disabled={isImporting}
|
|
>
|
|
Cancelar
|
|
</Button>
|
|
<Button
|
|
className="flex-1"
|
|
onClick={handleImport}
|
|
disabled={isImporting || !directory.trim()}
|
|
>
|
|
{isImporting ? 'Importando...' : 'Importar'}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</SheetContent>
|
|
</Sheet>
|
|
);
|
|
}
|