This commit is contained in:
2026-05-20 15:40:17 +07:00
parent 230eb9010c
commit dd3fd889a3
48 changed files with 3374 additions and 737 deletions

View File

@@ -0,0 +1,185 @@
"use client";
import { useState, useTransition } from "react";
import { CATEGORIES } from "@/lib/ui-config";
import type { CategoryId, Place } from "@/lib/types";
import type { Dispatch } from "@/lib/app-state";
import { FieldLabel } from "@/components/ui-primitives";
import { Icons } from "@/components/icons";
import { editPlace } from "@/lib/db/actions";
export function EditPlaceSheet({
place,
onClose,
dispatch,
}: {
place: Place;
onClose: () => void;
dispatch: Dispatch;
}) {
const [name, setName] = useState(place.name);
const [address, setAddress] = useState(place.address);
const [category, setCategory] = useState<CategoryId>(place.category);
const [tags, setTags] = useState<string[]>(place.tags);
const [tagInput, setTagInput] = useState("");
const [saving, setSaving] = useState(false);
const [, startTransition] = useTransition();
const isValid = name.trim() && address.trim();
const addTag = (raw: string) => {
const t = raw.trim().replace(/,$/, "");
if (t && !tags.includes(t) && tags.length < 10) setTags([...tags, t]);
setTagInput("");
};
const submit = () => {
if (!isValid) return;
setSaving(true);
const trimmedAddress = address.trim();
startTransition(() => {
editPlace(place.id, {
name: name.trim(),
address: trimmedAddress,
short_address: trimmedAddress.split(",").slice(0, 2).join(" · "),
city: trimmedAddress.split(",").pop()?.trim() || "",
category,
tags,
cover_url: place.cover_url ?? null,
})
.then(() => {
onClose();
dispatch({ type: "TOAST", value: "Đã lưu thay đổi" });
})
.catch(() => {
setSaving(false);
dispatch({ type: "TOAST", value: "Lưu thất bại" });
});
});
};
return (
<>
<div className="overlay" onClick={onClose} />
<div className="sheet" style={{ height: "85%" }}>
<div className="sheet-handle" />
<div
style={{
padding: "6px 12px 8px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
}}
>
<button
onClick={onClose}
style={{
background: "transparent",
border: 0,
color: "var(--muted-foreground)",
fontSize: 15,
fontWeight: 500,
padding: "8px 12px",
}}
>
Hủy
</button>
<div style={{ fontSize: 16, fontWeight: 600 }}>Sửa đa điểm</div>
<div style={{ width: 70 }} />
</div>
<div style={{ flex: 1, overflowY: "auto", padding: "4px 16px" }}>
<FieldLabel required>Tên đa điểm</FieldLabel>
<div className="input">
<input value={name} onChange={(e) => setName(e.target.value)} />
</div>
<FieldLabel required>Đa chỉ</FieldLabel>
<div className="input">
<Icons.MapPin size={18} stroke={1.75} style={{ color: "var(--muted-foreground)" }} />
<input value={address} onChange={(e) => setAddress(e.target.value)} />
</div>
<FieldLabel required>Danh mục</FieldLabel>
<div className="toggle-group">
{(Object.entries(CATEGORIES) as [CategoryId, typeof CATEGORIES[CategoryId]][]).map(
([k, c]) => {
const I = Icons[c.icon as keyof typeof Icons];
return (
<button
key={k}
data-active={category === k}
onClick={() => setCategory(k)}
>
<I size={20} stroke={1.75} />
<span>{c.label}</span>
</button>
);
},
)}
</div>
<FieldLabel>
Thẻ <span style={{ color: "var(--subtle-foreground)", fontWeight: 400 }}>· {tags.length}/10</span>
</FieldLabel>
<div
className="input"
style={{ flexWrap: "wrap", height: "auto", minHeight: 48, padding: "6px 10px", gap: 6 }}
>
{tags.map((t) => (
<span
key={t}
className="badge"
style={{ height: 26, background: "var(--primary-soft)", color: "var(--primary)" }}
>
{t}
<button
onClick={() => setTags(tags.filter((x) => x !== t))}
style={{ background: "transparent", border: 0, color: "inherit", padding: 2, marginLeft: 2 }}
>
<Icons.X size={12} stroke={2.5} />
</button>
</span>
))}
<input
value={tagInput}
onChange={(e) => {
const v = e.target.value;
if (v.endsWith(",")) addTag(v);
else setTagInput(v);
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
addTag(tagInput);
} else if (e.key === "Backspace" && !tagInput && tags.length) {
setTags(tags.slice(0, -1));
}
}}
placeholder={tags.length ? "" : "Enter để thêm thẻ"}
style={{ flex: 1, minWidth: 80, height: 32 }}
/>
</div>
<div style={{ height: 8 }} />
</div>
<div
style={{
padding: "12px 16px 4px",
borderTop: "0.5px solid var(--border)",
background: "color-mix(in oklch, var(--card) 90%, transparent)",
}}
>
<button
className="btn btn--block btn--lg"
disabled={!isValid || saving}
onClick={submit}
>
{saving ? "Đang lưu..." : "Lưu thay đổi"}
</button>
</div>
</div>
</>
);
}