From e1ea974b1806e5dd36f1125f84b24294a7820aa7 Mon Sep 17 00:00:00 2001 From: fullsizemalt <106900403+fullsizemalt@users.noreply.github.com> Date: Fri, 19 Dec 2025 18:24:52 -0800 Subject: [PATCH] feat(ui): Implement AuraUI Bento Grid Dashboard - Added BentoGrid and BentoCard components for modular widget layout - Refactored DashboardPage to use the new command center grid - Improved visual hierarchy with large KPI cards and operational activity feeds - Maintained responsive layout for mobile and desktop --- frontend/src/components/aura/BentoGrid.tsx | 129 +++++++++++ frontend/src/pages/DashboardPage.tsx | 239 ++++++++------------- 2 files changed, 216 insertions(+), 152 deletions(-) create mode 100644 frontend/src/components/aura/BentoGrid.tsx diff --git a/frontend/src/components/aura/BentoGrid.tsx b/frontend/src/components/aura/BentoGrid.tsx new file mode 100644 index 0000000..4f31d02 --- /dev/null +++ b/frontend/src/components/aura/BentoGrid.tsx @@ -0,0 +1,129 @@ +import { ReactNode } from "react"; +import { ArrowRight, LucideIcon } from "lucide-react"; +import { Link } from "react-router-dom"; +import { cn } from "../../lib/utils"; + +/** + * Bento Grid Components - Feature 14 from AuraUI + * + * A responsive, modular grid system for dashboard widgets. + */ + +// --- Grid Container --- +export const BentoGrid = ({ + children, + className, +}: { + children: ReactNode; + className?: string; +}) => { + return ( +
+ {children} +
+ ); +}; + +// --- Grid Item (Card) --- +interface BentoCardProps { + name: string; + className: string; + background?: ReactNode; + Icon: LucideIcon; + description: string; + href: string; + cta?: string; + children?: ReactNode; // For custom content inside the card + value?: string | number; // For metric displays +} + +export const BentoCard = ({ + name, + className, + background, + Icon, + description, + href, + cta = "View Details", + children, + value +}: BentoCardProps) => { + return ( +
+ {/* Background Graphic (optional) */} +
+ {background} +
+ + {/* Content Area */} +
+ + {/* Header */} +
+
+ +
+

+ {name} +

+
+ + {/* Metric Value (if provided) - Large display */} + {value && ( +
+ + {value} + +
+ )} + + {/* Description / Subtext */} +

+ {description} +

