Codex Deploy
Prepare local Hugging Face deployment
191b322
import React, { useState } from 'react';
import { ProjectState, UserRole, Priority } from '../types';
import {
PlusCircle,
Building2,
Calendar,
ArrowRight,
Activity,
DollarSign,
UserCircle,
Lock,
Flag
} from 'lucide-react';
interface ProjectListProps {
projects: ProjectState[];
onSelectProject: (projectId: string) => void;
onCreateProject: (project: Partial<ProjectState>) => void;
userRole: UserRole;
onSwitchRole: (role: UserRole) => void;
}
const ProjectList: React.FC<ProjectListProps> = ({ projects, onSelectProject, onCreateProject, userRole, onSwitchRole }) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [newProjectName, setNewProjectName] = useState('');
const [contractValue, setContractValue] = useState('');
const [priority, setPriority] = useState<Priority>('MEDIUM');
const canCreateProject = userRole === 'DIRECTOR';
console.log('Current projects IDs:', projects.map(p => p.id));
const handleCreate = (e: React.FormEvent) => {
e.preventDefault();
onCreateProject({
name: newProjectName,
contractValue: Number(contractValue),
priority,
startDate: new Date().toISOString().split('T')[0],
endDate: new Date().toISOString().split('T')[0],
status: 'ACTIVE',
boq: [],
dprs: [],
bills: [],
liabilities: [],
documents: []
});
setIsModalOpen(false);
setNewProjectName('');
setContractValue('');
setPriority('MEDIUM');
};
const getRoleLabel = (role: UserRole) => {
switch(role) {
case 'DIRECTOR': return 'Project Director';
case 'MANAGER': return 'Project Manager';
case 'ENGINEER': return 'Site Engineer';
case 'ACCOUNTANT': return 'Accountant';
default: return role;
}
};
const getPriorityColor = (p: Priority) => {
switch(p) {
case 'HIGH': return 'bg-red-50 text-red-700 border-red-200';
case 'MEDIUM': return 'bg-amber-50 text-amber-700 border-amber-200';
case 'LOW': return 'bg-blue-50 text-blue-700 border-blue-200';
default: return 'bg-slate-50 text-slate-700 border-slate-200';
}
};
return (
<div className="p-6 md:p-10 space-y-8 max-w-7xl mx-auto">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<h1 className="text-3xl font-bold text-slate-800">My Projects</h1>
<p className="text-slate-500 mt-1">Manage your construction portfolio across different sites.</p>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2 px-3 py-2 bg-white border border-slate-200 rounded-lg text-sm font-medium">
<UserCircle className="w-5 h-5 text-slate-400" />
<span>{getRoleLabel(userRole)}</span>
</div>
{canCreateProject ? (
<button
onClick={() => setIsModalOpen(true)}
className="flex items-center gap-2 bg-blue-600 text-white px-5 py-2.5 rounded-lg hover:bg-blue-700 transition-colors shadow-sm font-medium"
>
<PlusCircle className="w-5 h-5" />
Create New Project
</button>
) : (
<div className="flex items-center gap-2 text-slate-400 bg-slate-100 px-4 py-2.5 rounded-lg text-sm font-medium">
<Lock className="w-4 h-4" />
Create Disabled
</div>
)}
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{(() => {
const seen = new Set();
const duplicates = projects.filter(p => {
if (seen.has(p.id)) return true;
seen.add(p.id);
return false;
});
if (duplicates.length > 0) {
console.warn('Duplicate project IDs found:', duplicates.map(d => d.id));
}
return null;
})()}
{projects.map((project, index) => (
<div
key={`${project.id}-${index}`}
onClick={() => onSelectProject(project.id)}
className="group bg-white rounded-xl border border-slate-200 shadow-sm hover:shadow-md transition-all cursor-pointer overflow-hidden relative"
>
<div className={`absolute top-0 left-0 w-1 h-full ${project.status === 'ACTIVE' ? 'bg-emerald-500' : 'bg-slate-300'}`}></div>
<div className="p-6">
<div className="flex justify-between items-start mb-4">
<div className="p-3 bg-blue-50 text-blue-600 rounded-lg group-hover:bg-blue-600 group-hover:text-white transition-colors">
<Building2 className="w-6 h-6" />
</div>
<div className="flex flex-col items-end gap-1.5">
<div className={`px-2.5 py-0.5 rounded-full text-[10px] font-bold border uppercase tracking-wider ${
project.status === 'ACTIVE'
? 'bg-emerald-50 text-emerald-700 border-emerald-200'
: 'bg-slate-100 text-slate-600 border-slate-200'
}`}>
{project.status}
</div>
<div className={`px-2.5 py-0.5 rounded-full text-[10px] font-bold border uppercase tracking-wider flex items-center gap-1 ${getPriorityColor(project.priority)}`}>
<Flag className="w-2.5 h-2.5" />
{project.priority}
</div>
</div>
</div>
<h3 className="text-lg font-bold text-slate-800 mb-2 line-clamp-2 min-h-[3.5rem]">
{project.name}
</h3>
<div className="space-y-3 text-sm text-slate-600">
<div className="flex items-center gap-2">
<DollarSign className="w-4 h-4 text-slate-400" />
<span>৳{(project.contractValue / 1000000).toFixed(2)} Million</span>
</div>
<div className="flex items-center gap-2">
<Calendar className="w-4 h-4 text-slate-400" />
<span>{project.startDate} to {project.endDate}</span>
</div>
<div className="flex items-center gap-2">
<Activity className="w-4 h-4 text-slate-400" />
<span>{project.boq.length} BOQ Items</span>
</div>
</div>
</div>
<div className="px-6 py-4 bg-slate-50 border-t border-slate-200 flex justify-between items-center group-hover:bg-blue-50 transition-colors">
<span className="text-sm font-medium text-slate-600 group-hover:text-blue-700">Open Dashboard</span>
<ArrowRight className="w-4 h-4 text-slate-400 group-hover:text-blue-700 transform group-hover:translate-x-1 transition-all" />
</div>
</div>
))}
{projects.length === 0 && (
<div className="col-span-full py-20 text-center bg-slate-50 rounded-xl border-2 border-dashed border-slate-300">
<Building2 className="w-12 h-12 text-slate-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-slate-700">No projects yet</h3>
<p className="text-slate-500 mb-6">Create your first construction project to get started.</p>
{canCreateProject && (
<button
onClick={() => setIsModalOpen(true)}
className="text-blue-600 font-medium hover:underline"
>
Create Project
</button>
)}
</div>
)}
</div>
{isModalOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
<div className="bg-white rounded-xl shadow-xl w-full max-w-md overflow-hidden">
<div className="px-6 py-4 border-b border-slate-200">
<h3 className="font-semibold text-slate-800">New Project</h3>
</div>
<form onSubmit={handleCreate} className="p-6 space-y-4">
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">Project Name</label>
<input
type="text"
required
value={newProjectName}
onChange={(e) => setNewProjectName(e.target.value)}
placeholder="e.g., Bridge Construction at..."
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">Priority</label>
<select
value={priority}
onChange={(e) => setPriority(e.target.value as Priority)}
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none bg-white text-sm"
>
<option value="LOW">Low</option>
<option value="MEDIUM">Medium</option>
<option value="HIGH">High</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">Contract Value (৳)</label>
<input
type="number"
required
value={contractValue}
onChange={(e) => setContractValue(e.target.value)}
placeholder="0"
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none text-sm"
/>
</div>
</div>
<div className="pt-4 flex justify-end gap-3">
<button
type="button"
onClick={() => setIsModalOpen(false)}
className="px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-100 rounded-lg transition-colors"
>
Cancel
</button>
<button
type="submit"
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
>
Create Project
</button>
</div>
</form>
</div>
</div>
)}
</div>
);
};
export default ProjectList;