Feat: Implement Customer Edit page and functionality
This commit is contained in:
211
src/app/dashboard/customers/[id]/edit-customer-form.tsx
Normal file
211
src/app/dashboard/customers/[id]/edit-customer-form.tsx
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import { useForm } from "react-hook-form"
|
||||||
|
import * as z from "zod"
|
||||||
|
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 { updateCustomer, deleteCustomer } from "./actions"
|
||||||
|
import { useRouter } from "next/navigation"
|
||||||
|
import { useState } from "react"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
import { Trash2 } from "lucide-react"
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
full_name: z.string().min(2, "İsim en az 2 karakter olmalıdır."),
|
||||||
|
phone: z.string().optional(),
|
||||||
|
email: z.string().email("Geçerli bir e-posta adresi giriniz.").optional().or(z.literal("")),
|
||||||
|
city: z.string().optional(),
|
||||||
|
district: z.string().optional(),
|
||||||
|
address: z.string().optional(),
|
||||||
|
notes: z.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
interface EditCustomerFormProps {
|
||||||
|
customer: {
|
||||||
|
id: string
|
||||||
|
full_name: string
|
||||||
|
phone?: string | null
|
||||||
|
email?: string | null
|
||||||
|
city?: string | null
|
||||||
|
district?: string | null
|
||||||
|
address?: string | null
|
||||||
|
notes?: string | null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EditCustomerForm({ customer }: EditCustomerFormProps) {
|
||||||
|
const router = useRouter()
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: {
|
||||||
|
full_name: customer.full_name,
|
||||||
|
phone: customer.phone || "",
|
||||||
|
email: customer.email || "",
|
||||||
|
city: customer.city || "",
|
||||||
|
district: customer.district || "",
|
||||||
|
address: customer.address || "",
|
||||||
|
notes: customer.notes || "",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
async function onSubmit(values: z.infer<typeof formSchema>) {
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
await updateCustomer(customer.id, values)
|
||||||
|
toast.success("Müşteri başarıyla güncellendi")
|
||||||
|
router.push('/dashboard/customers')
|
||||||
|
router.refresh()
|
||||||
|
} catch (error) {
|
||||||
|
toast.error("Bir hata oluştu")
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDelete() {
|
||||||
|
if (!confirm("Bu müşteriyi silmek istediğinize emin misiniz?")) return
|
||||||
|
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
await deleteCustomer(customer.id)
|
||||||
|
toast.success("Müşteri silindi")
|
||||||
|
router.push('/dashboard/customers')
|
||||||
|
router.refresh()
|
||||||
|
} catch (error) {
|
||||||
|
toast.error("Silme işlemi başarısız")
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="full_name"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>İsim Soyisim</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="Ahmet Yılmaz" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="phone"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Telefon</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="0555 555 55 55" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="email"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>E-posta</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input type="email" placeholder="ornek@email.com" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="city"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>İl</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="İstanbul" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="district"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>İlçe</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="Kadıköy" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="address"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Adres</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea placeholder="Tam adres..." {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="notes"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Notlar</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea placeholder="Müşteri hakkında notlar..." {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex justify-between gap-4">
|
||||||
|
<Button type="button" variant="destructive" onClick={handleDelete} disabled={loading}>
|
||||||
|
<Trash2 className="mr-2 h-4 w-4" /> Sil
|
||||||
|
</Button>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button type="button" variant="outline" onClick={() => router.back()} disabled={loading}>
|
||||||
|
İptal
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" disabled={loading}>
|
||||||
|
{loading ? "Güncelleniyor..." : "Güncelle"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
)
|
||||||
|
}
|
||||||
31
src/app/dashboard/customers/[id]/page.tsx
Normal file
31
src/app/dashboard/customers/[id]/page.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
import { EditCustomerForm } from "./edit-customer-form"
|
||||||
|
import { createClient } from "@/lib/supabase/server"
|
||||||
|
import { notFound } from "next/navigation"
|
||||||
|
|
||||||
|
export default async function EditCustomerPage({ params }: { params: { id: string } }) {
|
||||||
|
const supabase = await createClient()
|
||||||
|
|
||||||
|
const { data: customer } = await supabase
|
||||||
|
.from('customers')
|
||||||
|
.select('*')
|
||||||
|
.eq('id', params.id)
|
||||||
|
.single()
|
||||||
|
|
||||||
|
if (!customer) {
|
||||||
|
notFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-2xl mx-auto">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Müşteri Düzenle</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<EditCustomerForm customer={customer} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -80,7 +80,9 @@ export default async function CustomersPage({
|
|||||||
<TableCell>{customer.email}</TableCell>
|
<TableCell>{customer.email}</TableCell>
|
||||||
<TableCell className="max-w-xs truncate">{customer.notes}</TableCell>
|
<TableCell className="max-w-xs truncate">{customer.notes}</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
<Button variant="ghost" size="sm">Düzenle</Button>
|
<Link href={`/dashboard/customers/${customer.id}`}>
|
||||||
|
<Button variant="ghost" size="sm">Düzenle</Button>
|
||||||
|
</Link>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))
|
))
|
||||||
@@ -100,9 +102,11 @@ export default async function CustomersPage({
|
|||||||
<Card key={customer.id} className="overflow-hidden">
|
<Card key={customer.id} className="overflow-hidden">
|
||||||
<CardHeader className="bg-muted/20 p-4 flex flex-row items-center justify-between space-y-0">
|
<CardHeader className="bg-muted/20 p-4 flex flex-row items-center justify-between space-y-0">
|
||||||
<span className="font-semibold text-lg">{customer.full_name}</span>
|
<span className="font-semibold text-lg">{customer.full_name}</span>
|
||||||
<Button variant="ghost" size="icon" className="h-8 w-8">
|
<Link href={`/dashboard/customers/${customer.id}`}>
|
||||||
<Edit className="h-4 w-4 text-muted-foreground" />
|
<Button variant="ghost" size="icon" className="h-8 w-8">
|
||||||
</Button>
|
<Edit className="h-4 w-4 text-muted-foreground" />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="p-4 space-y-3">
|
<CardContent className="p-4 space-y-3">
|
||||||
<div className="flex items-center text-sm">
|
<div className="flex items-center text-sm">
|
||||||
|
|||||||
Reference in New Issue
Block a user