Spaces:
Sleeping
Sleeping
| import React, { useEffect, useState } from "react"; | |
| import { Briefcase, Calendar, CheckSquare, DollarSign, Grid3x3, TrendingUp, Users } from "lucide-react"; | |
| import { Button } from "@/components/ui/button"; | |
| import Layout from "@/components/layout/layout"; | |
| import StatsCard from "@/components/dashboard/stats-card"; | |
| import ChartCard from "@/components/dashboard/chart-card"; | |
| import WorkFocus from "@/components/dashboard/work-focus"; | |
| import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; | |
| import { mockDashboardStats, mockRevenueData, mockSalesData } from "@/lib/data"; | |
| import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; | |
| import { Badge } from "@/components/ui/badge"; | |
| import { useAuth } from "@/lib/auth-context"; | |
| import { useNavigate } from "react-router-dom"; | |
| import { leaveApi } from "@/services/leaveApi"; | |
| import { employeeApi } from "@/services/employeeApi"; | |
| import { Leave } from "@/types"; | |
| const Dashboard = () => { | |
| const { userData } = useAuth(); | |
| const navigate = useNavigate(); | |
| const [leavesToday, setLeavesToday] = useState<Leave[]>([]); | |
| const [employees, setEmployees] = useState<any[]>([]); | |
| const [loading, setLoading] = useState(true); | |
| // Redirect client users to production-bugs page | |
| useEffect(() => { | |
| if (userData?.roleName?.toLowerCase() === "client") { | |
| navigate("/production-bugs", { state: { from: "dashboard" } }); | |
| } | |
| }, [userData, navigate]); | |
| // Fetch employees on leave today | |
| useEffect(() => { | |
| const fetchLeavesAndEmployees = async () => { | |
| try { | |
| setLoading(true); | |
| const [leavesData, employeesData] = await Promise.all([ | |
| leaveApi.getAllLeavesToday(), | |
| employeeApi.getAllForAdmin() | |
| ]); | |
| setLeavesToday(leavesData); | |
| setEmployees(employeesData); | |
| } catch (error) { | |
| console.error('Error fetching leaves or employees:', error); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| fetchLeavesAndEmployees(); | |
| }, []); | |
| // Get employee details by ID | |
| const getEmployeeById = (employeeId: number) => { | |
| return employees.find(emp => emp.id === employeeId); | |
| }; | |
| // Get initials from name | |
| const getInitials = (firstName: string, lastName: string) => { | |
| return `${firstName?.charAt(0) || ''}${lastName?.charAt(0) || ''}`.toUpperCase(); | |
| }; | |
| // Format date | |
| const formatDate = (dateString: string) => { | |
| const date = new Date(dateString); | |
| return date.toLocaleDateString('en-US', { day: 'numeric', month: 'short', year: 'numeric' }); | |
| }; | |
| // Get badge variant based on status | |
| const getStatusBadgeVariant = (status: string) => { | |
| switch (status) { | |
| case 'Approved': | |
| return 'default'; | |
| case 'Pending': | |
| return 'outline'; | |
| case 'Rejected': | |
| return 'destructive'; | |
| default: | |
| return 'outline'; | |
| } | |
| }; | |
| // Get badge color classes | |
| const getStatusBadgeClass = (status: string) => { | |
| switch (status) { | |
| case 'Approved': | |
| return 'bg-green-50 text-green-500 border-green-200'; | |
| case 'Pending': | |
| return 'bg-yellow-50 text-yellow-600 border-yellow-200'; | |
| case 'Rejected': | |
| return 'bg-red-50 text-red-500 border-red-200'; | |
| default: | |
| return 'bg-gray-50 text-gray-500 border-gray-200'; | |
| } | |
| }; | |
| // Format the revenue data for charts | |
| const revenueChartData = [mockRevenueData]; | |
| const salesChartData = [ | |
| mockSalesData.filter((_, i) => i < 9), | |
| mockSalesData.filter((_, i) => i >= 9), | |
| ]; | |
| return ( | |
| <Layout> | |
| <div className="space-y-6 animate-fadeIn"> | |
| <div className="flex flex-col gap-2"> | |
| <h1 className="text-3xl font-bold tracking-tight">Dashboard</h1> | |
| <p className="text-muted-foreground"> | |
| Welcome back! Here's an overview of your projects and team performance. | |
| </p> | |
| </div> | |
| <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"> | |
| <StatsCard | |
| title="Projects" | |
| value={mockDashboardStats.projects} | |
| icon={Briefcase} | |
| iconColor="orange" | |
| /> | |
| <StatsCard | |
| title="Clients" | |
| value={mockDashboardStats.clients} | |
| icon={Users} | |
| iconColor="blue" | |
| /> | |
| <StatsCard | |
| title="Tasks" | |
| value={mockDashboardStats.tasks} | |
| icon={CheckSquare} | |
| iconColor="green" | |
| /> | |
| <StatsCard | |
| title="Employees" | |
| value={mockDashboardStats.employees} | |
| icon={Users} | |
| iconColor="purple" | |
| /> | |
| </div> | |
| <div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> | |
| <ChartCard | |
| title="Total Revenue" | |
| data={revenueChartData} | |
| type="bar" | |
| showGrid | |
| /> | |
| <ChartCard | |
| title="Sales Overview" | |
| data={salesChartData} | |
| type="line" | |
| colors={["#E53E3E", "#F97316"]} | |
| showGrid | |
| /> | |
| </div> | |
| <div className="grid grid-cols-1 lg:grid-cols-4 gap-6"> | |
| <Card className="col-span-1 hover-scale"> | |
| <CardHeader className="pb-2"> | |
| <CardTitle className="text-lg">New Employees</CardTitle> | |
| <CardDescription> | |
| <span className="text-green-500">+20%</span> from last month | |
| </CardDescription> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="text-3xl font-bold mb-4">10</div> | |
| <div className="w-full bg-secondary rounded-full h-2 mb-1"> | |
| <div className="bg-orange-500 h-2 rounded-full" style={{ width: '65%' }}></div> | |
| </div> | |
| <div className="text-xs text-muted-foreground"> | |
| Overall Employees: 218 | |
| </div> | |
| </CardContent> | |
| </Card> | |
| <Card className="col-span-1 hover-scale"> | |
| <CardHeader className="pb-2"> | |
| <CardTitle className="text-lg">Earnings</CardTitle> | |
| <CardDescription> | |
| <span className="text-green-500">+35.3%</span> from previous month | |
| </CardDescription> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="text-3xl font-bold mb-4">$142,500</div> | |
| <div className="w-full bg-secondary rounded-full h-2 mb-1"> | |
| <div className="bg-orange-500 h-2 rounded-full" style={{ width: '78%' }}></div> | |
| </div> | |
| <div className="text-xs text-muted-foreground"> | |
| Previous Month: $105,400 | |
| </div> | |
| </CardContent> | |
| </Card> | |
| <Card className="col-span-1 hover-scale"> | |
| <CardHeader className="pb-2"> | |
| <CardTitle className="text-lg">Expenses</CardTitle> | |
| <CardDescription> | |
| <span className="text-red-500">-5.6%</span> from previous month | |
| </CardDescription> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="text-3xl font-bold mb-4">$58,500</div> | |
| <div className="w-full bg-secondary rounded-full h-2 mb-1"> | |
| <div className="bg-coral-500 h-2 rounded-full" style={{ width: '42%' }}></div> | |
| </div> | |
| <div className="text-xs text-muted-foreground"> | |
| Previous Month: $62,000 | |
| </div> | |
| </CardContent> | |
| </Card> | |
| <Card className="col-span-1 hover-scale"> | |
| <CardHeader className="pb-2"> | |
| <CardTitle className="text-lg">Profit</CardTitle> | |
| <CardDescription> | |
| <span className="text-green-500">+70%</span> from previous month | |
| </CardDescription> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="text-3xl font-bold mb-4">$84,000</div> | |
| <div className="w-full bg-secondary rounded-full h-2 mb-1"> | |
| <div className="bg-green-500 h-2 rounded-full" style={{ width: '85%' }}></div> | |
| </div> | |
| <div className="text-xs text-muted-foreground"> | |
| Previous Month: $49,400 | |
| </div> | |
| </CardContent> | |
| </Card> | |
| </div> | |
| <div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> | |
| <Card className="hover-scale"> | |
| <CardHeader> | |
| <CardTitle>Task Statistics</CardTitle> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="grid grid-cols-2 gap-4 mb-6"> | |
| <div className="text-center"> | |
| <div className="text-3xl font-bold">365</div> | |
| <div className="text-sm text-muted-foreground">Total Tasks</div> | |
| </div> | |
| <div className="text-center"> | |
| <div className="text-3xl font-bold">75</div> | |
| <div className="text-sm text-muted-foreground">Completed Tasks</div> | |
| </div> | |
| </div> | |
| <div className="space-y-5"> | |
| <div> | |
| <div className="flex justify-between mb-1"> | |
| <span className="text-sm font-medium">Completed Tasks</span> | |
| <span className="text-sm font-medium">156</span> | |
| </div> | |
| <div className="w-full bg-secondary rounded-full h-2"> | |
| <div className="bg-orange-500 h-2 rounded-full" style={{ width: '42%' }}></div> | |
| </div> | |
| </div> | |
| <div> | |
| <div className="flex justify-between mb-1"> | |
| <span className="text-sm font-medium">In Progress Tasks</span> | |
| <span className="text-sm font-medium">75</span> | |
| </div> | |
| <div className="w-full bg-secondary rounded-full h-2"> | |
| <div className="bg-purple-500 h-2 rounded-full" style={{ width: '21%' }}></div> | |
| </div> | |
| </div> | |
| <div> | |
| <div className="flex justify-between mb-1"> | |
| <span className="text-sm font-medium">On Hold Tasks</span> | |
| <span className="text-sm font-medium">21</span> | |
| </div> | |
| <div className="w-full bg-secondary rounded-full h-2"> | |
| <div className="bg-amber-500 h-2 rounded-full" style={{ width: '6%' }}></div> | |
| </div> | |
| </div> | |
| <div> | |
| <div className="flex justify-between mb-1"> | |
| <span className="text-sm font-medium">Pending Tasks</span> | |
| <span className="text-sm font-medium">32</span> | |
| </div> | |
| <div className="w-full bg-secondary rounded-full h-2"> | |
| <div className="bg-blue-500 h-2 rounded-full" style={{ width: '9%' }}></div> | |
| </div> | |
| </div> | |
| <div> | |
| <div className="flex justify-between mb-1"> | |
| <span className="text-sm font-medium">Review Tasks</span> | |
| <span className="text-sm font-medium">5</span> | |
| </div> | |
| <div className="w-full bg-secondary rounded-full h-2"> | |
| <div className="bg-teal-500 h-2 rounded-full" style={{ width: '1%' }}></div> | |
| </div> | |
| </div> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| <Card className="hover-scale"> | |
| <CardHeader> | |
| <CardTitle>Today Absent</CardTitle> | |
| {leavesToday.length > 2 && ( | |
| <Button variant="ghost" size="sm" className="text-xs">View all</Button> | |
| )} | |
| </CardHeader> | |
| <CardContent> | |
| {loading ? ( | |
| <div className="text-center py-8 text-muted-foreground">Loading...</div> | |
| ) : leavesToday.length === 0 ? ( | |
| <div className="text-center py-8 text-muted-foreground"> | |
| No employees on leave today | |
| </div> | |
| ) : ( | |
| <div className="space-y-4 max-h-[400px] overflow-y-auto pr-2"> | |
| {leavesToday.map((leave) => { | |
| const employee = getEmployeeById(leave.employeeId); | |
| if (!employee) return null; | |
| return ( | |
| <div | |
| key={leave.id} | |
| className="flex items-center justify-between p-2 rounded-lg transition-colors hover:bg-yellow-50 border border-yellow-200 bg-yellow-50/50" | |
| > | |
| <div className="flex items-center gap-3"> | |
| <Avatar className="border-2 border-yellow-400"> | |
| <AvatarImage src={employee.avatarUrl} /> | |
| <AvatarFallback className="bg-yellow-100 text-yellow-700"> | |
| {getInitials(employee.firstName, employee.lastName)} | |
| </AvatarFallback> | |
| </Avatar> | |
| <div> | |
| <p className="text-sm font-medium"> | |
| {employee.firstName} {employee.lastName} | |
| </p> | |
| <p className="text-xs text-muted-foreground"> | |
| {leave.type || 'Leave'} {leave.halfDay ? '(Half Day)' : ''} | |
| </p> | |
| </div> | |
| </div> | |
| <div className="text-right"> | |
| <p className="text-sm font-medium"> | |
| {formatDate(leave.startDate)} | |
| </p> | |
| <Badge | |
| variant="outline" | |
| className={getStatusBadgeClass(leave.status)} | |
| > | |
| {leave.status} | |
| </Badge> | |
| </div> | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| )} | |
| </CardContent> | |
| </Card> | |
| </div> | |
| {/* Daily Work Focus Component */} | |
| <WorkFocus /> | |
| <div className="grid grid-cols-1 gap-6"> | |
| <Card className="hover-scale"> | |
| <CardHeader className="pb-0"> | |
| <div className="flex justify-between items-center"> | |
| <CardTitle>Recent Projects</CardTitle> | |
| <Button variant="ghost" size="sm">View all projects</Button> | |
| </div> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="overflow-x-auto"> | |
| <table className="w-full border-collapse"> | |
| <thead> | |
| <tr className="border-b text-left"> | |
| <th className="py-3 px-4 text-sm font-medium text-muted-foreground">Project Name</th> | |
| <th className="py-3 px-4 text-sm font-medium text-muted-foreground">Progress</th> | |
| <th className="py-3 px-4 text-sm font-medium text-muted-foreground">Status</th> | |
| <th className="py-3 px-4 text-sm font-medium text-muted-foreground">Action</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr className="border-b"> | |
| <td className="py-3 px-4"> | |
| <div className="flex items-center gap-3"> | |
| <Avatar className="h-8 w-8"> | |
| <AvatarFallback className="bg-orange-100 text-orange-700">OM</AvatarFallback> | |
| </Avatar> | |
| <div> | |
| <p className="text-sm font-medium">Office Management</p> | |
| <p className="text-xs text-muted-foreground">Client: Delta Networks</p> | |
| </div> | |
| </div> | |
| </td> | |
| <td className="py-3 px-4"> | |
| <div className="space-y-1"> | |
| <div className="text-xs text-right">65%</div> | |
| <div className="w-32 h-2 bg-secondary rounded-full"> | |
| <div className="h-2 bg-orange-500 rounded-full" style={{ width: '65%' }}></div> | |
| </div> | |
| </div> | |
| </td> | |
| <td className="py-3 px-4"> | |
| <Badge variant="outline" className="bg-green-50 text-green-600 border-green-100">Active</Badge> | |
| </td> | |
| <td className="py-3 px-4"> | |
| <Button variant="ghost" size="sm" className="h-8 w-8 p-0"> | |
| <TrendingUp className="h-4 w-4" /> | |
| </Button> | |
| </td> | |
| </tr> | |
| <tr className="border-b"> | |
| <td className="py-3 px-4"> | |
| <div className="flex items-center gap-3"> | |
| <Avatar className="h-8 w-8"> | |
| <AvatarFallback className="bg-blue-100 text-blue-700">HA</AvatarFallback> | |
| </Avatar> | |
| <div> | |
| <p className="text-sm font-medium">Hospital Administration</p> | |
| <p className="text-xs text-muted-foreground">Client: City Medical</p> | |
| </div> | |
| </div> | |
| </td> | |
| <td className="py-3 px-4"> | |
| <div className="space-y-1"> | |
| <div className="text-xs text-right">40%</div> | |
| <div className="w-32 h-2 bg-secondary rounded-full"> | |
| <div className="h-2 bg-blue-500 rounded-full" style={{ width: '40%' }}></div> | |
| </div> | |
| </div> | |
| </td> | |
| <td className="py-3 px-4"> | |
| <Badge variant="outline" className="bg-green-50 text-green-600 border-green-100">Active</Badge> | |
| </td> | |
| <td className="py-3 px-4"> | |
| <Button variant="ghost" size="sm" className="h-8 w-8 p-0"> | |
| <TrendingUp className="h-4 w-4" /> | |
| </Button> | |
| </td> | |
| </tr> | |
| <tr className="border-b"> | |
| <td className="py-3 px-4"> | |
| <div className="flex items-center gap-3"> | |
| <Avatar className="h-8 w-8"> | |
| <AvatarFallback className="bg-purple-100 text-purple-700">PM</AvatarFallback> | |
| </Avatar> | |
| <div> | |
| <p className="text-sm font-medium">Project Management</p> | |
| <p className="text-xs text-muted-foreground">Client: CreativeTech</p> | |
| </div> | |
| </div> | |
| </td> | |
| <td className="py-3 px-4"> | |
| <div className="space-y-1"> | |
| <div className="text-xs text-right">25%</div> | |
| <div className="w-32 h-2 bg-secondary rounded-full"> | |
| <div className="h-2 bg-purple-500 rounded-full" style={{ width: '25%' }}></div> | |
| </div> | |
| </div> | |
| </td> | |
| <td className="py-3 px-4"> | |
| <Badge variant="outline" className="bg-green-50 text-green-600 border-green-100">Active</Badge> | |
| </td> | |
| <td className="py-3 px-4"> | |
| <Button variant="ghost" size="sm" className="h-8 w-8 p-0"> | |
| <TrendingUp className="h-4 w-4" /> | |
| </Button> | |
| </td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| </div> | |
| </div> | |
| </Layout> | |
| ); | |
| }; | |
| export default Dashboard; | |