From 7e166726da65f36a9f496e63b67b9f2c6fea787e Mon Sep 17 00:00:00 2001 From: Satyam <95536056+Satyam-Rastogi@users.noreply.github.com> Date: Fri, 20 Feb 2026 13:32:44 +0530 Subject: [PATCH] feat: UI/UX updates - Sidebar, ProCanvas, Dashboard, Schedule, Map --- src/App.jsx | 6 + src/components/IntelligenceMap.jsx | 5 + src/components/Layout.jsx | 43 ++- .../dashboard/TopSalesLeaderboard.jsx | 84 ++++ src/pages/AdminSchedule.jsx | 359 +++++++++++++----- src/pages/Dashboard.jsx | 6 +- src/pages/ProCanvas.jsx | 26 ++ 7 files changed, 412 insertions(+), 117 deletions(-) create mode 100644 src/components/dashboard/TopSalesLeaderboard.jsx create mode 100644 src/pages/ProCanvas.jsx diff --git a/src/App.jsx b/src/App.jsx index 1692295..173c825 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -20,6 +20,7 @@ import DocumentManagement from './pages/owner/DocumentManagement'; import OwnerProjectList from './pages/owner/OwnerProjectList'; import OwnerProjectDetail from './pages/owner/OwnerProjectDetail'; import PlaceholderDashboard from './pages/PlaceholderDashboard'; +import ProCanvas from './pages/ProCanvas'; import ProjectList from './pages/contractor/ProjectList'; import AiAssistantPage from './pages/AiAssistantPage'; // New Dashboards @@ -124,6 +125,11 @@ function App() { } /> + + + + } /> {/* Protected Admin Routes — Owner has full admin access */} { {/* Larger invisible touch target */}
+ {/* Urgent Status Alert Pulse */} + {visible && spot.status === 'Urgent' && ( +
+ )} + {/* Radar Ripple */} {visible && (
{ {label} - {'S'}{'.'}{'a'}{'.'}{'t'}{'.'}{'y'}{'.'}{'a'}{'.'}{'m'}{'.'} + {'S'}{'.'}{'y'}{'.'}{'s'}{'.'}{'t'}{'.'}{'e'}{'.'}{'m'}{'.'} {/* Collapsed Tooltip */} @@ -127,24 +127,26 @@ const Layout = () => { if (!user) return []; const commonItems = [ - { to: "/", icon: Home, label: "Home" }, { to: "/chat-assistant", icon: MessageSquare, label: "AI Assistant" }, ]; + const homeItem = { to: "/", icon: Home, label: "Home" }; + switch (user.role) { case 'OWNER': return [ - { to: "/owner/snapshot", icon: LayoutDashboard, label: "Dashboard" }, + { to: "/owner/snapshot", icon: LayoutDashboard, label: "Owners Box" }, + { to: "/admin/dashboard", icon: LayoutDashboard, label: "Dashboard" }, { to: "/owner/projects", icon: Briefcase, label: "Projects" }, - { to: "/owner/vendors", icon: Users, label: "Vendors" }, - { to: "/owner/people", icon: User, label: "People" }, - { to: "/owner/documents", icon: FileText, label: "Documents" }, { to: "/owner/maps", icon: Map, label: "Territory Map" }, - // Admin pages — Owner is superuser - { to: "/admin/dashboard", icon: LayoutDashboard, label: "Admin Panel" }, + { + to: "/owner/pro-canvas", + icon: LayoutDashboard, // Placeholder icon, maybe change later + label: ProCanvas + }, { to: "/admin/schedule", icon: Calendar, label: "Team Schedule" }, { to: "/admin/leaderboard", icon: Trophy, label: "Leaderboard" }, - ...commonItems + ...commonItems, ]; case 'ADMIN': return [ @@ -152,31 +154,31 @@ const Layout = () => { { to: "/admin/schedule", icon: Calendar, label: "Schedule" }, { to: "/admin/leaderboard", icon: Trophy, label: "Leaderboard" }, { to: "/admin/maps", icon: Map, label: "Territory Map" }, - ...commonItems + ...commonItems, ]; case 'CONTRACTOR': case 'SUBCONTRACTOR': return [ { to: user.role === 'CONTRACTOR' ? "/contractor/dashboard" : "/subcontractor/dashboard", icon: LayoutDashboard, label: "Dashboard" }, { to: user.role === 'CONTRACTOR' ? "/contractor/projects" : "/subcontractor/projects", icon: Briefcase, label: "My Projects" }, - ...commonItems + ...commonItems, ]; case 'VENDOR': return [ { to: "/vendor/dashboard", icon: LayoutDashboard, label: "Dashboard" }, { to: "/vendor/orders", icon: Briefcase, label: "Orders" }, - ...commonItems + ...commonItems, ]; case 'FIELD_AGENT': return [ { to: "/emp/fa/dashboard", icon: LayoutDashboard, label: "Dashboard" }, { to: "/emp/fa/maps", icon: Map, label: "My Map" }, - ...commonItems + ...commonItems, ]; default: // Customer or Fallback return [ { to: "/customer/profile", icon: LayoutDashboard, label: "Dashboard" }, - ...commonItems + ...commonItems, ]; } }; @@ -261,7 +263,18 @@ const Layout = () => { {/* 3. User Profile & Footer */} -
+
+ {/* Explicit Home Item */} + setIsMobileMenuOpen(false)} + /> + + {/* Divider */} +
{/* Avatar */}
diff --git a/src/components/dashboard/TopSalesLeaderboard.jsx b/src/components/dashboard/TopSalesLeaderboard.jsx new file mode 100644 index 0000000..8e17ef2 --- /dev/null +++ b/src/components/dashboard/TopSalesLeaderboard.jsx @@ -0,0 +1,84 @@ +import React from 'react'; +import { SpotlightCard } from '../SpotlightCard'; +import { Trophy, ChevronRight, User } from 'lucide-react'; +import { Link } from 'react-router-dom'; + +const MOCK_TOP_REPS = [ + { id: 1, name: "Sarah Connor", sales: "$1.2M", deals: 42, avatar: null }, + { id: 2, name: "Michael Ross", sales: "$980K", deals: 35, avatar: null }, + { id: 3, name: "Jessica Pearson", sales: "$850K", deals: 28, avatar: null }, + { id: 4, name: "Harvey Specter", sales: "$720K", deals: 24, avatar: null }, + { id: 5, name: "Louis Litt", sales: "$640K", deals: 20, avatar: null }, + { id: 6, name: "Donna Paulsen", sales: "$590K", deals: 18, avatar: null }, +]; + +export const TopSalesLeaderboard = () => { + return ( + +
+
+
+
+ +
+

Top Sales Reps

+
+ + View All + +
+ +
+ {MOCK_TOP_REPS.map((rep, index) => ( + +
+
+ {index + 1} +
+ +
+ {rep.avatar ? ( + {rep.name} + ) : ( + + )} +
+ +
+
+ {rep.name} +
+
+ {rep.deals} Deals +
+
+
+ +
+
+ {rep.sales} +
+
+ + ))} +
+
+ + {/* Subtle bottom gradient for scroll indication */} +
+ + ); +}; diff --git a/src/pages/AdminSchedule.jsx b/src/pages/AdminSchedule.jsx index 5475b6b..3577f87 100644 --- a/src/pages/AdminSchedule.jsx +++ b/src/pages/AdminSchedule.jsx @@ -122,6 +122,23 @@ const AdminSchedule = () => { setOpenDropdownId(null); }; + const [viewMode, setViewMode] = useState('list'); // 'list' or 'calendar' + + // Mock color mapping for agents + const agentColors = { + 'a1': 'bg-blue-100 text-blue-700 border-blue-200 dark:bg-blue-900/30 dark:text-blue-300 dark:border-blue-800', + 'a2': 'bg-emerald-100 text-emerald-700 border-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-300 dark:border-emerald-800', + 'a3': 'bg-purple-100 text-purple-700 border-purple-200 dark:bg-purple-900/30 dark:text-purple-300 dark:border-purple-800', + 'a4': 'bg-amber-100 text-amber-700 border-amber-200 dark:bg-amber-900/30 dark:text-amber-300 dark:border-amber-800', + 'a5': 'bg-pink-100 text-pink-700 border-pink-200 dark:bg-pink-900/30 dark:text-pink-300 dark:border-pink-800', + }; + + const getAgentColor = (agentId) => { + // Simple hash or lookup + const id = agentId?.toLowerCase() || 'a1'; + return agentColors[id] || agentColors['a1']; + }; + return (
{/* Ambient Background Glows */} @@ -132,12 +149,36 @@ const AdminSchedule = () => { {/* Content */}
-
-

Team Schedule

-

Manage field agent appointments

+
+
+

Team Schedule

+

Manage field agent appointments

+
+ + {/* View Toggle */} +
+ + +
- {/* Filters Section */} + {/* Filters Section (Only show relevant filters for list view potentially, or both) */}
{/* Date Filter */}
@@ -267,108 +308,112 @@ const AdminSchedule = () => {

- All Active Appointments + {viewMode === 'list' ? 'All Active Appointments' : 'Calendar Overview'}

-
- - - - - - - - - - - - - {filteredMeetings.length === 0 ? ( + {viewMode === 'list' ? ( +
+
AgentCustomerPropertyDate & TimeStatusActions
+ - + + + + + + - ) : ( - filteredMeetings.map((meeting) => ( - - - - - - - + + {filteredMeetings.length === 0 ? ( + + - )) - )} - -
- No appointments found matching the selected filters. - AgentCustomerPropertyDate & TimeStatusActions
-
-
- {meeting.agentId} -
- Agent {meeting.agentId} -
-
{meeting.customerName}{meeting.propertyId} -
- {meeting.date} - {meeting.time} -
-
- - {meeting.status} - - -
- -
- - - {openDropdownId === meeting.id && ( -
- - - - -
- )} -
-
+
+ No appointments found matching the selected filters.
-
+ ) : ( + filteredMeetings.map((meeting) => ( + + +
+
+ {meeting.agentId} +
+ Agent {meeting.agentId} +
+ + {meeting.customerName} + {meeting.propertyId} + +
+ {meeting.date} + {meeting.time} +
+ + + + {meeting.status} + + + +
+ +
+ + + {openDropdownId === meeting.id && ( +
+ + + + +
+ )} +
+
+ + + )) + )} + + +
+ ) : ( + + )}
igotsar.matyas | LynkedUpPro - Turns out roofing CRMs don't build themselves. Who knew? @@ -376,4 +421,120 @@ const AdminSchedule = () => { ); }; +// --- Custom Month View Calendar --- +const CalendarView = ({ meetings, getAgentColor }) => { + // Current month view state + const [currentDate, setCurrentDate] = useState(new Date()); + + const getDaysInMonth = (date) => { + const year = date.getFullYear(); + const month = date.getMonth(); + const days = new Date(year, month + 1, 0).getDate(); + const firstDay = new Date(year, month, 1).getDay(); + return { days, firstDay }; + }; + + const { days, firstDay } = useMemo(() => getDaysInMonth(currentDate), [currentDate]); + + const changeMonth = (offset) => { + setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() + offset, 1)); + }; + + // Group meetings by date (using local date string YYYY-MM-DD) + const meetingsByDate = useMemo(() => { + const map = {}; + meetings.forEach(m => { + // Assuming m.date is YYYY-MM-DD or parseable + const d = new Date(m.date); + // We need to compare local dates + const key = d.getDate(); // 1-31 + const monthMatches = d.getMonth() === currentDate.getMonth() && d.getFullYear() === currentDate.getFullYear(); + + if (monthMatches) { + if (!map[key]) map[key] = []; + map[key].push(m); + } + }); + return map; + }, [meetings, currentDate]); + + const renderCalendarCells = () => { + const cells = []; + // Empty cells for padding before the first day + for (let i = 0; i < firstDay; i++) { + cells.push(
); + } + + // Days + for (let d = 1; d <= days; d++) { + const dayMeetings = meetingsByDate[d] || []; + const isToday = new Date().toDateString() === new Date(currentDate.getFullYear(), currentDate.getMonth(), d).toDateString(); + + cells.push( +
+
+ + {d} + +
+
+ {dayMeetings.map(m => ( +
+ {m.time} + {m.customerName} +
+ ))} +
+
+ ); + } + return cells; + }; + + return ( +
+ {/* Calendar Header */} +
+

+ {currentDate.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })} +

+
+ + + +
+
+ + {/* Calendar Grid */} +
+ {/* Weekday Headers */} +
+ {['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map(day => ( +
+ {day} +
+ ))} +
+ {/* Days Grid */} +
+ {renderCalendarCells()} +
+
+ + {/* Legend */} +
+
Agent A1
+
Agent A2
+
Agent A3
+
Agent A4
+
Agent A5
+
+
+ ); +}; + export default AdminSchedule; diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx index aff44aa..1162e26 100644 --- a/src/pages/Dashboard.jsx +++ b/src/pages/Dashboard.jsx @@ -11,7 +11,7 @@ import { config } from '../config/env'; // New Widget Imports import { RoofConditionChart } from '../components/dashboard/RoofConditionChart'; -import { OwnerIntentFunnel } from '../components/dashboard/OwnerIntentFunnel'; +import { TopSalesLeaderboard } from '../components/dashboard/TopSalesLeaderboard'; import { RevenueByNeighborhood } from '../components/dashboard/RevenueByNeighborhood'; import { GoldenLeadsScatter } from '../components/dashboard/GoldenLeadsScatter'; import { ActionCenterWidget } from '../components/dashboard/ActionCenterWidget'; @@ -220,9 +220,9 @@ const Dashboard = () => {
- {/* Owner Intent Funnel */} + {/* Top 6 Sales Reps Leaderboard */}
- +
diff --git a/src/pages/ProCanvas.jsx b/src/pages/ProCanvas.jsx new file mode 100644 index 0000000..71c6bed --- /dev/null +++ b/src/pages/ProCanvas.jsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Construction } from 'lucide-react'; +import { SpotlightCard } from '../components/SpotlightCard'; + +const ProCanvas = () => { + return ( +
+ +
+ +
+

+ ProCanvas +

+

+ This module is currently under construction. +

+
+

STATUS: DEVELOPMENT_IN_PROGRESS

+
+
+
+ ); +}; + +export default ProCanvas;