Files
kitebeachakyaka/app/admin/features/FeaturesClient.tsx
T
2026-06-09 15:15:40 +03:00

251 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState, useTransition } from "react";
import { Feature } from "@prisma/client";
import { Plus, Edit2, Trash2, X, Save } from "lucide-react";
import * as Icons from "lucide-react";
import { loadMockFeatures, addFeature, updateFeature, deleteFeature } from "@/app/actions/features";
export default function FeaturesClient({ features }: { features: Feature[] }) {
const [isModalOpen, setIsModalOpen] = useState(false);
const [editingFeature, setEditingFeature] = useState<Feature | null>(null);
const [isPending, startTransition] = useTransition();
const [error, setError] = useState<string | null>(null);
const openAddModal = () => {
setEditingFeature(null);
setError(null);
setIsModalOpen(true);
};
const openEditModal = (feature: Feature) => {
setEditingFeature(feature);
setError(null);
setIsModalOpen(true);
};
const closeModal = () => {
setIsModalOpen(false);
setEditingFeature(null);
setError(null);
};
const handleDelete = (id: number) => {
if (confirm("Bu özelliği silmek istediğinize emin misiniz?")) {
startTransition(async () => {
const result = await deleteFeature(id);
if (result.error) {
alert(result.error);
}
});
}
};
const handleSubmit = async (formData: FormData) => {
setError(null);
if (editingFeature) {
formData.append("id", editingFeature.id.toString());
const result = await updateFeature(null, formData);
if (result.error) {
setError(result.error);
} else {
closeModal();
}
} else {
const result = await addFeature(null, formData);
if (result.error) {
setError(result.error);
} else {
closeModal();
}
}
};
const renderIcon = (iconName: string) => {
const IconComponent = (Icons as any)[iconName];
if (!IconComponent) return <div className="w-5 h-5 bg-gray-200 rounded-full" />;
return <IconComponent className="w-5 h-5 text-accent" />;
};
return (
<>
<div className="flex justify-between items-center mb-10">
<h1 className="text-4xl font-black font-serif text-dark tracking-tight">Özellikler</h1>
<div className="flex items-center gap-3">
<form action={loadMockFeatures}>
<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>
{features.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 özellik 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">İkon</th>
<th className="px-8 py-5">Ana Başlık</th>
<th className="px-8 py-5">Alt Başlık</th>
<th className="px-8 py-5">Dil</th>
<th className="px-8 py-5">Sıra</th>
<th className="px-8 py-5 text-right">İşlemler</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-50">
{features.map(feat => (
<tr key={feat.id} className="hover:bg-gray-50/80 transition-colors group">
<td className="px-8 py-5">
{renderIcon(feat.icon)}
</td>
<td className="px-8 py-5 font-bold text-dark">{feat.label}</td>
<td className="px-8 py-5">{feat.sub}</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">{feat.lang}</span>
</td>
<td className="px-8 py-5 font-medium">{feat.order}</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(feat)}
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(feat.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">
{editingFeature ? "Özellik Düzenle" : "Yeni Özellik 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={editingFeature?.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">Ana Başlık</label>
<input
type="text"
name="label"
defaultValue={editingFeature?.label || ""}
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: Kitesurf Okulu"
/>
</div>
<div>
<label className="block text-xs font-bold text-gray-500 tracking-wider uppercase mb-2">Alt Başlık</label>
<input
type="text"
name="sub"
defaultValue={editingFeature?.sub || ""}
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: IKO Sertifikalı"
/>
</div>
<div>
<label className="block text-xs font-bold text-gray-500 tracking-wider uppercase mb-2">Sıra</label>
<input
type="number"
name="order"
defaultValue={editingFeature?.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 className="col-span-1 md:col-span-2">
<label className="block text-xs font-bold text-gray-500 tracking-wider uppercase mb-2">İkon Adı (Lucide React)</label>
<input
type="text"
name="icon"
defaultValue={editingFeature?.icon || "Wind"}
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: Wind, Anchor, Utensils, BedDouble, Sun"
/>
<p className="text-xs text-gray-400 mt-2">
Geçerli ikon isimleri: Wind, Anchor, Utensils, BedDouble, Sun, vb.
</p>
</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>
)}
</>
);
}