- API Client (src/clients/rippling.ts): OAuth2/API key auth, pagination, error handling - 50+ tools across 10 categories: * employees-tools.ts: 7 tools (list, get, create, update, terminate, custom fields, org chart) * companies-tools.ts: 5 tools (company, departments, locations, teams) * payroll-tools.ts: 4 tools (pay runs, pay statements) * time-tools.ts: 11 tools (time entries, timesheets, PTO requests) * benefits-tools.ts: 4 tools (plans, enrollments) * ats-tools.ts: 6 tools (candidates, jobs, applications, pipeline) * learning-tools.ts: 4 tools (courses, assignments) * devices-tools.ts: 4 tools (devices, apps/licenses) * groups-tools.ts: 6 tools (CRUD + members) * custom-objects-tools.ts: 5 tools (CRUD + query) - 16 React UI apps: * employee-dashboard, employee-detail, employee-directory, org-chart * payroll-dashboard, payroll-detail * time-tracker, timesheet-approvals, time-off-calendar * benefits-overview, ats-pipeline, job-board * learning-dashboard, device-inventory, team-overview, department-grid - Full TypeScript types for all API entities - Comprehensive README with usage examples - Production-ready with proper error handling and pagination
130 lines
5.1 KiB
TypeScript
130 lines
5.1 KiB
TypeScript
import React from 'react';
|
|
import { BookOpen, Award, Clock, TrendingUp } from 'lucide-react';
|
|
import type { Course, CourseAssignment } from '../../types/index.js';
|
|
|
|
interface LearningDashboardProps {
|
|
courses?: Course[];
|
|
assignments?: CourseAssignment[];
|
|
}
|
|
|
|
export const LearningDashboard: React.FC<LearningDashboardProps> = ({
|
|
courses = [],
|
|
assignments = [],
|
|
}) => {
|
|
const completed = assignments.filter(a => a.status === 'COMPLETED');
|
|
const inProgress = assignments.filter(a => a.status === 'IN_PROGRESS');
|
|
const overdue = assignments.filter(a => a.status === 'OVERDUE');
|
|
const avgProgress = assignments.length > 0
|
|
? assignments.reduce((sum, a) => sum + (a.progress || 0), 0) / assignments.length
|
|
: 0;
|
|
|
|
return (
|
|
<div className="p-6 bg-white rounded-lg shadow">
|
|
<h2 className="text-2xl font-bold mb-6">Learning Dashboard</h2>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
|
<div className="bg-blue-50 p-4 rounded-lg">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-gray-600">Total Courses</p>
|
|
<p className="text-3xl font-bold text-blue-600">{courses.length}</p>
|
|
</div>
|
|
<BookOpen className="w-10 h-10 text-blue-400" />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-green-50 p-4 rounded-lg">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-gray-600">Completed</p>
|
|
<p className="text-3xl font-bold text-green-600">{completed.length}</p>
|
|
</div>
|
|
<Award className="w-10 h-10 text-green-400" />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-purple-50 p-4 rounded-lg">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-gray-600">In Progress</p>
|
|
<p className="text-3xl font-bold text-purple-600">{inProgress.length}</p>
|
|
</div>
|
|
<Clock className="w-10 h-10 text-purple-400" />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-orange-50 p-4 rounded-lg">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-gray-600">Avg Progress</p>
|
|
<p className="text-3xl font-bold text-orange-600">{avgProgress.toFixed(0)}%</p>
|
|
</div>
|
|
<TrendingUp className="w-10 h-10 text-orange-400" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{overdue.length > 0 && (
|
|
<div className="bg-red-50 border-l-4 border-red-500 p-4 mb-6">
|
|
<div className="flex items-center gap-2">
|
|
<Clock className="w-5 h-5 text-red-600" />
|
|
<p className="font-semibold text-red-800">
|
|
{overdue.length} overdue assignment{overdue.length !== 1 ? 's' : ''}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="space-y-6">
|
|
<h3 className="font-semibold text-lg">Recent Assignments</h3>
|
|
{assignments.slice(0, 10).map((assignment) => {
|
|
const course = courses.find(c => c.id === assignment.courseId);
|
|
const progress = assignment.progress || 0;
|
|
|
|
return (
|
|
<div key={assignment.id} className="border rounded-lg p-4">
|
|
<div className="flex items-start justify-between mb-3">
|
|
<div className="flex-1">
|
|
<h4 className="font-semibold">{course?.title || 'Unknown Course'}</h4>
|
|
<p className="text-sm text-gray-600">Employee ID: {assignment.employeeId}</p>
|
|
{assignment.dueDate && (
|
|
<p className="text-sm text-gray-500">Due: {assignment.dueDate}</p>
|
|
)}
|
|
</div>
|
|
<span className={`px-3 py-1 rounded-full text-xs font-medium ${
|
|
assignment.status === 'COMPLETED' ? 'bg-green-100 text-green-800' :
|
|
assignment.status === 'IN_PROGRESS' ? 'bg-blue-100 text-blue-800' :
|
|
assignment.status === 'OVERDUE' ? 'bg-red-100 text-red-800' :
|
|
'bg-gray-100 text-gray-800'
|
|
}`}>
|
|
{assignment.status.replace('_', ' ')}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="mb-2">
|
|
<div className="flex justify-between text-sm mb-1">
|
|
<span>Progress</span>
|
|
<span className="font-medium">{progress}%</span>
|
|
</div>
|
|
<div className="w-full bg-gray-200 rounded-full h-2">
|
|
<div
|
|
className="bg-blue-600 h-2 rounded-full transition-all"
|
|
style={{ width: `${progress}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{assignment.completedAt && (
|
|
<p className="text-xs text-gray-500">
|
|
Completed: {assignment.completedAt}
|
|
{assignment.score !== undefined && ` • Score: ${assignment.score}%`}
|
|
</p>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|