115 lines
4.1 KiB
TypeScript
115 lines
4.1 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useState, useCallback } from 'react';
|
|
import { api } from '@/lib/api';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { toast } from 'sonner';
|
|
import { Plus } from 'lucide-react';
|
|
|
|
interface City { id: string; name: string; }
|
|
interface Region { id: string; name: string; cities: City[]; }
|
|
interface Country { id: string; name: string; regions: Region[]; }
|
|
|
|
export default function CitiesPage() {
|
|
const [countries, setCountries] = useState<Country[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [newCity, setNewCity] = useState('');
|
|
const [selectedRegionId, setSelectedRegionId] = useState('');
|
|
|
|
const load = useCallback(() => {
|
|
setLoading(true);
|
|
setError(null);
|
|
api.get<Country[]>('/locations/countries')
|
|
.then(setCountries)
|
|
.catch(() => setError('Error al cargar ciudades'))
|
|
.finally(() => setLoading(false));
|
|
}, []);
|
|
|
|
useEffect(() => { load(); }, [load]);
|
|
|
|
const addCity = async () => {
|
|
if (!selectedRegionId || !newCity.trim()) {
|
|
toast.error('Selecciona una región y escribe un nombre');
|
|
return;
|
|
}
|
|
try {
|
|
await api.post('/locations/cities', { region_id: selectedRegionId, name: newCity.trim() });
|
|
toast.success('Ciudad agregada');
|
|
setNewCity('');
|
|
load();
|
|
} catch (e: any) {
|
|
toast.error(e?.message || 'Error al agregar ciudad');
|
|
}
|
|
};
|
|
|
|
if (loading) {
|
|
return <div className="flex items-center justify-center py-16 text-muted-foreground">Cargando...</div>;
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="flex flex-col items-center justify-center py-16 text-destructive">
|
|
<p>{error}</p>
|
|
<Button variant="outline" size="sm" onClick={load} className="mt-2">Reintentar</Button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<h1 className="text-2xl font-bold">Ciudades</h1>
|
|
|
|
<Card>
|
|
<CardHeader><CardTitle>Agregar ciudad</CardTitle></CardHeader>
|
|
<CardContent className="flex gap-2">
|
|
<select
|
|
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
|
value={selectedRegionId}
|
|
onChange={(e) => setSelectedRegionId(e.target.value)}
|
|
>
|
|
<option value="">Seleccionar región...</option>
|
|
{countries.map((c) => (
|
|
<optgroup key={c.id} label={c.name}>
|
|
{c.regions.map((r) => (
|
|
<option key={r.id} value={r.id}>{r.name}</option>
|
|
))}
|
|
</optgroup>
|
|
))}
|
|
</select>
|
|
<Input
|
|
value={newCity}
|
|
onChange={(e) => setNewCity(e.target.value)}
|
|
placeholder="Nombre de la ciudad"
|
|
/>
|
|
<Button onClick={addCity}><Plus className="mr-1 h-4 w-4" />Agregar</Button>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{countries.map((c) => (
|
|
<Card key={c.id}>
|
|
<CardContent className="pt-4">
|
|
<h2 className="text-lg font-semibold mb-2">{c.name}</h2>
|
|
{c.regions?.map((r) => (
|
|
<details key={r.id} className="ml-4 mb-2">
|
|
<summary className="cursor-pointer text-sm font-medium text-muted-foreground hover:text-foreground">
|
|
{r.name} ({r.cities?.length || 0} ciudades)
|
|
</summary>
|
|
<div className="ml-4 mt-1 flex flex-wrap gap-1">
|
|
{r.cities?.map((city) => (
|
|
<span key={city.id} className="inline-block rounded bg-muted px-2 py-0.5 text-xs">
|
|
{city.name}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</details>
|
|
))}
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|