File size: 7,602 Bytes
191b322 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
import React from 'react';
import { ProjectState } from '../types';
import { Globe, Camera, CheckCircle2, Calendar, TrendingUp, FileText, MessageSquare } from 'lucide-react';
interface ClientPortalProps {
project: ProjectState;
}
const ClientPortal: React.FC<ClientPortalProps> = ({ project }) => {
const completedMilestones = project.milestones.filter(m => m.status === 'COMPLETED');
const progress = (completedMilestones.length / (project.milestones.length || 1)) * 100;
return (
<div className="space-y-8 max-w-5xl mx-auto">
{/* Client Welcome Header */}
<div className="bg-slate-900 rounded-3xl p-8 text-white relative overflow-hidden">
<div className="relative z-10">
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 bg-blue-600 rounded-xl flex items-center justify-center">
<Globe className="w-6 h-6" />
</div>
<span className="text-sm font-bold text-blue-400 uppercase tracking-widest">Client Transparency Portal</span>
</div>
<h1 className="text-3xl font-bold mb-2">Welcome back to {project.name}</h1>
<p className="text-slate-400 max-w-xl">
Track your project's progress, view site photos, and download official reports in real-time.
</p>
</div>
<div className="absolute top-0 right-0 w-64 h-64 bg-blue-600/20 blur-[100px] rounded-full -mr-32 -mt-32"></div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{/* Progress Overview */}
<div className="md:col-span-2 bg-white rounded-2xl border border-slate-200 shadow-sm p-6">
<div className="flex items-center justify-between mb-6">
<h3 className="font-bold text-slate-800">Overall Progress</h3>
<span className="text-2xl font-black text-blue-600">{progress.toFixed(0)}%</span>
</div>
<div className="w-full bg-slate-100 h-4 rounded-full overflow-hidden mb-8">
<div
className="h-full bg-blue-600 transition-all duration-1000"
style={{ width: `${progress}%` }}
/>
</div>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
<div className="p-4 bg-slate-50 rounded-2xl">
<p className="text-[10px] font-bold text-slate-400 uppercase mb-1">Start Date</p>
<p className="text-sm font-bold text-slate-800">{project.startDate}</p>
</div>
<div className="p-4 bg-slate-50 rounded-2xl">
<p className="text-[10px] font-bold text-slate-400 uppercase mb-1">Est. Completion</p>
<p className="text-sm font-bold text-slate-800">{project.endDate}</p>
</div>
<div className="p-4 bg-slate-50 rounded-2xl">
<p className="text-[10px] font-bold text-slate-400 uppercase mb-1">Milestones</p>
<p className="text-sm font-bold text-slate-800">{completedMilestones.length}/{project.milestones.length}</p>
</div>
<div className="p-4 bg-slate-50 rounded-2xl">
<p className="text-[10px] font-bold text-slate-400 uppercase mb-1">Site Photos</p>
<p className="text-sm font-bold text-slate-800">{project.photoLogs?.length || 0}</p>
</div>
</div>
</div>
{/* Quick Actions */}
<div className="bg-white rounded-2xl border border-slate-200 shadow-sm p-6 space-y-4">
<h3 className="font-bold text-slate-800 mb-2">Quick Actions</h3>
<button className="w-full flex items-center justify-between p-3 bg-slate-50 hover:bg-blue-50 rounded-xl transition-all group">
<div className="flex items-center gap-3">
<FileText className="w-4 h-4 text-slate-400 group-hover:text-blue-600" />
<span className="text-sm font-bold text-slate-700">Latest Report</span>
</div>
<CheckCircle2 className="w-4 h-4 text-emerald-500" />
</button>
<button className="w-full flex items-center justify-between p-3 bg-slate-50 hover:bg-blue-50 rounded-xl transition-all group">
<div className="flex items-center gap-3">
<MessageSquare className="w-4 h-4 text-slate-400 group-hover:text-blue-600" />
<span className="text-sm font-bold text-slate-700">Contact Manager</span>
</div>
</button>
</div>
</div>
{/* Recent Site Photos */}
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="font-bold text-slate-800 flex items-center gap-2">
<Camera className="w-5 h-5 text-blue-600" />
Recent Site Photos
</h3>
<button className="text-sm font-bold text-blue-600 hover:underline">View All</button>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{project.photoLogs?.slice(0, 4).map(photo => (
<div key={photo.id} className="aspect-square rounded-2xl overflow-hidden border border-slate-200 shadow-sm group relative">
<img
src={photo.url}
alt={photo.caption}
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
referrerPolicy="no-referrer"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity p-4 flex flex-col justify-end">
<p className="text-white text-[10px] font-medium">{photo.caption}</p>
<p className="text-white/70 text-[8px]">{photo.createdAt}</p>
</div>
</div>
)) || (
<div className="col-span-full py-12 text-center bg-slate-50 rounded-2xl border border-dashed border-slate-300">
<p className="text-slate-400 text-sm">No photos uploaded yet</p>
</div>
)}
</div>
</div>
{/* Milestone Timeline */}
<div className="bg-white rounded-2xl border border-slate-200 shadow-sm p-6">
<h3 className="font-bold text-slate-800 mb-6 flex items-center gap-2">
<Calendar className="w-5 h-5 text-blue-600" />
Project Milestones
</h3>
<div className="space-y-6 relative before:absolute before:left-[11px] before:top-2 before:bottom-2 before:w-0.5 before:bg-slate-100">
{project.milestones.map((milestone, idx) => (
<div key={milestone.id} className="flex gap-4 relative">
<div className={`w-6 h-6 rounded-full border-4 border-white shadow-sm shrink-0 z-10 ${
milestone.status === 'COMPLETED' ? 'bg-emerald-500' :
milestone.status === 'AT_RISK' ? 'bg-red-500' : 'bg-slate-300'
}`} />
<div className="flex-1 pb-6 border-b border-slate-50 last:border-0 last:pb-0">
<div className="flex items-center justify-between mb-1">
<h4 className={`text-sm font-bold ${milestone.status === 'COMPLETED' ? 'text-slate-800' : 'text-slate-500'}`}>
{milestone.title}
</h4>
<span className="text-[10px] font-bold text-slate-400">{milestone.date}</span>
</div>
<p className="text-xs text-slate-500">{milestone.description}</p>
</div>
</div>
))}
</div>
</div>
</div>
);
};
export default ClientPortal;
|