full project: admin panel, backend modules, docs
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user