| <!DOCTYPE html> |
| <html lang="en" class="h-full"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>TaskSphere Dashboard</title> |
| <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> |
| <script src="https://unpkg.com/feather-icons"></script> |
| <script src="https://d3js.org/d3.v7.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.globe.min.js"></script> |
| <script> |
| tailwind.config = { |
| theme: { |
| extend: { |
| colors: { |
| primary: '#4F46E5', |
| secondary: '#10B981', |
| } |
| } |
| } |
| } |
| </script> |
| <style> |
| .pie-chart:hover { |
| transform: scale(1.05); |
| transition: transform 0.3s ease; |
| cursor: pointer; |
| } |
| .vanta-canvas { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| z-index: -1; |
| opacity: 0.1; |
| } |
| </style> |
| </head> |
| <body class="min-h-full bg-gray-50"> |
| <div id="vanta-globe" class="vanta-canvas"></div> |
| |
| |
| <nav class="bg-white shadow-sm"> |
| <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> |
| <div class="flex justify-between h-16"> |
| <div class="flex"> |
| <div class="flex-shrink-0 flex items-center"> |
| <i data-feather="compass" class="h-8 w-8 text-primary"></i> |
| <span class="ml-2 text-xl font-bold text-gray-900">TaskSphere</span> |
| </div> |
| <div class="hidden sm:ml-6 sm:flex sm:space-x-8"> |
| <a href="index.html" class="border-primary text-gray-900 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">Dashboard</a> |
| <a href="department.html" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">Departments</a> |
| <a href="tasks.html" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">Tasks</a> |
| <a href="reports.html" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">Reports</a> |
| </div> |
| </div> |
| <div class="hidden sm:ml-6 sm:flex sm:items-center"> |
| <button type="button" class="bg-white p-1 rounded-full text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary"> |
| <i data-feather="bell" class="h-6 w-6"></i> |
| </button> |
| <div class="ml-3 relative"> |
| <div> |
| <button type="button" class="bg-white rounded-full flex text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary" id="user-menu" aria-expanded="false" aria-haspopup="true"> |
| <span class="sr-only">Open user menu</span> |
| <img class="h-8 w-8 rounded-full" src="http://static.photos/office/200x200/1" alt=""> |
| </button> |
| </div> |
| </div> |
| </div> |
| <div class="-mr-2 flex items-center sm:hidden"> |
| <button type="button" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary" aria-controls="mobile-menu" aria-expanded="false"> |
| <span class="sr-only">Open main menu</span> |
| <i data-feather="menu" class="block h-6 w-6"></i> |
| <i data-feather="x" class="hidden h-6 w-6"></i> |
| </button> |
| </div> |
| </div> |
| </div> |
| </nav> |
|
|
| |
| <div class="py-10"> |
| <header> |
| <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> |
| <h1 class="text-3xl font-bold leading-tight text-gray-900">Corporate Dashboard</h1> |
| <div class="mt-4 flex items-center"> |
| <div class="relative rounded-md shadow-sm w-64"> |
| <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> |
| <i data-feather="search" class="h-5 w-5 text-gray-400"></i> |
| </div> |
| <input type="text" class="focus:ring-primary focus:border-primary block w-full pl-10 sm:text-sm border-gray-300 rounded-md py-2" placeholder="Search departments..."> |
| </div> |
| <div class="ml-4"> |
| <button type="button" onclick="openNewDepartmentModal()" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-primary hover:bg-primary-dark focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary"> |
| <i data-feather="plus" class="-ml-1 mr-2 h-5 w-5"></i> |
| New Department |
| </button> |
| </div> |
| <div class="ml-4"> |
| <button type="button" onclick="openImportModal()" class="inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary"> |
| <i data-feather="upload" class="-ml-1 mr-2 h-5 w-5 text-gray-500"></i> |
| Import |
| </button> |
| </div> |
| </div> |
| </div> |
| </header> |
| <main> |
| <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> |
| <div class="px-4 py-8 sm:px-0"> |
| |
| <div class="grid grid-cols-1 gap-5 sm:grid-cols-3"> |
| <div class="bg-white overflow-hidden shadow rounded-lg"> |
| <div class="px-4 py-5 sm:p-6"> |
| <div class="flex items-center"> |
| <div class="flex-shrink-0 bg-primary bg-opacity-10 rounded-md p-3"> |
| <i data-feather="users" class="h-6 w-6 text-primary"></i> |
| </div> |
| <div class="ml-5 w-0 flex-1"> |
| <dl> |
| <dt class="text-sm font-medium text-gray-500 truncate">Total Departments</dt> |
| <dd> |
| <div class="text-lg font-medium text-gray-900">12</div> |
| </dd> |
| </dl> |
| </div> |
| </div> |
| </div> |
| </div> |
| <div class="bg-white overflow-hidden shadow rounded-lg"> |
| <div class="px-4 py-5 sm:p-6"> |
| <div class="flex items-center"> |
| <div class="flex-shrink-0 bg-secondary bg-opacity-10 rounded-md p-3"> |
| <i data-feather="user" class="h-6 w-6 text-secondary"></i> |
| </div> |
| <div class="ml-5 w-0 flex-1"> |
| <dl> |
| <dt class="text-sm font-medium text-gray-500 truncate">Total Employees</dt> |
| <dd> |
| <div class="text-lg font-medium text-gray-900">247</div> |
| </dd> |
| </dl> |
| </div> |
| </div> |
| </div> |
| </div> |
| <div class="bg-white overflow-hidden shadow rounded-lg"> |
| <div class="px-4 py-5 sm:p-6"> |
| <div class="flex items-center"> |
| <div class="flex-shrink-0 bg-purple-100 rounded-md p-3"> |
| <i data-feather="check-circle" class="h-6 w-6 text-purple-600"></i> |
| </div> |
| <div class="ml-5 w-0 flex-1"> |
| <dl> |
| <dt class="text-sm font-medium text-gray-500 truncate">Tasks Completed</dt> |
| <dd> |
| <div class="text-lg font-medium text-gray-900">1,248</div> |
| </dd> |
| </dl> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="mt-8"> |
| <h2 class="text-lg leading-6 font-medium text-gray-900">Department Performance</h2> |
| <div class="mt-4 grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3"> |
| |
| <div class="bg-white overflow-hidden shadow rounded-lg pie-chart" onclick="window.location.href='/department/marketing'"> |
| <div class="px-4 py-5 sm:p-6"> |
| <h3 class="text-lg leading-6 font-medium text-gray-900">Marketing</h3> |
| <div class="mt-2 flex justify-between items-baseline"> |
| <p class="text-sm text-gray-500"><span class="font-semibold text-secondary">68%</span> tasks completed</p> |
| </div> |
| <div class="mt-4" id="marketing-chart"></div> |
| </div> |
| </div> |
| |
| |
| <div class="bg-white overflow-hidden shadow rounded-lg pie-chart" onclick="window.location.href='/department/development'"> |
| <div class="px-4 py-5 sm:p-6"> |
| <h3 class="text-lg leading-6 font-medium text-gray-900">Development</h3> |
| <div class="mt-2 flex justify-between items-baseline"> |
| <p class="text-sm text-gray-500"><span class="font-semibold text-secondary">82%</span> tasks completed</p> |
| </div> |
| <div class="mt-4" id="development-chart"></div> |
| </div> |
| </div> |
| |
| |
| <div class="bg-white overflow-hidden shadow rounded-lg pie-chart" onclick="window.location.href='/department/sales'"> |
| <div class="px-4 py-5 sm:p-6"> |
| <h3 class="text-lg leading-6 font-medium text-gray-900">Sales</h3> |
| <div class="mt-2 flex justify-between items-baseline"> |
| <p class="text-sm text-gray-500"><span class="font-semibold text-secondary">54%</span> tasks completed</p> |
| </div> |
| <div class="mt-4" id="sales-chart"></div> |
| </div> |
| </div> |
| |
| |
| <div class="bg-white overflow-hidden shadow rounded-lg pie-chart" onclick="window.location.href='/department/hr'"> |
| <div class="px-4 py-5 sm:p-6"> |
| <h3 class="text-lg leading-6 font-medium text-gray-900">Human Resources</h3> |
| <div class="mt-2 flex justify-between items-baseline"> |
| <p class="text-sm text-gray-500"><span class="font-semibold text-secondary">91%</span> tasks completed</p> |
| </div> |
| <div class="mt-4" id="hr-chart"></div> |
| </div> |
| </div> |
| |
| |
| <div class="bg-white overflow-hidden shadow rounded-lg pie-chart" onclick="window.location.href='/department/finance'"> |
| <div class="px-4 py-5 sm:p-6"> |
| <h3 class="text-lg leading-6 font-medium text-gray-900">Finance</h3> |
| <div class="mt-2 flex justify-between items-baseline"> |
| <p class="text-sm text-gray-500"><span class="font-semibold text-secondary">76%</span> tasks completed</p> |
| </div> |
| <div class="mt-4" id="finance-chart"></div> |
| </div> |
| </div> |
| |
| |
| <div class="bg-white overflow-hidden shadow rounded-lg pie-chart" onclick="window.location.href='/department/operations'"> |
| <div class="px-4 py-5 sm:p-6"> |
| <h3 class="text-lg leading-6 font-medium text-gray-900">Operations</h3> |
| <div class="mt-2 flex justify-between items-baseline"> |
| <p class="text-sm text-gray-500"><span class="font-semibold text-secondary">63%</span> tasks completed</p> |
| </div> |
| <div class="mt-4" id="operations-chart"></div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="mt-8"> |
| <h2 class="text-lg leading-6 font-medium text-gray-900">Recent Tasks</h2> |
| <div class="mt-4 bg-white shadow overflow-hidden sm:rounded-md"> |
| <ul class="divide-y divide-gray-200"> |
| <li> |
| <div class="px-4 py-4 sm:px-6"> |
| <div class="flex items-center justify-between"> |
| <p class="text-sm font-medium text-primary truncate">Launch new marketing campaign</p> |
| <div class="ml-2 flex-shrink-0 flex"> |
| <p class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">Completed</p> |
| </div> |
| </div> |
| <div class="mt-2 sm:flex sm:justify-between"> |
| <div class="sm:flex"> |
| <p class="flex items-center text-sm text-gray-500"> |
| <i data-feather="users" class="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400"></i> |
| Marketing |
| </p> |
| <p class="mt-2 flex items-center text-sm text-gray-500 sm:mt-0 sm:ml-6"> |
| <i data-feather="calendar" class="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400"></i> |
| Due May 15, 2023 |
| </p> |
| </div> |
| <div class="mt-2 flex items-center text-sm text-gray-500 sm:mt-0"> |
| <i data-feather="clock" class="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400"></i> |
| <p>Completed <time datetime="2023-05-12">May 12, 2023</time></p> |
| </div> |
| </div> |
| </div> |
| </li> |
| <li> |
| <div class="px-4 py-4 sm:px-6"> |
| <div class="flex items-center justify-between"> |
| <p class="text-sm font-medium text-primary truncate">Implement new API endpoints</p> |
| <div class="ml-2 flex-shrink-0 flex"> |
| <p class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-secondary-100 text-secondary-800">In Progress</p> |
| </div> |
| </div> |
| <div class="mt-2 sm:flex sm:justify-between"> |
| <div class="sm:flex"> |
| <p class="flex items-center text-sm text-gray-500"> |
| <i data-feather="users" class="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400"></i> |
| Development |
| </p> |
| <p class="mt-2 flex items-center text-sm text-gray-500 sm:mt-0 sm:ml-6"> |
| <i data-feather="calendar" class="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400"></i> |
| Due May 20, 2023 |
| </p> |
| </div> |
| <div class="mt-2 flex items-center text-sm text-gray-500 sm:mt-0"> |
| <i data-feather="clock" class="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400"></i> |
| <p>Started <time datetime="2023-05-10">May 10, 2023</time></p> |
| </div> |
| </div> |
| </div> |
| </li> |
| <li> |
| <div class="px-4 py-4 sm:px-6"> |
| <div class="flex items-center justify-between"> |
| <p class="text-sm font-medium text-primary truncate">Hire new sales representatives</p> |
| <div class="ml-2 flex-shrink-0 flex"> |
| <p class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800">Pending</p> |
| </div> |
| </div> |
| <div class="mt-2 sm:flex sm:justify-between"> |
| <div class="sm:flex"> |
| <p class="flex items-center text-sm text-gray-500"> |
| <i data-feather="users" class="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400"></i> |
| Human Resources |
| </p> |
| <p class="mt-2 flex items-center text-sm text-gray-500 sm:mt-0 sm:ml-6"> |
| <i data-feather="calendar" class="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400"></i> |
| Due June 1, 2023 |
| </p> |
| </div> |
| <div class="mt-2 flex items-center text-sm text-gray-500 sm:mt-0"> |
| <i data-feather="clock" class="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400"></i> |
| <p>Created <time datetime="2023-05-05">May 5, 2023</time></p> |
| </div> |
| </div> |
| </div> |
| </li> |
| </ul> |
| </div> |
| </div> |
| </div> |
| </div> |
| </main> |
| </div> |
| |
| <div id="newDepartmentModal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"> |
| <div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white"> |
| <div class="mt-3 text-center"> |
| <h3 class="text-lg leading-6 font-medium text-gray-900">Create New Department</h3> |
| <div class="mt-2 px-7 py-3"> |
| <div class="mb-4"> |
| <label for="deptName" class="block text-sm font-medium text-gray-700 text-left">Department Name</label> |
| <input type="text" id="deptName" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-primary focus:border-primary sm:text-sm"> |
| </div> |
| <div class="mb-4"> |
| <label for="deptDescription" class="block text-sm font-medium text-gray-700 text-left">Description</label> |
| <textarea id="deptDescription" rows="3" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-primary focus:border-primary sm:text-sm"></textarea> |
| </div> |
| <div class="mb-4"> |
| <label for="deptManager" class="block text-sm font-medium text-gray-700 text-left">Manager</label> |
| <select id="deptManager" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-primary focus:border-primary sm:text-sm"> |
| <option value="">Select a manager</option> |
| </select> |
| </div> |
| </div> |
| <div class="items-center px-4 py-3"> |
| <button id="createDeptBtn" onclick="createDepartment()" class="px-4 py-2 bg-primary text-white text-base font-medium rounded-md shadow-sm hover:bg-primary-dark focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary"> |
| Create |
| </button> |
| <button onclick="closeModal('newDepartmentModal')" class="ml-3 px-4 py-2 bg-gray-200 text-gray-700 text-base font-medium rounded-md shadow-sm hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"> |
| Cancel |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="importModal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"> |
| <div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white"> |
| <div class="mt-3 text-center"> |
| <h3 class="text-lg leading-6 font-medium text-gray-900">Import Departments</h3> |
| <div class="mt-2 px-7 py-3"> |
| <div class="mb-4"> |
| <label class="block text-sm font-medium text-gray-700 text-left">File</label> |
| <div class="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md"> |
| <div class="space-y-1 text-center"> |
| <div class="flex text-sm text-gray-600"> |
| <label for="file-upload" class="relative cursor-pointer bg-white rounded-md font-medium text-primary hover:text-primary-dark focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-primary"> |
| <span>Upload a file</span> |
| <input id="file-upload" name="file-upload" type="file" class="sr-only"> |
| </label> |
| <p class="pl-1">or drag and drop</p> |
| </div> |
| <p class="text-xs text-gray-500"> |
| CSV files only |
| </p> |
| </div> |
| </div> |
| </div> |
| </div> |
| <div class="items-center px-4 py-3"> |
| <button onclick="importDepartments()" class="px-4 py-2 bg-primary text-white text-base font-medium rounded-md shadow-sm hover:bg-primary-dark focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary"> |
| Import |
| </button> |
| <button onclick="closeModal('importModal')" class="ml-3 px-4 py-2 bg-gray-200 text-gray-700 text-base font-medium rounded-md shadow-sm hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"> |
| Cancel |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| const API_BASE_URL = 'http://localhost:5000/api'; |
| let authToken = localStorage.getItem('authToken'); |
| |
| |
| document.addEventListener('DOMContentLoaded', function() { |
| if (!authToken) { |
| |
| window.location.href = '/login.html'; |
| return; |
| } |
| |
| loadManagers(); |
| loadDepartments(); |
| loadRecentTasks(); |
| }); |
| |
| |
| function openNewDepartmentModal() { |
| document.getElementById('newDepartmentModal').classList.remove('hidden'); |
| } |
| |
| function openImportModal() { |
| document.getElementById('importModal').classList.remove('hidden'); |
| } |
| |
| function closeModal(modalId) { |
| document.getElementById(modalId).classList.add('hidden'); |
| } |
| |
| |
| async function loadManagers() { |
| try { |
| const response = await fetch(`${API_BASE_URL}/users`, { |
| headers: { |
| 'x-auth-token': authToken |
| } |
| }); |
| const users = await response.json(); |
| |
| const managerSelect = document.getElementById('deptManager'); |
| users.forEach(user => { |
| if (user.role === 'manager') { |
| const option = document.createElement('option'); |
| option.value = user._id; |
| option.textContent = user.name; |
| managerSelect.appendChild(option); |
| } |
| }); |
| } catch (error) { |
| console.error('Error loading managers:', error); |
| alert('Failed to load managers'); |
| } |
| } |
| |
| |
| async function createDepartment() { |
| const name = document.getElementById('deptName').value; |
| const description = document.getElementById('deptDescription').value; |
| const manager = document.getElementById('deptManager').value; |
| |
| if (!name) { |
| alert('Department name is required'); |
| return; |
| } |
| |
| try { |
| const response = await fetch(`${API_BASE_URL}/departments`, { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| 'x-auth-token': authToken |
| }, |
| body: JSON.stringify({ name, description, manager }) |
| }); |
| |
| if (response.ok) { |
| const newDept = await response.json(); |
| alert('Department created successfully!'); |
| closeModal('newDepartmentModal'); |
| loadDepartments(); |
| } else { |
| const error = await response.json(); |
| alert(error.message || 'Failed to create department'); |
| } |
| } catch (error) { |
| console.error('Error creating department:', error); |
| alert('Failed to create department'); |
| } |
| } |
| |
| |
| async function loadDepartments() { |
| try { |
| const response = await fetch(`${API_BASE_URL}/departments`, { |
| headers: { |
| 'x-auth-token': authToken |
| } |
| }); |
| const departments = await response.json(); |
| |
| |
| console.log('Loaded departments:', departments); |
| |
| |
| } catch (error) { |
| console.error('Error loading departments:', error); |
| alert('Failed to load departments'); |
| } |
| } |
| |
| |
| async function loadRecentTasks() { |
| try { |
| const response = await fetch(`${API_BASE_URL}/tasks`, { |
| headers: { |
| 'x-auth-token': authToken |
| } |
| }); |
| const tasks = await response.json(); |
| |
| |
| console.log('Loaded tasks:', tasks); |
| |
| |
| } catch (error) { |
| console.error('Error loading tasks:', error); |
| alert('Failed to load tasks'); |
| } |
| } |
| |
| |
| async function importDepartments() { |
| const fileInput = document.getElementById('file-upload'); |
| const file = fileInput.files[0]; |
| |
| if (!file) { |
| alert('Please select a file to upload'); |
| return; |
| } |
| |
| const formData = new FormData(); |
| formData.append('file', file); |
| |
| try { |
| const response = await fetch(`${API_BASE_URL}/departments/import`, { |
| method: 'POST', |
| headers: { |
| 'x-auth-token': authToken |
| }, |
| body: formData |
| }); |
| |
| if (response.ok) { |
| alert('Departments imported successfully!'); |
| closeModal('importModal'); |
| loadDepartments(); |
| } else { |
| const error = await response.json(); |
| alert(error.message || 'Failed to import departments'); |
| } |
| } catch (error) { |
| console.error('Error importing departments:', error); |
| alert('Failed to import departments'); |
| } |
| } |
| |
| |
| VANTA.GLOBE({ |
| el: "#vanta-globe", |
| mouseControls: true, |
| touchControls: true, |
| gyroControls: false, |
| minHeight: 200.00, |
| minWidth: 200.00, |
| scale: 1.00, |
| scaleMobile: 1.00, |
| color: 0x4f46e5, |
| backgroundColor: 0xf3f4f6, |
| size: 1.00 |
| }); |
| |
| |
| feather.replace(); |
| |
| |
| const departmentData = { |
| marketing: { completed: 68, remaining: 32 }, |
| development: { completed: 82, remaining: 18 }, |
| sales: { completed: 54, remaining: 46 }, |
| hr: { completed: 91, remaining: 9 }, |
| finance: { completed: 76, remaining: 24 }, |
| operations: { completed: 63, remaining: 37 } |
| }; |
| |
| |
| Object.keys(departmentData).forEach(dept => { |
| drawPieChart(`${dept}-chart`, departmentData[dept].completed, departmentData[dept].remaining); |
| }); |
| |
| function drawPieChart(containerId, completedPercent, remainingPercent) { |
| const width = 150; |
| const height = 150; |
| const radius = Math.min(width, height) / 2; |
| |
| const svg = d3.select(`#${containerId}`) |
| .append("svg") |
| .attr("width", width) |
| .attr("height", height) |
| .append("g") |
| .attr("transform", `translate(${width/2},${height/2})`); |
| |
| const color = d3.scaleOrdinal() |
| .domain(["completed", "remaining"]) |
| .range(["#10B981", "#E5E7EB"]); |
| |
| const pie = d3.pie() |
| .value(d => d.value) |
| .sort(null); |
| |
| const data = [ |
| { label: "completed", value: completedPercent }, |
| { label: "remaining", value: remainingPercent } |
| ]; |
| |
| const arc = d3.arc() |
| .innerRadius(0) |
| .outerRadius(radius); |
| |
| const arcs = svg.selectAll(".arc") |
| .data(pie(data)) |
| .enter() |
| .append("g") |
| .attr("class", "arc"); |
| |
| arcs.append("path") |
| .attr("d", arc) |
| .attr("fill", d => color(d.data.label)) |
| .attr("stroke", "#fff") |
| .attr("stroke-width", 2); |
| |
| |
| svg.append("text") |
| .attr("text-anchor", "middle") |
| .attr("dy", ".3em") |
| .style("font-size", "20px") |
| .style("font-weight", "bold") |
| .text(`${completedPercent}%`); |
| } |
| </script> |
| </body> |
| </html> |
|
|