feat: make category sections collapsible in products page
This commit is contained in:
@@ -0,0 +1,108 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { Pencil, Image as ImageIcon, ChevronDown } from 'lucide-react'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import DeleteButton from './DeleteButton'
|
||||||
|
|
||||||
|
type Product = {
|
||||||
|
id: number
|
||||||
|
name_tr: string
|
||||||
|
price: any
|
||||||
|
status: number | null
|
||||||
|
image_url: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
type Category = {
|
||||||
|
id: number
|
||||||
|
name_tr: string
|
||||||
|
products: Product[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CategoryAccordion({ category, defaultOpen = false }: { category: Category, defaultOpen?: boolean }) {
|
||||||
|
const [isOpen, setIsOpen] = useState(defaultOpen)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-white rounded-2xl shadow-[0_2px_10px_-3px_rgba(6,81,237,0.1)] border border-slate-100 overflow-hidden">
|
||||||
|
<button
|
||||||
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
|
className="w-full bg-slate-50/50 px-6 py-5 flex justify-between items-center hover:bg-slate-100/50 transition-colors cursor-pointer"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<ChevronDown
|
||||||
|
size={20}
|
||||||
|
className={`text-slate-400 transition-transform duration-300 ${isOpen ? 'rotate-180' : ''}`}
|
||||||
|
/>
|
||||||
|
<h2 className="text-lg font-bold text-slate-800 tracking-tight">{category.name_tr}</h2>
|
||||||
|
</div>
|
||||||
|
<span className="bg-white px-3 py-1 rounded-full text-xs font-semibold text-slate-500 border border-slate-200 shadow-sm">
|
||||||
|
{category.products.length} ürün
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={`transition-all duration-300 ease-in-out ${isOpen ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0'} grid`}
|
||||||
|
>
|
||||||
|
<div className="overflow-hidden">
|
||||||
|
{category.products.length > 0 ? (
|
||||||
|
<div className="overflow-x-auto border-t border-slate-100">
|
||||||
|
<table className="w-full text-left border-collapse">
|
||||||
|
<thead>
|
||||||
|
<tr className="border-b border-slate-100 text-xs uppercase tracking-wider font-semibold text-slate-500">
|
||||||
|
<th className="py-4 px-6 w-16">Resim</th>
|
||||||
|
<th className="py-4 px-6">İsim</th>
|
||||||
|
<th className="py-4 px-6">Fiyat</th>
|
||||||
|
<th className="py-4 px-6 text-center">Durum</th>
|
||||||
|
<th className="py-4 px-6 text-right">İşlemler</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-slate-100/80">
|
||||||
|
{category.products.map((product) => (
|
||||||
|
<tr key={product.id} className="hover:bg-slate-50/50 transition-colors group">
|
||||||
|
<td className="py-4 px-6">
|
||||||
|
{product.image_url ? (
|
||||||
|
<div className="relative w-12 h-12 rounded-xl overflow-hidden shadow-sm border border-slate-200 group-hover:scale-105 transition-transform">
|
||||||
|
<Image
|
||||||
|
src={product.image_url}
|
||||||
|
alt={product.name_tr}
|
||||||
|
fill
|
||||||
|
className="object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="w-12 h-12 rounded-xl bg-slate-100 flex items-center justify-center text-slate-400 border border-slate-200 shadow-sm">
|
||||||
|
<ImageIcon size={20} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td className="py-4 px-6 font-semibold text-slate-800">{product.name_tr}</td>
|
||||||
|
<td className="py-4 px-6 font-medium text-slate-600">₺{product.price.toString()}</td>
|
||||||
|
<td className="py-4 px-6 text-center">
|
||||||
|
<span className={`inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-semibold ${product.status === 1 ? 'bg-emerald-50 text-emerald-600' : 'bg-slate-100 text-slate-500'}`}>
|
||||||
|
{product.status === 1 ? 'Aktif' : 'Pasif'}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="py-4 px-6 text-right">
|
||||||
|
<div className="flex justify-end gap-1 opacity-100 sm:opacity-0 group-hover:opacity-100 transition-opacity">
|
||||||
|
<Link href={`/admin/products/${product.id}/edit`} className="p-2 text-blue-600 hover:bg-blue-50 rounded-lg transition-colors" title="Düzenle">
|
||||||
|
<Pencil size={18} />
|
||||||
|
</Link>
|
||||||
|
<DeleteButton id={product.id} />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="py-10 px-6 text-center text-slate-500 text-sm border-t border-slate-100">
|
||||||
|
Bu kategoride henüz ürün bulunmuyor.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import prisma from '@/lib/prisma'
|
import prisma from '@/lib/prisma'
|
||||||
import { Pencil, Plus, Image as ImageIcon } from 'lucide-react'
|
import { Plus } from 'lucide-react'
|
||||||
import Image from 'next/image'
|
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import DeleteButton from './DeleteButton'
|
import CategoryAccordion from './CategoryAccordion'
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: 'Ürün Yönetimi - Admin',
|
title: 'Ürün Yönetimi - Admin',
|
||||||
@@ -30,73 +29,9 @@ export default async function ProductsPage() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-8">
|
<div className="space-y-4">
|
||||||
{categories.map((category) => (
|
{categories.map((category) => (
|
||||||
<div key={category.id} className="bg-white rounded-2xl shadow-[0_2px_10px_-3px_rgba(6,81,237,0.1)] border border-slate-100 overflow-hidden">
|
<CategoryAccordion key={category.id} category={category} defaultOpen={false} />
|
||||||
<div className="bg-slate-50/50 px-6 py-5 border-b border-slate-100 flex justify-between items-center">
|
|
||||||
<h2 className="text-lg font-bold text-slate-800 tracking-tight">{category.name_tr}</h2>
|
|
||||||
<span className="bg-white px-3 py-1 rounded-full text-xs font-semibold text-slate-500 border border-slate-200 shadow-sm">
|
|
||||||
{category.products.length} ürün
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{category.products.length > 0 ? (
|
|
||||||
<div className="overflow-x-auto">
|
|
||||||
<table className="w-full text-left border-collapse">
|
|
||||||
<thead>
|
|
||||||
<tr className="border-b border-slate-100 text-xs uppercase tracking-wider font-semibold text-slate-500">
|
|
||||||
<th className="py-4 px-6 w-16">Resim</th>
|
|
||||||
<th className="py-4 px-6">İsim</th>
|
|
||||||
<th className="py-4 px-6">Fiyat</th>
|
|
||||||
<th className="py-4 px-6 text-center">Durum</th>
|
|
||||||
<th className="py-4 px-6 text-right">İşlemler</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y divide-slate-100/80">
|
|
||||||
{category.products.map((product) => (
|
|
||||||
<tr key={product.id} className="hover:bg-slate-50/50 transition-colors group">
|
|
||||||
<td className="py-4 px-6">
|
|
||||||
{product.image_url ? (
|
|
||||||
<div className="relative w-12 h-12 rounded-xl overflow-hidden shadow-sm border border-slate-200 group-hover:scale-105 transition-transform">
|
|
||||||
<Image
|
|
||||||
src={product.image_url}
|
|
||||||
alt={product.name_tr}
|
|
||||||
fill
|
|
||||||
className="object-cover"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="w-12 h-12 rounded-xl bg-slate-100 flex items-center justify-center text-slate-400 border border-slate-200 shadow-sm">
|
|
||||||
<ImageIcon size={20} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
<td className="py-4 px-6 font-semibold text-slate-800">{product.name_tr}</td>
|
|
||||||
<td className="py-4 px-6 font-medium text-slate-600">₺{product.price.toString()}</td>
|
|
||||||
<td className="py-4 px-6 text-center">
|
|
||||||
<span className={`inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-semibold ${product.status === 1 ? 'bg-emerald-50 text-emerald-600' : 'bg-slate-100 text-slate-500'}`}>
|
|
||||||
{product.status === 1 ? 'Aktif' : 'Pasif'}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td className="py-4 px-6 text-right">
|
|
||||||
<div className="flex justify-end gap-1 opacity-100 sm:opacity-0 group-hover:opacity-100 transition-opacity">
|
|
||||||
<Link href={`/admin/products/${product.id}/edit`} className="p-2 text-blue-600 hover:bg-blue-50 rounded-lg transition-colors" title="Düzenle">
|
|
||||||
<Pencil size={18} />
|
|
||||||
</Link>
|
|
||||||
<DeleteButton id={product.id} />
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="py-10 px-6 text-center text-slate-500 text-sm">
|
|
||||||
Bu kategoride henüz ürün bulunmuyor.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user