feat: multi-popular toggle for pricing, BYN price field for master classes

- Replace single popular dropdown with per-item toggle switch in pricing admin
- Add PriceField component to master classes admin (strips/adds BYN suffix)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-15 20:03:41 +03:00
parent f5e80c792a
commit 340a1d2f7f
2 changed files with 76 additions and 49 deletions

View File

@@ -7,6 +7,30 @@ import { ArrayEditor } from "../_components/ArrayEditor";
import { Plus, X, Upload, Loader2, ImageIcon, AlertCircle, Check, ChevronDown, ChevronUp, Instagram, Send, Trash2, Pencil } from "lucide-react"; import { Plus, X, Upload, Loader2, ImageIcon, AlertCircle, Check, ChevronDown, ChevronUp, Instagram, Send, Trash2, Pencil } from "lucide-react";
import type { MasterClassItem, MasterClassSlot } from "@/types/content"; import type { MasterClassItem, MasterClassSlot } from "@/types/content";
function PriceField({ label, value, onChange, placeholder }: { label: string; value: string; onChange: (v: string) => void; placeholder?: string }) {
const raw = value.replace(/\s*BYN\s*$/i, "").trim();
return (
<div>
<label className="block text-sm text-neutral-400 mb-1.5">{label}</label>
<div className="flex rounded-lg border border-white/10 bg-neutral-800 focus-within:border-gold transition-colors">
<input
type="text"
value={raw}
onChange={(e) => {
const v = e.target.value;
onChange(v ? `${v} BYN` : "");
}}
placeholder={placeholder ?? "0"}
className="flex-1 bg-transparent px-4 py-2.5 text-white placeholder-neutral-500 outline-none min-w-0"
/>
<span className="flex items-center pr-4 text-sm font-medium text-gold select-none">
BYN
</span>
</div>
</div>
);
}
interface MasterClassesData { interface MasterClassesData {
title: string; title: string;
successMessage?: string; successMessage?: string;
@@ -888,11 +912,11 @@ export default function MasterClassesEditorPage() {
/> />
</div> </div>
<InputField <PriceField
label="Стоимость" label="Стоимость"
value={item.cost} value={item.cost}
onChange={(v) => updateItem({ ...item, cost: v })} onChange={(v) => updateItem({ ...item, cost: v })}
placeholder="40 BYN" placeholder="40"
/> />
{locations.length > 0 && ( {locations.length > 0 && (

View File

@@ -63,45 +63,28 @@ export default function PricingEditorPage() {
onChange={(v) => update({ ...data, subtitle: v })} onChange={(v) => update({ ...data, subtitle: v })}
/> />
{/* Popular & Featured selectors */} {/* Featured selector */}
{(() => { {(() => {
const itemOptions = data.items const itemOptions = data.items
.map((it, idx) => ({ value: String(idx), label: it.name })) .map((it, idx) => ({ value: String(idx), label: it.name }))
.filter((o) => o.label.trim() !== ""); .filter((o) => o.label.trim() !== "");
const noneOption = { value: "", label: "— Нет —" }; const noneOption = { value: "", label: "— Нет —" };
const popularIdx = data.items.findIndex((it) => it.popular);
const featuredIdx = data.items.findIndex((it) => it.featured); const featuredIdx = data.items.findIndex((it) => it.featured);
return ( return (
<div className="grid gap-3 sm:grid-cols-2"> <SelectField
<SelectField label="Выделенный абонемент (безлимит)"
label="Популярный абонемент" value={featuredIdx >= 0 ? String(featuredIdx) : ""}
value={popularIdx >= 0 ? String(popularIdx) : ""} onChange={(v) => {
onChange={(v) => { const items = data.items.map((it, idx) => ({
const items = data.items.map((it, idx) => ({ ...it,
...it, featured: v ? idx === Number(v) : false,
popular: v ? idx === Number(v) : false, }));
})); update({ ...data, items });
update({ ...data, items }); }}
}} options={[noneOption, ...itemOptions]}
options={[noneOption, ...itemOptions]} placeholder="Выберите..."
placeholder="Выберите..." />
/>
<SelectField
label="Выделенный абонемент (безлимит)"
value={featuredIdx >= 0 ? String(featuredIdx) : ""}
onChange={(v) => {
const items = data.items.map((it, idx) => ({
...it,
featured: v ? idx === Number(v) : false,
}));
update({ ...data, items });
}}
options={[noneOption, ...itemOptions]}
placeholder="Выберите..."
/>
</div>
); );
})()} })()}
@@ -110,22 +93,42 @@ export default function PricingEditorPage() {
items={data.items} items={data.items}
onChange={(items) => update({ ...data, items })} onChange={(items) => update({ ...data, items })}
renderItem={(item, _i, updateItem) => ( renderItem={(item, _i, updateItem) => (
<div className="grid gap-3 sm:grid-cols-3"> <div className="space-y-3">
<InputField <div className="grid gap-3 sm:grid-cols-3">
label="Название" <InputField
value={item.name} label="Название"
onChange={(v) => updateItem({ ...item, name: v })} value={item.name}
/> onChange={(v) => updateItem({ ...item, name: v })}
<PriceField />
label="Цена" <PriceField
value={item.price} label="Цена"
onChange={(v) => updateItem({ ...item, price: v })} value={item.price}
/> onChange={(v) => updateItem({ ...item, price: v })}
<InputField />
label="Примечание" <InputField
value={item.note || ""} label="Примечание"
onChange={(v) => updateItem({ ...item, note: v })} value={item.note || ""}
/> onChange={(v) => updateItem({ ...item, note: v })}
/>
</div>
<label className="inline-flex items-center gap-2 cursor-pointer select-none">
<button
type="button"
role="switch"
aria-checked={!!item.popular}
onClick={() => updateItem({ ...item, popular: !item.popular })}
className={`relative h-5 w-9 rounded-full transition-colors ${
item.popular ? "bg-gold" : "bg-neutral-600"
}`}
>
<span
className={`absolute top-0.5 left-0.5 h-4 w-4 rounded-full bg-white transition-transform ${
item.popular ? "translate-x-4" : ""
}`}
/>
</button>
<span className="text-sm text-neutral-400">Популярный</span>
</label>
</div> </div>
)} )}
createItem={() => ({ name: "", price: "", note: "" })} createItem={() => ({ name: "", price: "", note: "" })}