This commit is contained in:
2026-06-09 15:15:40 +03:00
parent f6b4acb760
commit d908d83ca5
58 changed files with 4320 additions and 419 deletions
+250
View File
@@ -0,0 +1,250 @@
"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>
)}
</>
);
}