Refactor modal implementation across admin components: Replace Card components with a reusable Modal component in ChallengeManagement, EventManagement, and UserManagement, enhancing UI consistency and maintainability. Update Modal to use React portals for improved rendering.
This commit is contained in:
@@ -9,7 +9,7 @@ import {
|
||||
adminCancelChallenge,
|
||||
reactivateChallenge,
|
||||
} from "@/actions/admin/challenges";
|
||||
import { Button, Card, Input, Textarea, Alert } from "@/components/ui";
|
||||
import { Button, Card, Input, Textarea, Alert, Modal, CloseButton } from "@/components/ui";
|
||||
import { Avatar } from "@/components/ui";
|
||||
|
||||
interface Challenge {
|
||||
@@ -417,23 +417,29 @@ export default function ChallengeManagement() {
|
||||
|
||||
{/* Modal de validation */}
|
||||
{selectedChallenge && (
|
||||
<div
|
||||
className="fixed inset-0 z-[200] flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm"
|
||||
onClick={() => {
|
||||
<Modal
|
||||
isOpen={!!selectedChallenge}
|
||||
onClose={() => {
|
||||
setSelectedChallenge(null);
|
||||
setWinnerId("");
|
||||
setAdminComment("");
|
||||
}}
|
||||
size="lg"
|
||||
>
|
||||
<Card
|
||||
variant="dark"
|
||||
className="max-w-2xl w-full max-h-[90vh] overflow-y-auto"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="p-6">
|
||||
<h2 className="text-2xl font-bold text-pixel-gold mb-4">
|
||||
<div className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-2xl font-bold text-pixel-gold">
|
||||
Désigner le gagnant
|
||||
</h2>
|
||||
<CloseButton
|
||||
onClick={() => {
|
||||
setSelectedChallenge(null);
|
||||
setWinnerId("");
|
||||
setAdminComment("");
|
||||
}}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-6">
|
||||
<h3 className="text-lg font-bold text-gray-300 mb-2">
|
||||
@@ -544,31 +550,37 @@ export default function ChallengeManagement() {
|
||||
Annuler
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
{/* Modal d'édition */}
|
||||
{editingChallenge && (
|
||||
<div
|
||||
className="fixed inset-0 z-[200] flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm"
|
||||
onClick={() => {
|
||||
<Modal
|
||||
isOpen={!!editingChallenge}
|
||||
onClose={() => {
|
||||
setEditingChallenge(null);
|
||||
setEditTitle("");
|
||||
setEditDescription("");
|
||||
setEditPointsReward(0);
|
||||
}}
|
||||
size="lg"
|
||||
>
|
||||
<Card
|
||||
variant="dark"
|
||||
className="max-w-2xl w-full max-h-[90vh] overflow-y-auto"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="p-6">
|
||||
<h2 className="text-2xl font-bold text-pixel-gold mb-4">
|
||||
<div className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-2xl font-bold text-pixel-gold">
|
||||
Modifier le défi
|
||||
</h2>
|
||||
<CloseButton
|
||||
onClick={() => {
|
||||
setEditingChallenge(null);
|
||||
setEditTitle("");
|
||||
setEditDescription("");
|
||||
setEditPointsReward(0);
|
||||
}}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<Input
|
||||
@@ -631,9 +643,8 @@ export default function ChallengeManagement() {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,15 @@
|
||||
import { useState, useEffect, useTransition } from "react";
|
||||
import { calculateEventStatus } from "@/lib/eventStatus";
|
||||
import { createEvent, updateEvent, deleteEvent } from "@/actions/admin/events";
|
||||
import { Input, Textarea, Button, Card, Badge } from "@/components/ui";
|
||||
import {
|
||||
Input,
|
||||
Textarea,
|
||||
Button,
|
||||
Card,
|
||||
Badge,
|
||||
Modal,
|
||||
CloseButton,
|
||||
} from "@/components/ui";
|
||||
|
||||
interface Event {
|
||||
id: string;
|
||||
@@ -221,116 +229,126 @@ export default function EventManagement() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Modal de création/édition */}
|
||||
{(isCreating || editingEvent) && (
|
||||
<Card variant="default" className="p-3 sm:p-4 mb-4">
|
||||
<h4 className="text-pixel-gold font-bold mb-4 text-base sm:text-lg break-words">
|
||||
{isCreating ? "Créer un événement" : "Modifier l'événement"}
|
||||
</h4>
|
||||
<div className="space-y-4">
|
||||
<Input
|
||||
type="date"
|
||||
label="Date"
|
||||
value={formData.date}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, date: e.target.value })
|
||||
}
|
||||
className="text-xs sm:text-sm px-3 py-2"
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
label="Nom"
|
||||
value={formData.name}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, name: e.target.value })
|
||||
}
|
||||
placeholder="Nom de l'événement"
|
||||
className="text-xs sm:text-sm px-3 py-2"
|
||||
/>
|
||||
<Textarea
|
||||
label="Description"
|
||||
value={formData.description}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, description: e.target.value })
|
||||
}
|
||||
placeholder="Description de l'événement"
|
||||
rows={4}
|
||||
className="text-xs sm:text-sm px-3 py-2"
|
||||
/>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs sm:text-sm text-gray-300 mb-1">
|
||||
Type
|
||||
</label>
|
||||
<select
|
||||
value={formData.type}
|
||||
<Modal
|
||||
isOpen={isCreating || !!editingEvent}
|
||||
onClose={handleCancel}
|
||||
size="lg"
|
||||
>
|
||||
<div className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h4 className="text-pixel-gold font-bold text-base sm:text-lg break-words">
|
||||
{isCreating ? "Créer un événement" : "Modifier l'événement"}
|
||||
</h4>
|
||||
<CloseButton onClick={handleCancel} size="lg" />
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<Input
|
||||
type="date"
|
||||
label="Date"
|
||||
value={formData.date}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, date: e.target.value })
|
||||
}
|
||||
className="text-xs sm:text-sm px-3 py-2"
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
label="Nom"
|
||||
value={formData.name}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, name: e.target.value })
|
||||
}
|
||||
placeholder="Nom de l'événement"
|
||||
className="text-xs sm:text-sm px-3 py-2"
|
||||
/>
|
||||
<Textarea
|
||||
label="Description"
|
||||
value={formData.description}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, description: e.target.value })
|
||||
}
|
||||
placeholder="Description de l'événement"
|
||||
rows={4}
|
||||
className="text-xs sm:text-sm px-3 py-2"
|
||||
/>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs sm:text-sm text-gray-300 mb-1">
|
||||
Type
|
||||
</label>
|
||||
<select
|
||||
value={formData.type}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
type: e.target.value as Event["type"],
|
||||
})
|
||||
}
|
||||
className="w-full px-3 py-2 bg-black/60 border border-pixel-gold/30 rounded text-white text-xs sm:text-sm"
|
||||
>
|
||||
{eventTypes.map((type) => (
|
||||
<option key={type} value={type}>
|
||||
{getEventTypeLabel(type)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||
<Input
|
||||
type="text"
|
||||
label="Salle"
|
||||
value={formData.room || ""}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, room: e.target.value })
|
||||
}
|
||||
placeholder="Ex: Nautilus"
|
||||
className="text-xs sm:text-sm px-3 py-2"
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
label="Heure"
|
||||
value={formData.time || ""}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, time: e.target.value })
|
||||
}
|
||||
placeholder="Ex: 11h-12h"
|
||||
className="text-xs sm:text-sm px-3 py-2"
|
||||
/>
|
||||
<Input
|
||||
type="number"
|
||||
label="Places max"
|
||||
value={formData.maxPlaces || ""}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
type: e.target.value as Event["type"],
|
||||
maxPlaces: e.target.value
|
||||
? parseInt(e.target.value)
|
||||
: undefined,
|
||||
})
|
||||
}
|
||||
className="w-full px-3 py-2 bg-black/60 border border-pixel-gold/30 rounded text-white text-xs sm:text-sm"
|
||||
placeholder="Ex: 25"
|
||||
className="text-xs sm:text-sm px-3 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row gap-2">
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
variant="success"
|
||||
size="md"
|
||||
disabled={saving}
|
||||
>
|
||||
{eventTypes.map((type) => (
|
||||
<option key={type} value={type}>
|
||||
{getEventTypeLabel(type)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{saving ? "Enregistrement..." : "Enregistrer"}
|
||||
</Button>
|
||||
<Button onClick={handleCancel} variant="secondary" size="md">
|
||||
Annuler
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||
<Input
|
||||
type="text"
|
||||
label="Salle"
|
||||
value={formData.room || ""}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, room: e.target.value })
|
||||
}
|
||||
placeholder="Ex: Nautilus"
|
||||
className="text-xs sm:text-sm px-3 py-2"
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
label="Heure"
|
||||
value={formData.time || ""}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, time: e.target.value })
|
||||
}
|
||||
placeholder="Ex: 11h-12h"
|
||||
className="text-xs sm:text-sm px-3 py-2"
|
||||
/>
|
||||
<Input
|
||||
type="number"
|
||||
label="Places max"
|
||||
value={formData.maxPlaces || ""}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
maxPlaces: e.target.value
|
||||
? parseInt(e.target.value)
|
||||
: undefined,
|
||||
})
|
||||
}
|
||||
placeholder="Ex: 25"
|
||||
className="text-xs sm:text-sm px-3 py-2"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row gap-2">
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
variant="success"
|
||||
size="md"
|
||||
disabled={saving}
|
||||
>
|
||||
{saving ? "Enregistrement..." : "Enregistrer"}
|
||||
</Button>
|
||||
<Button onClick={handleCancel} variant="secondary" size="md">
|
||||
Annuler
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
{events.length === 0 ? (
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user