From 4d229d0b9467744fb91239baf2fe7f485f9c503e Mon Sep 17 00:00:00 2001 From: brooktewabe Date: Wed, 1 Apr 2026 11:28:05 +0300 Subject: [PATCH] stats and settings --- src/pages/ManageUsersPage.tsx | 193 ++++++++++++++++++++++++++++++++++ src/pages/SettingsPage.tsx | 63 ++++++++--- src/pages/VisitsPage.tsx | 10 +- 3 files changed, 249 insertions(+), 17 deletions(-) create mode 100644 src/pages/ManageUsersPage.tsx diff --git a/src/pages/ManageUsersPage.tsx b/src/pages/ManageUsersPage.tsx new file mode 100644 index 0000000..da69929 --- /dev/null +++ b/src/pages/ManageUsersPage.tsx @@ -0,0 +1,193 @@ +import { useCallback, useEffect, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent} from "@/components/ui/card"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { Trash2 } from "lucide-react"; +import { useAuthStore } from "@/store/authStore"; +import { HotelStaffRole } from "@/lib/types"; +import type { RegisterHotelStaffDto, StaffAccess } from "@/lib/types"; +import { getStaffAccess, postStaff, deleteStaffAccess } from "@/lib/auth-api"; +import { Spinner } from "@/components/ui/spinner"; + +export function ManageUsersPage() { + const token = useAuthStore((s) => s.accessToken); + const selectedPropertyId = useAuthStore((s) => s.selectedPropertyId); + const [staff, setStaff] = useState([]); + const [loading, setLoading] = useState(true); + const [submitting, setSubmitting] = useState(false); + const [open, setOpen] = useState(false); + const [formData, setFormData] = useState({ + name: "", + email: "", + phone: "", + password: "", + hotelRole: HotelStaffRole.FRONT_DESK, + }); + + const loadStaff = useCallback(async () => { + if (!token) return; + setLoading(true); + try { + const data = await getStaffAccess(token); + setStaff(data); + } catch (error) { + console.error("Failed to load staff", error); + } finally { + setLoading(false); + } + }, [token, selectedPropertyId]); + + useEffect(() => { + loadStaff(); + }, [loadStaff]); + + const handleInputChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData({ ...formData, [name]: value }); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!token) return; + setSubmitting(true); + try { + await postStaff(token, formData); + setOpen(false); + setFormData({ name: "", email: "", phone: "", password: "", hotelRole: HotelStaffRole.FRONT_DESK }); + loadStaff(); + } catch (error) { + console.error("Failed to create staff", error); + } finally { + setSubmitting(false); + } + }; + + const handleDelete = async (id: string) => { + if (!token || !confirm("Are you sure you want to delete this user?")) return; + try { + await deleteStaffAccess(token, id); + loadStaff(); + } catch (error) { + console.error("Failed to delete staff", error); + } + }; + + if (loading) return ( +
+ +
+ ); + + return ( +
+
+

Manage Users

+ + + + + + + Add New Staff Member + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+ + + + + + + Name + Email + Phone + Role + Created + Actions + + + + {staff.map((user) => ( + + {user.user.name} + {user.user.email} + {user.user.phone || "-"} + {user.user.role} + {new Date(user.createdAt).toLocaleDateString()} + + + + + ))} + {staff.length === 0 && ( + + + No staff members found. Add one above. + + + )} + +
+
+
+
+ ); +} + diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index 5ddd3bf..fd0f159 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -1,19 +1,54 @@ -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { TAX_RATE } from "@/lib/constants"; +import { ChevronRight, Users } from "lucide-react"; +import { Link } from "react-router-dom"; export function SettingsPage() { + + const menuItems = [ + { + label: "Manage Users", + path: "/settings/users", + icon: , + description: "View and manage hotel staff members" + }, + ]; + return ( -
-

Settings

- - - Property (mock) - - -

Tax rate used in MSW pricing: {(TAX_RATE * 100).toFixed(0)}%

-

Connect real backend, auth, and PSP in a future phase.

-
-
-
+
+
+
+ +
+ +
+
+ {menuItems.map((item) => ( + +
+
+ {item.icon} +
+
+ + {item.label} + + + {item.description} + +
+
+
+ +
+ + ))} +
+
+
+
+
); } diff --git a/src/pages/VisitsPage.tsx b/src/pages/VisitsPage.tsx index 9c7f59f..19cf4a9 100644 --- a/src/pages/VisitsPage.tsx +++ b/src/pages/VisitsPage.tsx @@ -31,9 +31,13 @@ export function VisitsPage() { const refresh = useCallback(async () => { const v = await apiGet<{ - series: { date: string; views: number; sessions: number }[]; + series: Array<{ date: string; count: number }>; }>("/analytics/visits"); - setSeries(v.series.slice(-21)); + setSeries(v.series.map((item) => ({ + date: item.date, + views: item.count || 0, + sessions: 0 + })).slice(-21)); const r = await apiGet<{ data: SiteVisit[] }>("/analytics/visits/recent"); setRecent(r.data); }, []); @@ -45,7 +49,7 @@ export function VisitsPage() { async function simulateHit() { await apiPost("/analytics/visits", { path: "/booking", - device: "desktop", + // device: "desktop", }); await refresh(); }