feat: Multi-role platform expansion, mobile nav redesign, and UI polish

- Add Owner, Contractor, Vendor, Subcontractor dashboards and role-based routing
- Owner now has superuser access to all Admin pages (dashboard, schedule, leaderboard)
- Redesign landing page mobile menu as slide-in sidebar (replaces broken Framer Motion overlay)
- Add body scroll lock to app sidebar for mobile consistency
- Fix Team Schedule to match Admin Dashboard design language (ambient glows, gradient header, SpotlightCard, zinc palette)
- Fix login page tab overflow — use abbreviated labels and grid layout for 6 role tabs
- Fix Recharts ResponsiveContainer warnings — replace height="100%" with fixed pixel heights
- Fix lightbox image viewer — unified nav bar, touch swipe support, no more overlapping text
- Add AI Assistant page, People/Vendor/Document management for Owner role
- Expand mock data store with contractor, vendor, and subcontractor data
This commit is contained in:
Satyam
2026-02-17 01:19:41 +05:30
parent 35cbdeb33c
commit 2eaac6b84a
44 changed files with 6857 additions and 662 deletions
@@ -46,8 +46,8 @@ export const GoldenLeadsScatter = ({ properties }) => {
</div>
</div>
<div className="flex-1 w-full min-h-[200px]">
<ResponsiveContainer width="100%" height="100%">
<div className="w-full">
<ResponsiveContainer width="100%" height={200}>
<ScatterChart
margin={{ top: 20, right: 20, bottom: 20, left: 0 }}
>
@@ -61,6 +61,19 @@ const IntelligenceSidePanel = ({ data, onClose, variant = 'default' }) => {
return () => document.removeEventListener('keydown', handleLightboxKeys);
}, [lightboxIndex, photos.length]);
// Lightbox Touch Swipe Navigation
const touchStartX = useRef(null);
const handleTouchStart = (e) => { touchStartX.current = e.touches[0].clientX; };
const handleTouchEnd = (e) => {
if (touchStartX.current === null) return;
const diff = touchStartX.current - e.changedTouches[0].clientX;
if (Math.abs(diff) > 50) {
if (diff > 0) setLightboxIndex((prev) => (prev + 1) % photos.length);
else setLightboxIndex((prev) => (prev - 1 + photos.length) % photos.length);
}
touchStartX.current = null;
};
if (!data) return null;
return (
@@ -303,6 +316,9 @@ const IntelligenceSidePanel = ({ data, onClose, variant = 'default' }) => {
ref={lightboxRef}
role="dialog"
aria-label="Photo Lightbox"
data-lenis-prevent
onTouchStart={handleTouchStart}
onTouchEnd={handleTouchEnd}
>
{/* Close Button */}
<button
@@ -313,41 +329,34 @@ const IntelligenceSidePanel = ({ data, onClose, variant = 'default' }) => {
<X size={24} />
</button>
{/* Navigation Left */}
<button
onClick={(e) => { e.stopPropagation(); setLightboxIndex((prev) => (prev - 1 + photos.length) % photos.length); }}
className="absolute left-4 text-cyan-400/70 hover:text-cyan-400 p-3 rounded-full hover:bg-white/10 transition-colors z-50 focus:ring-2 focus:ring-cyan-400 hidden md:block"
aria-label="Previous photo"
>
<ChevronLeft size={32} />
</button>
{/* Main Image */}
<div className="relative max-w-5xl max-h-[85vh] w-full h-full flex flex-col items-center justify-center">
<div className="relative max-w-5xl max-h-[75vh] w-full h-full flex flex-col items-center justify-center">
<img
src={photos[lightboxIndex].url}
alt={photos[lightboxIndex].caption || "Full screen view"}
className="max-w-full max-h-full object-contain rounded-lg shadow-2xl border border-white/10"
/>
<div className="absolute bottom-[-40px] text-cyan-400/50 text-xs uppercase tracking-widest font-mono">
IMG_SEQ: {lightboxIndex + 1} / {photos.length}
</div>
</div>
{/* Navigation Right */}
<button
onClick={(e) => { e.stopPropagation(); setLightboxIndex((prev) => (prev + 1) % photos.length); }}
className="absolute right-4 text-cyan-400/70 hover:text-cyan-400 p-3 rounded-full hover:bg-white/10 transition-colors z-50 focus:ring-2 focus:ring-cyan-400 hidden md:block"
aria-label="Next photo"
>
<ChevronRight size={32} />
</button>
{/* Mobile Navigation Hints */}
<div className="md:hidden absolute bottom-8 flex space-x-8 text-cyan-400/30 text-xs pointer-events-none font-mono uppercase tracking-widest">
<span>&lt; PREV</span>
<span>NEXT &gt;</span>
{/* Navigation Bar — ← IMG_SEQ: 2/3 → */}
<div className="absolute bottom-6 left-1/2 -translate-x-1/2 flex items-center gap-3 z-50 bg-black/60 rounded-full px-2 py-1.5 backdrop-blur-sm border border-white/5">
<button
onClick={(e) => { e.stopPropagation(); setLightboxIndex((prev) => (prev - 1 + photos.length) % photos.length); }}
className="text-cyan-400/70 hover:text-cyan-400 p-2.5 rounded-full hover:bg-white/10 transition-colors active:bg-white/20"
aria-label="Previous photo"
>
<ChevronLeft size={22} />
</button>
<span className="text-cyan-400/50 text-xs uppercase tracking-widest font-mono select-none whitespace-nowrap px-1">
IMG_SEQ: {lightboxIndex + 1}/{photos.length}
</span>
<button
onClick={(e) => { e.stopPropagation(); setLightboxIndex((prev) => (prev + 1) % photos.length); }}
className="text-cyan-400/70 hover:text-cyan-400 p-2.5 rounded-full hover:bg-white/10 transition-colors active:bg-white/20"
aria-label="Next photo"
>
<ChevronRight size={22} />
</button>
</div>
</div>
)}
@@ -43,8 +43,8 @@ export const OwnerIntentFunnel = ({ properties }) => {
<p className="text-xs text-zinc-500 dark:text-zinc-400">Conversion pipeline</p>
</div>
<div className="flex-1 w-full min-h-[200px]">
<ResponsiveContainer width="100%" height="100%">
<div className="w-full">
<ResponsiveContainer width="100%" height={200}>
<BarChart
layout="vertical"
data={data}
@@ -46,8 +46,8 @@ export const RevenueByNeighborhood = ({ properties }) => {
<p className="text-xs text-zinc-500 dark:text-zinc-400">By Neighborhood Rating</p>
</div>
<div className="flex-1 w-full min-h-[200px]">
<ResponsiveContainer width="100%" height="100%">
<div className="w-full">
<ResponsiveContainer width="100%" height={200}>
<BarChart
data={data}
margin={{ top: 10, right: 10, left: -20, bottom: 0 }}
@@ -63,8 +63,8 @@ export const RoofConditionChart = ({ properties }) => {
</div>
</div>
<div className="flex-1 w-full min-h-[250px] relative">
<ResponsiveContainer width="100%" height="100%">
<div className="w-full relative" style={{ height: 250 }}>
<ResponsiveContainer width="100%" height={250}>
<PieChart>
<Pie
data={data}
@@ -56,7 +56,7 @@ export const WeatherRiskGauge = ({ properties, weather }) => {
<div className="relative w-full h-[140px] flex items-end justify-center">
{/* Speedometer Chart */}
<ResponsiveContainer width="100%" height="100%">
<ResponsiveContainer width="100%" height={140}>
<PieChart>
<Pie
data={data}