|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Team Performance Dashboard</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
<script> |
|
tailwind.config = { |
|
theme: { |
|
extend: { |
|
colors: { |
|
primary: '#3B82F6', |
|
secondary: '#10B981', |
|
dark: '#1F2937', |
|
light: '#F9FAFB' |
|
} |
|
} |
|
} |
|
} |
|
</script> |
|
<style> |
|
.sidebar { |
|
transition: all 0.3s ease; |
|
} |
|
.modal { |
|
transition: opacity 0.3s ease, transform 0.3s ease; |
|
} |
|
.modal-hidden { |
|
opacity: 0; |
|
transform: translateY(-20px); |
|
pointer-events: none; |
|
} |
|
.kpi-progress { |
|
transition: width 1s ease-in-out; |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gray-100 font-sans"> |
|
<div class="flex h-screen overflow-hidden"> |
|
|
|
<div class="sidebar bg-white w-64 md:w-20 lg:w-64 border-r border-gray-200 flex flex-col"> |
|
<div class="p-4 border-b border-gray-200 flex items-center justify-between"> |
|
<h1 class="text-xl font-bold text-primary md:hidden lg:block">TeamMetrics</h1> |
|
<i class="fas fa-chart-line text-primary text-xl md:block lg:hidden"></i> |
|
<button class="md:hidden text-gray-500" onclick="toggleSidebar()"> |
|
<i class="fas fa-bars"></i> |
|
</button> |
|
</div> |
|
<nav class="flex-1 overflow-y-auto p-4"> |
|
<ul> |
|
<li class="mb-2"> |
|
<a href="#" class="flex items-center p-2 text-primary bg-blue-50 rounded-lg"> |
|
<i class="fas fa-home mr-3 md:mr-0 lg:mr-3"></i> |
|
<span class="md:hidden lg:block">Dashboard</span> |
|
</a> |
|
</li> |
|
<li class="mb-2"> |
|
<a href="#" class="flex items-center p-2 text-gray-600 hover:bg-gray-100 rounded-lg" onclick="showSection('team-section')"> |
|
<i class="fas fa-users mr-3 md:mr-0 lg:mr-3"></i> |
|
<span class="md:hidden lg:block">Team Members</span> |
|
</a> |
|
</li> |
|
<li class="mb-2"> |
|
<a href="#" class="flex items-center p-2 text-gray-600 hover:bg-gray-100 rounded-lg" onclick="showSection('kpi-section')"> |
|
<i class="fas fa-bullseye mr-3 md:mr-0 lg:mr-3"></i> |
|
<span class="md:hidden lg:block">KPIs</span> |
|
</a> |
|
</li> |
|
<li class="mb-2"> |
|
<a href="#" class="flex items-center p-2 text-gray-600 hover:bg-gray-100 rounded-lg" onclick="showSection('reports-section')"> |
|
<i class="fas fa-chart-pie mr-3 md:mr-0 lg:mr-3"></i> |
|
<span class="md:hidden lg:block">Reports</span> |
|
</a> |
|
</li> |
|
</ul> |
|
</nav> |
|
<div class="p-4 border-t border-gray-200"> |
|
<div class="flex items-center"> |
|
<img src="https://randomuser.me/api/portraits/women/44.jpg" alt="Profile" class="w-8 h-8 rounded-full mr-2"> |
|
<div class="md:hidden lg:block"> |
|
<p class="text-sm font-medium">Sarah Johnson</p> |
|
<p class="text-xs text-gray-500">Admin</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="flex-1 overflow-auto"> |
|
<header class="bg-white shadow-sm p-4 flex justify-between items-center"> |
|
<h2 class="text-xl font-semibold text-gray-800" id="section-title">Dashboard</h2> |
|
<div class="flex items-center space-x-4"> |
|
<div class="relative"> |
|
<input type="text" placeholder="Search..." class="pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"> |
|
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i> |
|
</div> |
|
<button class="p-2 text-gray-500 hover:text-gray-700"> |
|
<i class="fas fa-bell"></i> |
|
</button> |
|
</div> |
|
</header> |
|
|
|
|
|
<main class="p-4" id="dashboard-section"> |
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6"> |
|
<div class="bg-white p-6 rounded-lg shadow"> |
|
<div class="flex items-center justify-between"> |
|
<div> |
|
<p class="text-gray-500">Team Members</p> |
|
<h3 class="text-2xl font-bold" id="team-count">0</h3> |
|
</div> |
|
<div class="bg-blue-100 p-3 rounded-full"> |
|
<i class="fas fa-users text-blue-500 text-xl"></i> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="bg-white p-6 rounded-lg shadow"> |
|
<div class="flex items-center justify-between"> |
|
<div> |
|
<p class="text-gray-500">Active KPIs</p> |
|
<h3 class="text-2xl font-bold" id="kpi-count">0</h3> |
|
</div> |
|
<div class="bg-green-100 p-3 rounded-full"> |
|
<i class="fas fa-bullseye text-green-500 text-xl"></i> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="bg-white p-6 rounded-lg shadow"> |
|
<div class="flex items-center justify-between"> |
|
<div> |
|
<p class="text-gray-500">Avg. Performance</p> |
|
<h3 class="text-2xl font-bold" id="avg-performance">0%</h3> |
|
</div> |
|
<div class="bg-purple-100 p-3 rounded-full"> |
|
<i class="fas fa-chart-line text-purple-500 text-xl"></i> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="bg-white p-6 rounded-lg shadow"> |
|
<div class="flex items-center justify-between"> |
|
<div> |
|
<p class="text-gray-500">Top Performer</p> |
|
<h3 class="text-2xl font-bold" id="top-performer">-</h3> |
|
</div> |
|
<div class="bg-yellow-100 p-3 rounded-full"> |
|
<i class="fas fa-trophy text-yellow-500 text-xl"></i> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4 mb-6"> |
|
<div class="bg-white p-6 rounded-lg shadow lg:col-span-2"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h3 class="text-lg font-semibold">Performance Overview</h3> |
|
<select class="border border-gray-300 rounded px-3 py-1 text-sm"> |
|
<option>Last 7 Days</option> |
|
<option>Last 30 Days</option> |
|
<option>Last Quarter</option> |
|
</select> |
|
</div> |
|
<div class="h-64"> |
|
<canvas id="performance-chart"></canvas> |
|
</div> |
|
</div> |
|
<div class="bg-white p-6 rounded-lg shadow"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h3 class="text-lg font-semibold">Top KPIs</h3> |
|
<button class="text-primary text-sm font-medium" onclick="showSection('kpi-section')">View All</button> |
|
</div> |
|
<div id="top-kpis"> |
|
|
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="bg-white p-6 rounded-lg shadow"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h3 class="text-lg font-semibold">Recent Activities</h3> |
|
<button class="text-primary text-sm font-medium">View All</button> |
|
</div> |
|
<div class="space-y-4"> |
|
<div class="flex items-start"> |
|
<div class="bg-blue-100 p-2 rounded-full mr-3"> |
|
<i class="fas fa-user-plus text-blue-500"></i> |
|
</div> |
|
<div> |
|
<p class="text-sm"><span class="font-medium">John Doe</span> was added to the team</p> |
|
<p class="text-xs text-gray-500">2 hours ago</p> |
|
</div> |
|
</div> |
|
<div class="flex items-start"> |
|
<div class="bg-green-100 p-2 rounded-full mr-3"> |
|
<i class="fas fa-bullseye text-green-500"></i> |
|
</div> |
|
<div> |
|
<p class="text-sm">New KPI <span class="font-medium">"Customer Satisfaction"</span> was created</p> |
|
<p class="text-xs text-gray-500">Yesterday</p> |
|
</div> |
|
</div> |
|
<div class="flex items-start"> |
|
<div class="bg-purple-100 p-2 rounded-full mr-3"> |
|
<i class="fas fa-trophy text-purple-500"></i> |
|
</div> |
|
<div> |
|
<p class="text-sm"><span class="font-medium">Sarah Johnson</span> achieved 95% of her monthly target</p> |
|
<p class="text-xs text-gray-500">3 days ago</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</main> |
|
|
|
|
|
<main class="p-4 hidden" id="team-section"> |
|
<div class="flex justify-between items-center mb-6"> |
|
<h2 class="text-2xl font-semibold text-gray-800">Team Members</h2> |
|
<button class="bg-primary text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition" onclick="showAddTeamMemberModal()"> |
|
<i class="fas fa-plus mr-2"></i> Add Member |
|
</button> |
|
</div> |
|
|
|
<div class="bg-white rounded-lg shadow overflow-hidden"> |
|
<div class="overflow-x-auto"> |
|
<table class="min-w-full divide-y divide-gray-200"> |
|
<thead class="bg-gray-50"> |
|
<tr> |
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th> |
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Position</th> |
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Department</th> |
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Performance</th> |
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th> |
|
</tr> |
|
</thead> |
|
<tbody class="bg-white divide-y divide-gray-200" id="team-members-table"> |
|
|
|
</tbody> |
|
</table> |
|
</div> |
|
</div> |
|
</main> |
|
|
|
|
|
<main class="p-4 hidden" id="kpi-section"> |
|
<div class="flex justify-between items-center mb-6"> |
|
<h2 class="text-2xl font-semibold text-gray-800">Key Performance Indicators</h2> |
|
<button class="bg-primary text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition" onclick="showAddKPIModal()"> |
|
<i class="fas fa-plus mr-2"></i> Add KPI |
|
</button> |
|
</div> |
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" id="kpi-cards"> |
|
|
|
</div> |
|
</main> |
|
|
|
|
|
<main class="p-4 hidden" id="reports-section"> |
|
<div class="flex justify-between items-center mb-6"> |
|
<h2 class="text-2xl font-semibold text-gray-800">Performance Reports</h2> |
|
<button class="bg-primary text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition"> |
|
<i class="fas fa-download mr-2"></i> Export |
|
</button> |
|
</div> |
|
|
|
<div class="bg-white rounded-lg shadow p-6"> |
|
<div class="mb-6"> |
|
<h3 class="text-lg font-semibold mb-2">Generate Report</h3> |
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4"> |
|
<div> |
|
<label class="block text-sm font-medium text-gray-700 mb-1">Report Type</label> |
|
<select class="w-full border border-gray-300 rounded px-3 py-2"> |
|
<option>Team Performance</option> |
|
<option>Individual Performance</option> |
|
<option>KPI Analysis</option> |
|
</select> |
|
</div> |
|
<div> |
|
<label class="block text-sm font-medium text-gray-700 mb-1">Time Period</label> |
|
<select class="w-full border border-gray-300 rounded px-3 py-2"> |
|
<option>Last Week</option> |
|
<option>Last Month</option> |
|
<option>Last Quarter</option> |
|
<option>Custom Range</option> |
|
</select> |
|
</div> |
|
<div class="flex items-end"> |
|
<button class="bg-primary text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full"> |
|
Generate Report |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="border-t border-gray-200 pt-6"> |
|
<h3 class="text-lg font-semibold mb-4">Recent Reports</h3> |
|
<div class="space-y-4"> |
|
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg"> |
|
<div> |
|
<p class="font-medium">Team Performance - Q2 2023</p> |
|
<p class="text-sm text-gray-500">Generated on July 1, 2023</p> |
|
</div> |
|
<div class="flex space-x-2"> |
|
<button class="text-blue-500 hover:text-blue-700"> |
|
<i class="fas fa-eye"></i> |
|
</button> |
|
<button class="text-green-500 hover:text-green-700"> |
|
<i class="fas fa-download"></i> |
|
</button> |
|
</div> |
|
</div> |
|
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg"> |
|
<div> |
|
<p class="font-medium">Individual Performance - June 2023</p> |
|
<p class="text-sm text-gray-500">Generated on June 30, 2023</p> |
|
</div> |
|
<div class="flex space-x-2"> |
|
<button class="text-blue-500 hover:text-blue-700"> |
|
<i class="fas fa-eye"></i> |
|
</button> |
|
<button class="text-green-500 hover:text-green-700"> |
|
<i class="fas fa-download"></i> |
|
</button> |
|
</div> |
|
</div> |
|
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg"> |
|
<div> |
|
<p class="font-medium">KPI Analysis - Q2 2023</p> |
|
<p class="text-sm text-gray-500">Generated on June 15, 2023</p> |
|
</div> |
|
<div class="flex space-x-2"> |
|
<button class="text-blue-500 hover:text-blue-700"> |
|
<i class="fas fa-eye"></i> |
|
</button> |
|
<button class="text-green-500 hover:text-green-700"> |
|
<i class="fas fa-download"></i> |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</main> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="team-member-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50 modal-hidden"> |
|
<div class="bg-white rounded-lg shadow-xl w-full max-w-md"> |
|
<div class="p-6"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h3 class="text-xl font-semibold" id="team-member-modal-title">Add Team Member</h3> |
|
<button onclick="hideModal('team-member-modal')" class="text-gray-500 hover:text-gray-700"> |
|
<i class="fas fa-times"></i> |
|
</button> |
|
</div> |
|
<form id="team-member-form"> |
|
<input type="hidden" id="team-member-id"> |
|
<div class="mb-4"> |
|
<label class="block text-sm font-medium text-gray-700 mb-1">Full Name</label> |
|
<input type="text" id="team-member-name" class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary"> |
|
</div> |
|
<div class="mb-4"> |
|
<label class="block text-sm font-medium text-gray-700 mb-1">Position</label> |
|
<input type="text" id="team-member-position" class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary"> |
|
</div> |
|
<div class="mb-4"> |
|
<label class="block text-sm font-medium text-gray-700 mb-1">Department</label> |
|
<select id="team-member-department" class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary"> |
|
<option value="Sales">Sales</option> |
|
<option value="Marketing">Marketing</option> |
|
<option value="Development">Development</option> |
|
<option value="Support">Support</option> |
|
<option value="HR">Human Resources</option> |
|
</select> |
|
</div> |
|
<div class="mb-4"> |
|
<label class="block text-sm font-medium text-gray-700 mb-1">Email</label> |
|
<input type="email" id="team-member-email" class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary"> |
|
</div> |
|
<div class="flex justify-end space-x-3 mt-6"> |
|
<button type="button" onclick="hideModal('team-member-modal')" class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50">Cancel</button> |
|
<button type="button" onclick="saveTeamMember()" class="px-4 py-2 bg-primary text-white rounded-lg hover:bg-blue-600">Save</button> |
|
</div> |
|
</form> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="kpi-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50 modal-hidden"> |
|
<div class="bg-white rounded-lg shadow-xl w-full max-w-md"> |
|
<div class="p-6"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h3 class="text-xl font-semibold" id="kpi-modal-title">Add KPI</h3> |
|
<button onclick="hideModal('kpi-modal')" class="text-gray-500 hover:text-gray-700"> |
|
<i class="fas fa-times"></i> |
|
</button> |
|
</div> |
|
<form id="kpi-form"> |
|
<input type="hidden" id="kpi-id"> |
|
<div class="mb-4"> |
|
<label class="block text-sm font-medium text-gray-700 mb-1">KPI Name</label> |
|
<input type="text" id="kpi-name" class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary"> |
|
</div> |
|
<div class="mb-4"> |
|
<label class="block text-sm font-medium text-gray-700 mb-1">Description</label> |
|
<textarea id="kpi-description" rows="3" class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary"></textarea> |
|
</div> |
|
<div class="mb-4"> |
|
<label class="block text-sm font-medium text-gray-700 mb-1">Target Value</label> |
|
<input type="number" id="kpi-target" class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary"> |
|
</div> |
|
<div class="mb-4"> |
|
<label class="block text-sm font-medium text-gray-700 mb-1">Current Value</label> |
|
<input type="number" id="kpi-current" class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary"> |
|
</div> |
|
<div class="mb-4"> |
|
<label class="block text-sm font-medium text-gray-700 mb-1">Frequency</label> |
|
<select id="kpi-frequency" class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary"> |
|
<option value="Daily">Daily</option> |
|
<option value="Weekly">Weekly</option> |
|
<option value="Monthly">Monthly</option> |
|
<option value="Quarterly">Quarterly</option> |
|
</select> |
|
</div> |
|
<div class="mb-4"> |
|
<label class="block text-sm font-medium text-gray-700 mb-1">Assigned To</label> |
|
<select id="kpi-assigned" class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary"> |
|
|
|
</select> |
|
</div> |
|
<div class="flex justify-end space-x-3 mt-6"> |
|
<button type="button" onclick="hideModal('kpi-modal')" class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50">Cancel</button> |
|
<button type="button" onclick="saveKPI()" class="px-4 py-2 bg-primary text-white rounded-lg hover:bg-blue-600">Save</button> |
|
</div> |
|
</form> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="confirm-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50 modal-hidden"> |
|
<div class="bg-white rounded-lg shadow-xl w-full max-w-md"> |
|
<div class="p-6"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h3 class="text-xl font-semibold" id="confirm-modal-title">Confirm Action</h3> |
|
<button onclick="hideModal('confirm-modal')" class="text-gray-500 hover:text-gray-700"> |
|
<i class="fas fa-times"></i> |
|
</button> |
|
</div> |
|
<p id="confirm-modal-message">Are you sure you want to perform this action?</p> |
|
<div class="flex justify-end space-x-3 mt-6"> |
|
<button type="button" onclick="hideModal('confirm-modal')" class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50">Cancel</button> |
|
<button type="button" onclick="confirmAction()" class="px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600">Confirm</button> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
|
<script> |
|
|
|
let teamMembers = [ |
|
{ id: 1, name: "John Doe", position: "Sales Manager", department: "Sales", email: "[email protected]", performance: 85 }, |
|
{ id: 2, name: "Jane Smith", position: "Marketing Specialist", department: "Marketing", email: "[email protected]", performance: 92 }, |
|
{ id: 3, name: "Mike Johnson", position: "Developer", department: "Development", email: "[email protected]", performance: 78 }, |
|
{ id: 4, name: "Sarah Williams", position: "HR Manager", department: "HR", email: "[email protected]", performance: 88 } |
|
]; |
|
|
|
let kpis = [ |
|
{ id: 1, name: "Sales Revenue", description: "Monthly sales revenue target", target: 100000, current: 85000, frequency: "Monthly", assigned: 1, progress: 85 }, |
|
{ id: 2, name: "Customer Satisfaction", description: "Customer satisfaction score", target: 90, current: 87, frequency: "Monthly", assigned: 2, progress: 96.7 }, |
|
{ id: 3, name: "Project Completion", description: "Projects completed on time", target: 95, current: 80, frequency: "Quarterly", assigned: 3, progress: 84.2 }, |
|
{ id: 4, name: "Employee Retention", description: "Employee retention rate", target: 95, current: 92, frequency: "Quarterly", assigned: 4, progress: 96.8 } |
|
]; |
|
|
|
|
|
let currentAction = null; |
|
let currentId = null; |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
updateDashboard(); |
|
renderTeamMembers(); |
|
renderKPIs(); |
|
populateAssignedDropdown(); |
|
setupPerformanceChart(); |
|
}); |
|
|
|
|
|
function showSection(sectionId) { |
|
document.querySelectorAll('main').forEach(section => { |
|
section.classList.add('hidden'); |
|
}); |
|
document.getElementById(sectionId).classList.remove('hidden'); |
|
|
|
|
|
const titles = { |
|
'team-section': 'Team Members', |
|
'kpi-section': 'Key Performance Indicators', |
|
'reports-section': 'Performance Reports' |
|
}; |
|
document.getElementById('section-title').textContent = titles[sectionId] || 'Dashboard'; |
|
} |
|
|
|
|
|
function showModal(modalId) { |
|
document.getElementById(modalId).classList.remove('modal-hidden'); |
|
} |
|
|
|
function hideModal(modalId) { |
|
document.getElementById(modalId).classList.add('modal-hidden'); |
|
} |
|
|
|
function showAddTeamMemberModal() { |
|
document.getElementById('team-member-modal-title').textContent = 'Add Team Member'; |
|
document.getElementById('team-member-id').value = ''; |
|
document.getElementById('team-member-name').value = ''; |
|
document.getElementById('team-member-position').value = ''; |
|
document.getElementById('team-member-department').value = 'Sales'; |
|
document.getElementById('team-member-email').value = ''; |
|
showModal('team-member-modal'); |
|
} |
|
|
|
function showEditTeamMemberModal(id) { |
|
const member = teamMembers.find(m => m.id === id); |
|
if (member) { |
|
document.getElementById('team-member-modal-title').textContent = 'Edit Team Member'; |
|
document.getElementById('team-member-id').value = member.id; |
|
document.getElementById('team-member-name').value = member.name; |
|
document.getElementById('team-member-position').value = member.position; |
|
document.getElementById('team-member-department').value = member.department; |
|
document.getElementById('team-member-email').value = member.email; |
|
showModal('team-member-modal'); |
|
} |
|
} |
|
|
|
function showAddKPIModal() { |
|
document.getElementById('kpi-modal-title').textContent = 'Add KPI'; |
|
document.getElementById('kpi-id').value = ''; |
|
document.getElementById('kpi-name').value = ''; |
|
document.getElementById('kpi-description').value = ''; |
|
document.getElementById('kpi-target').value = ''; |
|
document.getElementById('kpi-current').value = ''; |
|
document.getElementById('kpi-frequency').value = 'Monthly'; |
|
showModal('kpi-modal'); |
|
} |
|
|
|
function showEditKPIModal(id) { |
|
const kpi = kpis.find(k => k.id === id); |
|
if (kpi) { |
|
document.getElementById('kpi-modal-title').textContent = 'Edit KPI'; |
|
document.getElementById('kpi-id').value = kpi.id; |
|
document.getElementById('kpi-name').value = kpi.name; |
|
document.getElementById('kpi-description').value = kpi.description; |
|
document.getElementById('kpi-target').value = kpi.target; |
|
document.getElementById('kpi-current').value = kpi.current; |
|
document.getElementById('kpi-frequency').value = kpi.frequency; |
|
document.getElementById('kpi-assigned').value = kpi.assigned; |
|
showModal('kpi-modal'); |
|
} |
|
} |
|
|
|
function showConfirmModal(action, id, message) { |
|
currentAction = action; |
|
currentId = id; |
|
document.getElementById('confirm-modal-message').textContent = message || 'Are you sure you want to perform this action?'; |
|
showModal('confirm-modal'); |
|
} |
|
|
|
function confirmAction() { |
|
if (currentAction === 'deleteTeamMember') { |
|
deleteTeamMember(currentId); |
|
} else if (currentAction === 'deleteKPI') { |
|
deleteKPI(currentId); |
|
} |
|
hideModal('confirm-modal'); |
|
} |
|
|
|
|
|
function saveTeamMember() { |
|
const id = document.getElementById('team-member-id').value; |
|
const name = document.getElementById('team-member-name').value; |
|
const position = document.getElementById('team-member-position').value; |
|
const department = document.getElementById('team-member-department').value; |
|
const email = document.getElementById('team-member-email').value; |
|
|
|
if (!name || !position || !department || !email) { |
|
alert('Please fill in all fields'); |
|
return; |
|
} |
|
|
|
if (id) { |
|
|
|
const index = teamMembers.findIndex(m => m.id === parseInt(id)); |
|
if (index !== -1) { |
|
teamMembers[index] = { |
|
...teamMembers[index], |
|
name, |
|
position, |
|
department, |
|
email |
|
}; |
|
} |
|
} else { |
|
|
|
const newId = teamMembers.length > 0 ? Math.max(...teamMembers.map(m => m.id)) + 1 : 1; |
|
teamMembers.push({ |
|
id: newId, |
|
name, |
|
position, |
|
department, |
|
email, |
|
performance: Math.floor(Math.random() * 30) + 70 |
|
}); |
|
} |
|
|
|
hideModal('team-member-modal'); |
|
renderTeamMembers(); |
|
updateDashboard(); |
|
populateAssignedDropdown(); |
|
} |
|
|
|
function deleteTeamMember(id) { |
|
teamMembers = teamMembers.filter(m => m.id !== id); |
|
|
|
kpis = kpis.filter(k => k.assigned !== id); |
|
renderTeamMembers(); |
|
renderKPIs(); |
|
updateDashboard(); |
|
populateAssignedDropdown(); |
|
} |
|
|
|
|
|
function saveKPI() { |
|
const id = document.getElementById('kpi-id').value; |
|
const name = document.getElementById('kpi-name').value; |
|
const description = document.getElementById('kpi-description').value; |
|
const target = parseFloat(document.getElementById('kpi-target').value); |
|
const current = parseFloat(document.getElementById('kpi-current').value); |
|
const frequency = document.getElementById('kpi-frequency').value; |
|
const assigned = parseInt(document.getElementById('kpi-assigned').value); |
|
|
|
if (!name || isNaN(target) || isNaN(current) || !frequency || !assigned) { |
|
alert('Please fill in all required fields with valid values'); |
|
return; |
|
} |
|
|
|
const progress = Math.min(100, (current / target) * 100); |
|
|
|
if (id) { |
|
|
|
const index = kpis.findIndex(k => k.id === parseInt(id)); |
|
if (index !== -1) { |
|
kpis[index] = { |
|
...kpis[index], |
|
name, |
|
description, |
|
target, |
|
current, |
|
frequency, |
|
assigned, |
|
progress |
|
}; |
|
} |
|
} else { |
|
|
|
const newId = kpis.length > 0 ? Math.max(...kpis.map(k => k.id)) + 1 : 1; |
|
kpis.push({ |
|
id: newId, |
|
name, |
|
description, |
|
target, |
|
current, |
|
frequency, |
|
assigned, |
|
progress |
|
}); |
|
} |
|
|
|
hideModal('kpi-modal'); |
|
renderKPIs(); |
|
updateDashboard(); |
|
} |
|
|
|
function deleteKPI(id) { |
|
kpis = kpis.filter(k => k.id !== id); |
|
renderKPIs(); |
|
updateDashboard(); |
|
} |
|
|
|
function updateKPIProgress(id, newValue) { |
|
const kpi = kpis.find(k => k.id === id); |
|
if (kpi) { |
|
kpi.current = newValue; |
|
kpi.progress = Math.min(100, (newValue / kpi.target) * 100); |
|
renderKPIs(); |
|
updateDashboard(); |
|
} |
|
} |
|
|
|
|
|
function renderTeamMembers() { |
|
const tableBody = document.getElementById('team-members-table'); |
|
tableBody.innerHTML = ''; |
|
|
|
teamMembers.forEach(member => { |
|
const row = document.createElement('tr'); |
|
row.innerHTML = ` |
|
<td class="px-6 py-4 whitespace-nowrap"> |
|
<div class="flex items-center"> |
|
<div class="flex-shrink-0 h-10 w-10"> |
|
<img class="h-10 w-10 rounded-full" src="https://ui-avatars.com/api/?name=${encodeURIComponent(member.name)}&background=random" alt=""> |
|
</div> |
|
<div class="ml-4"> |
|
<div class="text-sm font-medium text-gray-900">${member.name}</div> |
|
<div class="text-sm text-gray-500">${member.email}</div> |
|
</div> |
|
</div> |
|
</td> |
|
<td class="px-6 py-4 whitespace-nowrap"> |
|
<div class="text-sm text-gray-900">${member.position}</div> |
|
</td> |
|
<td class="px-6 py-4 whitespace-nowrap"> |
|
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800">${member.department}</span> |
|
</td> |
|
<td class="px-6 py-4 whitespace-nowrap"> |
|
<div class="flex items-center"> |
|
<div class="w-24 mr-2"> |
|
<div class="w-full bg-gray-200 rounded-full h-2.5"> |
|
<div class="bg-${getPerformanceColor(member.performance)} h-2.5 rounded-full" style="width: ${member.performance}%"></div> |
|
</div> |
|
</div> |
|
<span class="text-sm font-medium text-gray-700">${member.performance}%</span> |
|
</div> |
|
</td> |
|
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> |
|
<button onclick="showEditTeamMemberModal(${member.id})" class="text-blue-600 hover:text-blue-900 mr-3"><i class="fas fa-edit"></i></button> |
|
<button onclick="showConfirmModal('deleteTeamMember', ${member.id}, 'Are you sure you want to delete ${member.name}?')" class="text-red-600 hover:text-red-900"><i class="fas fa-trash"></i></button> |
|
</td> |
|
`; |
|
tableBody.appendChild(row); |
|
}); |
|
} |
|
|
|
function renderKPIs() { |
|
const kpiContainer = document.getElementById('kpi-cards'); |
|
kpiContainer.innerHTML = ''; |
|
|
|
kpis.forEach(kpi => { |
|
const assignedMember = teamMembers.find(m => m.id === kpi.assigned); |
|
const card = document.createElement('div'); |
|
card.className = 'bg-white rounded-lg shadow overflow-hidden'; |
|
card.innerHTML = ` |
|
<div class="p-5"> |
|
<div class="flex justify-between items-start mb-2"> |
|
<h3 class="text-lg font-semibold">${kpi.name}</h3> |
|
<div class="flex space-x-2"> |
|
<button onclick="showEditKPIModal(${kpi.id})" class="text-blue-500 hover:text-blue-700"><i class="fas fa-edit"></i></button> |
|
<button onclick="showConfirmModal('deleteKPI', ${kpi.id}, 'Are you sure you want to delete the KPI ${kpi.name}?')" class="text-red-500 hover:text-red-700"><i class="fas fa-trash"></i></button> |
|
</div> |
|
</div> |
|
<p class="text-sm text-gray-600 mb-4">${kpi.description}</p> |
|
|
|
<div class="mb-4"> |
|
<div class="flex justify-between text-sm mb-1"> |
|
<span>Progress</span> |
|
<span>${kpi.progress.toFixed(1)}%</span> |
|
</div> |
|
<div class="w-full bg-gray-200 rounded-full h-2.5"> |
|
<div class="bg-${getPerformanceColor(kpi.progress)} h-2.5 rounded-full kpi-progress" style="width: ${kpi.progress}%"></div> |
|
</div> |
|
</div> |
|
|
|
<div class="grid grid-cols-2 gap-4 text-sm mb-4"> |
|
<div> |
|
<p class="text-gray-500">Target</p> |
|
<p class="font-medium">${formatNumber(kpi.target)}</p> |
|
</div> |
|
<div> |
|
<p class="text-gray-500">Current</p> |
|
<p class="font-medium">${formatNumber(kpi.current)}</p> |
|
</div> |
|
<div> |
|
<p class="text-gray-500">Frequency</p> |
|
<p class="font-medium">${kpi.frequency}</p> |
|
</div> |
|
<div> |
|
<p class="text-gray-500">Assigned To</p> |
|
<p class="font-medium">${assignedMember ? assignedMember.name : 'Unassigned'}</p> |
|
</div> |
|
</div> |
|
|
|
<div class="flex items-center justify-between"> |
|
<div class="text-sm"> |
|
<p class="text-gray-500">Update Value</p> |
|
</div> |
|
<div class="flex"> |
|
<input type="number" id="kpi-update-${kpi.id}" value="${kpi.current}" class="w-20 border border-gray-300 rounded-l px-2 py-1 text-sm"> |
|
<button onclick="updateKPIProgress(${kpi.id}, parseFloat(document.getElementById('kpi-update-${kpi.id}').value))" class="bg-primary text-white px-3 py-1 rounded-r text-sm hover:bg-blue-600">Update</button> |
|
</div> |
|
</div> |
|
</div> |
|
`; |
|
kpiContainer.appendChild(card); |
|
}); |
|
} |
|
|
|
function populateAssignedDropdown() { |
|
const dropdown = document.getElementById('kpi-assigned'); |
|
dropdown.innerHTML = ''; |
|
|
|
teamMembers.forEach(member => { |
|
const option = document.createElement('option'); |
|
option.value = member.id; |
|
option.textContent = member.name; |
|
dropdown.appendChild(option); |
|
}); |
|
} |
|
|
|
|
|
function updateDashboard() { |
|
document.getElementById('team-count').textContent = teamMembers.length; |
|
document.getElementById('kpi-count').textContent = kpis.length; |
|
|
|
if (teamMembers.length > 0) { |
|
const avgPerformance = teamMembers.reduce((sum, member) => sum + member.performance, 0) / teamMembers.length; |
|
document.getElementById('avg-performance').textContent = avgPerformance.toFixed(1) + '%'; |
|
|
|
const topPerformer = teamMembers.reduce((top, member) => member.performance > top.performance ? member : top, teamMembers[0]); |
|
document.getElementById('top-performer').textContent = topPerformer.name; |
|
} else { |
|
document.getElementById('avg-performance').textContent = '0%'; |
|
document.getElementById('top-performer').textContent = '-'; |
|
} |
|
|
|
renderTopKPIs(); |
|
} |
|
|
|
function renderTopKPIs() { |
|
const topKPIsContainer = document.getElementById('top-kpis'); |
|
topKPIsContainer.innerHTML = ''; |
|
|
|
|
|
const sortedKPIs = [...kpis].sort((a, b) => b.progress - a.progress).slice(0, 3); |
|
|
|
if (sortedKPIs.length === 0) { |
|
topKPIsContainer.innerHTML = '<p class="text-gray-500 text-sm">No KPIs available</p>'; |
|
return; |
|
} |
|
|
|
sortedKPIs.forEach(kpi => { |
|
const assignedMember = teamMembers.find(m => m.id === kpi.assigned); |
|
const kpiElement = document.createElement('div'); |
|
kpiElement.className = 'mb-4'; |
|
kpiElement.innerHTML = ` |
|
<div class="flex justify-between mb-1"> |
|
<span class="text-sm font-medium">${kpi.name}</span> |
|
<span class="text-sm font-medium">${kpi.progress.toFixed(1)}%</span> |
|
</div> |
|
<div class="w-full bg-gray-200 rounded-full h-1.5 mb-1"> |
|
<div class="bg-${getPerformanceColor(kpi.progress)} h-1.5 rounded-full" style="width: ${kpi.progress}%"></div> |
|
</div> |
|
<div class="flex justify-between text-xs text-gray-500"> |
|
<span>${assignedMember ? assignedMember.name : 'Unassigned'}</span> |
|
<span>${kpi.current} of ${kpi.target}</span> |
|
</div> |
|
`; |
|
topKPIsContainer.appendChild(kpiElement); |
|
}); |
|
} |
|
|
|
function setupPerformanceChart() { |
|
const ctx = document.getElementById('performance-chart').getContext('2d'); |
|
|
|
|
|
const labels = []; |
|
const today = new Date(); |
|
for (let i = 6; i >= 0; i--) { |
|
const date = new Date(today); |
|
date.setDate(date.getDate() - i); |
|
labels.push(date.toLocaleDateString('en-US', { weekday: 'short' })); |
|
} |
|
|
|
const data = labels.map(() => Math.floor(Math.random() * 30) + 70); |
|
|
|
new Chart(ctx, { |
|
type: 'line', |
|
data: { |
|
labels: labels, |
|
datasets: [{ |
|
label: 'Team Performance', |
|
data: data, |
|
backgroundColor: 'rgba(59, 130, 246, 0.1)', |
|
borderColor: 'rgba(59, 130, 246, 1)', |
|
borderWidth: 2, |
|
tension: 0.4, |
|
fill: true |
|
}] |
|
}, |
|
options: { |
|
responsive: true, |
|
maintainAspectRatio: false, |
|
plugins: { |
|
legend: { |
|
display: false |
|
} |
|
}, |
|
scales: { |
|
y: { |
|
beginAtZero: false, |
|
min: 50, |
|
max: 100 |
|
} |
|
} |
|
} |
|
}); |
|
} |
|
|
|
|
|
function getPerformanceColor(percentage) { |
|
if (percentage >= 90) return 'green-500'; |
|
if (percentage >= 75) return 'blue-500'; |
|
if (percentage >= 50) return 'yellow-500'; |
|
return 'red-500'; |
|
} |
|
|
|
function formatNumber(num) { |
|
if (num >= 1000000) { |
|
return (num / 1000000).toFixed(1) + 'M'; |
|
} |
|
if (num >= 1000) { |
|
return (num / 1000).toFixed(1) + 'K'; |
|
} |
|
return num.toString(); |
|
} |
|
|
|
function toggleSidebar() { |
|
const sidebar = document.querySelector('.sidebar'); |
|
sidebar.classList.toggle('md:w-20'); |
|
sidebar.classList.toggle('lg:w-64'); |
|
} |
|
</script> |
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=JayStormX8/performance-management-v2" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
</html> |