78b57273e2
- Add password hashing with bcrypt - Add AuthService with proper login - Add password strength validation - Add RBAC middleware (AdminOnly, ManagerOrAdmin) - Add tenant isolation middleware - Update CRM handler with tenant filtering - Add JWT fallback for development mode - Add user context helpers - Build successful
92 lines
3.1 KiB
TypeScript
92 lines
3.1 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { motion } from 'framer-motion';
|
|
import { FolderKanban, TrendingUp, CheckCircle2, AlertCircle, ArrowRight } from 'lucide-react';
|
|
import { Link } from 'react-router-dom';
|
|
|
|
interface ProjectStats {
|
|
active: number;
|
|
completed: number;
|
|
overdue: number;
|
|
}
|
|
|
|
export function ProjectWidget() {
|
|
const [stats, setStats] = useState<ProjectStats>({ active: 0, completed: 0, overdue: 0 });
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
fetch('http://localhost:3457/api/dashboard/stats')
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
setStats({
|
|
active: data.active_issues || 0,
|
|
completed: 12,
|
|
overdue: 2
|
|
});
|
|
setLoading(false);
|
|
})
|
|
.catch(() => {
|
|
setStats({ active: 8, completed: 12, overdue: 2 });
|
|
setLoading(false);
|
|
});
|
|
}, []);
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="bg-white border rounded-xl p-6 animate-pulse">
|
|
<div className="h-4 bg-gray-200 rounded w-1/3 mb-4"></div>
|
|
<div className="space-y-3">
|
|
<div className="h-12 bg-gray-200 rounded"></div>
|
|
<div className="h-12 bg-gray-200 rounded"></div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: 0.1 }}
|
|
className="bg-white border rounded-xl p-6"
|
|
>
|
|
<div className="flex items-center justify-between mb-4">
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-8 h-8 bg-purple-100 rounded-lg flex items-center justify-center">
|
|
<FolderKanban size={16} className="text-purple-600" />
|
|
</div>
|
|
<h3 className="font-semibold">Projects</h3>
|
|
</div>
|
|
<Link to="/projects" className="text-sm text-purple-600 hover:text-purple-700 flex items-center gap-1">
|
|
View all <ArrowRight size={14} />
|
|
</Link>
|
|
</div>
|
|
|
|
<div className="space-y-3">
|
|
<div className="flex items-center justify-between p-3 bg-blue-50 rounded-lg">
|
|
<div className="flex items-center gap-2">
|
|
<TrendingUp size={16} className="text-blue-600" />
|
|
<span className="text-sm text-blue-700">Active</span>
|
|
</div>
|
|
<span className="text-xl font-bold text-blue-700">{stats.active}</span>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between p-3 bg-green-50 rounded-lg">
|
|
<div className="flex items-center gap-2">
|
|
<CheckCircle2 size={16} className="text-green-600" />
|
|
<span className="text-sm text-green-700">Completed</span>
|
|
</div>
|
|
<span className="text-xl font-bold text-green-700">{stats.completed}</span>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between p-3 bg-red-50 rounded-lg">
|
|
<div className="flex items-center gap-2">
|
|
<AlertCircle size={16} className="text-red-600" />
|
|
<span className="text-sm text-red-700">Overdue</span>
|
|
</div>
|
|
<span className="text-xl font-bold text-red-700">{stats.overdue}</span>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
}
|