"use client" import { useState, useEffect } from "react" import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import * as z from "zod" import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog" import { Button } from "@/components/ui/button" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import { Post } from "@/types/post" import { categoryService } from "@/services/categoryService" import { postService } from "@/services/postService" import { tagService } from "@/services/tagService" import { Category } from "@/types/category" import { Tag } from "@/types/tag" import { toast } from "sonner" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { useSlug } from "@/hooks/useSlug" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" // MultiSelect component specifically for Shadcn UI // Since Shadcn doesn't have a native MultiSelect, we'll use a simple implementation or standard select with multiple // For better UI, using a basic select list for now, ideally should use a proper MultiSelect component const MultiSelect = ({ options, selected, onChange }: { options: { label: string; value: string }[] selected: string[] onChange: (values: string[]) => void }) => { return (
{options.map((option) => (
{ if (e.target.checked) { onChange([...selected, option.value]) } else { onChange(selected.filter((v) => v !== option.value)) } }} className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary" />
))} {options.length === 0 &&

Veri bulunamadı.

}
) } const formSchema = z.object({ title: z.string().min(2, "Başlık en az 2 karakter olmalı"), slug: z.string().min(2, "Slug en az 2 karakter olmalı"), content: z.string().min(10, "İçerik en az 10 karakter olmalı"), category_ids: z.array(z.string()).min(1, "En az bir kategori seçilmelidir"), tag_names: z.array(z.string()).optional(), // Changed to array for MultiSelect // Image Config images: z.any().optional(), width: z.coerce.number().min(1).default(800), height: z.coerce.number().min(1).default(600), quality: z.coerce.number().min(1).max(100).default(85), format: z.string().default("webp"), }) interface PostDialogProps { open: boolean onOpenChange: (open: boolean) => void post?: Post | null onSuccess?: () => void } export function PostDialog({ open, onOpenChange, post, onSuccess }: PostDialogProps) { const [loading, setLoading] = useState(false) const [categories, setCategories] = useState([]) const [tags, setTags] = useState([]) const [preview, setPreview] = useState(null) const { slugify } = useSlug() const form = useForm>({ // eslint-disable-next-line @typescript-eslint/no-explicit-any resolver: zodResolver(formSchema) as any, defaultValues: { title: "", slug: "", content: "", category_ids: [], tag_names: [], width: 800, height: 600, quality: 85, format: "webp", }, }) useEffect(() => { const loadData = async () => { try { const catRes = await categoryService.getCategories(1, 100) setCategories(catRes.items || []) const tagRes = await tagService.getTags(1, 100) setTags(tagRes.items || []) } catch (error) { console.error("Failed to load categories/tags", error) } } if (open) { loadData() } }, [open]) useEffect(() => { if (post) { const categoryIds = post.categories ?.map(c => { const id = c.id ?? c.ID return id != null ? id.toString() : null }) .filter((id): id is string => !!id) || [] form.reset({ title: post.title, slug: post.slug, content: post.content, category_ids: categoryIds, tag_names: post.tags?.map(t => t.name) || [], width: post.width || 800, height: post.height || 600, quality: post.quality || 85, format: post.format || "webp", }) const apiUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080" if (post.images) { // Backend \"images\" alanı birden fazla path'i virgülle birleştirebiliyor. // Dialog önizlemesi için ilk path'i kullan. const firstImage = post.images .split(",") .map(p => p.trim()) .filter(Boolean)[0] if (firstImage) { setPreview(firstImage.startsWith("http") ? firstImage : `${apiUrl}${firstImage}`) } } } else { form.reset({ title: "", slug: "", content: "", category_ids: [], tag_names: [], width: 800, height: 600, quality: 85, format: "webp", }) setPreview(null) } }, [post, form, open]) const handleTitleChange = (e: React.ChangeEvent) => { const title = e.target.value form.setValue("title", title) if (!post) { // Only auto-slug on create form.setValue("slug", slugify(title)) } } const handleImageChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (file) { form.setValue("images", file) const reader = new FileReader() reader.onloadend = () => { setPreview(reader.result as string) } reader.readAsDataURL(file) } } const onSubmit = async (values: z.infer) => { setLoading(true) const formData = new FormData() formData.append("title", values.title) formData.append("slug", values.slug) formData.append("content", values.content) // Append categories - Backend likely expects multiple fields with same name or comma separated // Based on curl example: -F 'category_ids=1' // If multiple, standard is usually repeating the field values.category_ids.forEach(id => { formData.append("category_ids", id) }) // Tags - Backend, dokümana göre tekrar eden 'tag_names' alanlarını bekliyor: // -F 'tag_names=tag1' -F 'tag_names=tag2' if (values.tag_names && values.tag_names.length > 0) { values.tag_names.forEach(name => { formData.append("tag_names", name) }) } // Image config formData.append("width", values.width.toString()) formData.append("height", values.height.toString()) formData.append("quality", values.quality.toString()) formData.append("format", values.format) if (values.images instanceof File) { formData.append("images", values.images) } try { if (post) { await postService.updatePost(post.id || post.ID!, formData) toast.success("Yazı başarıyla güncellendi") } else { await postService.createPost(formData) toast.success("Yazı başarıyla oluşturuldu") } onOpenChange(false) if (onSuccess) onSuccess() } catch (error: unknown) { console.error("Post save error:", error) toast.error((error as Error).message || "Bir hata oluştu") } finally { setLoading(false) } } return ( {post ? "Yazı Düzenle" : "Yeni Yazı Ekle"}
İçerik Medya & SEO {/* CONTENT TAB */} ( Başlık )} /> ( SEO URL (Slug) )} />
( Kategoriler ({ label: c.title, value: c.id.toString() }))} selected={field.value} onChange={field.onChange} /> )} /> ( Etiketler ({ label: t.name, value: t.name }))} selected={field.value || []} onChange={field.onChange} /> )} />
( İçerik