+ + {/* Custom Children (Charts, Lists, etc) */} + {children && ( +
+ {children} +
+ )} +
+ + {/* Interactive Footer / CTA */} +
+ + {cta} + + +
+ + {/* Hover Effect Details */} +
+
+ ); +}; diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index 511fd6e..fd920d2 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -1,14 +1,13 @@ import { useState, useEffect } from 'react'; -import { Link } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; import { useToast } from '../context/ToastContext'; -import { Plus, Leaf, ClipboardCheck, AlertTriangle, Activity, Droplet, Salad, Bug, Eye, ArrowRight } from 'lucide-react'; +import { Leaf, ClipboardCheck, Activity, Droplet, Salad, Bug, Eye, CalendarDays, Zap } from 'lucide-react'; import TouchPointModal from '../components/touchpoints/TouchPointModal'; -import TasksDueTodayWidget from '../components/tasks/TasksDueTodayWidget'; import { SmartAlerts } from '../components/dashboard/SmartAlerts'; import { touchPointsApi } from '../lib/touchPointsApi'; import { analyticsApi, AnalyticsOverview } from '../lib/analyticsApi'; import { PullToRefresh } from '../components/ui/PullToRefresh'; +import { BentoGrid, BentoCard } from '../components/aura/BentoGrid'; export default function DashboardPage() { const { user } = useAuth(); @@ -17,7 +16,6 @@ export default function DashboardPage() { const [recentActivity, setRecentActivity] = useState([]); const [overview, setOverview] = useState(null); const [loading, setLoading] = useState(true); - const [activityLoading, setActivityLoading] = useState(true); useEffect(() => { loadData(); @@ -25,7 +23,6 @@ export default function DashboardPage() { const loadData = async () => { setLoading(true); - setActivityLoading(true); try { const [overviewData, activityData] = await Promise.all([ analyticsApi.getOverview(), @@ -38,7 +35,6 @@ export default function DashboardPage() { addToast('Failed to load dashboard data', 'error'); } finally { setLoading(false); - setActivityLoading(false); } }; @@ -47,6 +43,10 @@ export default function DashboardPage() { addToast('Activity logged successfully', 'success'); }; + const handlePullRefresh = async () => { + await loadData(); + }; + const getActivityIcon = (type: string) => { const iconClass = "transition-transform duration-fast"; switch (type) { @@ -58,176 +58,111 @@ export default function DashboardPage() { } }; - const metrics = overview ? [ - { label: 'Active Batches', value: overview.activeBatches.toString(), icon: Leaf, accent: 'accent', link: '/batches' }, - { label: 'Total Plants', value: overview.totalPlants.toString(), icon: Leaf, accent: 'success', link: '/batches' }, - { label: 'Tasks Completed', value: overview.tasksCompletedThisWeek.toString(), icon: ClipboardCheck, accent: 'accent', link: '/tasks' }, - { label: 'Tasks Pending', value: overview.tasksPending.toString(), icon: AlertTriangle, accent: 'warning', link: '/tasks' }, - { label: 'Touch Points', value: overview.touchPointsToday.toString(), icon: Activity, accent: 'accent', link: '/touch-points' }, - { label: 'Rooms', value: overview.totalRooms.toString(), icon: Leaf, accent: 'neutral', link: '/rooms' }, - ] : []; - - const handlePullRefresh = async () => { - await loadData(); - }; - - const getAccentClasses = (accent: string) => { - switch (accent) { - case 'success': return 'bg-success-muted text-success'; - case 'warning': return 'bg-warning-muted text-warning'; - case 'destructive': return 'bg-destructive-muted text-destructive'; - case 'accent': return 'bg-accent-muted text-accent'; - default: return 'bg-tertiary text-secondary'; - } - }; - return ( -
+
{/* Header */} -
+
-

- Welcome back, {user?.name || user?.email?.split('@')[0]} +

+ Command Center

-

+

{new Date().toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' })}

-
-
- Online +
+
- {/* Smart Alerts */} - - - {/* Metrics Grid */} -
- {loading ? ( - Array.from({ length: 6 }).map((_, i) => ( -
-
-
-
-
- )) - ) : ( - metrics.map((m, i) => ( - -
-
- -
- -
-

- {m.value} -

-

- {m.label} -

- - )) - )} -
- -
- {/* Tasks Widget */} - - - {/* Recent Activity */} -
-
-

Recent Activity

- + + {/* 1. Smart Alerts & Overview (Top Left - Large) */} + } + > +
+
+
- {activityLoading ? ( -
- {Array.from({ length: 3 }).map((_, i) => ( -
-
-
-
-
-
-
- ))} -
- ) : recentActivity.length === 0 ? ( -
- -

No recent activity

- -
- ) : ( -
- {recentActivity.map((activity: any) => ( + {/* 2. Active Batches (Top Right - Small) */} + } + /> + + {/* 3. Tasks (Bottom Left - Small) */} + } + /> + + {/* 4. Recent Activity (Bottom Right - Large) */} + +
+ {loading ? ( +
+
+
+
+ ) : recentActivity.length === 0 ? ( +
No recent activity logged.
+ ) : ( + recentActivity.map((activity) => (
-
+
{getActivityIcon(activity.type)}
-

- {activity.type} - +

{activity.batch?.name || 'Unknown Batch'} + — {activity.type}

-

- {activity.user?.name || 'User'} · {new Date(activity.createdAt).toLocaleString()} +

+ {activity.user?.name} · {new Date(activity.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}

- {activity.notes && ( -

- "{activity.notes}" -

- )}
- ))} -
- )} -
-
- - {/* Floating Action Button (Mobile) */} - + )) + )} +
+
+