aaa
This commit is contained in:
185
src/sheets/edit-place-sheet.tsx
Normal file
185
src/sheets/edit-place-sheet.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user