Files
goGin/frontend/app/admin/posts/page.tsx
Beyhan Oğur 2a5b661443 first commit
2026-04-26 21:46:42 +03:00

239 lines
8.3 KiB
TypeScript
Raw Permalink 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 { useEffect, useState, useCallback } from "react"
import { useSession } from "next-auth/react"
import { DataTable } from "@/components/ui/data-table"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Plus } from "lucide-react"
import { postService } from "@/services/postService"
import { Post } from "@/types/post"
import { PostDialog } from "./_components/post-dialog"
import { getPostColumns } from "./_components/columns"
import { toast } from "sonner"
import Swal from "sweetalert2"
import withReactContent from "sweetalert2-react-content"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const MySwal = withReactContent(Swal)
export default function PostsPage() {
const { data: session } = useSession()
const [posts, setPosts] = useState<Post[]>([])
const [total, setTotal] = useState(0)
const [page, setPage] = useState(1)
const [perPage] = useState(10)
const [search, setSearch] = useState("")
const [statusFilter, setStatusFilter] = useState("with") // "with" | "only" (backend defaults to active if not with/only)
const [dialogOpen, setDialogOpen] = useState(false)
const [deletedIds, setDeletedIds] = useState<number[]>([])
const [selectedPost, setSelectedPost] = useState<Post | null>(null)
const fetchPosts = useCallback(async () => {
try {
const res = await postService.getPosts(page, perPage, search, statusFilter)
// Liste verisini al
const baseItems = res.items || []
// images alanı boş olanlar için, detay endpoint'inden gerçek images değerini çek
const itemsWithImages = await Promise.all(
baseItems.map(async (p) => {
if (p.images && p.images.trim() !== "") {
return p
}
const id = p.id || p.ID
if (!id) {
return p
}
try {
const detail = await postService.getPost(id)
return {
...p,
images: detail.data.images,
}
} catch {
return p
}
})
)
setPosts(itemsWithImages)
setTotal(res.total)
// Silinmiş post ID'lerini ayrıca takip et:
if (statusFilter === "only") {
const ids = itemsWithImages
.map(p => p.id || p.ID)
.filter((id): id is number => typeof id === "number")
setDeletedIds(ids)
} else if (statusFilter === "with") {
// 'with' görünümünde, silinmişleri ayrı bir çağrı ile çekelim
try {
const deletedRes = await postService.getPosts(1, 200, search, "only")
const ids = (deletedRes.items || [])
.map(p => p.id || p.ID)
.filter((id): id is number => typeof id === "number")
setDeletedIds(ids)
} catch (e) {
console.error("Silinmiş yazılar alınamadı:", e)
setDeletedIds([])
}
} else {
// Sadece aktif filtresinde silinmiş saymayalım
setDeletedIds([])
}
} catch (error) {
toast.error("Yazılar yüklenirken hata oluştu")
console.error(error)
}
}, [page, perPage, search, statusFilter])
useEffect(() => {
if (session) {
// eslint-disable-next-line react-hooks/set-state-in-effect
fetchPosts()
}
}, [session, fetchPosts])
const handleDelete = async (id: number) => {
const result = await MySwal.fire({
title: "Emin misiniz?",
text: "Bu yazıyı silmek istediğinize emin misiniz?",
icon: "warning",
showCancelButton: true,
confirmButtonText: "Evet, Sil",
cancelButtonText: "İptal",
customClass: {
popup: "dark:bg-gray-800 dark:text-white",
title: "dark:text-white",
},
})
if (result.isConfirmed) {
try {
await postService.deletePost(id)
toast.success("Yazı başarıyla silindi")
fetchPosts()
} catch (error) {
toast.error("Silme işlemi başarısız oldu")
console.error(error)
}
}
}
const handleRestore = async (id: number) => {
const result = await MySwal.fire({
title: "Geri Yükle?",
text: "Bu yazıyı geri yüklemek istediğinize emin misiniz?",
icon: "question",
showCancelButton: true,
confirmButtonText: "Evet, Geri Yükle",
cancelButtonText: "İptal",
customClass: {
popup: "dark:bg-gray-800 dark:text-white",
title: "dark:text-white",
},
})
if (result.isConfirmed) {
try {
await postService.restorePost(id)
toast.success("Yazı başarıyla geri yüklendi")
fetchPosts()
} catch (error) {
toast.error("Geri yükleme başarısız oldu")
console.error(error)
}
}
}
const handleEdit = async (post: Post) => {
try {
const id = post.id || post.ID
if (!id) {
toast.error("Yazı ID'si bulunamadı")
return
}
// Detay endpoint'inden güncel veriyi çek
const res = await postService.getPost(id)
setSelectedPost(res.data)
setDialogOpen(true)
} catch (error) {
console.error("Yazı detayı alınamadı:", error)
toast.error("Yazı detayı alınamadı")
}
}
const handleCreate = () => {
setSelectedPost(null)
setDialogOpen(true)
}
const columns = getPostColumns({
onEdit: handleEdit,
onDelete: handleDelete,
onRestore: handleRestore,
statusFilter,
deletedIds,
})
return (
<div className="p-8">
<div className="flex items-center justify-between mb-6">
<div>
<h1 className="text-3xl font-bold tracking-tight">Blog Yazıları</h1>
<p className="text-muted-foreground">
Blog içeriğini, kategorileri ve etiketleri yönetin.
</p>
</div>
<Button onClick={handleCreate}>
<Plus className="mr-2 h-4 w-4" /> Yeni Yazı
</Button>
</div>
<div className="flex items-center py-4 gap-4">
<Input
placeholder="Başlık ara..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="max-w-sm"
/>
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Durum" />
</SelectTrigger>
<SelectContent>
<SelectItem value="with">Tümü (Dahil)</SelectItem>
{/* Backend logic: empty 'soft' param usually means active only, 'only' means deleted only */}
<SelectItem value="active">Sadece Aktif</SelectItem>
<SelectItem value="only">Sadece Silinenler</SelectItem>
</SelectContent>
</Select>
</div>
<DataTable
columns={columns}
data={posts}
pageCount={Math.ceil(total / perPage)}
page={page}
onPageChange={setPage}
/>
<PostDialog
open={dialogOpen}
onOpenChange={setDialogOpen}
post={selectedPost}
onSuccess={fetchPosts}
/>
</div>
)
}