sms entegrasyonu ve ana sayfa işlemleri

This commit is contained in:
2026-01-26 00:19:09 +03:00
parent 1e1baa84ff
commit 5c34df0f09
19 changed files with 1018 additions and 17 deletions

View File

@@ -0,0 +1,198 @@
'use client'
import { useState } from "react"
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import * as z from "zod"
import { Customer } from "@/types/customer"
import { sendBulkSms } from "@/lib/sms/actions"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import { Checkbox } from "@/components/ui/checkbox"
import { Label } from "@/components/ui/label"
import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from "@/components/ui/card"
import { Loader2, Send } from "lucide-react"
import { toast } from "sonner"
import { ScrollArea } from "@/components/ui/scroll-area"
const formSchema = z.object({
manualNumbers: z.string().optional(),
message: z.string().min(1, "Mesaj içeriği boş olamaz").max(900, "Mesaj çok uzun (max 900 karakter)"),
selectedCustomers: z.array(z.string()).optional()
})
interface SmsPageProps {
customers: Customer[]
}
export default function SmsPageClient({ customers }: SmsPageProps) {
const [loading, setLoading] = useState(false)
const [selectAll, setSelectAll] = useState(false)
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
manualNumbers: "",
message: "",
selectedCustomers: []
},
})
const handleSelectAll = (checked: boolean) => {
setSelectAll(checked)
if (checked) {
const allPhones = customers.map(c => c.phone).filter(Boolean) as string[]
form.setValue('selectedCustomers', allPhones)
} else {
form.setValue('selectedCustomers', [])
}
}
async function onSubmit(values: z.infer<typeof formSchema>) {
const manualPhones = values.manualNumbers
?.split(/[,\n]/) // Split by comma or newline
.map(p => p.trim())
.filter(p => p !== "") || []
const customerPhones = values.selectedCustomers || []
const allPhones = [...manualPhones, ...customerPhones]
if (allPhones.length === 0) {
toast.error("Lütfen en az bir alıcı seçin veya numara girin.")
return
}
setLoading(true)
try {
const result = await sendBulkSms(allPhones, values.message)
if (result.success) {
toast.success(result.message)
form.reset()
setSelectAll(false)
} else {
toast.error(result.error || "SMS gönderilirken hata oluştu")
}
} catch {
toast.error("Bir hata oluştu")
} finally {
setLoading(false)
}
}
const watchedSelected = form.watch("selectedCustomers") || []
return (
<div className="flex-1 space-y-4 p-8 pt-6">
<h2 className="text-3xl font-bold tracking-tight">SMS Gönderimi</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<Card>
<CardHeader>
<CardTitle>Mesaj Bilgileri</CardTitle>
<CardDescription>
Toplu veya tekil SMS gönderin. (Türkçe karakter desteklenir)
</CardDescription>
</CardHeader>
<CardContent>
<form id="sms-form" onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<div className="space-y-2">
<Label>Manuel Numaralar</Label>
<Textarea
placeholder="5551234567, 5329876543 (Virgül veya alt satır ile ayırın)"
{...form.register("manualNumbers")}
/>
<p className="text-xs text-muted-foreground">Veritabanında olmayan numaraları buraya girebilirsiniz.</p>
</div>
<div className="space-y-2">
<Label>Gönderilecek Mesaj</Label>
<Textarea
className="min-h-[120px]"
placeholder="Mesajınızı buraya yazın..."
{...form.register("message")}
/>
<div className="flex justify-between text-xs text-muted-foreground">
<span>Türkçe karakterler otomatik desteklenir.</span>
<span>{form.watch("message")?.length || 0} / 900</span>
</div>
</div>
<div className="pt-4">
<div className="p-4 bg-slate-50 dark:bg-slate-900 rounded-md">
<h4 className="font-semibold mb-2">Özet</h4>
<ul className="list-disc list-inside text-sm">
<li>Manuel: {(form.watch("manualNumbers")?.split(/[,\n]/).filter(x => x.trim()).length || 0)} Kişi</li>
<li>Seçili Müşteri: {watchedSelected.length} Kişi</li>
<li className="font-bold mt-1">Toplam: {(form.watch("manualNumbers")?.split(/[,\n]/).filter(x => x.trim()).length || 0) + watchedSelected.length} Kişi</li>
</ul>
</div>
</div>
</form>
</CardContent>
<CardFooter>
<Button form="sms-form" type="submit" disabled={loading} className="w-full">
{loading ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Send className="mr-2 h-4 w-4" />}
Gönderimi Başlat
</Button>
</CardFooter>
</Card>
<Card className="h-full flex flex-col">
<CardHeader>
<CardTitle>Müşteri Listesi</CardTitle>
<CardDescription>
Listeden toplu seçim yapabilirsiniz.
</CardDescription>
</CardHeader>
<CardContent className="flex-1 min-h-[400px]">
<div className="flex items-center space-x-2 mb-4 pb-4 border-b">
<Checkbox
id="select-all"
checked={selectAll}
onCheckedChange={handleSelectAll}
/>
<Label htmlFor="select-all" className="font-bold">Tümünü Seç ({customers.length})</Label>
</div>
<ScrollArea className="h-[400px] w-full pr-4">
<div className="space-y-2">
{customers.length === 0 && <p className="text-muted-foreground">Kayıtlı müşteri bulunamadı.</p>}
{customers.map((customer) => (
<div key={customer.id} className="flex items-start space-x-2 py-2 hover:bg-slate-50 dark:hover:bg-slate-900 rounded px-2">
<Checkbox
id={`customer-${customer.id}`}
checked={watchedSelected.includes(customer.phone || "")}
disabled={!customer.phone}
onCheckedChange={(checked) => {
const current = form.getValues("selectedCustomers") || []
if (checked) {
form.setValue("selectedCustomers", [...current, customer.phone || ""])
} else {
form.setValue("selectedCustomers", current.filter(p => p !== customer.phone))
setSelectAll(false)
}
}}
/>
<div className="grid gap-1.5 leading-none">
<Label
htmlFor={`customer-${customer.id}`}
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{customer.full_name}
</Label>
<p className="text-xs text-muted-foreground">
{customer.phone || "Telefon Yok"}
</p>
</div>
</div>
))}
</div>
</ScrollArea>
</CardContent>
</Card>
</div>
</div>
)
}