get in touch

Hello There!

Lorem ipsum dolor sit amet consectetur adipisicing elit. Animi natus quasi sunt eum ducimus.

Information

Get Started
import { useState, useEffect } from ‘react’;
import { Card, CardContent, CardHeader, CardTitle } from ‘@/components/ui/card’;
import { Button } from ‘@/components/ui/button’;
import { Input } from ‘@/components/ui/input’;
import { Textarea } from ‘@/components/ui/textarea’;
import { Label } from ‘@/components/ui/label’;
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from ‘@/components/ui/table’;
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from ‘@/components/ui/dropdown-menu’;
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from ‘@/components/ui/dialog’;
import { Badge } from ‘@/components/ui/badge’;
import {
Plus, Search, MoreHorizontal, Eye, Edit, Trash2,
Folder, Tag
} from ‘lucide-react’;
import DashboardLayout from ‘@/components/dashboard/DashboardLayout’;
import { categoryService, type Category } from ‘@/services’;
import { useToast } from ‘@/hooks/use-toast’;
const CategoriesPage = () => {
const { toast } =useToast();
const [categories, setCategories] =useState<Category[]>([]);
const [isLoading, setIsLoading] =useState(true);
const [searchQuery, setSearchQuery] =useState( »);
const [isDialogOpen, setIsDialogOpen] =useState(false);
const [isSubmitting, setIsSubmitting] =useState(false);
// Form state
const [formData, setFormData] =useState({
title: »,
description: »,
});
// Charger les catégories depuis l’API
constfetchCategories=async () => {
try {
setIsLoading(true);
constresponse=awaitcategoryService.getAll();
console.log(‘Categories response:’, response);
if (response.success) {
setCategories(response.data|| []);
}
} catch (error: any) {
console.error(‘Erreur lors du chargement des catégories:’, error);
toast({
title: »Erreur »,
description: »Impossible de charger les catégories »,
variant: »destructive »,
});
} finally {
setIsLoading(false);
}
};
useEffect(() => {
fetchCategories();
}, []);
// Filtrer les catégories
constfilteredCategories=categories.filter(category=>
category.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
(category.description && category.description.toLowerCase().includes(searchQuery.toLowerCase()))
);
// Soumettre le formulaire
consthandleSubmit=async (e:React.FormEvent) => {
e.preventDefault();
setIsSubmitting(true);
try {
constresponse=awaitcategoryService.create({
title:formData.title,
description:formData.description||null,
});
console.log(‘Create category response:’, response);
if (response.success) {
toast({
title: »Succès »,
description:response.message|| »Catégorie créée avec succès »,
});
// Réinitialiser le formulaire
setFormData({ title: », description: » });
setIsDialogOpen(false);
// Recharger les catégories
fetchCategories();
}
} catch (error: any) {
console.error(‘Erreur lors de la création:’, error);
leterrorMessage= »Impossible de créer la catégorie »;
if (error.response?.data?.message) {
errorMessage=error.response.data.message;
} else if (error.response?.data?.errors) {
consterrors=error.response.data.errors;
errorMessage=Object.values(errors).flat().join(‘, ‘);
}
toast({
title: »Erreur »,
description:errorMessage,
variant: »destructive »,
});
} finally {
setIsSubmitting(false);
}
};
// Supprimer une catégorie
consthandleDelete=async (id:number) => {
if (!confirm(‘Êtes-vous sûr de vouloir supprimer cette catégorie ?’)) {
return;
}
try {
constresponse=awaitcategoryService.delete(id);
if (response.success) {
toast({
title: »Succès »,
description:response.message|| »Catégorie supprimée »,
});
fetchCategories();
}
} catch (error: any) {
console.error(‘Erreur lors de la suppression:’, error);
toast({
title: »Erreur »,
description:error.response?.data?.message|| »Impossible de supprimer la catégorie »,
variant: »destructive »,
});
}
};
conststats= {
total:categories.length,
};
return (
<DashboardLayouttitle= »Catégories »>
<divclassName= »space-y-6″>
{/* Stats */}
<divclassName= »grid grid-cols-2 md:grid-cols-4 gap-4″>
<Card>
<CardContentclassName= »p-4″>
<divclassName= »flex items-center gap-3″>
<divclassName= »w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center »>
<TagclassName= »w-5 h-5 text-primary »/>
</div>
<div>
<pclassName= »text-2xl font-bold »>{stats.total}</p>
<pclassName= »text-sm text-muted-foreground »>Total</p>
</div>
</div>
</CardContent>
</Card>
</div>
{/* Filters and Actions */}
<Card>
<CardHeaderclassName= »pb-4″>
<divclassName= »flex flex-col md:flex-row md:items-center justify-between gap-4″>
<CardTitle>Liste des catégories</CardTitle>
{/* Modal pour ajouter une catégorie */}
<Dialogopen={isDialogOpen}onOpenChange={setIsDialogOpen}>
<DialogTriggerasChild>
<ButtonclassName= »gap-2″>
<PlusclassName= »w-4 h-4″/>
Nouvelle catégorie
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Créer une nouvelle catégorie</DialogTitle>
<DialogDescription>
Ajoutez une catégorie pour organiser vos contenus.
</DialogDescription>
</DialogHeader>
<formonSubmit={handleSubmit}>
<divclassName= »grid gap-4 py-4″>
<divclassName= »grid gap-2″>
<LabelhtmlFor= »title »>Titre *</Label>
<Input
id= »title »
placeholder= »Nom de la catégorie »
value={formData.title}
onChange={(e) =>setFormData({ …formData, title:e.target.value })}
required
/>
</div>
<divclassName= »grid gap-2″>
<LabelhtmlFor= »description »>Description</Label>
<Textarea
id= »description »
placeholder= »Description de la catégorie (optionnel) »
value={formData.description}
onChange={(e) =>setFormData({ …formData, description:e.target.value })}
rows={3}
/>
</div>
</div>
<DialogFooter>
<Button
type= »button »
variant= »outline »
onClick={() =>setIsDialogOpen(false)}
>
Annuler
</Button>
<Button
type= »submit »
disabled={isSubmitting || !formData.title.trim()}
>
{isSubmitting ? ‘Création…’ : ‘Créer la catégorie’}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</div>
</CardHeader>
<CardContent>
{/* Search */}
<divclassName= »flex flex-col md:flex-row gap-4 mb-6″>
<divclassName= »relative flex-1″>
<SearchclassName= »absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground »/>
<Input
placeholder= »Rechercher une catégorie… »
value={searchQuery}
onChange={(e) =>setSearchQuery(e.target.value)}
className= »pl-10″
/>
</div>
</div>
{/* Table */}
{isLoading ? (
<divclassName= »flex items-center justify-center py-12″>
<divclassName= »w-8 h-8 border-4 border-primary border-t-transparent rounded-full animate-spin »></div>
</div>
) : filteredCategories.length === 0 ? (
<divclassName= »text-center py-12″>
<FolderclassName= »w-12 h-12 mx-auto text-muted-foreground mb-4″/>
<pclassName= »text-muted-foreground »>
{searchQuery ? ‘Aucune catégorie trouvée’ : ‘Aucune catégorie disponible’}
</p>
</div>
) : (
<divclassName= »border rounded-lg »>
<Table>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead>Titre</TableHead>
<TableHead>Description</TableHead>
<TableHead>Date de création</TableHead>
<TableHeadclassName= »text-right »>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredCategories.map((category) => (
<TableRowkey={category.id}>
<TableCellclassName= »font-medium »>{category.id}</TableCell>
<TableCell>
<divclassName= »flex items-center gap-2″>
<FolderclassName= »w-4 h-4 text-primary »/>
<spanclassName= »font-medium »>{category.title}</span>
</div>
</TableCell>
<TableCellclassName= »text-muted-foreground »>
{category.description || ‘-‘}
</TableCell>
<TableCell>
{category.created_at
? new Date(category.created_at).toLocaleDateString(‘fr-FR’)
: ‘-‘
}
</TableCell>
<TableCellclassName= »text-right »>
<DropdownMenu>
<DropdownMenuTriggerasChild>
<Buttonvariant= »ghost »className= »h-8 w-8 p-0″>
<MoreHorizontalclassName= »h-4 w-4″/>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContentalign= »end »>
<DropdownMenuItem>
<EyeclassName= »mr-2 h-4 w-4″/>
Voir
</DropdownMenuItem>
<DropdownMenuItem>
<EditclassName= »mr-2 h-4 w-4″/>
Modifier
</DropdownMenuItem>
<DropdownMenuItem
className= »text-red-600″
onClick={() =>handleDelete(category.id)}
>
<Trash2className= »mr-2 h-4 w-4″/>
Supprimer
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
</CardContent>
</Card>
</div>
</DashboardLayout>
);
};
export default CategoriesPage;

Caysurivoyages est une agence de voyages basée en Guyane française, spécialisée dans l’organisation de séjours, circuits et croisières à destination des Caraïbes, de l’Amérique du Sud et de destinations internationales.

Contactez-nous

Pour toutes questions

0694.35.30.11

Adresse Email

caysurivoyages@gmail.com

©2026, Caysurivoyages. Tous les droits sont réservés.