feat: implement core platform features including business directory, admin dashboard, authentication, and API infrastructure
Some checks failed
Build and Push App / build (push) Failing after 16m2s
Some checks failed
Build and Push App / build (push) Failing after 16m2s
This commit is contained in:
159
admin/src/app/countries/page.tsx
Normal file
159
admin/src/app/countries/page.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect, useTransition } from 'react';
|
||||
import { getCountries, addCountry, deleteCountry, toggleCountryStatus } from '@/app/actions/countries';
|
||||
import { Globe, Plus, Trash2, MapPin, Search, Loader2, Flag } from 'lucide-react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
||||
export default function CountriesPage() {
|
||||
const [countries, setCountries] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isAdding, setIsAdding] = useState(false);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const loadData = async () => {
|
||||
const data = await getCountries();
|
||||
setCountries(data);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
const handleAdd = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
const name = formData.get('name') as string;
|
||||
const code = formData.get('code') as string;
|
||||
const flag = formData.get('flag') as string;
|
||||
|
||||
startTransition(async () => {
|
||||
const result = await addCountry({ name, code, flag });
|
||||
if (result.success) {
|
||||
toast.success("Pays ajouté avec succès");
|
||||
setIsAdding(false);
|
||||
loadData();
|
||||
} else {
|
||||
toast.error(result.error || "Erreur lors de l'ajout");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = async (id: string, name: string) => {
|
||||
if (!confirm(`Supprimer le pays "${name}" ? Cela pourrait affecter les entreprises liées.`)) return;
|
||||
|
||||
const result = await deleteCountry(id);
|
||||
if (result.success) {
|
||||
toast.success("Pays supprimé");
|
||||
loadData();
|
||||
} else {
|
||||
toast.error("Erreur lors de la suppression");
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleStatus = async (id: string, current: boolean) => {
|
||||
const result = await toggleCountryStatus(id, !current);
|
||||
if (result.success) {
|
||||
loadData();
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center p-20">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-indigo-500" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-8">
|
||||
<div className="flex justify-between items-end mb-8">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Gestion des Pays</h1>
|
||||
<p className="text-slate-400">Gérez les pays disponibles pour les entreprises et le filtrage du répertoire.</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setIsAdding(!isAdding)}
|
||||
className="bg-indigo-600 hover:bg-indigo-700 text-white px-6 py-2 rounded-xl font-bold transition-all shadow-lg flex items-center gap-2"
|
||||
>
|
||||
<Plus className="w-5 h-5" /> Ajouter un pays
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{isAdding && (
|
||||
<div className="bg-slate-900/50 border border-slate-700 rounded-2xl p-6 mb-8 animate-in fade-in slide-in-from-top-2">
|
||||
<h2 className="text-lg font-bold text-white mb-4">Nouveau Pays</h2>
|
||||
<form onSubmit={handleAdd} className="grid grid-cols-1 md:grid-cols-4 gap-4 items-end">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-slate-400">Nom du pays</label>
|
||||
<input name="name" required placeholder="Ex: Sénégal" className="w-full bg-slate-950 border border-slate-700 rounded-xl p-3 text-white focus:outline-none focus:border-indigo-500" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-slate-400">Code ISO (2 lettres)</label>
|
||||
<input name="code" required maxLength={2} placeholder="Ex: SN" className="w-full bg-slate-950 border border-slate-700 rounded-xl p-3 text-white focus:outline-none focus:border-indigo-500 uppercase" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-slate-400">Emoji Drapeau / URL Icone</label>
|
||||
<input name="flag" placeholder="Ex: 🇸🇳" className="w-full bg-slate-950 border border-slate-700 rounded-xl p-3 text-white focus:outline-none focus:border-indigo-500" />
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button disabled={isPending} type="submit" className="flex-1 bg-emerald-600 hover:bg-emerald-700 text-white p-3 rounded-xl font-bold transition-all disabled:opacity-50">
|
||||
{isPending ? 'Chargement...' : 'Enregistrer'}
|
||||
</button>
|
||||
<button type="button" onClick={() => setIsAdding(false)} className="bg-slate-800 hover:bg-slate-700 text-white p-3 rounded-xl font-bold transition-all">
|
||||
Annuler
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="bg-slate-900/50 border border-slate-700 rounded-2xl overflow-hidden">
|
||||
<table className="w-full text-left">
|
||||
<thead className="bg-slate-800/50 text-slate-400 uppercase text-xs font-bold tracking-wider">
|
||||
<tr>
|
||||
<th className="px-6 py-4">Pays</th>
|
||||
<th className="px-6 py-4">Code</th>
|
||||
<th className="px-6 py-4">Status</th>
|
||||
<th className="px-6 py-4 text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-800">
|
||||
{countries.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={4} className="px-6 py-10 text-center text-slate-500">Aucun pays configuré.</td>
|
||||
</tr>
|
||||
) : countries.map((country) => (
|
||||
<tr key={country.id} className="hover:bg-slate-800/30 transition-colors">
|
||||
<td className="px-6 py-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-2xl">{country.flag || <MapPin className="w-5 h-5 text-slate-600" />}</span>
|
||||
<span className="font-bold text-white">{country.name}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 font-mono text-indigo-400">{country.code}</td>
|
||||
<td className="px-6 py-4">
|
||||
<button
|
||||
onClick={() => handleToggleStatus(country.id, country.isActive)}
|
||||
className={`inline-flex items-center px-3 py-1 rounded-full text-xs font-bold ${
|
||||
country.isActive ? 'bg-emerald-500/10 text-emerald-500' : 'bg-slate-500/10 text-slate-500'
|
||||
}`}
|
||||
>
|
||||
{country.isActive ? 'Actif' : 'Inactif'}
|
||||
</button>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-right">
|
||||
<button onClick={() => handleDelete(country.id, country.name)} className="p-2 text-slate-500 hover:text-red-500 transition-colors">
|
||||
<Trash2 className="w-5 h-5" />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user