256 lines
12 KiB
TypeScript
256 lines
12 KiB
TypeScript
"use client";
|
||
|
||
import { useState, useTransition } from "react";
|
||
import { Activity } from "@prisma/client";
|
||
import { Plus, Edit2, Trash2, X, Save } from "lucide-react";
|
||
import { loadMockActivities, addActivity, updateActivity, deleteActivity } from "@/app/actions/activities";
|
||
|
||
export default function ActivitiesClient({ activities }: { activities: Activity[] }) {
|
||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||
const [editingActivity, setEditingActivity] = useState<Activity | null>(null);
|
||
const [isPending, startTransition] = useTransition();
|
||
const [error, setError] = useState<string | null>(null);
|
||
|
||
const openAddModal = () => {
|
||
setEditingActivity(null);
|
||
setError(null);
|
||
setIsModalOpen(true);
|
||
};
|
||
|
||
const openEditModal = (activity: Activity) => {
|
||
setEditingActivity(activity);
|
||
setError(null);
|
||
setIsModalOpen(true);
|
||
};
|
||
|
||
const closeModal = () => {
|
||
setIsModalOpen(false);
|
||
setEditingActivity(null);
|
||
setError(null);
|
||
};
|
||
|
||
const handleDelete = (id: number) => {
|
||
if (confirm("Bu aktiviteyi silmek istediğinize emin misiniz?")) {
|
||
startTransition(async () => {
|
||
const result = await deleteActivity(id);
|
||
if (result.error) {
|
||
alert(result.error);
|
||
}
|
||
});
|
||
}
|
||
};
|
||
|
||
const handleSubmit = async (formData: FormData) => {
|
||
setError(null);
|
||
if (editingActivity) {
|
||
formData.append("id", editingActivity.id.toString());
|
||
const result = await updateActivity(null, formData);
|
||
if (result.error) {
|
||
setError(result.error);
|
||
} else {
|
||
closeModal();
|
||
}
|
||
} else {
|
||
const result = await addActivity(null, formData);
|
||
if (result.error) {
|
||
setError(result.error);
|
||
} else {
|
||
closeModal();
|
||
}
|
||
}
|
||
};
|
||
|
||
return (
|
||
<>
|
||
<div className="flex justify-between items-center mb-10">
|
||
<h1 className="text-4xl font-black font-serif text-dark tracking-tight">Aktiviteler</h1>
|
||
<div className="flex items-center gap-3">
|
||
<form action={loadMockActivities}>
|
||
<button type="submit" className="bg-white text-dark border border-gray-200 px-6 py-2.5 rounded-xl font-bold tracking-wide hover:bg-gray-50 hover:shadow-lg transition-all text-sm flex items-center gap-2">
|
||
<span className="w-2 h-2 rounded-full bg-accent animate-pulse" />
|
||
Örnek Verileri Yükle
|
||
</button>
|
||
</form>
|
||
<button
|
||
onClick={openAddModal}
|
||
className="bg-dark text-white px-6 py-2.5 rounded-xl font-bold tracking-wide hover:bg-dark/90 hover:shadow-lg transition-all text-sm flex items-center gap-2"
|
||
>
|
||
<Plus className="w-4 h-4" />
|
||
Yeni Ekle
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{activities.length === 0 ? (
|
||
<div className="bg-white rounded-3xl shadow-xl border border-gray-100 p-12 text-center text-gray-500 font-medium">
|
||
Henüz aktivite eklenmemiş. Sağ üstten yeni ekleyebilir veya örnek verileri yükleyebilirsiniz.
|
||
</div>
|
||
) : (
|
||
<div className="bg-white rounded-3xl shadow-xl border border-gray-100 overflow-hidden animate-fade-up">
|
||
<table className="w-full text-left text-sm text-gray-600">
|
||
<thead className="bg-gray-50/50 border-b border-gray-100 text-gray-500 uppercase tracking-widest text-[10px] font-bold">
|
||
<tr>
|
||
<th className="px-8 py-5">Başlık</th>
|
||
<th className="px-8 py-5">Dil</th>
|
||
<th className="px-8 py-5">Fiyat</th>
|
||
<th className="px-8 py-5 text-right">İşlemler</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody className="divide-y divide-gray-50">
|
||
{activities.map(act => (
|
||
<tr key={act.id} className="hover:bg-gray-50/80 transition-colors group">
|
||
<td className="px-8 py-5 font-bold text-dark">{act.title}</td>
|
||
<td className="px-8 py-5">
|
||
<span className="bg-gray-100 text-gray-600 px-3 py-1 rounded-full text-[10px] uppercase font-black tracking-widest border border-gray-200">{act.lang}</span>
|
||
</td>
|
||
<td className="px-8 py-5 font-medium">{act.price}</td>
|
||
<td className="px-8 py-5 text-right">
|
||
<div className="flex items-center justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||
<button
|
||
onClick={() => openEditModal(act)}
|
||
className="p-2 bg-primary/10 text-primary rounded-lg hover:bg-primary/20 transition-colors"
|
||
title="Düzenle"
|
||
>
|
||
<Edit2 className="w-4 h-4" />
|
||
</button>
|
||
<button
|
||
onClick={() => handleDelete(act.id)}
|
||
disabled={isPending}
|
||
className="p-2 bg-red-500/10 text-red-500 rounded-lg hover:bg-red-500/20 transition-colors disabled:opacity-50"
|
||
title="Sil"
|
||
>
|
||
<Trash2 className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
)}
|
||
|
||
{/* Modal */}
|
||
{isModalOpen && (
|
||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||
<div className="absolute inset-0 bg-dark/60 backdrop-blur-sm" onClick={closeModal} />
|
||
|
||
<div className="bg-white rounded-3xl shadow-2xl w-full max-w-2xl relative z-10 animate-fade-up overflow-hidden max-h-[90vh] flex flex-col">
|
||
<div className="flex justify-between items-center p-6 md:p-8 border-b border-gray-100 bg-gray-50/50">
|
||
<h2 className="text-2xl font-bold font-serif text-dark">
|
||
{editingActivity ? "Aktivite Düzenle" : "Yeni Aktivite Ekle"}
|
||
</h2>
|
||
<button onClick={closeModal} className="p-2 text-gray-400 hover:text-dark hover:bg-gray-200 rounded-full transition-colors">
|
||
<X className="w-5 h-5" />
|
||
</button>
|
||
</div>
|
||
|
||
<div className="p-6 md:p-8 overflow-y-auto">
|
||
<form action={handleSubmit} className="space-y-6">
|
||
|
||
{error && (
|
||
<div className="p-4 bg-red-50 border border-red-200 rounded-xl text-red-600 text-sm">
|
||
{error}
|
||
</div>
|
||
)}
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<div>
|
||
<label className="block text-xs font-bold text-gray-500 tracking-wider uppercase mb-2">Dil</label>
|
||
<select
|
||
name="lang"
|
||
defaultValue={editingActivity?.lang || "tr"}
|
||
className="w-full px-5 py-3.5 bg-gray-50 border border-gray-200 text-dark rounded-xl focus:ring-4 focus:ring-primary/20 focus:border-primary outline-none transition-all hover:border-gray-300"
|
||
>
|
||
<option value="tr">Türkçe</option>
|
||
<option value="en">English</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-xs font-bold text-gray-500 tracking-wider uppercase mb-2">Başlık</label>
|
||
<input
|
||
type="text"
|
||
name="title"
|
||
defaultValue={editingActivity?.title || ""}
|
||
required
|
||
className="w-full px-5 py-3.5 bg-gray-50 border border-gray-200 text-dark rounded-xl focus:ring-4 focus:ring-primary/20 focus:border-primary outline-none transition-all hover:border-gray-300"
|
||
/>
|
||
</div>
|
||
|
||
<div className="col-span-1 md:col-span-2">
|
||
<label className="block text-xs font-bold text-gray-500 tracking-wider uppercase mb-2">Açıklama</label>
|
||
<textarea
|
||
name="description"
|
||
defaultValue={editingActivity?.description || ""}
|
||
required
|
||
rows={3}
|
||
className="w-full px-5 py-3.5 bg-gray-50 border border-gray-200 text-dark rounded-xl focus:ring-4 focus:ring-primary/20 focus:border-primary outline-none transition-all hover:border-gray-300 resize-none"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-xs font-bold text-gray-500 tracking-wider uppercase mb-2">Fiyat (Metin)</label>
|
||
<input
|
||
type="text"
|
||
name="price"
|
||
defaultValue={editingActivity?.price || ""}
|
||
required
|
||
className="w-full px-5 py-3.5 bg-gray-50 border border-gray-200 text-dark rounded-xl focus:ring-4 focus:ring-primary/20 focus:border-primary outline-none transition-all hover:border-gray-300"
|
||
placeholder="Örn: ₺2.000/Saat"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-xs font-bold text-gray-500 tracking-wider uppercase mb-2">Rozet (Opsiyonel)</label>
|
||
<input
|
||
type="text"
|
||
name="badge"
|
||
defaultValue={editingActivity?.badge || ""}
|
||
className="w-full px-5 py-3.5 bg-gray-50 border border-gray-200 text-dark rounded-xl focus:ring-4 focus:ring-primary/20 focus:border-primary outline-none transition-all hover:border-gray-300"
|
||
placeholder="Örn: Çok Satan"
|
||
/>
|
||
</div>
|
||
|
||
<div className="col-span-1 md:col-span-2">
|
||
<label className="block text-xs font-bold text-gray-500 tracking-wider uppercase mb-2">Görsel URL</label>
|
||
<input
|
||
type="url"
|
||
name="image"
|
||
defaultValue={editingActivity?.image || ""}
|
||
required
|
||
className="w-full px-5 py-3.5 bg-gray-50 border border-gray-200 text-dark rounded-xl focus:ring-4 focus:ring-primary/20 focus:border-primary outline-none transition-all hover:border-gray-300"
|
||
placeholder="https://..."
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-xs font-bold text-gray-500 tracking-wider uppercase mb-2">Sıra (Görünüm Sırası)</label>
|
||
<input
|
||
type="number"
|
||
name="order"
|
||
defaultValue={editingActivity?.order || 0}
|
||
className="w-full px-5 py-3.5 bg-gray-50 border border-gray-200 text-dark rounded-xl focus:ring-4 focus:ring-primary/20 focus:border-primary outline-none transition-all hover:border-gray-300"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex justify-end pt-6 mt-6 border-t border-gray-100">
|
||
<button
|
||
type="submit"
|
||
className="flex items-center gap-2 bg-accent text-white font-bold tracking-widest uppercase py-4 px-8 rounded-xl hover:bg-accent/90 transition-all shadow-lg shadow-accent/20 text-sm"
|
||
>
|
||
<Save className="w-4 h-4" />
|
||
Kaydet
|
||
</button>
|
||
</div>
|
||
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</>
|
||
);
|
||
}
|