PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
← All changes | resources/js/components/common/StatCard.tsx +80 -16 3.0.2.9 → 3.0.16 View file →
@@ -1,11 +1,22 @@
1 1 /**
2 - * Stat Card Component
3 - * Ultra-minimal SaaS-style stat card
2 + * Stat Card — KPI tile used on Dashboard and Reports pages.
3 + *
4 + * Two-line content layout: caption (label) on top, value below. A
5 + * coloured pill icon sits flush-right. When period-over-period
6 + * comparison data is available, a small delta line appears under the
7 + * value with up/down arrow + colour-coded percentage and an optional
8 + * "vs {period}" caption (previously hard-coded to "vs last month").
9 + *
10 + * Optional `tooltip` surfaces a `?` icon next to the title; the
11 + * tooltip text appears on hover. Used by Dashboard to disambiguate
12 + * confusable metrics ("Booked Revenue" vs "Collected Revenue") that
13 + * operators routinely mix up.
4 14 */
5 15
6 16 import React from "react";
7 17 import { LucideIcon } from "lucide-react";
18 +import { Tooltip } from "../ui/tooltip";
8 19
9 20 interface StatCardProps {
10 21 title: string;
11 22 value: string | number;
@@ -12,15 +23,21 @@
12 23 icon: LucideIcon;
13 24 trend?: {
14 25 value: number;
15 26 isPositive: boolean;
27 + /** Caption next to the percentage. Defaults to "vs prev. period". */
28 + label?: string;
16 29 };
17 - color?: "blue" | "green" | "purple" | "orange";
30 + color?: "blue" | "green" | "purple" | "orange" | "red";
18 31 loading?: boolean;
32 + /** Hover-tooltip text. Renders a `?` icon next to the title. */
33 + tooltip?: string;
34 + /** Click handler — turns the card into a navigable surface. */
35 + onClick?: () => void;
19 36 }
20 37
21 38 /**
22 - * Stat Card Component - Ultra minimal design
39 + * Stat Card Component — clean SaaS-style KPI tile.
23 40 */
24 41 export const StatCard: React.FC<StatCardProps> = ({
25 42 title,
26 43 value,
@@ -27,20 +44,60 @@
27 44 icon: Icon,
28 45 trend,
29 46 color = "blue",
30 47 loading = false,
48 + tooltip,
49 + onClick,
31 50 }) => {
51 + const interactive = !!onClick;
32 52 return (
33 - <div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-100 dark:border-gray-700/50 p-3 hover:border-gray-200 dark:hover:border-gray-600 transition-colors">
34 - <div className="flex items-start justify-between">
35 - <div className="flex-1">
36 - <p className="text-xs text-gray-500 dark:text-gray-400 mb-1">
37 - {title}
38 - </p>
53 + <div
54 + className={`group relative bg-white dark:bg-gray-800 rounded-lg border border-gray-100 dark:border-gray-700/50 p-3 transition-colors ${
55 + interactive
56 + ? "cursor-pointer hover:border-blue-300 dark:hover:border-blue-600/70"
57 + : "hover:border-gray-200 dark:hover:border-gray-600"
58 + }`}
59 + onClick={onClick}
60 + role={interactive ? "button" : undefined}
61 + tabIndex={interactive ? 0 : undefined}
62 + onKeyDown={
63 + interactive
64 + ? (e) => {
65 + if (e.key === "Enter" || e.key === " ") {
66 + e.preventDefault();
67 + onClick?.();
68 + }
69 + }
70 + : undefined
71 + }
72 + >
73 + <div className="flex items-start justify-between gap-2">
74 + <div className="flex-1 min-w-0">
75 + <div className="flex items-center gap-1.5 mb-1">
76 + <p className="text-xs text-gray-500 dark:text-gray-400 truncate">
77 + {title}
78 + </p>
79 + {tooltip && (
80 + // Real hover-popover (see components/ui/tooltip.tsx). Native
81 + // `title=` was previously used here but had a ~700ms delay
82 + // and an easy-to-miss OS-default style — operators reported
83 + // the `?` icon "didn't show anything". The new tooltip
84 + // appears instantly on hover/focus and matches admin theme.
85 + <Tooltip content={tooltip} side="top">
86 + <span
87 + className="inline-flex h-3.5 w-3.5 shrink-0 cursor-help items-center justify-center rounded-full bg-gray-100 text-[10px] font-bold leading-none text-gray-500 transition-colors hover:bg-gray-200 hover:text-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-gray-100"
88 + aria-label={typeof tooltip === "string" ? tooltip : undefined}
89 + role="img"
90 + >
91 + ?
92 + </span>
93 + </Tooltip>
94 + )}
95 + </div>
39 96 {loading ? (
40 97 <div className="h-6 w-20 bg-gray-100 dark:bg-gray-700 rounded animate-pulse" />
41 98 ) : (
42 - <p className="text-xl font-semibold text-gray-900 dark:text-white">
99 + <p className="text-xl font-semibold tabular-nums text-gray-900 dark:text-white truncate">
43 100 {value}
44 101 </p>
45 102 )}
46 103 {trend && !loading && (
@@ -45,19 +102,24 @@
45 102 )}
46 103 {trend && !loading && (
47 104 <div className="flex items-center gap-1 mt-2">
48 105 <span
49 - className={`text-xs font-medium ${trend.isPositive ? "text-green-600 dark:text-green-400" : "text-red-600 dark:text-red-400"}`}
106 + className={`text-xs font-medium tabular-nums ${
107 + trend.isPositive
108 + ? "text-green-600 dark:text-green-400"
109 + : "text-red-600 dark:text-red-400"
110 + }`}
50 111 >
51 - {trend.isPositive ? "↑" : "↓"} {Math.abs(trend.value)}%
112 + {trend.isPositive ? "↑" : "↓"}{" "}
113 + {Math.abs(trend.value).toFixed(1)}%
52 114 </span>
53 115 <span className="text-xs text-gray-400 dark:text-gray-500">
54 - vs last month
116 + {trend.label || "vs prev. period"}
55 117 </span>
56 118 </div>
57 119 )}
58 120 </div>
59 - <div className={`p-2.5 rounded-lg bg-gray-50 dark:bg-gray-700/50`}>
121 + <div className="p-2.5 rounded-lg bg-gray-50 dark:bg-gray-700/50 shrink-0">
60 122 <Icon
61 123 className={`w-4 h-4 ${
62 124 color === "blue"
63 125 ? "text-blue-600 dark:text-blue-400"
@@ -64,9 +126,11 @@
64 126 : color === "green"
65 127 ? "text-green-600 dark:text-green-400"
66 128 : color === "purple"
67 129 ? "text-purple-600 dark:text-purple-400"
68 - : "text-orange-600 dark:text-orange-400"
130 + : color === "red"
131 + ? "text-red-600 dark:text-red-400"
132 + : "text-orange-600 dark:text-orange-400"
69 133 }`}
70 134 />
71 135 </div>
72 136 </div>