Files
kovakemlak-crm/src/components/properties/property-form-sheet.tsx
T
egecankomur 4ef0482732 feat: all core modules — properties, customers, searches, matches, presentations, activities, investors + public sunum page
- Server actions: property/customer/search/presentation/activity/investor CRUD
- Matching engine: matchPropertyToSearches + syncMatchesForSearch on search save
- UI: form sheets + table clients for all modules
- Public /sunum/[token] page (no auth) with property card grid + expiry check
- All pages force-dynamic for auth guard compatibility
2026-05-05 12:03:48 +03:00

182 lines
7.7 KiB
TypeScript
Raw 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 { useActionState, useEffect } from "react";
import { Loader2 } from "lucide-react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import {
Sheet,
SheetContent,
SheetFooter,
SheetHeader,
SheetTitle,
} from "@/components/ui/sheet";
import { createPropertyAction, updatePropertyAction } from "@/lib/appwrite/property-actions";
import type { Property } from "@/lib/appwrite/schema";
type ActionState = { ok: boolean; error?: string; fieldErrors?: Record<string, string[]> };
const INITIAL: ActionState = { ok: false };
interface PropertyFormSheetProps {
open: boolean;
onOpenChange: (v: boolean) => void;
property?: Property | null;
onSuccess?: () => void;
}
export function PropertyFormSheet({ open, onOpenChange, property, onSuccess }: PropertyFormSheetProps) {
const action = property
? updatePropertyAction.bind(null, property.$id)
: createPropertyAction;
const [state, formAction, isPending] = useActionState(action, INITIAL);
useEffect(() => {
if (state.ok) {
toast.success(property ? "İlan güncellendi." : "İlan oluşturuldu.");
onSuccess?.();
onOpenChange(false);
} else if (state.error) {
toast.error(state.error);
}
}, [state]);
const fe = state.fieldErrors ?? {};
return (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent className="w-full sm:max-w-xl overflow-y-auto">
<SheetHeader>
<SheetTitle>{property ? "İlanı Düzenle" : "Yeni İlan"}</SheetTitle>
</SheetHeader>
<form action={formAction} className="mt-4 space-y-4 pb-6">
<div className="grid gap-1.5">
<Label htmlFor="title">Başlık *</Label>
<Input id="title" name="title" defaultValue={property?.title} placeholder="3+1 Daire, Kadıköy" />
{fe.title && <p className="text-destructive text-xs">{fe.title[0]}</p>}
</div>
<div className="grid grid-cols-2 gap-3">
<div className="grid gap-1.5">
<Label>Emlak tipi *</Label>
<select name="propertyType" defaultValue={property?.propertyType ?? "daire"}
className="border-input bg-background h-9 rounded-md border px-3 text-sm">
<option value="daire">Daire</option>
<option value="villa">Villa</option>
<option value="arsa">Arsa</option>
<option value="dukkan">Dükkan</option>
<option value="ofis">Ofis</option>
<option value="depo">Depo</option>
</select>
</div>
<div className="grid gap-1.5">
<Label>İlan türü *</Label>
<select name="listingType" defaultValue={property?.listingType ?? "satilik"}
className="border-input bg-background h-9 rounded-md border px-3 text-sm">
<option value="satilik">Satılık</option>
<option value="kiralik">Kiralık</option>
</select>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="grid gap-1.5">
<Label htmlFor="price">Fiyat *</Label>
<Input id="price" name="price" type="number" min="0" defaultValue={property?.price} placeholder="0" />
{fe.price && <p className="text-destructive text-xs">{fe.price[0]}</p>}
</div>
<div className="grid gap-1.5">
<Label>Durum</Label>
<select name="status" defaultValue={property?.status ?? "aktif"}
className="border-input bg-background h-9 rounded-md border px-3 text-sm">
<option value="aktif">Aktif</option>
<option value="pasif">Pasif</option>
<option value="satildi">Satıldı</option>
<option value="kiralandit">Kiralandı</option>
</select>
</div>
</div>
<div className="grid grid-cols-3 gap-3">
<div className="grid gap-1.5">
<Label htmlFor="city">Şehir *</Label>
<Input id="city" name="city" defaultValue={property?.city} placeholder="İstanbul" />
{fe.city && <p className="text-destructive text-xs">{fe.city[0]}</p>}
</div>
<div className="grid gap-1.5">
<Label htmlFor="district">İlçe</Label>
<Input id="district" name="district" defaultValue={property?.district ?? ""} placeholder="Kadıköy" />
</div>
<div className="grid gap-1.5">
<Label htmlFor="neighborhood">Mahalle</Label>
<Input id="neighborhood" name="neighborhood" defaultValue={property?.neighborhood ?? ""} placeholder="Moda" />
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="grid gap-1.5">
<Label htmlFor="roomCount">Oda sayısı</Label>
<select name="roomCount" defaultValue={property?.roomCount ?? ""}
className="border-input bg-background h-9 rounded-md border px-3 text-sm">
<option value="">Seçiniz</option>
<option value="Stüdyo">Stüdyo</option>
<option value="1+0">1+0</option>
<option value="1+1">1+1</option>
<option value="2+1">2+1</option>
<option value="3+1">3+1</option>
<option value="4+1">4+1</option>
<option value="4+2">4+2</option>
<option value="5+1">5+1</option>
<option value="5+2">5+2</option>
<option value="6+">6+</option>
</select>
</div>
<div className="grid gap-1.5">
<Label htmlFor="netM2">Net m²</Label>
<Input id="netM2" name="netM2" type="number" min="0" defaultValue={property?.netM2 ?? ""} placeholder="90" />
</div>
</div>
<div className="grid grid-cols-3 gap-3">
<div className="grid gap-1.5">
<Label htmlFor="floor">Kat</Label>
<Input id="floor" name="floor" type="number" defaultValue={property?.floor ?? ""} />
</div>
<div className="grid gap-1.5">
<Label htmlFor="totalFloors">Top. kat</Label>
<Input id="totalFloors" name="totalFloors" type="number" defaultValue={property?.totalFloors ?? ""} />
</div>
<div className="grid gap-1.5">
<Label htmlFor="buildingAge">Bina yaşı</Label>
<Input id="buildingAge" name="buildingAge" type="number" min="0" defaultValue={property?.buildingAge ?? ""} />
</div>
</div>
<div className="grid gap-1.5">
<Label htmlFor="address">Tam adres</Label>
<Textarea id="address" name="address" rows={2} defaultValue={property?.address ?? ""} placeholder="Sokak, kapı no..." />
</div>
<div className="grid gap-1.5">
<Label htmlFor="description">Açıklama</Label>
<Textarea id="description" name="description" rows={3} defaultValue={property?.description ?? ""} placeholder="İlan detayları..." />
</div>
<SheetFooter>
<Button type="submit" disabled={isPending} className="w-full">
{isPending && <Loader2 className="mr-2 size-4 animate-spin" />}
{property ? "Güncelle" : "Oluştur"}
</Button>
</SheetFooter>
</form>
</SheetContent>
</Sheet>
);
}