first commit
This commit is contained in:
259
components/mana-tracker/ExcursionBooking.tsx
Normal file
259
components/mana-tracker/ExcursionBooking.tsx
Normal file
@ -0,0 +1,259 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { Calendar, Users, CheckCircle } from "lucide-react";
|
||||
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Excursion } from "@/app/api/excursions/route";
|
||||
|
||||
export default function ExcursionBooking() {
|
||||
const [excursions, setExcursions] = useState<Excursion[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selectedExcursion, setSelectedExcursion] = useState<Excursion | null>(null);
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
date: "",
|
||||
participants: 1,
|
||||
});
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [success, setSuccess] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchExcursions = async () => {
|
||||
try {
|
||||
const response = await fetch("/api/excursions");
|
||||
const data = await response.json();
|
||||
setExcursions(data);
|
||||
} catch (error) {
|
||||
console.error("Erreur lors du chargement des excursions:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchExcursions();
|
||||
}, []);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!selectedExcursion) return;
|
||||
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const response = await fetch("/api/excursions", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
excursionId: selectedExcursion.id,
|
||||
...formData,
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
setSuccess(true);
|
||||
setFormData({ name: "", email: "", phone: "", date: "", participants: 1 });
|
||||
setTimeout(() => {
|
||||
setSuccess(false);
|
||||
setSelectedExcursion(null);
|
||||
}, 3000);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la réservation:", error);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getExcursionTypeLabel = (type: string) => {
|
||||
switch (type) {
|
||||
case "tour-lagon":
|
||||
return "Tour Lagon";
|
||||
case "plongee":
|
||||
return "Plongée";
|
||||
case "4x4":
|
||||
return "4x4";
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<div className="animate-pulse">Chargement des excursions...</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (success) {
|
||||
return (
|
||||
<Card className="bg-secondary">
|
||||
<CardContent className="p-6 text-center">
|
||||
<CheckCircle className="h-12 w-12 text-primary mx-auto mb-4" />
|
||||
<h3 className="text-xl font-semibold text-primary mb-2">
|
||||
Réservation confirmée !
|
||||
</h3>
|
||||
<p className="text-gray-700">
|
||||
Votre demande de réservation a été enregistrée. Nous vous contacterons bientôt.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (selectedExcursion) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Réserver : {selectedExcursion.name}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Nom complet
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
required
|
||||
value={formData.email}
|
||||
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Téléphone
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
required
|
||||
value={formData.phone}
|
||||
onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Date
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
required
|
||||
min={new Date().toISOString().split("T")[0]}
|
||||
value={formData.date}
|
||||
onChange={(e) => setFormData({ ...formData, date: e.target.value })}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Participants
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max="10"
|
||||
required
|
||||
value={formData.participants}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, participants: parseInt(e.target.value) })
|
||||
}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-secondary rounded-xl p-4">
|
||||
<p className="text-sm text-gray-600 mb-1">Total</p>
|
||||
<p className="text-2xl font-bold text-primary">
|
||||
{selectedExcursion.price * formData.participants} XPF
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setSelectedExcursion(null)}
|
||||
className="flex-1"
|
||||
>
|
||||
Annuler
|
||||
</Button>
|
||||
<Button type="submit" disabled={submitting} className="flex-1">
|
||||
{submitting ? "Envoi..." : "Réserver"}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Réservation d'excursions</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{excursions.map((excursion) => (
|
||||
<div
|
||||
key={excursion.id}
|
||||
className="border border-gray-200 rounded-xl p-4 hover:border-primary transition-colors"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<div className="flex-1">
|
||||
<h3 className="font-semibold text-lg text-primary mb-1">
|
||||
{excursion.name}
|
||||
</h3>
|
||||
<span className="inline-block px-3 py-1 bg-secondary text-primary text-xs font-medium rounded-lg mb-2">
|
||||
{getExcursionTypeLabel(excursion.type)}
|
||||
</span>
|
||||
<p className="text-sm text-gray-600 mb-2">{excursion.description}</p>
|
||||
<div className="flex items-center gap-4 text-xs text-gray-500">
|
||||
<span className="flex items-center gap-1">
|
||||
<Calendar className="h-4 w-4" />
|
||||
{excursion.duration}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right ml-4">
|
||||
<p className="text-2xl font-bold text-primary">
|
||||
{excursion.price.toLocaleString()} XPF
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">par personne</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => setSelectedExcursion(excursion)}
|
||||
disabled={!excursion.available}
|
||||
className="w-full mt-3"
|
||||
variant={excursion.available ? "default" : "outline"}
|
||||
>
|
||||
{excursion.available ? "Réserver" : "Indisponible"}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user