setNewBookingCustomer(e.currentTarget.value)} />
setNewBookingEmail(e.currentTarget.value)} />
-
setNewBookingService(e.currentTarget.value)} />
+
0} fallback={
+ setNewBookingService(e.currentTarget.value)} />
+ }>
+
setNewBookingDate(e.currentTarget.value)} />
setNewBookingTime(e.currentTarget.value)} />
diff --git a/apps/frontend/src/routes/dashboard/dashboard-data.ts b/apps/frontend/src/routes/dashboard/dashboard-data.ts
index fd829bc..15802b5 100644
--- a/apps/frontend/src/routes/dashboard/dashboard-data.ts
+++ b/apps/frontend/src/routes/dashboard/dashboard-data.ts
@@ -4,7 +4,7 @@ import { useAuth } from "../../providers/auth-provider";
import { useI18n } from "../../providers/i18n-provider";
import { getInitials, getBookingDuration } from "../../components/dashboard/types";
-export type Section = "overview" | "bookings" | "customers" | "zones" | "billing" | "settings";
+export type Section = "overview" | "bookings" | "customers" | "services" | "staff" | "zones" | "billing" | "settings";
function createDemoData(i18n: ReturnType) {
const cs = i18n.locale() === "cs";
@@ -48,6 +48,7 @@ function createDemoData(i18n: ReturnType) {
{ id: "b6", customerName: customers[0].name, customerEmail: customers[0].email, service: services[1], status: "confirmed", startsAt: fmt(at(3, 10)), endsAt: fmt(at(3, 11)), reference: "BOK-006", location: salonA, staffName: staff1, notes: "" },
{ id: "b8", customerName: customers[2].name, customerEmail: customers[2].email, service: services[3], status: "pending", startsAt: fmt(at(4, 9)), endsAt: fmt(at(4, 10)), reference: "BOK-008", location: salonA, staffName: staff2, notes: "" },
{ id: "b9", customerName: customers[3].name, customerEmail: customers[3].email, service: services[0], status: "confirmed", startsAt: fmt(at(5, 11)), endsAt: fmt(at(5, 12)), reference: "BOK-009", location: salonA, staffName: staff1, notes: "" },
+ { id: "b13", customerName: customers[0].name, customerEmail: customers[0].email, service: cs ? "Jóga pro začátečníky" : "Beginner yoga", status: "waitlisted", startsAt: fmt(at(3, 18)), endsAt: fmt(at(3, 19)), reference: "BOK-013", location: salonA, staffName: "", notes: "" },
];
return {
@@ -210,8 +211,18 @@ export function DashboardDataProvider(props: { children: any }) {
};
};
+ const kpiLabelKeys: Record = {
+ bookings_this_week: "dashboard.kpi.bookingsThisWeek",
+ completed: "dashboard.kpi.completed",
+ cancellations: "dashboard.kpi.cancellations",
+ utilization: "dashboard.kpi.utilization",
+ new_clients: "dashboard.kpi.newClients",
+ };
const normalizedKpis = createMemo(() => (resolvedSummary()?.kpis ?? []).map((kpi: any) => ({
- ...kpi, trend: kpi.trend ?? "neutral", change: kpi.change ?? "—",
+ ...kpi,
+ label: kpi.code && kpiLabelKeys[kpi.code] ? i18n.t(kpiLabelKeys[kpi.code]) : kpi.label,
+ trend: kpi.trend ?? "neutral",
+ change: kpi.change ?? "—",
})));
const normalizedUpcomingBookings = createMemo(() => (resolvedSummary()?.upcomingBookings ?? []).map((booking: any) => ({
diff --git a/apps/frontend/src/routes/dashboard/layout.tsx b/apps/frontend/src/routes/dashboard/layout.tsx
index dd053ee..fcf0ca2 100644
--- a/apps/frontend/src/routes/dashboard/layout.tsx
+++ b/apps/frontend/src/routes/dashboard/layout.tsx
@@ -8,9 +8,10 @@ import { getInitials, getBookingDuration } from "../../components/dashboard/type
import {
LayoutDashboardIcon, CalendarDaysIcon, CreditCardIcon, Settings2Icon,
LogOutIcon, MenuIcon, XIcon, SparklesIcon, GlobeIcon, SunIcon, MoonIcon,
- UserCircleIcon, MapPinIcon
+ UserCircleIcon, MapPinIcon, ScissorsIcon, UsersIcon
} from "../../components/dashboard/icons";
import { DashboardContext, useDashboardData, DashboardDataProvider } from "./dashboard-data";
+import { apiClient } from "../../lib/api-client";
export { useDashboardData };
@@ -18,6 +19,8 @@ const navItems = [
{ id: "overview", label: "dashboard.overview", icon: LayoutDashboardIcon, path: "/dashboard" },
{ id: "bookings", label: "dashboard.bookings", icon: CalendarDaysIcon, path: "/dashboard/bookings" },
{ id: "customers", label: "dashboard.customers", icon: UserCircleIcon, path: "/dashboard/customers" },
+ { id: "services", label: "dashboard.services", icon: ScissorsIcon, path: "/dashboard/services" },
+ { id: "staff", label: "dashboard.staff", icon: UsersIcon, path: "/dashboard/staff" },
{ id: "zones", label: "dashboard.zones", icon: MapPinIcon, path: "/dashboard/zones" },
{ id: "billing", label: "dashboard.billing", icon: CreditCardIcon, path: "/dashboard/billing" },
{ id: "settings", label: "dashboard.settings", icon: Settings2Icon, path: "/dashboard/settings" },
@@ -28,6 +31,31 @@ function DashboardShell(props: { children: JSX.Element }) {
const theme = useTheme();
const location = useLocation();
const [isMobileMenuOpen, setIsMobileMenuOpen] = createSignal(false);
+ const [cancelConfirm, setCancelConfirm] = createSignal<"idle" | "armed" | "busy">("idle");
+
+ const handleCancelBooking = async () => {
+ if (cancelConfirm() === "busy") return;
+ if (cancelConfirm() === "idle") { setCancelConfirm("armed"); return; }
+ const booking = data.selectedBooking();
+ const bearer = data.token();
+ if (!booking) return;
+ if (!bearer || bearer.startsWith("demo.")) {
+ data.setDemoNotice(data.i18n.locale() === "cs" ? "V demo režimu nelze rušit rezervace." : "Cannot cancel bookings in demo mode.");
+ setCancelConfirm("idle");
+ return;
+ }
+ setCancelConfirm("busy");
+ try {
+ const response = await (apiClient as any).POST(`/v1/catalog/bookings/${booking.id}/cancel`, { headers: { Authorization: `Bearer ${bearer}` } });
+ if (response.error) throw new Error("cancel failed");
+ data.setShowBookingDetail(false);
+ void data.refetchSummary();
+ } catch {
+ data.setDemoNotice(data.i18n.locale() === "cs" ? "Zrušení rezervace selhalo." : "Failed to cancel booking.");
+ } finally {
+ setCancelConfirm("idle");
+ }
+ };
const activeSection = () => {
const p = location.pathname;
@@ -240,6 +268,7 @@ function DashboardShell(props: { children: JSX.Element }) {
@@ -253,6 +282,25 @@ function DashboardShell(props: { children: JSX.Element }) {