first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:46:42 +03:00
commit 2a5b661443
202 changed files with 49770 additions and 0 deletions

View File

@@ -0,0 +1,238 @@
"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>
)
}