fix: dashboard card order, nav labels from DB, pricing rental info

- Dashboard cards reordered to match sidebar nav sequence
- Added missing "Формы записи" card to dashboard
- Nav menu reads section titles from DOM headings (not hardcoded)
- Pricing: moved subtitle inside Абонементы tab, renamed to "Доп. информация"
- Pricing: added "Доп. информация" field to Аренда tab
This commit is contained in:
2026-03-31 00:17:26 +03:00
parent ae30be8f9d
commit 2d13b82507
4 changed files with 40 additions and 17 deletions
+6 -4
View File
@@ -16,6 +16,7 @@ import {
Phone,
ClipboardList,
DoorOpen,
MessageSquare,
UserPlus,
} from "lucide-react";
import { adminFetch } from "@/lib/csrf";
@@ -29,17 +30,18 @@ interface UnreadCounts {
const CARDS = [
{ href: "/admin/meta", label: "SEO / Мета", icon: Globe, desc: "Заголовок и описание сайта" },
{ href: "/admin/bookings", label: "Записи", icon: ClipboardList, desc: "Все записи и заявки" },
{ href: "/admin/popups", label: "Формы записи", icon: MessageSquare, desc: "Тексты в формах записи" },
{ href: "/admin/hero", label: "Главный экран", icon: Sparkles, desc: "Заголовок, подзаголовок, кнопка" },
{ href: "/admin/about", label: "О студии", icon: FileText, desc: "Текст о студии" },
{ href: "/admin/team", label: "Команда", icon: Users, desc: "Тренеры и инструкторы" },
{ href: "/admin/classes", label: "Направления", icon: BookOpen, desc: "Типы занятий" },
{ href: "/admin/master-classes", label: "Мастер-классы", icon: Star, desc: "Мастер-классы и записи" },
{ href: "/admin/team", label: "Команда", icon: Users, desc: "Тренеры и инструкторы" },
{ href: "/admin/open-day", label: "День открытых дверей", icon: DoorOpen, desc: "Открытые занятия, расписание, записи" },
{ href: "/admin/schedule", label: "Расписание", icon: Calendar, desc: "Расписание занятий" },
{ href: "/admin/bookings", label: "Записи", icon: ClipboardList, desc: "Все записи и заявки" },
{ href: "/admin/pricing", label: "Цены", icon: DollarSign, desc: "Абонементы и аренда" },
{ href: "/admin/faq", label: "FAQ", icon: HelpCircle, desc: "Часто задаваемые вопросы" },
{ href: "/admin/master-classes", label: "Мастер-классы", icon: Star, desc: "Мастер-классы и записи" },
{ href: "/admin/news", label: "Новости", icon: Newspaper, desc: "Новости и анонсы" },
{ href: "/admin/faq", label: "FAQ", icon: HelpCircle, desc: "Часто задаваемые вопросы" },
{ href: "/admin/contact", label: "Контакты", icon: Phone, desc: "Адреса, телефон, карта" },
];
+12 -7
View File
@@ -42,18 +42,11 @@ function PricingContent({ data, update }: { data: PricingData; update: (d: Prici
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="grid gap-3 sm:grid-cols-2 flex-1">
<InputField
label="Заголовок секции"
value={data.title}
onChange={(v) => update({ ...data, title: v })}
/>
<InputField
label="Подзаголовок"
value={data.subtitle}
onChange={(v) => update({ ...data, subtitle: v })}
/>
</div>
<button
type="button"
onClick={toggleAll}
@@ -66,6 +59,12 @@ function PricingContent({ data, update }: { data: PricingData; update: (d: Prici
{/* Абонементы */}
<CollapsibleSection title="Абонементы" count={data.items.length} isOpen={sections.subscriptions} onToggle={() => toggleSection("subscriptions")}>
<InputField
label="Доп. информация"
value={data.subtitle}
onChange={(v) => update({ ...data, subtitle: v })}
placeholder="Например: Действует акция до конца месяца"
/>
{(() => {
const itemOptions = data.items
.map((it, idx) => ({ value: String(idx), label: it.name }))
@@ -154,6 +153,12 @@ function PricingContent({ data, update }: { data: PricingData; update: (d: Prici
value={data.rentalTitle}
onChange={(v) => update({ ...data, rentalTitle: v })}
/>
<InputField
label="Доп. информация"
value={data.rentalSubtitle || ""}
onChange={(v) => update({ ...data, rentalSubtitle: v })}
placeholder="Например: Минимальная аренда — 1 час"
/>
<ArrayEditor
items={data.rentalItems}
+12 -1
View File
@@ -62,7 +62,18 @@ export function Header() {
// Filter out nav links whose target section doesn't exist on the page
const [visibleLinks, setVisibleLinks] = useState(NAV_LINKS);
useEffect(() => {
setVisibleLinks(NAV_LINKS.filter((l) => document.getElementById(l.href.replace("#", ""))));
setVisibleLinks(
NAV_LINKS
.filter((l) => document.getElementById(l.href.replace("#", "")))
.map((l) => {
const section = document.getElementById(l.href.replace("#", ""));
const heading = section?.querySelector("h2");
if (heading?.textContent && l.href !== "#hero") {
return { ...l, label: heading.textContent };
}
return l;
})
);
}, []);
useEffect(() => {
+5
View File
@@ -156,6 +156,11 @@ export function Pricing({ data: pricing }: PricingProps) {
{/* Rental tab */}
<div id="tabpanel-rental" role="tabpanel" aria-labelledby="tab-rental" className={activeTab === "rental" ? "block" : "hidden"}>
{pricing.rentalSubtitle && (
<p className="mx-auto mt-6 max-w-2xl text-center text-sm text-neutral-500 dark:text-neutral-400">
{pricing.rentalSubtitle}
</p>
)}
<div className="mx-auto mt-10 max-w-2xl space-y-3">
{pricing.rentalItems.map((item, i) => (
<div