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

583 lines
32 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 { 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,
FormDescription,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Switch } from "@/components/ui/switch"
import { Textarea } from "@/components/ui/textarea"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" // Assume these exist or need verify
import { Setting } from "@/types/setting"
import { settingService } from "@/services/settingService"
import { toast } from "sonner"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const formSchema = z.object({
title: z.string().min(2, "Başlık en az 2 karakter olmalı"),
slogan: z.string().optional(),
url: z.string().url("Geçerli bir URL giriniz").optional().or(z.literal("")),
email: z.string().email("Geçerli bir e-posta giriniz").optional().or(z.literal("")),
phone: z.string().optional(),
address: z.string().optional(),
copyright: z.string().optional(),
map_embed: z.string().optional(),
meta_title: z.string().optional(),
meta_description: z.string().optional(),
// Social
facebook: z.string().url().optional().or(z.literal("")),
x: z.string().url().optional().or(z.literal("")),
instagram: z.string().url().optional().or(z.literal("")),
whatsapp: z.string().optional(), // clean number usually
linkedin: z.string().url().optional().or(z.literal("")),
pinterest: z.string().url().optional().or(z.literal("")),
// Config
is_active: z.boolean().default(false),
// Images W Logo
w_logo: z.any().optional(),
w_width: z.coerce.number().min(1).default(100),
w_height: z.coerce.number().min(1).default(100),
w_quality: z.coerce.number().min(1).max(100).default(85),
w_format: z.string().default("avif"),
// Images B Logo
b_logo: z.any().optional(),
b_width: z.coerce.number().min(1).default(100),
b_height: z.coerce.number().min(1).default(100),
b_quality: z.coerce.number().min(1).max(100).default(85),
b_format: z.string().default("avif"),
})
interface SettingDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
setting?: Setting | null
onSuccess?: () => void
}
export function SettingDialog({ open, onOpenChange, setting, onSuccess }: SettingDialogProps) {
const [loading, setLoading] = useState(false)
const [wPreview, setWPreview] = useState<string | null>(null)
const [bPreview, setBPreview] = useState<string | null>(null)
const form = useForm<z.infer<typeof formSchema>>({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
resolver: zodResolver(formSchema) as any,
defaultValues: {
title: "",
slogan: "",
url: "",
email: "",
phone: "",
address: "",
copyright: "",
map_embed: "",
meta_title: "",
meta_description: "",
facebook: "",
x: "",
instagram: "",
whatsapp: "",
linkedin: "",
pinterest: "",
is_active: false,
w_width: 100,
w_height: 100,
w_quality: 85,
w_format: "avif",
b_width: 100,
b_height: 100,
b_quality: 85,
b_format: "avif",
},
})
useEffect(() => {
if (setting) {
form.reset({
title: setting.title,
slogan: setting.slogan || "",
url: setting.url || "",
email: setting.email || "",
phone: setting.phone || "",
address: setting.address || "",
copyright: setting.copyright || "",
map_embed: setting.map_embed || "",
meta_title: setting.meta_title || "",
meta_description: setting.meta_description || "",
facebook: setting.facebook || "",
x: setting.x || "",
instagram: setting.instagram || "",
whatsapp: setting.whatsapp || "",
linkedin: setting.linkedin || "",
pinterest: setting.pinterest || "",
is_active: !!setting.is_active,
w_width: setting.w_width || 100,
w_height: setting.w_height || 100,
w_quality: setting.w_quality || 85,
w_format: setting.w_format || "avif",
b_width: setting.b_width || 100,
b_height: setting.b_height || 100,
b_quality: setting.b_quality || 85,
b_format: setting.b_format || "avif",
})
// Set previews
// Assuming backend serves images from a static path, need env URL
const apiUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080"
if (setting.w_logo) {
// Check if it's already a full URL or relative
setWPreview(setting.w_logo.startsWith("http") ? setting.w_logo : `${apiUrl}${setting.w_logo}`)
}
if (setting.b_logo) {
setBPreview(setting.b_logo.startsWith("http") ? setting.b_logo : `${apiUrl}${setting.b_logo}`)
}
} else {
form.reset({
title: "",
slogan: "",
url: "",
email: "",
phone: "",
address: "",
copyright: "",
map_embed: "",
meta_title: "",
meta_description: "",
facebook: "",
x: "",
instagram: "",
whatsapp: "",
linkedin: "",
pinterest: "",
is_active: false,
w_width: 100,
w_height: 100,
w_quality: 85,
w_format: "avif",
b_width: 100,
b_height: 100,
b_quality: 85,
b_format: "avif",
})
setWPreview(null)
setBPreview(null)
}
}, [setting, form, open])
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>, fieldName: "w_logo" | "b_logo") => {
const file = e.target.files?.[0]
if (file) {
form.setValue(fieldName, file)
const reader = new FileReader()
reader.onloadend = () => {
if (fieldName === "w_logo") setWPreview(reader.result as string)
else setBPreview(reader.result as string)
}
reader.readAsDataURL(file)
}
}
const onSubmit = async (values: z.infer<typeof formSchema>) => {
setLoading(true)
const formData = new FormData()
// Append basic fields
Object.entries(values).forEach(([key, value]) => {
if (key !== "w_logo" && key !== "b_logo") {
formData.append(key, String(value))
}
})
// Append images if they are files
if (values.w_logo instanceof File) {
formData.append("w_logo", values.w_logo)
}
if (values.b_logo instanceof File) {
formData.append("b_logo", values.b_logo)
}
try {
if (setting) {
await settingService.updateSetting(setting.ID, formData)
toast.success("Ayar başarıyla güncellendi")
} else {
await settingService.createSetting(formData)
toast.success("Ayar başarıyla oluşturuldu")
}
onOpenChange(false)
if (onSuccess) onSuccess()
} catch (error: unknown) {
console.error("Setting save error:", error)
toast.error((error as Error).message || "Bir hata oluştu")
} finally {
setLoading(false)
}
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[800px] h-[90vh] overflow-y-auto flex flex-col">
<DialogHeader>
<DialogTitle>{setting ? "Ayar Düzenle" : "Yeni Ayar Ekle"}</DialogTitle>
</DialogHeader>
<div className="flex-1 overflow-y-auto py-2">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<Tabs defaultValue="general" className="w-full">
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="general">Genel</TabsTrigger>
<TabsTrigger value="contact">İletişim</TabsTrigger>
<TabsTrigger value="social">Sosyal Medya</TabsTrigger>
<TabsTrigger value="images">Görseller</TabsTrigger>
</TabsList>
{/* GENERAL TAB */}
<TabsContent value="general" className="space-y-4 pt-4">
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Site Başlığı</FormLabel>
<FormControl>
<Input placeholder="Site Başlığı" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="slogan"
render={({ field }) => (
<FormItem>
<FormLabel>Slogan</FormLabel>
<FormControl>
<Input placeholder="Slogan" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="meta_title"
render={({ field }) => (
<FormItem>
<FormLabel>Meta Başlığı</FormLabel>
<FormControl>
<Input placeholder="SEO için Başlık" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="meta_description"
render={({ field }) => (
<FormItem>
<FormLabel>Meta ıklama</FormLabel>
<FormControl>
<Input placeholder="SEO için Açıklama" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormField
control={form.control}
name="is_active"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm bg-destructive/10">
<div className="space-y-0.5">
<FormLabel className="font-bold text-destructive">Aktif Ayar</FormLabel>
<FormDescription>
Bu ayarı aktif yaparsanız, diğer tüm ayarlar otomatik olarak pasif duruma geçer.
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</TabsContent>
{/* CONTACT TAB */}
<TabsContent value="contact" className="space-y-4 pt-4">
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>E-posta</FormLabel>
<FormControl>
<Input placeholder="ornek@site.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="phone"
render={({ field }) => (
<FormItem>
<FormLabel>Telefon</FormLabel>
<FormControl>
<Input placeholder="+90 555 ..." {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormField
control={form.control}
name="url"
render={({ field }) => (
<FormItem>
<FormLabel>Site URL</FormLabel>
<FormControl>
<Input placeholder="https://site.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="address"
render={({ field }) => (
<FormItem>
<FormLabel>Adres</FormLabel>
<FormControl>
<Textarea placeholder="Adres bilgisi" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="map_embed"
render={({ field }) => (
<FormItem>
<FormLabel>Harita Embed Kodu (Iframe)</FormLabel>
<FormControl>
<Textarea placeholder='<iframe src="..." ...></iframe>' {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="copyright"
render={({ field }) => (
<FormItem>
<FormLabel>Telif Hakkı Metni</FormLabel>
<FormControl>
<Input placeholder="© 2024 Tüm hakları saklıdır." {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</TabsContent>
{/* SOCIAL TAB */}
<TabsContent value="social" className="space-y-4 pt-4">
<div className="grid grid-cols-2 gap-4">
<FormField control={form.control} name="facebook" render={({ field }) => (
<FormItem><FormLabel>Facebook</FormLabel><FormControl><Input placeholder="URL" {...field} /></FormControl><FormMessage /></FormItem>
)} />
<FormField control={form.control} name="x" render={({ field }) => (
<FormItem><FormLabel>X (Twitter)</FormLabel><FormControl><Input placeholder="URL" {...field} /></FormControl><FormMessage /></FormItem>
)} />
<FormField control={form.control} name="instagram" render={({ field }) => (
<FormItem><FormLabel>Instagram</FormLabel><FormControl><Input placeholder="URL" {...field} /></FormControl><FormMessage /></FormItem>
)} />
<FormField control={form.control} name="linkedin" render={({ field }) => (
<FormItem><FormLabel>LinkedIn</FormLabel><FormControl><Input placeholder="URL" {...field} /></FormControl><FormMessage /></FormItem>
)} />
<FormField control={form.control} name="pinterest" render={({ field }) => (
<FormItem><FormLabel>Pinterest</FormLabel><FormControl><Input placeholder="URL" {...field} /></FormControl><FormMessage /></FormItem>
)} />
<FormField control={form.control} name="whatsapp" render={({ field }) => (
<FormItem><FormLabel>Whatsapp</FormLabel><FormControl><Input placeholder="Numara" {...field} /></FormControl><FormMessage /></FormItem>
)} />
</div>
</TabsContent>
{/* IMAGES TAB */}
<TabsContent value="images" className="space-y-4 pt-4">
{/* White Logo Config */}
<div className="border p-4 rounded-md space-y-4">
<h3 className="font-semibold text-lg">Beyaz Yazılı Logo (w_logo)</h3>
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="w_width"
render={({ field }) => (
<FormItem><FormLabel>Genişlik</FormLabel><FormControl><Input type="number" {...field} /></FormControl><FormMessage /></FormItem>
)}
/>
<FormField
control={form.control}
name="w_height"
render={({ field }) => (
<FormItem><FormLabel>Yükseklik</FormLabel><FormControl><Input type="number" {...field} /></FormControl><FormMessage /></FormItem>
)}
/>
<FormField
control={form.control}
name="w_quality"
render={({ field }) => (
<FormItem><FormLabel>Kalite</FormLabel><FormControl><Input type="number" {...field} /></FormControl><FormMessage /></FormItem>
)}
/>
<FormField
control={form.control}
name="w_format"
render={({ field }) => (
<FormItem>
<FormLabel>Format</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger><SelectValue placeholder="Format" /></SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="avif">AVIF</SelectItem>
<SelectItem value="webp">WebP</SelectItem>
<SelectItem value="png">PNG</SelectItem>
<SelectItem value="jpeg">JPEG</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormItem>
<FormLabel>Logo Dosyası</FormLabel>
<FormControl>
<Input type="file" accept="image/*" onChange={(e) => handleImageChange(e, "w_logo")} />
</FormControl>
{wPreview && (
<div className="mt-2 w-full h-20 bg-gray-100 rounded flex items-center justify-center overflow-hidden border">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={wPreview} alt="W Logo Preview" className="h-full object-contain" />
</div>
)}
</FormItem>
</div>
{/* Black Logo Config */}
<div className="border p-4 rounded-md space-y-4">
<h3 className="font-semibold text-lg">Siyah Yazılı Logo (b_logo)</h3>
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="b_width"
render={({ field }) => (
<FormItem><FormLabel>Genişlik</FormLabel><FormControl><Input type="number" {...field} /></FormControl><FormMessage /></FormItem>
)}
/>
<FormField
control={form.control}
name="b_height"
render={({ field }) => (
<FormItem><FormLabel>Yükseklik</FormLabel><FormControl><Input type="number" {...field} /></FormControl><FormMessage /></FormItem>
)}
/>
<FormField
control={form.control}
name="b_quality"
render={({ field }) => (
<FormItem><FormLabel>Kalite</FormLabel><FormControl><Input type="number" {...field} /></FormControl><FormMessage /></FormItem>
)}
/>
<FormField
control={form.control}
name="b_format"
render={({ field }) => (
<FormItem>
<FormLabel>Format</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger><SelectValue placeholder="Format" /></SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="avif">AVIF</SelectItem>
<SelectItem value="webp">WebP</SelectItem>
<SelectItem value="png">PNG</SelectItem>
<SelectItem value="jpeg">JPEG</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormItem>
<FormLabel>Logo Dosyası</FormLabel>
<FormControl>
<Input type="file" accept="image/*" onChange={(e) => handleImageChange(e, "b_logo")} />
</FormControl>
{bPreview && (
<div className="mt-2 w-full h-20 bg-gray-100 rounded flex items-center justify-center overflow-hidden border">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={bPreview} alt="B Logo Preview" className="h-full object-contain" />
</div>
)}
</FormItem>
</div>
</TabsContent>
</Tabs>
<DialogFooter>
<Button type="submit" disabled={loading}>
{loading ? "Kaydediliyor..." : "Kaydet"}
</Button>
</DialogFooter>
</form>
</Form>
</div>
</DialogContent>
</Dialog>
)
}