dummy data removed
This commit is contained in:
parent
4d229d0b94
commit
4ed7161a33
|
|
@ -24,9 +24,11 @@ import {
|
|||
} from "@/components/ui/table";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import { apiGet } from "@/lib/api";
|
||||
import type { Booking, DashboardPayload } from "@/lib/types";
|
||||
import { formatDate, formatMoney } from "@/lib/format";
|
||||
import { roomDisplayName } from "@/lib/room-utils";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { useAuthStore } from "@/store/authStore";
|
||||
import type { Booking, DashboardPayload } from "@/lib/types";
|
||||
|
||||
const tooltipStyle = {
|
||||
backgroundColor: "var(--navy)",
|
||||
|
|
@ -35,18 +37,111 @@ const tooltipStyle = {
|
|||
color: "#fff",
|
||||
};
|
||||
|
||||
type HotelSummaryResponse = {
|
||||
arrivalsToday?: number;
|
||||
arrivals?: number;
|
||||
departuresToday?: number;
|
||||
departures?: number;
|
||||
unpaidHolds?: number;
|
||||
revenueMonth?: string | number;
|
||||
bookingsByStatus?: Record<string, number>;
|
||||
};
|
||||
|
||||
function isDashboardPayload(d: unknown): d is DashboardPayload {
|
||||
return typeof d === "object" && d !== null && "bookingSeries" in d;
|
||||
}
|
||||
|
||||
function isHotelSummary(d: unknown): d is HotelSummaryResponse {
|
||||
if (typeof d !== "object" || d === null) return false;
|
||||
const x = d as HotelSummaryResponse;
|
||||
return (
|
||||
typeof x.arrivalsToday === "number" ||
|
||||
typeof x.arrivals === "number" ||
|
||||
x.bookingsByStatus !== undefined
|
||||
);
|
||||
}
|
||||
|
||||
export function DashboardPage() {
|
||||
const [data, setData] = useState<DashboardPayload | null>(null);
|
||||
const selectedPropertyId = useAuthStore((s) => s.selectedPropertyId);
|
||||
const [data, setData] = useState<DashboardPayload | HotelSummaryResponse | null>(
|
||||
null
|
||||
);
|
||||
const [err, setErr] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
apiGet<DashboardPayload>("/dashboard")
|
||||
setErr(null);
|
||||
setData(null);
|
||||
apiGet<DashboardPayload | HotelSummaryResponse>(`/dashboard/summary`)
|
||||
.then(setData)
|
||||
.catch((e) => setErr(String(e)));
|
||||
}, []);
|
||||
}, [selectedPropertyId]);
|
||||
|
||||
if (err) return <p className="text-destructive">{err}</p>;
|
||||
if (!data) return <p className="text-muted-foreground">Loading…</p>;
|
||||
if (!data) return (
|
||||
<div className="flex min-h-[400px] items-center justify-center">
|
||||
<Spinner size={32} />
|
||||
</div>
|
||||
);
|
||||
|
||||
if (isHotelSummary(data) && !isDashboardPayload(data)) {
|
||||
const arrivals = data.arrivalsToday ?? data.arrivals ?? 0;
|
||||
const departures = data.departuresToday ?? data.departures ?? 0;
|
||||
const revenueRaw = data.revenueMonth ?? 0;
|
||||
const revenueNum =
|
||||
typeof revenueRaw === "string" ? parseFloat(revenueRaw) : Number(revenueRaw);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">Dashboard</h1>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{[
|
||||
{ label: "Arrivals today", value: arrivals },
|
||||
{ label: "Departures today", value: departures },
|
||||
{ label: "Unpaid holds", value: data.unpaidHolds ?? 0 },
|
||||
{
|
||||
label: "Revenue (month)",
|
||||
value: formatMoney(Number.isFinite(revenueNum) ? revenueNum : 0),
|
||||
},
|
||||
].map((c) => (
|
||||
<Card key={c.label} className="rounded-2xl">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">
|
||||
{c.label}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-2xl font-bold">{c.value}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
{data.bookingsByStatus && Object.keys(data.bookingsByStatus).length > 0 ? (
|
||||
<Card className="rounded-2xl">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">Bookings by status</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-wrap gap-2">
|
||||
{Object.entries(data.bookingsByStatus).map(([k, v]) => (
|
||||
<Badge key={k} variant="secondary">
|
||||
{k}: {v}
|
||||
</Badge>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDashboardPayload(data)) {
|
||||
return (
|
||||
<p className="text-muted-foreground">
|
||||
Unexpected dashboard response. Try again or check the API.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
|
|
@ -263,7 +358,7 @@ export function DashboardPage() {
|
|||
{formatDate(b.checkIn)} → {formatDate(b.checkOut)}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs">
|
||||
{roomDisplayName(b.roomId)}
|
||||
{b.roomDisplayLabel ?? roomDisplayName(b.roomId)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="secondary">{b.status}</Badge>
|
||||
|
|
|
|||
|
|
@ -11,15 +11,17 @@ import {
|
|||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { apiGet } from "@/lib/api";
|
||||
import { useAuthStore } from "@/store/authStore";
|
||||
import type { Payment } from "@/lib/types";
|
||||
import { formatDateTime, formatMoney } from "@/lib/format";
|
||||
|
||||
export function PaymentsPage() {
|
||||
const selectedPropertyId = useAuthStore((s) => s.selectedPropertyId);
|
||||
const [rows, setRows] = useState<Payment[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
apiGet<{ data: Payment[] }>("/payments").then((r) => setRows(r.data));
|
||||
}, []);
|
||||
}, [selectedPropertyId]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
|
|
|
|||
|
|
@ -11,17 +11,19 @@ import {
|
|||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { apiGet } from "@/lib/api";
|
||||
import { useAuthStore } from "@/store/authStore";
|
||||
import type { Transaction } from "@/lib/types";
|
||||
import { formatDateTime, formatMoney } from "@/lib/format";
|
||||
|
||||
export function TransactionsPage() {
|
||||
const selectedPropertyId = useAuthStore((s) => s.selectedPropertyId);
|
||||
const [rows, setRows] = useState<Transaction[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
apiGet<{ data: Transaction[] }>("/transactions").then((r) =>
|
||||
setRows(r.data)
|
||||
);
|
||||
}, []);
|
||||
}, [selectedPropertyId]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user