winofy-front/src/components/customer/address-list.tsx
2026-08-19 13:01:19 +03:30

198 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 { useMemo, useState, useTransition } from "react";
import { useRouter } from "next/navigation";
import { createAddress, deleteAddress, type AddressInput } from "@/lib/api/addresses";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import type { Address, AddressLabel, City, Neighborhood } from "@/types/api";
const LABEL_OPTIONS: { value: AddressLabel; label: string }[] = [
{ value: "home", label: "خانه" },
{ value: "work", label: "محل کار" },
{ value: "other", label: "سایر" },
];
const emptyForm = {
label: "home" as AddressLabel,
title: "",
full_address: "",
plaque: "",
floor: "",
unit: "",
recipient_name: "",
recipient_phone: "",
city_uuid: "",
neighborhood_uuid: "",
is_default: false,
};
export function AddressList({
addresses,
cities,
neighborhoods,
}: {
addresses: Address[];
cities: City[];
neighborhoods: Neighborhood[];
}) {
const router = useRouter();
const [isAdding, setIsAdding] = useState(false);
const [form, setForm] = useState(emptyForm);
const [error, setError] = useState<string | null>(null);
const [isPending, startTransition] = useTransition();
const filteredNeighborhoods = useMemo(
() => neighborhoods.filter((n) => n.city.uuid === form.city_uuid),
[neighborhoods, form.city_uuid],
);
function handleDelete(uuid: string) {
startTransition(async () => {
await deleteAddress(uuid);
router.refresh();
});
}
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setError(null);
startTransition(async () => {
const input: AddressInput = {
...form,
neighborhood_uuid: form.neighborhood_uuid || undefined,
latitude: 0,
longitude: 0,
};
const result = await createAddress(input);
if (result.ok) {
setForm(emptyForm);
setIsAdding(false);
router.refresh();
} else {
setError(result.message);
}
});
}
return (
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-3">
{addresses.map((address) => (
<div key={address.uuid} className="flex items-start justify-between gap-3 rounded-2xl border border-border p-4">
<div className="flex flex-col gap-1 text-sm">
<span className="font-medium">
{LABEL_OPTIONS.find((o) => o.value === address.label)?.label} {address.is_default && "· پیش‌فرض"}
</span>
<span className="text-muted-foreground">{address.full_address}</span>
<span className="text-muted-foreground">
{address.recipient_name} — {address.recipient_phone}
</span>
</div>
<Button variant="ghost" size="sm" disabled={isPending} onClick={() => handleDelete(address.uuid)}>
حذف
</Button>
</div>
))}
{addresses.length === 0 && !isAdding && <p className="text-sm text-muted-foreground">آدرسی ثبت نشده است.</p>}
</div>
{isAdding ? (
<form onSubmit={handleSubmit} className="flex flex-col gap-3 rounded-2xl border border-border p-4">
<div className="flex gap-2">
{LABEL_OPTIONS.map((opt) => (
<button
key={opt.value}
type="button"
onClick={() => setForm((f) => ({ ...f, label: opt.value }))}
className={`rounded-full px-3 py-1 text-xs ${form.label === opt.value ? "bg-primary text-primary-foreground" : "border border-border"}`}
>
{opt.label}
</button>
))}
</div>
<Input
placeholder="آدرس کامل"
value={form.full_address}
onChange={(e) => setForm((f) => ({ ...f, full_address: e.target.value }))}
required
/>
<div className="grid grid-cols-3 gap-2">
<Input placeholder="پلاک" value={form.plaque} onChange={(e) => setForm((f) => ({ ...f, plaque: e.target.value }))} />
<Input placeholder="طبقه" value={form.floor} onChange={(e) => setForm((f) => ({ ...f, floor: e.target.value }))} />
<Input placeholder="واحد" value={form.unit} onChange={(e) => setForm((f) => ({ ...f, unit: e.target.value }))} />
</div>
<div className="grid grid-cols-2 gap-2">
<select
value={form.city_uuid}
onChange={(e) => setForm((f) => ({ ...f, city_uuid: e.target.value, neighborhood_uuid: "" }))}
className="h-11 rounded-xl border border-border bg-background px-3 text-sm"
required
>
<option value="">شهر</option>
{cities.map((city) => (
<option key={city.uuid} value={city.uuid}>
{city.name}
</option>
))}
</select>
<select
value={form.neighborhood_uuid}
onChange={(e) => setForm((f) => ({ ...f, neighborhood_uuid: e.target.value }))}
className="h-11 rounded-xl border border-border bg-background px-3 text-sm"
disabled={!form.city_uuid}
>
<option value="">محله (اختیاری)</option>
{filteredNeighborhoods.map((n) => (
<option key={n.uuid} value={n.uuid}>
{n.name}
</option>
))}
</select>
</div>
<Input
placeholder="نام گیرنده"
value={form.recipient_name}
onChange={(e) => setForm((f) => ({ ...f, recipient_name: e.target.value }))}
required
/>
<Input
type="tel"
dir="ltr"
placeholder="09123456789"
value={form.recipient_phone}
onChange={(e) => setForm((f) => ({ ...f, recipient_phone: e.target.value }))}
required
/>
<label className="flex items-center gap-2 text-sm">
<input
type="checkbox"
checked={form.is_default}
onChange={(e) => setForm((f) => ({ ...f, is_default: e.target.checked }))}
/>
آدرس پیش‌فرض
</label>
{error && <p className="text-sm text-destructive">{error}</p>}
<div className="flex gap-2">
<Button type="submit" disabled={isPending}>
{isPending ? "در حال ثبت..." : "ثبت آدرس"}
</Button>
<Button type="button" variant="outline" onClick={() => setIsAdding(false)}>
انصراف
</Button>
</div>
</form>
) : (
<Button variant="outline" onClick={() => setIsAdding(true)}>
+ افزودن آدرس جدید
</Button>
)}
</div>
);
}