bamboohr: Build 18 React MCP Apps with complete structure

- Created 18 standalone React apps in src/ui/react-app/{app-name}/
- Each app has App.tsx, index.html, and vite.config.ts
- All apps use dark theme (#0f172a/#1e293b)
- Apps built: employee-dashboard, employee-detail, employee-directory,
  time-off-calendar, time-off-requests, time-off-balances, report-builder,
  benefits-overview, benefits-enrollment, payroll-dashboard, goal-tracker,
  training-catalog, training-progress, file-manager, org-chart,
  headcount-analytics, turnover-report, new-hires
- Each app self-contained with inline components
- Client-side state management with React hooks
- Tailwind CSS via CDN for styling
- Unique dev server ports (3000-3017)
This commit is contained in:
Jake Shore 2026-02-12 17:43:17 -05:00
parent 7ee40342c8
commit f6d8b43694
54 changed files with 3626 additions and 0 deletions

View File

@ -0,0 +1,129 @@
import React, { useState } from 'react';
import { Heart, CheckCircle, XCircle, Search } from 'lucide-react';
interface Enrollment {
id: string;
employee: string;
plan: string;
type: string;
status: 'Enrolled' | 'Pending' | 'Declined';
startDate: string;
premium: string;
}
const Card: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="bg-slate-800 rounded-lg shadow-lg p-6">{children}</div>
);
export default function App() {
const [searchTerm, setSearchTerm] = useState('');
const enrollments: Enrollment[] = [
{ id: '1', employee: 'Sarah Johnson', plan: 'Health Insurance - PPO', type: 'Health', status: 'Enrolled', startDate: '2025-01-01', premium: '$450' },
{ id: '2', employee: 'Sarah Johnson', plan: 'Dental Insurance', type: 'Dental', status: 'Enrolled', startDate: '2025-01-01', premium: '$50' },
{ id: '3', employee: 'Sarah Johnson', plan: '401(k) Plan', type: 'Retirement', status: 'Enrolled', startDate: '2025-01-01', premium: 'Varies' },
{ id: '4', employee: 'Mike Chen', plan: 'Health Insurance - HMO', type: 'Health', status: 'Enrolled', startDate: '2025-01-01', premium: '$350' },
{ id: '5', employee: 'Mike Chen', plan: 'Vision Insurance', type: 'Vision', status: 'Pending', startDate: '2025-02-01', premium: '$25' },
{ id: '6', employee: 'Emily Davis', plan: 'Health Insurance - PPO', type: 'Health', status: 'Enrolled', startDate: '2025-01-01', premium: '$450' },
{ id: '7', employee: 'Emily Davis', plan: 'Dental Insurance', type: 'Dental', status: 'Declined', startDate: 'N/A', premium: 'N/A' },
{ id: '8', employee: 'James Wilson', plan: 'Life Insurance', type: 'Life', status: 'Enrolled', startDate: '2025-01-01', premium: '$15' },
];
const filteredEnrollments = enrollments.filter(enrollment =>
enrollment.employee.toLowerCase().includes(searchTerm.toLowerCase()) ||
enrollment.plan.toLowerCase().includes(searchTerm.toLowerCase())
);
const groupedEnrollments = filteredEnrollments.reduce((acc, enrollment) => {
if (!acc[enrollment.employee]) {
acc[enrollment.employee] = [];
}
acc[enrollment.employee].push(enrollment);
return acc;
}, {} as Record<string, Enrollment[]>);
return (
<div className="min-h-screen bg-slate-900 text-white p-6">
<div className="max-w-6xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white flex items-center">
<Heart className="mr-3 h-10 w-10 text-blue-400" />
Benefits Enrollment
</h1>
{/* Search */}
<Card>
<div className="relative">
<Search className="absolute left-3 top-3 h-5 w-5 text-slate-400" />
<input
type="text"
placeholder="Search by employee or plan..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
</Card>
{/* Enrollments by Employee */}
<div className="mt-8 space-y-6">
{Object.entries(groupedEnrollments).map(([employee, enrollments]) => (
<Card key={employee}>
<div className="flex items-center mb-4 pb-4 border-b border-slate-700">
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center text-lg font-bold mr-4">
{employee.split(' ').map(n => n[0]).join('')}
</div>
<div>
<h3 className="text-xl font-semibold text-white">{employee}</h3>
<p className="text-sm text-slate-400">{enrollments.length} plan{enrollments.length > 1 ? 's' : ''}</p>
</div>
</div>
<div className="space-y-3">
{enrollments.map((enrollment) => (
<div key={enrollment.id} className="flex items-center justify-between p-4 bg-slate-700 rounded-lg">
<div className="flex-1">
<div className="flex items-center space-x-3 mb-2">
{enrollment.status === 'Enrolled' && <CheckCircle className="h-5 w-5 text-green-400" />}
{enrollment.status === 'Pending' && <Clock className="h-5 w-5 text-yellow-400" />}
{enrollment.status === 'Declined' && <XCircle className="h-5 w-5 text-red-400" />}
<h4 className="font-semibold text-white">{enrollment.plan}</h4>
</div>
<div className="grid grid-cols-3 gap-4 ml-8 text-sm">
<div>
<p className="text-slate-400">Type</p>
<p className="text-white">{enrollment.type}</p>
</div>
<div>
<p className="text-slate-400">Start Date</p>
<p className="text-white">{enrollment.startDate}</p>
</div>
<div>
<p className="text-slate-400">Premium</p>
<p className="text-white">{enrollment.premium}</p>
</div>
</div>
</div>
<span className={`px-4 py-2 rounded-full text-sm font-medium ml-4 ${
enrollment.status === 'Enrolled' ? 'bg-green-600 text-white' :
enrollment.status === 'Pending' ? 'bg-yellow-600 text-white' :
'bg-red-600 text-white'
}`}>
{enrollment.status}
</span>
</div>
))}
</div>
</Card>
))}
{Object.keys(groupedEnrollments).length === 0 && (
<div className="text-center py-12">
<Heart className="h-16 w-16 text-slate-600 mx-auto mb-4" />
<p className="text-slate-400 text-lg">No enrollments found</p>
</div>
)}
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Benefits Enrollment - BambooHR</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./App.tsx"></script>
<script type="module">
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
ReactDOM.createRoot(document.getElementById('root')).render(
React.createElement(React.StrictMode, null, React.createElement(App))
);
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3008,
open: true
},
build: {
outDir: 'dist',
sourcemap: true
}
});

View File

@ -0,0 +1,144 @@
import React from 'react';
import { Heart, Users, DollarSign, Shield } from 'lucide-react';
interface BenefitPlan {
id: string;
name: string;
type: string;
enrolled: number;
total: number;
cost: string;
provider: string;
}
const Card: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="bg-slate-800 rounded-lg shadow-lg p-6">{children}</div>
);
export default function App() {
const plans: BenefitPlan[] = [
{ id: '1', name: 'Health Insurance - PPO', type: 'Health', enrolled: 185, total: 245, cost: '$450/mo', provider: 'BlueCross' },
{ id: '2', name: 'Health Insurance - HMO', type: 'Health', enrolled: 45, total: 245, cost: '$350/mo', provider: 'BlueCross' },
{ id: '3', name: 'Dental Insurance', type: 'Dental', enrolled: 210, total: 245, cost: '$50/mo', provider: 'Delta Dental' },
{ id: '4', name: 'Vision Insurance', type: 'Vision', enrolled: 195, total: 245, cost: '$25/mo', provider: 'VSP' },
{ id: '5', name: '401(k) Plan', type: 'Retirement', enrolled: 220, total: 245, cost: 'Varies', provider: 'Fidelity' },
{ id: '6', name: 'Life Insurance', type: 'Life', enrolled: 245, total: 245, cost: '$15/mo', provider: 'MetLife' },
{ id: '7', name: 'Disability Insurance', type: 'Disability', enrolled: 230, total: 245, cost: '$30/mo', provider: 'Unum' },
{ id: '8', name: 'FSA', type: 'Flexible Spending', enrolled: 120, total: 245, cost: 'N/A', provider: 'WageWorks' },
];
const stats = {
totalEnrolled: plans.reduce((sum, plan) => sum + plan.enrolled, 0),
avgEnrollment: Math.round((plans.reduce((sum, plan) => sum + (plan.enrolled / plan.total), 0) / plans.length) * 100),
totalPlans: plans.length,
employees: 245
};
const getIcon = (type: string) => {
switch (type) {
case 'Health': return <Heart className="h-6 w-6" />;
case 'Dental': return <Heart className="h-6 w-6" />;
case 'Vision': return <Heart className="h-6 w-6" />;
case 'Retirement': return <DollarSign className="h-6 w-6" />;
case 'Life': return <Shield className="h-6 w-6" />;
case 'Disability': return <Shield className="h-6 w-6" />;
default: return <Shield className="h-6 w-6" />;
}
};
const getColor = (type: string) => {
switch (type) {
case 'Health': return 'from-blue-500 to-blue-600';
case 'Dental': return 'from-green-500 to-green-600';
case 'Vision': return 'from-purple-500 to-purple-600';
case 'Retirement': return 'from-yellow-500 to-yellow-600';
case 'Life': return 'from-red-500 to-red-600';
case 'Disability': return 'from-indigo-500 to-indigo-600';
default: return 'from-gray-500 to-gray-600';
}
};
return (
<div className="min-h-screen bg-slate-900 text-white p-6">
<div className="max-w-7xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white flex items-center">
<Heart className="mr-3 h-10 w-10 text-blue-400" />
Benefits Overview
</h1>
{/* Summary Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Total Plans</p>
<p className="text-3xl font-bold text-white">{stats.totalPlans}</p>
</div>
<Shield className="h-10 w-10 text-blue-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Total Employees</p>
<p className="text-3xl font-bold text-white">{stats.employees}</p>
</div>
<Users className="h-10 w-10 text-green-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Avg Enrollment</p>
<p className="text-3xl font-bold text-white">{stats.avgEnrollment}%</p>
</div>
<DollarSign className="h-10 w-10 text-purple-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Total Enrollments</p>
<p className="text-3xl font-bold text-white">{stats.totalEnrolled}</p>
</div>
<Heart className="h-10 w-10 text-red-400" />
</div>
</Card>
</div>
{/* Benefits Plans Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{plans.map((plan) => {
const enrollmentPercentage = (plan.enrolled / plan.total) * 100;
return (
<Card key={plan.id}>
<div className={`w-12 h-12 bg-gradient-to-br ${getColor(plan.type)} rounded-lg flex items-center justify-center text-white mb-4`}>
{getIcon(plan.type)}
</div>
<h3 className="text-lg font-semibold text-white mb-2">{plan.name}</h3>
<p className="text-sm text-slate-400 mb-4">{plan.provider}</p>
<div className="space-y-3">
<div className="flex justify-between text-sm">
<span className="text-slate-400">Enrollment</span>
<span className="text-white font-medium">{plan.enrolled} / {plan.total}</span>
</div>
<div className="w-full bg-slate-700 rounded-full h-2">
<div
className={`bg-gradient-to-r ${getColor(plan.type)} h-2 rounded-full transition-all duration-300`}
style={{ width: `${enrollmentPercentage}%` }}
/>
</div>
<div className="flex justify-between text-sm pt-2 border-t border-slate-700">
<span className="text-slate-400">Cost</span>
<span className="text-white font-medium">{plan.cost}</span>
</div>
</div>
</Card>
);
})}
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Benefits Overview - BambooHR</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./App.tsx"></script>
<script type="module">
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
ReactDOM.createRoot(document.getElementById('root')).render(
React.createElement(React.StrictMode, null, React.createElement(App))
);
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3007,
open: true
},
build: {
outDir: 'dist',
sourcemap: true
}
});

View File

@ -0,0 +1,139 @@
import React, { useState, useEffect } from 'react';
import { Users, Clock, Target, TrendingUp, TrendingDown, UserPlus } from 'lucide-react';
interface Stats {
totalEmployees: number;
activeEmployees: number;
newHires: number;
turnoverRate: number;
}
const Card: React.FC<{ children: React.ReactNode; className?: string }> = ({ children, className = '' }) => (
<div className={`bg-slate-800 rounded-lg shadow-lg p-6 ${className}`}>{children}</div>
);
const StatCard: React.FC<{ label: string; value: string | number; icon: React.ReactNode; color: string }> =
({ label, value, icon, color }) => (
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm mb-1">{label}</p>
<p className="text-3xl font-bold text-white">{value}</p>
</div>
<div className={`${color} p-3 rounded-lg`}>
{icon}
</div>
</div>
</Card>
);
export default function App() {
const [stats, setStats] = useState<Stats>({
totalEmployees: 245,
activeEmployees: 238,
newHires: 12,
turnoverRate: 2.8
});
const [timeOffRequests] = useState([
{ id: 1, name: 'Sarah Johnson', dates: 'Jan 15 - Jan 17', status: 'Pending' },
{ id: 2, name: 'Mike Chen', dates: 'Jan 18 - Jan 20', status: 'Approved' },
{ id: 3, name: 'Emily Davis', dates: 'Jan 22 - Jan 24', status: 'Pending' },
{ id: 4, name: 'James Wilson', dates: 'Jan 25 - Jan 27', status: 'Pending' }
]);
const [activeGoals] = useState([
{ id: 1, title: 'Q1 Revenue Growth', progress: 75 },
{ id: 2, title: 'Customer Satisfaction', progress: 60 },
{ id: 3, title: 'Team Productivity', progress: 85 },
{ id: 4, title: 'Product Development', progress: 50 }
]);
return (
<div className="min-h-screen bg-slate-900 text-white p-6">
<div className="max-w-7xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white">Employee Dashboard</h1>
{/* Stats Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<StatCard
label="Total Employees"
value={stats.totalEmployees}
icon={<Users className="h-8 w-8 text-white" />}
color="bg-blue-600"
/>
<StatCard
label="Active Employees"
value={stats.activeEmployees}
icon={<TrendingUp className="h-8 w-8 text-white" />}
color="bg-green-600"
/>
<StatCard
label="New This Month"
value={stats.newHires}
icon={<UserPlus className="h-8 w-8 text-white" />}
color="bg-purple-600"
/>
<StatCard
label="Turnover Rate"
value={`${stats.turnoverRate}%`}
icon={<TrendingDown className="h-8 w-8 text-white" />}
color="bg-orange-600"
/>
</div>
{/* Activity Grid */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Time Off Requests */}
<Card>
<h2 className="text-xl font-semibold mb-4 flex items-center text-white">
<Clock className="mr-2 h-5 w-5 text-blue-400" />
Recent Time Off Requests
</h2>
<div className="space-y-3">
{timeOffRequests.map((request) => (
<div key={request.id} className="flex justify-between items-center p-3 bg-slate-700 rounded-lg">
<div>
<p className="font-medium text-white">{request.name}</p>
<p className="text-sm text-slate-400">{request.dates}</p>
</div>
<span className={`px-3 py-1 rounded-full text-xs font-medium ${
request.status === 'Approved'
? 'bg-green-600 text-white'
: 'bg-yellow-600 text-white'
}`}>
{request.status}
</span>
</div>
))}
</div>
</Card>
{/* Active Goals */}
<Card>
<h2 className="text-xl font-semibold mb-4 flex items-center text-white">
<Target className="mr-2 h-5 w-5 text-purple-400" />
Active Goals
</h2>
<div className="space-y-4">
{activeGoals.map((goal) => (
<div key={goal.id} className="p-3 bg-slate-700 rounded-lg">
<div className="flex justify-between items-center mb-2">
<p className="font-medium text-white">{goal.title}</p>
<span className="text-sm text-slate-400">{goal.progress}%</span>
</div>
<div className="w-full bg-slate-600 rounded-full h-2">
<div
className="bg-gradient-to-r from-blue-500 to-purple-500 h-2 rounded-full transition-all duration-300"
style={{ width: `${goal.progress}%` }}
/>
</div>
</div>
))}
</div>
</Card>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Employee Dashboard - BambooHR</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./App.tsx"></script>
<script type="module">
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
ReactDOM.createRoot(document.getElementById('root')).render(
React.createElement(React.StrictMode, null, React.createElement(App))
);
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3000,
open: true
},
build: {
outDir: 'dist',
sourcemap: true
}
});

View File

@ -0,0 +1,140 @@
import React, { useState } from 'react';
import { User, Mail, Phone, Calendar, Briefcase, DollarSign, MapPin, Award } from 'lucide-react';
interface Employee {
id: string;
firstName: string;
lastName: string;
email: string;
phone: string;
department: string;
jobTitle: string;
hireDate: string;
location: string;
salary: string;
manager: string;
status: string;
}
const Card: React.FC<{ children: React.ReactNode; className?: string }> = ({ children, className = '' }) => (
<div className={`bg-slate-800 rounded-lg shadow-lg p-6 ${className}`}>{children}</div>
);
const InfoRow: React.FC<{ icon: React.ReactNode; label: string; value: string }> = ({ icon, label, value }) => (
<div className="flex items-start space-x-3 p-3 bg-slate-700 rounded-lg">
<div className="text-blue-400 mt-1">{icon}</div>
<div className="flex-1">
<p className="text-sm text-slate-400">{label}</p>
<p className="text-white font-medium">{value}</p>
</div>
</div>
);
export default function App() {
const [employee] = useState<Employee>({
id: '12345',
firstName: 'Sarah',
lastName: 'Johnson',
email: 'sarah.johnson@company.com',
phone: '+1 (555) 123-4567',
department: 'Engineering',
jobTitle: 'Senior Software Engineer',
hireDate: 'January 15, 2020',
location: 'San Francisco, CA',
salary: '$125,000',
manager: 'John Smith',
status: 'Active'
});
const customFields = [
{ label: 'Employee Type', value: 'Full-time' },
{ label: 'Work Schedule', value: 'Monday - Friday, 9:00 AM - 5:00 PM' },
{ label: 'Emergency Contact', value: 'Michael Johnson - +1 (555) 987-6543' },
{ label: 'T-Shirt Size', value: 'Medium' }
];
return (
<div className="min-h-screen bg-slate-900 text-white p-6">
<div className="max-w-5xl mx-auto">
{/* Header */}
<div className="mb-8">
<div className="flex items-center space-x-4 mb-4">
<div className="w-20 h-20 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center text-2xl font-bold">
{employee.firstName[0]}{employee.lastName[0]}
</div>
<div>
<h1 className="text-4xl font-bold text-white">
{employee.firstName} {employee.lastName}
</h1>
<p className="text-xl text-slate-400">{employee.jobTitle}</p>
</div>
</div>
<span className={`inline-block px-4 py-2 rounded-full text-sm font-medium ${
employee.status === 'Active' ? 'bg-green-600 text-white' : 'bg-red-600 text-white'
}`}>
{employee.status}
</span>
</div>
{/* Main Info Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<Card>
<h2 className="text-xl font-semibold mb-4 text-white">Contact Information</h2>
<div className="space-y-3">
<InfoRow icon={<Mail className="h-5 w-5" />} label="Email" value={employee.email} />
<InfoRow icon={<Phone className="h-5 w-5" />} label="Phone" value={employee.phone} />
<InfoRow icon={<MapPin className="h-5 w-5" />} label="Location" value={employee.location} />
</div>
</Card>
<Card>
<h2 className="text-xl font-semibold mb-4 text-white">Job Information</h2>
<div className="space-y-3">
<InfoRow icon={<Briefcase className="h-5 w-5" />} label="Department" value={employee.department} />
<InfoRow icon={<User className="h-5 w-5" />} label="Manager" value={employee.manager} />
<InfoRow icon={<Calendar className="h-5 w-5" />} label="Hire Date" value={employee.hireDate} />
</div>
</Card>
</div>
{/* Compensation */}
<Card className="mb-8">
<h2 className="text-xl font-semibold mb-4 text-white flex items-center">
<DollarSign className="mr-2 h-5 w-5 text-green-400" />
Compensation
</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="p-4 bg-slate-700 rounded-lg">
<p className="text-sm text-slate-400 mb-1">Annual Salary</p>
<p className="text-2xl font-bold text-white">{employee.salary}</p>
</div>
<div className="p-4 bg-slate-700 rounded-lg">
<p className="text-sm text-slate-400 mb-1">Last Review</p>
<p className="text-2xl font-bold text-white">Dec 2024</p>
</div>
<div className="p-4 bg-slate-700 rounded-lg">
<p className="text-sm text-slate-400 mb-1">Next Review</p>
<p className="text-2xl font-bold text-white">Jun 2025</p>
</div>
</div>
</Card>
{/* Custom Fields */}
<Card>
<h2 className="text-xl font-semibold mb-4 text-white flex items-center">
<Award className="mr-2 h-5 w-5 text-purple-400" />
Additional Information
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{customFields.map((field, idx) => (
<div key={idx} className="p-3 bg-slate-700 rounded-lg">
<p className="text-sm text-slate-400">{field.label}</p>
<p className="text-white font-medium">{field.value}</p>
</div>
))}
</div>
</Card>
</div>
</div>
);
}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Employee Detail - BambooHR</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./App.tsx"></script>
<script type="module">
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
ReactDOM.createRoot(document.getElementById('root')).render(
React.createElement(React.StrictMode, null, React.createElement(App))
);
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3001,
open: true
},
build: {
outDir: 'dist',
sourcemap: true
}
});

View File

@ -0,0 +1,139 @@
import React, { useState, useMemo } from 'react';
import { Search, Filter, Mail, Phone } from 'lucide-react';
interface Employee {
id: string;
name: string;
title: string;
department: string;
email: string;
phone: string;
location: string;
status: string;
}
const Card: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="bg-slate-800 rounded-lg shadow-lg p-6">{children}</div>
);
export default function App() {
const [searchTerm, setSearchTerm] = useState('');
const [filterDept, setFilterDept] = useState('All');
const [filterStatus, setFilterStatus] = useState('All');
const employees: Employee[] = [
{ id: '1', name: 'Sarah Johnson', title: 'Senior Software Engineer', department: 'Engineering', email: 'sarah.j@company.com', phone: '555-0101', location: 'San Francisco', status: 'Active' },
{ id: '2', name: 'Mike Chen', title: 'Product Manager', department: 'Product', email: 'mike.c@company.com', phone: '555-0102', location: 'New York', status: 'Active' },
{ id: '3', name: 'Emily Davis', title: 'UX Designer', department: 'Design', email: 'emily.d@company.com', phone: '555-0103', location: 'Austin', status: 'Active' },
{ id: '4', name: 'James Wilson', title: 'DevOps Engineer', department: 'Engineering', email: 'james.w@company.com', phone: '555-0104', location: 'Seattle', status: 'Active' },
{ id: '5', name: 'Lisa Anderson', title: 'Marketing Director', department: 'Marketing', email: 'lisa.a@company.com', phone: '555-0105', location: 'Boston', status: 'Active' },
{ id: '6', name: 'David Brown', title: 'Sales Manager', department: 'Sales', email: 'david.b@company.com', phone: '555-0106', location: 'Chicago', status: 'Active' },
{ id: '7', name: 'Jennifer Taylor', title: 'HR Manager', department: 'HR', email: 'jen.t@company.com', phone: '555-0107', location: 'Denver', status: 'Active' },
{ id: '8', name: 'Robert Martinez', title: 'Software Engineer', department: 'Engineering', email: 'rob.m@company.com', phone: '555-0108', location: 'San Francisco', status: 'Inactive' }
];
const departments = ['All', ...Array.from(new Set(employees.map(e => e.department)))];
const filteredEmployees = useMemo(() => {
return employees.filter(emp => {
const matchesSearch = emp.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
emp.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
emp.email.toLowerCase().includes(searchTerm.toLowerCase());
const matchesDept = filterDept === 'All' || emp.department === filterDept;
const matchesStatus = filterStatus === 'All' || emp.status === filterStatus;
return matchesSearch && matchesDept && matchesStatus;
});
}, [searchTerm, filterDept, filterStatus, employees]);
return (
<div className="min-h-screen bg-slate-900 text-white p-6">
<div className="max-w-7xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white">Employee Directory</h1>
{/* Search and Filters */}
<Card>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<div className="relative">
<Search className="absolute left-3 top-3 h-5 w-5 text-slate-400" />
<input
type="text"
placeholder="Search employees..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<select
value={filterDept}
onChange={(e) => setFilterDept(e.target.value)}
className="px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
>
{departments.map(dept => (
<option key={dept} value={dept}>{dept} Department</option>
))}
</select>
<select
value={filterStatus}
onChange={(e) => setFilterStatus(e.target.value)}
className="px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="All">All Status</option>
<option value="Active">Active</option>
<option value="Inactive">Inactive</option>
</select>
</div>
<p className="text-slate-400 text-sm">
Showing {filteredEmployees.length} of {employees.length} employees
</p>
</Card>
{/* Employee Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mt-8">
{filteredEmployees.map((employee) => (
<Card key={employee.id}>
<div className="flex items-start space-x-4">
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center text-lg font-bold flex-shrink-0">
{employee.name.split(' ').map(n => n[0]).join('')}
</div>
<div className="flex-1 min-w-0">
<h3 className="text-lg font-semibold text-white truncate">{employee.name}</h3>
<p className="text-sm text-slate-400 truncate">{employee.title}</p>
<p className="text-xs text-slate-500 mt-1">{employee.department}</p>
</div>
</div>
<div className="mt-4 space-y-2">
<div className="flex items-center space-x-2 text-sm">
<Mail className="h-4 w-4 text-blue-400" />
<a href={`mailto:${employee.email}`} className="text-blue-400 hover:underline truncate">
{employee.email}
</a>
</div>
<div className="flex items-center space-x-2 text-sm">
<Phone className="h-4 w-4 text-green-400" />
<span className="text-slate-300">{employee.phone}</span>
</div>
</div>
<div className="mt-4 flex items-center justify-between">
<span className="text-xs text-slate-400">{employee.location}</span>
<span className={`px-2 py-1 rounded-full text-xs font-medium ${
employee.status === 'Active' ? 'bg-green-600 text-white' : 'bg-slate-600 text-slate-300'
}`}>
{employee.status}
</span>
</div>
</Card>
))}
</div>
{filteredEmployees.length === 0 && (
<div className="text-center py-12">
<p className="text-slate-400 text-lg">No employees found matching your criteria</p>
</div>
)}
</div>
</div>
);
}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Employee Directory - BambooHR</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./App.tsx"></script>
<script type="module">
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
ReactDOM.createRoot(document.getElementById('root')).render(
React.createElement(React.StrictMode, null, React.createElement(App))
);
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3002,
open: true
},
build: {
outDir: 'dist',
sourcemap: true
}
});

View File

@ -0,0 +1,177 @@
import React, { useState } from 'react';
import { FileText, Folder, Download, Upload, Search, Calendar } from 'lucide-react';
interface FileItem {
id: string;
name: string;
type: 'file' | 'folder';
category: string;
employee: string;
size?: string;
uploadDate: string;
}
const Card: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="bg-slate-800 rounded-lg shadow-lg p-6">{children}</div>
);
export default function App() {
const [searchTerm, setSearchTerm] = useState('');
const [filterCategory, setFilterCategory] = useState('All');
const files: FileItem[] = [
{ id: '1', name: 'Employee Handbook 2025', type: 'file', category: 'Policies', employee: 'HR Department', size: '2.5 MB', uploadDate: '2025-01-01' },
{ id: '2', name: 'Tax Documents', type: 'folder', category: 'Tax', employee: 'Sarah Johnson', uploadDate: '2025-01-15' },
{ id: '3', name: 'W-2 Form 2024', type: 'file', category: 'Tax', employee: 'Sarah Johnson', size: '156 KB', uploadDate: '2025-01-15' },
{ id: '4', name: 'Performance Review Q4', type: 'file', category: 'Reviews', employee: 'Mike Chen', size: '890 KB', uploadDate: '2024-12-20' },
{ id: '5', name: 'Benefits Enrollment', type: 'folder', category: 'Benefits', employee: 'Emily Davis', uploadDate: '2024-11-01' },
{ id: '6', name: 'Training Certificates', type: 'folder', category: 'Training', employee: 'James Wilson', uploadDate: '2024-12-10' },
{ id: '7', name: 'Employment Contract', type: 'file', category: 'Contracts', employee: 'Lisa Anderson', size: '1.2 MB', uploadDate: '2024-06-15' },
{ id: '8', name: 'Time Off Request Form', type: 'file', category: 'Forms', employee: 'David Brown', size: '450 KB', uploadDate: '2025-01-08' },
];
const categories = ['All', ...Array.from(new Set(files.map(f => f.category)))];
const filteredFiles = files.filter(file => {
const matchesSearch = file.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
file.employee.toLowerCase().includes(searchTerm.toLowerCase());
const matchesCategory = filterCategory === 'All' || file.category === filterCategory;
return matchesSearch && matchesCategory;
});
const stats = {
totalFiles: files.filter(f => f.type === 'file').length,
totalFolders: files.filter(f => f.type === 'folder').length,
recentUploads: files.filter(f => new Date(f.uploadDate) >= new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)).length
};
return (
<div className="min-h-screen bg-slate-900 text-white p-6">
<div className="max-w-7xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white flex items-center">
<FileText className="mr-3 h-10 w-10 text-blue-400" />
File Manager
</h1>
{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Total Files</p>
<p className="text-3xl font-bold text-white">{stats.totalFiles}</p>
</div>
<FileText className="h-10 w-10 text-blue-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Total Folders</p>
<p className="text-3xl font-bold text-white">{stats.totalFolders}</p>
</div>
<Folder className="h-10 w-10 text-yellow-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Recent Uploads</p>
<p className="text-3xl font-bold text-white">{stats.recentUploads}</p>
</div>
<Upload className="h-10 w-10 text-green-400" />
</div>
</Card>
</div>
{/* Toolbar */}
<Card>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4 flex-1">
<div className="relative flex-1">
<Search className="absolute left-3 top-3 h-5 w-5 text-slate-400" />
<input
type="text"
placeholder="Search files..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<select
value={filterCategory}
onChange={(e) => setFilterCategory(e.target.value)}
className="px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
>
{categories.map(cat => (
<option key={cat} value={cat}>{cat === 'All' ? 'All Categories' : cat}</option>
))}
</select>
</div>
<button className="ml-4 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg font-medium flex items-center space-x-2 transition-colors">
<Upload className="h-5 w-5" />
<span>Upload</span>
</button>
</div>
</Card>
{/* Files List */}
<div className="mt-8 space-y-3">
{filteredFiles.map((file) => (
<Card key={file.id}>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<div className={`w-12 h-12 rounded-lg flex items-center justify-center ${
file.type === 'folder'
? 'bg-yellow-600'
: 'bg-blue-600'
}`}>
{file.type === 'folder' ? (
<Folder className="h-6 w-6 text-white" />
) : (
<FileText className="h-6 w-6 text-white" />
)}
</div>
<div>
<h3 className="text-lg font-semibold text-white">{file.name}</h3>
<div className="flex items-center space-x-4 text-sm text-slate-400 mt-1">
<span>{file.employee}</span>
<span></span>
<span className="px-2 py-0.5 bg-slate-700 rounded">{file.category}</span>
{file.size && (
<>
<span></span>
<span>{file.size}</span>
</>
)}
</div>
</div>
</div>
<div className="flex items-center space-x-4">
<div className="text-right">
<p className="text-sm text-slate-400 flex items-center">
<Calendar className="h-4 w-4 mr-1" />
{file.uploadDate}
</p>
</div>
{file.type === 'file' && (
<button className="p-2 bg-slate-700 hover:bg-slate-600 rounded-lg transition-colors">
<Download className="h-5 w-5 text-white" />
</button>
)}
</div>
</div>
</Card>
))}
{filteredFiles.length === 0 && (
<div className="text-center py-12">
<FileText className="h-16 w-16 text-slate-600 mx-auto mb-4" />
<p className="text-slate-400 text-lg">No files found</p>
</div>
)}
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>File Manager - BambooHR</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./App.tsx"></script>
<script type="module">
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
ReactDOM.createRoot(document.getElementById('root')).render(
React.createElement(React.StrictMode, null, React.createElement(App))
);
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3013,
open: true
},
build: {
outDir: 'dist',
sourcemap: true
}
});

View File

@ -0,0 +1,157 @@
import React, { useState } from 'react';
import { Target, TrendingUp, Clock, CheckCircle } from 'lucide-react';
interface Goal {
id: string;
employee: string;
title: string;
description: string;
progress: number;
dueDate: string;
status: 'On Track' | 'At Risk' | 'Completed';
category: string;
}
const Card: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="bg-slate-800 rounded-lg shadow-lg p-6">{children}</div>
);
export default function App() {
const goals: Goal[] = [
{ id: '1', employee: 'Sarah Johnson', title: 'Q1 Revenue Growth', description: 'Increase quarterly revenue by 15%', progress: 75, dueDate: '2025-03-31', status: 'On Track', category: 'Performance' },
{ id: '2', employee: 'Sarah Johnson', title: 'Team Leadership', description: 'Lead 2 major projects', progress: 50, dueDate: '2025-06-30', status: 'On Track', category: 'Leadership' },
{ id: '3', employee: 'Mike Chen', title: 'Product Launch', description: 'Launch new product feature', progress: 90, dueDate: '2025-02-15', status: 'On Track', category: 'Development' },
{ id: '4', employee: 'Mike Chen', title: 'Code Quality', description: 'Reduce bug count by 30%', progress: 30, dueDate: '2025-04-30', status: 'At Risk', category: 'Quality' },
{ id: '5', employee: 'Emily Davis', title: 'Design System', description: 'Complete design system overhaul', progress: 100, dueDate: '2025-01-15', status: 'Completed', category: 'Design' },
{ id: '6', employee: 'James Wilson', title: 'Infrastructure Upgrade', description: 'Migrate to new cloud infrastructure', progress: 65, dueDate: '2025-05-31', status: 'On Track', category: 'Infrastructure' },
];
const stats = {
total: goals.length,
onTrack: goals.filter(g => g.status === 'On Track').length,
atRisk: goals.filter(g => g.status === 'At Risk').length,
completed: goals.filter(g => g.status === 'Completed').length
};
const groupedGoals = goals.reduce((acc, goal) => {
if (!acc[goal.employee]) {
acc[goal.employee] = [];
}
acc[goal.employee].push(goal);
return acc;
}, {} as Record<string, Goal[]>);
return (
<div className="min-h-screen bg-slate-900 text-white p-6">
<div className="max-w-7xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white flex items-center">
<Target className="mr-3 h-10 w-10 text-purple-400" />
Goal Tracker
</h1>
{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Total Goals</p>
<p className="text-3xl font-bold text-white">{stats.total}</p>
</div>
<Target className="h-10 w-10 text-blue-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">On Track</p>
<p className="text-3xl font-bold text-white">{stats.onTrack}</p>
</div>
<TrendingUp className="h-10 w-10 text-green-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">At Risk</p>
<p className="text-3xl font-bold text-white">{stats.atRisk}</p>
</div>
<Clock className="h-10 w-10 text-orange-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Completed</p>
<p className="text-3xl font-bold text-white">{stats.completed}</p>
</div>
<CheckCircle className="h-10 w-10 text-purple-400" />
</div>
</Card>
</div>
{/* Goals by Employee */}
<div className="space-y-6">
{Object.entries(groupedGoals).map(([employee, employeeGoals]) => (
<Card key={employee}>
<div className="flex items-center mb-6 pb-4 border-b border-slate-700">
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center text-lg font-bold mr-4">
{employee.split(' ').map(n => n[0]).join('')}
</div>
<div>
<h3 className="text-xl font-semibold text-white">{employee}</h3>
<p className="text-sm text-slate-400">{employeeGoals.length} goal{employeeGoals.length > 1 ? 's' : ''}</p>
</div>
</div>
<div className="space-y-4">
{employeeGoals.map((goal) => (
<div key={goal.id} className="p-4 bg-slate-700 rounded-lg">
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
<h4 className="text-lg font-semibold text-white mb-1">{goal.title}</h4>
<p className="text-sm text-slate-400 mb-2">{goal.description}</p>
<div className="flex items-center space-x-4 text-xs">
<span className="text-slate-400">
<Clock className="h-3 w-3 inline mr-1" />
Due: {goal.dueDate}
</span>
<span className="px-2 py-1 bg-slate-600 rounded text-slate-300">
{goal.category}
</span>
</div>
</div>
<span className={`px-3 py-1 rounded-full text-xs font-medium whitespace-nowrap ml-4 ${
goal.status === 'Completed' ? 'bg-purple-600 text-white' :
goal.status === 'On Track' ? 'bg-green-600 text-white' :
'bg-orange-600 text-white'
}`}>
{goal.status}
</span>
</div>
<div>
<div className="flex justify-between text-sm mb-2">
<span className="text-slate-400">Progress</span>
<span className="text-white font-medium">{goal.progress}%</span>
</div>
<div className="w-full bg-slate-600 rounded-full h-2">
<div
className={`h-2 rounded-full transition-all duration-300 ${
goal.status === 'Completed' ? 'bg-purple-500' :
goal.status === 'On Track' ? 'bg-gradient-to-r from-green-500 to-blue-500' :
'bg-orange-500'
}`}
style={{ width: `${goal.progress}%` }}
/>
</div>
</div>
</div>
))}
</div>
</Card>
))}
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Goal Tracker - BambooHR</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./App.tsx"></script>
<script type="module">
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
ReactDOM.createRoot(document.getElementById('root')).render(
React.createElement(React.StrictMode, null, React.createElement(App))
);
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3010,
open: true
},
build: {
outDir: 'dist',
sourcemap: true
}
});

View File

@ -0,0 +1,165 @@
import React from 'react';
import { Users, TrendingUp, Building, Calendar } from 'lucide-react';
const Card: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="bg-slate-800 rounded-lg shadow-lg p-6">{children}</div>
);
export default function App() {
const monthlyData = [
{ month: 'Jul 2024', headcount: 220, hires: 5, terminations: 2 },
{ month: 'Aug 2024', headcount: 225, hires: 7, terminations: 2 },
{ month: 'Sep 2024', headcount: 230, hires: 6, terminations: 1 },
{ month: 'Oct 2024', headcount: 235, hires: 8, terminations: 3 },
{ month: 'Nov 2024', headcount: 238, hires: 5, terminations: 2 },
{ month: 'Dec 2024', headcount: 242, hires: 6, terminations: 2 },
{ month: 'Jan 2025', headcount: 245, hires: 4, terminations: 1 }
];
const departmentBreakdown = [
{ dept: 'Engineering', count: 85, change: '+5' },
{ dept: 'Product', count: 32, change: '+2' },
{ dept: 'Design', count: 18, change: '+1' },
{ dept: 'Marketing', count: 28, change: '+3' },
{ dept: 'Sales', count: 45, change: '+4' },
{ dept: 'HR', count: 12, change: '0' },
{ dept: 'Finance', count: 15, change: '+1' },
{ dept: 'Operations', count: 10, change: '0' }
];
const currentHeadcount = monthlyData[monthlyData.length - 1].headcount;
const previousHeadcount = monthlyData[0].headcount;
const growthRate = ((currentHeadcount - previousHeadcount) / previousHeadcount * 100).toFixed(1);
const maxHeadcount = Math.max(...monthlyData.map(d => d.headcount));
return (
<div className="min-h-screen bg-slate-900 text-white p-6">
<div className="max-w-7xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white flex items-center">
<Users className="mr-3 h-10 w-10 text-blue-400" />
Headcount Analytics
</h1>
{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Current Headcount</p>
<p className="text-3xl font-bold text-white">{currentHeadcount}</p>
</div>
<Users className="h-10 w-10 text-blue-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">6-Month Growth</p>
<p className="text-3xl font-bold text-green-400">+{growthRate}%</p>
</div>
<TrendingUp className="h-10 w-10 text-green-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Departments</p>
<p className="text-3xl font-bold text-white">{departmentBreakdown.length}</p>
</div>
<Building className="h-10 w-10 text-purple-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Net Change</p>
<p className="text-3xl font-bold text-white">+{currentHeadcount - previousHeadcount}</p>
</div>
<Calendar className="h-10 w-10 text-orange-400" />
</div>
</Card>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Trend Chart */}
<div className="lg:col-span-2">
<Card>
<h2 className="text-xl font-semibold mb-6 text-white">Headcount Trend</h2>
<div className="space-y-4">
{monthlyData.map((data, idx) => {
const barWidth = (data.headcount / maxHeadcount) * 100;
return (
<div key={idx}>
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-slate-400 w-24">{data.month}</span>
<span className="text-lg font-bold text-white">{data.headcount}</span>
</div>
<div className="w-full bg-slate-700 rounded-full h-8 relative overflow-hidden">
<div
className="bg-gradient-to-r from-blue-500 to-purple-500 h-8 rounded-full flex items-center justify-end pr-3 transition-all duration-500"
style={{ width: `${barWidth}%` }}
>
<div className="flex items-center space-x-2 text-xs font-medium text-white">
<span className="bg-green-600 px-2 py-0.5 rounded">+{data.hires}</span>
<span className="bg-red-600 px-2 py-0.5 rounded">-{data.terminations}</span>
</div>
</div>
</div>
</div>
);
})}
</div>
<div className="mt-6 flex items-center justify-center space-x-6 text-sm">
<div className="flex items-center space-x-2">
<div className="w-4 h-4 bg-green-600 rounded"></div>
<span className="text-slate-300">New Hires</span>
</div>
<div className="flex items-center space-x-2">
<div className="w-4 h-4 bg-red-600 rounded"></div>
<span className="text-slate-300">Terminations</span>
</div>
</div>
</Card>
</div>
{/* Department Breakdown */}
<div>
<Card>
<h2 className="text-xl font-semibold mb-6 text-white">By Department</h2>
<div className="space-y-4">
{departmentBreakdown.map((dept, idx) => {
const percentage = (dept.count / currentHeadcount) * 100;
return (
<div key={idx}>
<div className="flex items-center justify-between mb-2">
<span className="text-white font-medium">{dept.dept}</span>
<div className="flex items-center space-x-2">
<span className="text-white font-bold">{dept.count}</span>
<span className={`text-xs px-2 py-0.5 rounded ${
dept.change.startsWith('+')
? 'bg-green-600 text-white'
: 'bg-slate-600 text-slate-300'
}`}>
{dept.change}
</span>
</div>
</div>
<div className="w-full bg-slate-700 rounded-full h-2">
<div
className="bg-gradient-to-r from-blue-500 to-purple-500 h-2 rounded-full transition-all duration-300"
style={{ width: `${percentage}%` }}
/>
</div>
<p className="text-xs text-slate-500 mt-1">{percentage.toFixed(1)}% of total</p>
</div>
);
})}
</div>
</Card>
</div>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Headcount Analytics - BambooHR</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./App.tsx"></script>
<script type="module">
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
ReactDOM.createRoot(document.getElementById('root')).render(
React.createElement(React.StrictMode, null, React.createElement(App))
);
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3015,
open: true
},
build: {
outDir: 'dist',
sourcemap: true
}
});

View File

@ -0,0 +1,222 @@
import React, { useState } from 'react';
import { UserPlus, Calendar, CheckCircle, Clock, AlertCircle } from 'lucide-react';
interface NewHire {
id: string;
name: string;
title: string;
department: string;
startDate: string;
manager: string;
onboardingProgress: number;
status: 'Scheduled' | 'In Progress' | 'Completed';
daysUntilStart?: number;
}
const Card: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="bg-slate-800 rounded-lg shadow-lg p-6">{children}</div>
);
export default function App() {
const [filterStatus, setFilterStatus] = useState<string>('All');
const newHires: NewHire[] = [
{ id: '1', name: 'Alex Martinez', title: 'Software Engineer', department: 'Engineering', startDate: '2025-01-20', manager: 'Sarah Johnson', onboardingProgress: 100, status: 'Completed', daysUntilStart: 0 },
{ id: '2', name: 'Rachel Green', title: 'Product Designer', department: 'Design', startDate: '2025-02-01', manager: 'Emily Davis', onboardingProgress: 65, status: 'In Progress', daysUntilStart: 12 },
{ id: '3', name: 'Tom Harris', title: 'Marketing Specialist', department: 'Marketing', startDate: '2025-02-05', manager: 'Lisa Anderson', onboardingProgress: 30, status: 'In Progress', daysUntilStart: 16 },
{ id: '4', name: 'Jessica Lee', title: 'Sales Representative', department: 'Sales', startDate: '2025-02-10', manager: 'David Brown', onboardingProgress: 0, status: 'Scheduled', daysUntilStart: 21 },
{ id: '5', name: 'Kevin Wu', title: 'Data Analyst', department: 'Engineering', startDate: '2025-02-15', manager: 'Mike Chen', onboardingProgress: 0, status: 'Scheduled', daysUntilStart: 26 },
{ id: '6', name: 'Maria Garcia', title: 'HR Coordinator', department: 'HR', startDate: '2025-01-15', manager: 'Jennifer Taylor', onboardingProgress: 100, status: 'Completed', daysUntilStart: 0 },
];
const filteredHires = filterStatus === 'All'
? newHires
: newHires.filter(h => h.status === filterStatus);
const stats = {
total: newHires.length,
scheduled: newHires.filter(h => h.status === 'Scheduled').length,
inProgress: newHires.filter(h => h.status === 'In Progress').length,
completed: newHires.filter(h => h.status === 'Completed').length
};
const onboardingTasks = [
'Complete HR paperwork',
'Set up workstation',
'IT account creation',
'Benefits enrollment',
'Team introductions',
'First week training'
];
return (
<div className="min-h-screen bg-slate-900 text-white p-6">
<div className="max-w-7xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white flex items-center">
<UserPlus className="mr-3 h-10 w-10 text-green-400" />
New Hires
</h1>
{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Total New Hires</p>
<p className="text-3xl font-bold text-white">{stats.total}</p>
</div>
<UserPlus className="h-10 w-10 text-blue-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Scheduled</p>
<p className="text-3xl font-bold text-white">{stats.scheduled}</p>
</div>
<Calendar className="h-10 w-10 text-purple-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">In Progress</p>
<p className="text-3xl font-bold text-white">{stats.inProgress}</p>
</div>
<Clock className="h-10 w-10 text-yellow-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Completed</p>
<p className="text-3xl font-bold text-white">{stats.completed}</p>
</div>
<CheckCircle className="h-10 w-10 text-green-400" />
</div>
</Card>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* New Hires List */}
<div className="lg:col-span-2">
<Card>
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-semibold text-white">Upcoming & Recent Hires</h2>
<select
value={filterStatus}
onChange={(e) => setFilterStatus(e.target.value)}
className="px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="All">All Status</option>
<option value="Scheduled">Scheduled</option>
<option value="In Progress">In Progress</option>
<option value="Completed">Completed</option>
</select>
</div>
<div className="space-y-4">
{filteredHires.map((hire) => (
<div key={hire.id} className="p-4 bg-slate-700 rounded-lg">
<div className="flex items-start justify-between mb-3">
<div className="flex items-center space-x-3">
<div className="w-12 h-12 bg-gradient-to-br from-green-500 to-blue-600 rounded-full flex items-center justify-center text-lg font-bold">
{hire.name.split(' ').map(n => n[0]).join('')}
</div>
<div>
<h3 className="text-lg font-semibold text-white">{hire.name}</h3>
<p className="text-sm text-slate-400">{hire.title}</p>
</div>
</div>
<span className={`px-3 py-1 rounded-full text-xs font-medium ${
hire.status === 'Completed' ? 'bg-green-600 text-white' :
hire.status === 'In Progress' ? 'bg-yellow-600 text-white' :
'bg-blue-600 text-white'
}`}>
{hire.status}
</span>
</div>
<div className="grid grid-cols-2 gap-4 mb-3 text-sm">
<div>
<p className="text-slate-400">Department</p>
<p className="text-white">{hire.department}</p>
</div>
<div>
<p className="text-slate-400">Manager</p>
<p className="text-white">{hire.manager}</p>
</div>
<div>
<p className="text-slate-400">Start Date</p>
<p className="text-white">{hire.startDate}</p>
</div>
<div>
<p className="text-slate-400">Days Until Start</p>
<p className="text-white">
{hire.daysUntilStart === 0 ? 'Started' : `${hire.daysUntilStart} days`}
</p>
</div>
</div>
<div>
<div className="flex justify-between text-sm mb-2">
<span className="text-slate-400">Onboarding Progress</span>
<span className="text-white font-medium">{hire.onboardingProgress}%</span>
</div>
<div className="w-full bg-slate-600 rounded-full h-2">
<div
className={`h-2 rounded-full transition-all duration-300 ${
hire.onboardingProgress === 100
? 'bg-green-500'
: 'bg-gradient-to-r from-blue-500 to-purple-500'
}`}
style={{ width: `${hire.onboardingProgress}%` }}
/>
</div>
</div>
</div>
))}
</div>
</Card>
</div>
{/* Onboarding Checklist */}
<div>
<Card>
<h2 className="text-xl font-semibold mb-6 text-white">Onboarding Checklist</h2>
<div className="space-y-3">
{onboardingTasks.map((task, idx) => (
<div key={idx} className="flex items-center space-x-3 p-3 bg-slate-700 rounded-lg">
<CheckCircle className="h-5 w-5 text-green-400 flex-shrink-0" />
<span className="text-white">{task}</span>
</div>
))}
</div>
</Card>
<Card className="mt-6">
<h2 className="text-xl font-semibold mb-4 text-white flex items-center">
<AlertCircle className="mr-2 h-5 w-5 text-orange-400" />
Quick Stats
</h2>
<div className="space-y-3 text-sm">
<div className="flex justify-between">
<span className="text-slate-400">This Month</span>
<span className="text-white font-bold">3 hires</span>
</div>
<div className="flex justify-between">
<span className="text-slate-400">Next Month</span>
<span className="text-white font-bold">3 hires</span>
</div>
<div className="flex justify-between">
<span className="text-slate-400">Avg Onboarding Time</span>
<span className="text-white font-bold">14 days</span>
</div>
</div>
</Card>
</div>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>New Hires - BambooHR</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./App.tsx"></script>
<script type="module">
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
ReactDOM.createRoot(document.getElementById('root')).render(
React.createElement(React.StrictMode, null, React.createElement(App))
);
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3017,
open: true
},
build: {
outDir: 'dist',
sourcemap: true
}
});

View File

@ -0,0 +1,183 @@
import React, { useState } from 'react';
import { Network, Users, ChevronDown, ChevronRight } from 'lucide-react';
interface OrgNode {
id: string;
name: string;
title: string;
department: string;
reports: OrgNode[];
}
const Card: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="bg-slate-800 rounded-lg shadow-lg p-6">{children}</div>
);
const OrgCard: React.FC<{ node: OrgNode; depth: number }> = ({ node, depth }) => {
const [isExpanded, setIsExpanded] = useState(true);
return (
<div className="relative">
<div className={`flex items-center space-x-2 mb-2 ${depth > 0 ? 'ml-8' : ''}`}>
{node.reports.length > 0 && (
<button
onClick={() => setIsExpanded(!isExpanded)}
className="p-1 hover:bg-slate-700 rounded transition-colors"
>
{isExpanded ? (
<ChevronDown className="h-4 w-4 text-slate-400" />
) : (
<ChevronRight className="h-4 w-4 text-slate-400" />
)}
</button>
)}
<div className="flex-1 p-4 bg-slate-700 hover:bg-slate-600 rounded-lg transition-colors">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center text-sm font-bold">
{node.name.split(' ').map(n => n[0]).join('')}
</div>
<div>
<h4 className="font-semibold text-white">{node.name}</h4>
<p className="text-sm text-slate-400">{node.title}</p>
{node.reports.length > 0 && (
<p className="text-xs text-slate-500 mt-1">
{node.reports.length} direct report{node.reports.length > 1 ? 's' : ''}
</p>
)}
</div>
</div>
</div>
</div>
{isExpanded && node.reports.length > 0 && (
<div className="mt-2">
{node.reports.map((report) => (
<OrgCard key={report.id} node={report} depth={depth + 1} />
))}
</div>
)}
</div>
);
};
export default function App() {
const orgData: OrgNode = {
id: '1',
name: 'Jennifer Taylor',
title: 'CEO',
department: 'Executive',
reports: [
{
id: '2',
name: 'John Smith',
title: 'CTO',
department: 'Technology',
reports: [
{
id: '5',
name: 'Sarah Johnson',
title: 'Engineering Manager',
department: 'Engineering',
reports: [
{ id: '8', name: 'Mike Chen', title: 'Senior Engineer', department: 'Engineering', reports: [] },
{ id: '9', name: 'James Wilson', title: 'DevOps Engineer', department: 'Engineering', reports: [] }
]
},
{
id: '6',
name: 'Emily Davis',
title: 'Design Lead',
department: 'Design',
reports: [
{ id: '10', name: 'Alex Martinez', title: 'UX Designer', department: 'Design', reports: [] }
]
}
]
},
{
id: '3',
name: 'Lisa Anderson',
title: 'VP Marketing',
department: 'Marketing',
reports: [
{ id: '7', name: 'David Brown', title: 'Marketing Manager', department: 'Marketing', reports: [] },
{ id: '11', name: 'Rachel Green', title: 'Content Manager', department: 'Marketing', reports: [] }
]
},
{
id: '4',
name: 'Robert Martinez',
title: 'CFO',
department: 'Finance',
reports: [
{ id: '12', name: 'Tom Harris', title: 'Controller', department: 'Finance', reports: [] }
]
}
]
};
const countEmployees = (node: OrgNode): number => {
return 1 + node.reports.reduce((sum, report) => sum + countEmployees(report), 0);
};
const countManagers = (node: OrgNode): number => {
return (node.reports.length > 0 ? 1 : 0) +
node.reports.reduce((sum, report) => sum + countManagers(report), 0);
};
const stats = {
totalEmployees: countEmployees(orgData),
totalManagers: countManagers(orgData),
departments: 5
};
return (
<div className="min-h-screen bg-slate-900 text-white p-6">
<div className="max-w-6xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white flex items-center">
<Network className="mr-3 h-10 w-10 text-purple-400" />
Organization Chart
</h1>
{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Total Employees</p>
<p className="text-3xl font-bold text-white">{stats.totalEmployees}</p>
</div>
<Users className="h-10 w-10 text-blue-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Managers</p>
<p className="text-3xl font-bold text-white">{stats.totalManagers}</p>
</div>
<Network className="h-10 w-10 text-green-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Departments</p>
<p className="text-3xl font-bold text-white">{stats.departments}</p>
</div>
<Users className="h-10 w-10 text-purple-400" />
</div>
</Card>
</div>
{/* Organization Tree */}
<Card>
<div className="mb-4 pb-4 border-b border-slate-700">
<h2 className="text-xl font-semibold text-white">Organizational Hierarchy</h2>
<p className="text-sm text-slate-400 mt-1">Click the arrows to expand/collapse</p>
</div>
<OrgCard node={orgData} depth={0} />
</Card>
</div>
</div>
);
}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Org Chart - BambooHR</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./App.tsx"></script>
<script type="module">
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
ReactDOM.createRoot(document.getElementById('root')).render(
React.createElement(React.StrictMode, null, React.createElement(App))
);
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3014,
open: true
},
build: {
outDir: 'dist',
sourcemap: true
}
});

View File

@ -0,0 +1,165 @@
import React, { useState } from 'react';
import { DollarSign, TrendingUp, FileText, Calendar } from 'lucide-react';
interface PayStub {
id: string;
period: string;
grossPay: number;
netPay: number;
deductions: number;
date: string;
}
const Card: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="bg-slate-800 rounded-lg shadow-lg p-6">{children}</div>
);
export default function App() {
const payStubs: PayStub[] = [
{ id: '1', period: 'Jan 1-15, 2025', grossPay: 5200, netPay: 3850, deductions: 1350, date: '2025-01-15' },
{ id: '2', period: 'Dec 16-31, 2024', grossPay: 5200, netPay: 3850, deductions: 1350, date: '2024-12-31' },
{ id: '3', period: 'Dec 1-15, 2024', grossPay: 5200, netPay: 3850, deductions: 1350, date: '2024-12-15' },
{ id: '4', period: 'Nov 16-30, 2024', grossPay: 5200, netPay: 3850, deductions: 1350, date: '2024-11-30' },
];
const deductionsBreakdown = [
{ name: 'Federal Tax', amount: 780 },
{ name: 'State Tax', amount: 310 },
{ name: 'Social Security', amount: 120 },
{ name: 'Medicare', amount: 80 },
{ name: 'Health Insurance', amount: 450 },
{ name: '401(k)', amount: 260 },
];
const ytdStats = {
grossPay: 10400,
netPay: 7700,
totalDeductions: 2700,
avgDeductions: 1350
};
return (
<div className="min-h-screen bg-slate-900 text-white p-6">
<div className="max-w-7xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white flex items-center">
<DollarSign className="mr-3 h-10 w-10 text-green-400" />
Payroll Dashboard
</h1>
{/* YTD Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<Card>
<div className="flex items-center justify-between mb-2">
<p className="text-slate-400 text-sm">YTD Gross Pay</p>
<TrendingUp className="h-6 w-6 text-green-400" />
</div>
<p className="text-3xl font-bold text-white">${ytdStats.grossPay.toLocaleString()}</p>
</Card>
<Card>
<div className="flex items-center justify-between mb-2">
<p className="text-slate-400 text-sm">YTD Net Pay</p>
<DollarSign className="h-6 w-6 text-blue-400" />
</div>
<p className="text-3xl font-bold text-white">${ytdStats.netPay.toLocaleString()}</p>
</Card>
<Card>
<div className="flex items-center justify-between mb-2">
<p className="text-slate-400 text-sm">Total Deductions</p>
<FileText className="h-6 w-6 text-purple-400" />
</div>
<p className="text-3xl font-bold text-white">${ytdStats.totalDeductions.toLocaleString()}</p>
</Card>
<Card>
<div className="flex items-center justify-between mb-2">
<p className="text-slate-400 text-sm">Avg Deductions</p>
<Calendar className="h-6 w-6 text-orange-400" />
</div>
<p className="text-3xl font-bold text-white">${ytdStats.avgDeductions.toLocaleString()}</p>
</Card>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Pay Stubs */}
<div className="lg:col-span-2">
<h2 className="text-2xl font-bold mb-4 text-white">Recent Pay Stubs</h2>
<div className="space-y-4">
{payStubs.map((stub) => (
<Card key={stub.id}>
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-lg font-semibold text-white">{stub.period}</h3>
<p className="text-sm text-slate-400">Paid on {stub.date}</p>
</div>
<button className="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg text-sm font-medium transition-colors">
Download
</button>
</div>
<div className="grid grid-cols-3 gap-4">
<div className="p-3 bg-slate-700 rounded-lg">
<p className="text-xs text-slate-400 mb-1">Gross Pay</p>
<p className="text-xl font-bold text-white">${stub.grossPay.toLocaleString()}</p>
</div>
<div className="p-3 bg-slate-700 rounded-lg">
<p className="text-xs text-slate-400 mb-1">Deductions</p>
<p className="text-xl font-bold text-red-400">-${stub.deductions.toLocaleString()}</p>
</div>
<div className="p-3 bg-green-900/30 border border-green-600 rounded-lg">
<p className="text-xs text-green-400 mb-1">Net Pay</p>
<p className="text-xl font-bold text-green-400">${stub.netPay.toLocaleString()}</p>
</div>
</div>
</Card>
))}
</div>
</div>
{/* Deductions Breakdown */}
<div>
<h2 className="text-2xl font-bold mb-4 text-white">Deductions Breakdown</h2>
<Card>
<div className="space-y-4">
{deductionsBreakdown.map((deduction, idx) => (
<div key={idx}>
<div className="flex justify-between items-center mb-2">
<span className="text-white font-medium">{deduction.name}</span>
<span className="text-white font-bold">${deduction.amount}</span>
</div>
<div className="w-full bg-slate-700 rounded-full h-2">
<div
className="bg-gradient-to-r from-blue-500 to-purple-500 h-2 rounded-full"
style={{ width: `${(deduction.amount / 1350) * 100}%` }}
/>
</div>
</div>
))}
<div className="pt-4 border-t border-slate-700">
<div className="flex justify-between items-center">
<span className="text-white font-bold">Total</span>
<span className="text-xl font-bold text-white">
${deductionsBreakdown.reduce((sum, d) => sum + d.amount, 0)}
</span>
</div>
</div>
</div>
</Card>
{/* Tax Documents */}
<Card className="mt-6">
<h3 className="text-lg font-semibold mb-4 text-white">Tax Documents</h3>
<div className="space-y-3">
<button className="w-full flex items-center justify-between p-3 bg-slate-700 hover:bg-slate-600 rounded-lg transition-colors">
<span className="text-white">W-2 Form 2024</span>
<FileText className="h-5 w-5 text-blue-400" />
</button>
<button className="w-full flex items-center justify-between p-3 bg-slate-700 hover:bg-slate-600 rounded-lg transition-colors">
<span className="text-white">1099 Form 2024</span>
<FileText className="h-5 w-5 text-blue-400" />
</button>
</div>
</Card>
</div>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Payroll Dashboard - BambooHR</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./App.tsx"></script>
<script type="module">
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
ReactDOM.createRoot(document.getElementById('root')).render(
React.createElement(React.StrictMode, null, React.createElement(App))
);
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3009,
open: true
},
build: {
outDir: 'dist',
sourcemap: true
}
});

View File

@ -0,0 +1,201 @@
import React, { useState } from 'react';
import { FileText, Plus, Trash2, Download } from 'lucide-react';
interface ReportField {
id: string;
name: string;
type: string;
}
const Card: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="bg-slate-800 rounded-lg shadow-lg p-6">{children}</div>
);
export default function App() {
const [reportName, setReportName] = useState('Custom Employee Report');
const [selectedFields, setSelectedFields] = useState<ReportField[]>([
{ id: '1', name: 'Employee Name', type: 'text' },
{ id: '2', name: 'Department', type: 'text' },
{ id: '3', name: 'Hire Date', type: 'date' }
]);
const availableFields: ReportField[] = [
{ id: '4', name: 'Job Title', type: 'text' },
{ id: '5', name: 'Email', type: 'text' },
{ id: '6', name: 'Phone', type: 'text' },
{ id: '7', name: 'Location', type: 'text' },
{ id: '8', name: 'Manager', type: 'text' },
{ id: '9', name: 'Salary', type: 'currency' },
{ id: '10', name: 'Status', type: 'select' },
{ id: '11', name: 'Birth Date', type: 'date' },
{ id: '12', name: 'Address', type: 'text' },
{ id: '13', name: 'Employee ID', type: 'number' }
].filter(field => !selectedFields.find(sf => sf.id === field.id));
const addField = (field: ReportField) => {
setSelectedFields([...selectedFields, field]);
};
const removeField = (fieldId: string) => {
setSelectedFields(selectedFields.filter(f => f.id !== fieldId));
};
const handleGenerateReport = () => {
alert('Report generated! In a real app, this would export the data.');
};
return (
<div className="min-h-screen bg-slate-900 text-white p-6">
<div className="max-w-6xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white flex items-center">
<FileText className="mr-3 h-10 w-10 text-blue-400" />
Report Builder
</h1>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Configuration Panel */}
<div className="space-y-6">
<Card>
<h2 className="text-xl font-semibold mb-4 text-white">Report Configuration</h2>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">
Report Name
</label>
<input
type="text"
value={reportName}
onChange={(e) => setReportName(e.target.value)}
className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">
Report Type
</label>
<select className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
<option>Employee List</option>
<option>Time Off Summary</option>
<option>Compensation Report</option>
<option>Headcount Report</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">
Export Format
</label>
<select className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
<option>CSV</option>
<option>Excel (XLSX)</option>
<option>PDF</option>
</select>
</div>
</div>
</Card>
<Card>
<h2 className="text-xl font-semibold mb-4 text-white">Available Fields</h2>
<div className="space-y-2 max-h-96 overflow-y-auto">
{availableFields.map((field) => (
<div
key={field.id}
className="flex items-center justify-between p-3 bg-slate-700 rounded-lg hover:bg-slate-600 transition-colors"
>
<div>
<p className="text-white font-medium">{field.name}</p>
<p className="text-xs text-slate-400">{field.type}</p>
</div>
<button
onClick={() => addField(field)}
className="p-2 bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
>
<Plus className="h-4 w-4 text-white" />
</button>
</div>
))}
</div>
</Card>
</div>
{/* Preview Panel */}
<div className="space-y-6">
<Card>
<h2 className="text-xl font-semibold mb-4 text-white">Selected Fields</h2>
<div className="space-y-2 min-h-[200px]">
{selectedFields.map((field, index) => (
<div
key={field.id}
className="flex items-center justify-between p-3 bg-slate-700 rounded-lg"
>
<div className="flex items-center space-x-3">
<span className="text-slate-400 font-mono text-sm">{index + 1}</span>
<div>
<p className="text-white font-medium">{field.name}</p>
<p className="text-xs text-slate-400">{field.type}</p>
</div>
</div>
<button
onClick={() => removeField(field.id)}
className="p-2 bg-red-600 hover:bg-red-700 rounded-lg transition-colors"
>
<Trash2 className="h-4 w-4 text-white" />
</button>
</div>
))}
{selectedFields.length === 0 && (
<div className="text-center py-8 text-slate-400">
No fields selected. Add fields from the left panel.
</div>
)}
</div>
</Card>
<Card>
<h2 className="text-xl font-semibold mb-4 text-white">Report Preview</h2>
<div className="bg-slate-700 rounded-lg p-4 overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-slate-600">
{selectedFields.map((field) => (
<th key={field.id} className="text-left py-2 px-3 text-slate-300 font-medium">
{field.name}
</th>
))}
</tr>
</thead>
<tbody>
<tr className="border-b border-slate-600">
{selectedFields.map((field) => (
<td key={field.id} className="py-2 px-3 text-slate-400">
Sample data
</td>
))}
</tr>
<tr>
{selectedFields.map((field) => (
<td key={field.id} className="py-2 px-3 text-slate-400">
Sample data
</td>
))}
</tr>
</tbody>
</table>
</div>
</Card>
<button
onClick={handleGenerateReport}
disabled={selectedFields.length === 0}
className="w-full py-3 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 rounded-lg font-semibold text-white flex items-center justify-center space-x-2 disabled:opacity-50 disabled:cursor-not-allowed transition-all"
>
<Download className="h-5 w-5" />
<span>Generate Report</span>
</button>
</div>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Report Builder - BambooHR</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./App.tsx"></script>
<script type="module">
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
ReactDOM.createRoot(document.getElementById('root')).render(
React.createElement(React.StrictMode, null, React.createElement(App))
);
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3006,
open: true
},
build: {
outDir: 'dist',
sourcemap: true
}
});

View File

@ -0,0 +1,196 @@
import React, { useState } from 'react';
import { Calendar, TrendingUp, Search } from 'lucide-react';
interface EmployeeBalance {
id: string;
employee: string;
department: string;
vacation: { used: number; total: number };
sick: { used: number; total: number };
personal: { used: number; total: number };
}
const Card: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="bg-slate-800 rounded-lg shadow-lg p-6">{children}</div>
);
const ProgressBar: React.FC<{ used: number; total: number; color: string }> = ({ used, total, color }) => {
const percentage = (used / total) * 100;
return (
<div>
<div className="flex justify-between text-sm mb-1">
<span className="text-slate-400">{used} / {total} days</span>
<span className="text-slate-400">{Math.round(percentage)}%</span>
</div>
<div className="w-full bg-slate-700 rounded-full h-2">
<div
className={`${color} h-2 rounded-full transition-all duration-300`}
style={{ width: `${percentage}%` }}
/>
</div>
</div>
);
};
export default function App() {
const [searchTerm, setSearchTerm] = useState('');
const balances: EmployeeBalance[] = [
{
id: '1',
employee: 'Sarah Johnson',
department: 'Engineering',
vacation: { used: 8, total: 15 },
sick: { used: 2, total: 10 },
personal: { used: 1, total: 5 }
},
{
id: '2',
employee: 'Mike Chen',
department: 'Product',
vacation: { used: 12, total: 15 },
sick: { used: 3, total: 10 },
personal: { used: 2, total: 5 }
},
{
id: '3',
employee: 'Emily Davis',
department: 'Design',
vacation: { used: 5, total: 15 },
sick: { used: 1, total: 10 },
personal: { used: 0, total: 5 }
},
{
id: '4',
employee: 'James Wilson',
department: 'Engineering',
vacation: { used: 10, total: 15 },
sick: { used: 4, total: 10 },
personal: { used: 3, total: 5 }
},
{
id: '5',
employee: 'Lisa Anderson',
department: 'Marketing',
vacation: { used: 14, total: 15 },
sick: { used: 5, total: 10 },
personal: { used: 4, total: 5 }
},
];
const filteredBalances = balances.filter(balance =>
balance.employee.toLowerCase().includes(searchTerm.toLowerCase()) ||
balance.department.toLowerCase().includes(searchTerm.toLowerCase())
);
const totalStats = balances.reduce((acc, balance) => ({
vacation: acc.vacation + balance.vacation.used,
sick: acc.sick + balance.sick.used,
personal: acc.personal + balance.personal.used
}), { vacation: 0, sick: 0, personal: 0 });
return (
<div className="min-h-screen bg-slate-900 text-white p-6">
<div className="max-w-7xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white flex items-center">
<Calendar className="mr-3 h-10 w-10 text-blue-400" />
Time Off Balances
</h1>
{/* Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<Card>
<div className="flex items-center justify-between mb-2">
<h3 className="text-lg font-semibold text-white">Vacation Days Used</h3>
<TrendingUp className="h-6 w-6 text-blue-400" />
</div>
<p className="text-3xl font-bold text-white">{totalStats.vacation}</p>
<p className="text-sm text-slate-400 mt-1">Across all employees</p>
</Card>
<Card>
<div className="flex items-center justify-between mb-2">
<h3 className="text-lg font-semibold text-white">Sick Days Used</h3>
<TrendingUp className="h-6 w-6 text-green-400" />
</div>
<p className="text-3xl font-bold text-white">{totalStats.sick}</p>
<p className="text-sm text-slate-400 mt-1">Across all employees</p>
</Card>
<Card>
<div className="flex items-center justify-between mb-2">
<h3 className="text-lg font-semibold text-white">Personal Days Used</h3>
<TrendingUp className="h-6 w-6 text-purple-400" />
</div>
<p className="text-3xl font-bold text-white">{totalStats.personal}</p>
<p className="text-sm text-slate-400 mt-1">Across all employees</p>
</Card>
</div>
{/* Search */}
<Card>
<div className="relative">
<Search className="absolute left-3 top-3 h-5 w-5 text-slate-400" />
<input
type="text"
placeholder="Search by employee or department..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
</Card>
{/* Balances List */}
<div className="mt-8 space-y-4">
{filteredBalances.map((balance) => (
<Card key={balance.id}>
<div className="flex items-center mb-4">
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center text-lg font-bold mr-4">
{balance.employee.split(' ').map(n => n[0]).join('')}
</div>
<div>
<h3 className="text-lg font-semibold text-white">{balance.employee}</h3>
<p className="text-sm text-slate-400">{balance.department}</p>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 ml-16">
<div>
<p className="text-sm font-medium text-blue-400 mb-2">Vacation</p>
<ProgressBar
used={balance.vacation.used}
total={balance.vacation.total}
color="bg-blue-500"
/>
</div>
<div>
<p className="text-sm font-medium text-green-400 mb-2">Sick Leave</p>
<ProgressBar
used={balance.sick.used}
total={balance.sick.total}
color="bg-green-500"
/>
</div>
<div>
<p className="text-sm font-medium text-purple-400 mb-2">Personal</p>
<ProgressBar
used={balance.personal.used}
total={balance.personal.total}
color="bg-purple-500"
/>
</div>
</div>
</Card>
))}
{filteredBalances.length === 0 && (
<div className="text-center py-12">
<p className="text-slate-400 text-lg">No employees found</p>
</div>
)}
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Time Off Balances - BambooHR</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./App.tsx"></script>
<script type="module">
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
ReactDOM.createRoot(document.getElementById('root')).render(
React.createElement(React.StrictMode, null, React.createElement(App))
);
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3005,
open: true
},
build: {
outDir: 'dist',
sourcemap: true
}
});

View File

@ -0,0 +1,167 @@
import React, { useState } from 'react';
import { ChevronLeft, ChevronRight, Calendar } from 'lucide-react';
interface TimeOffRequest {
id: string;
employee: string;
type: string;
startDate: Date;
endDate: Date;
status: 'Approved' | 'Pending' | 'Denied';
}
const Card: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="bg-slate-800 rounded-lg shadow-lg p-6">{children}</div>
);
export default function App() {
const [currentDate, setCurrentDate] = useState(new Date(2025, 0, 1)); // January 2025
const timeOffRequests: TimeOffRequest[] = [
{ id: '1', employee: 'Sarah Johnson', type: 'Vacation', startDate: new Date(2025, 0, 15), endDate: new Date(2025, 0, 17), status: 'Approved' },
{ id: '2', employee: 'Mike Chen', type: 'Sick Leave', startDate: new Date(2025, 0, 8), endDate: new Date(2025, 0, 8), status: 'Approved' },
{ id: '3', employee: 'Emily Davis', type: 'Vacation', startDate: new Date(2025, 0, 22), endDate: new Date(2025, 0, 24), status: 'Pending' },
{ id: '4', employee: 'James Wilson', type: 'Personal', startDate: new Date(2025, 0, 10), endDate: new Date(2025, 0, 10), status: 'Approved' },
];
const getDaysInMonth = (date: Date) => {
return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
};
const getFirstDayOfMonth = (date: Date) => {
return new Date(date.getFullYear(), date.getMonth(), 1).getDay();
};
const getRequestsForDay = (day: number) => {
const checkDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), day);
return timeOffRequests.filter(req => {
return checkDate >= req.startDate && checkDate <= req.endDate;
});
};
const changeMonth = (delta: number) => {
setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() + delta, 1));
};
const daysInMonth = getDaysInMonth(currentDate);
const firstDay = getFirstDayOfMonth(currentDate);
const monthName = currentDate.toLocaleString('default', { month: 'long', year: 'numeric' });
const days = [];
for (let i = 0; i < firstDay; i++) {
days.push(<div key={`empty-${i}`} className="h-24 bg-slate-700/30 border border-slate-700"></div>);
}
for (let day = 1; day <= daysInMonth; day++) {
const requests = getRequestsForDay(day);
days.push(
<div key={day} className="h-24 bg-slate-700 border border-slate-600 p-2 overflow-hidden hover:bg-slate-600 transition-colors">
<div className="font-semibold text-white mb-1">{day}</div>
<div className="space-y-1">
{requests.slice(0, 2).map((req, idx) => (
<div
key={idx}
className={`text-xs px-1 py-0.5 rounded truncate ${
req.status === 'Approved' ? 'bg-green-600 text-white' :
req.status === 'Pending' ? 'bg-yellow-600 text-white' :
'bg-red-600 text-white'
}`}
title={`${req.employee} - ${req.type}`}
>
{req.employee.split(' ')[0]}
</div>
))}
{requests.length > 2 && (
<div className="text-xs text-slate-400">+{requests.length - 2} more</div>
)}
</div>
</div>
);
}
return (
<div className="min-h-screen bg-slate-900 text-white p-6">
<div className="max-w-7xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white flex items-center">
<Calendar className="mr-3 h-10 w-10 text-blue-400" />
Time Off Calendar
</h1>
<Card>
{/* Calendar Header */}
<div className="flex items-center justify-between mb-6">
<button
onClick={() => changeMonth(-1)}
className="p-2 hover:bg-slate-700 rounded-lg transition-colors"
>
<ChevronLeft className="h-6 w-6 text-white" />
</button>
<h2 className="text-2xl font-bold text-white">{monthName}</h2>
<button
onClick={() => changeMonth(1)}
className="p-2 hover:bg-slate-700 rounded-lg transition-colors"
>
<ChevronRight className="h-6 w-6 text-white" />
</button>
</div>
{/* Day Headers */}
<div className="grid grid-cols-7 gap-0 mb-2">
{['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map(day => (
<div key={day} className="text-center font-semibold text-slate-400 py-2">
{day}
</div>
))}
</div>
{/* Calendar Grid */}
<div className="grid grid-cols-7 gap-0">
{days}
</div>
{/* Legend */}
<div className="mt-6 flex items-center space-x-6 text-sm">
<div className="flex items-center space-x-2">
<div className="w-4 h-4 bg-green-600 rounded"></div>
<span className="text-slate-300">Approved</span>
</div>
<div className="flex items-center space-x-2">
<div className="w-4 h-4 bg-yellow-600 rounded"></div>
<span className="text-slate-300">Pending</span>
</div>
<div className="flex items-center space-x-2">
<div className="w-4 h-4 bg-red-600 rounded"></div>
<span className="text-slate-300">Denied</span>
</div>
</div>
</Card>
{/* Upcoming Requests */}
<div className="mt-8">
<h2 className="text-2xl font-bold mb-4 text-white">Upcoming Requests</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{timeOffRequests.map(req => (
<Card key={req.id}>
<div className="flex items-center justify-between">
<div>
<h3 className="font-semibold text-white">{req.employee}</h3>
<p className="text-sm text-slate-400">{req.type}</p>
<p className="text-xs text-slate-500 mt-1">
{req.startDate.toLocaleDateString()} - {req.endDate.toLocaleDateString()}
</p>
</div>
<span className={`px-3 py-1 rounded-full text-xs font-medium ${
req.status === 'Approved' ? 'bg-green-600 text-white' :
req.status === 'Pending' ? 'bg-yellow-600 text-white' :
'bg-red-600 text-white'
}`}>
{req.status}
</span>
</div>
</Card>
))}
</div>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Time Off Calendar - BambooHR</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./App.tsx"></script>
<script type="module">
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
ReactDOM.createRoot(document.getElementById('root')).render(
React.createElement(React.StrictMode, null, React.createElement(App))
);
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3003,
open: true
},
build: {
outDir: 'dist',
sourcemap: true
}
});

View File

@ -0,0 +1,151 @@
import React, { useState } from 'react';
import { Check, X, Clock, Filter } from 'lucide-react';
interface TimeOffRequest {
id: string;
employee: string;
type: string;
startDate: string;
endDate: string;
days: number;
reason: string;
status: 'Pending' | 'Approved' | 'Denied';
submittedDate: string;
}
const Card: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="bg-slate-800 rounded-lg shadow-lg p-6">{children}</div>
);
export default function App() {
const [filterStatus, setFilterStatus] = useState<string>('All');
const [requests, setRequests] = useState<TimeOffRequest[]>([
{ id: '1', employee: 'Sarah Johnson', type: 'Vacation', startDate: '2025-01-15', endDate: '2025-01-17', days: 3, reason: 'Family vacation', status: 'Pending', submittedDate: '2025-01-05' },
{ id: '2', employee: 'Mike Chen', type: 'Sick Leave', startDate: '2025-01-08', endDate: '2025-01-08', days: 1, reason: 'Medical appointment', status: 'Approved', submittedDate: '2025-01-07' },
{ id: '3', employee: 'Emily Davis', type: 'Vacation', startDate: '2025-01-22', endDate: '2025-01-24', days: 3, reason: 'Personal time', status: 'Pending', submittedDate: '2025-01-10' },
{ id: '4', employee: 'James Wilson', type: 'Personal', startDate: '2025-01-10', endDate: '2025-01-10', days: 1, reason: 'Personal errands', status: 'Approved', submittedDate: '2025-01-08' },
{ id: '5', employee: 'Lisa Anderson', type: 'Vacation', startDate: '2025-02-01', endDate: '2025-02-05', days: 5, reason: 'Holiday trip', status: 'Pending', submittedDate: '2025-01-12' },
{ id: '6', employee: 'David Brown', type: 'Sick Leave', startDate: '2025-01-03', endDate: '2025-01-03', days: 1, reason: 'Illness', status: 'Denied', submittedDate: '2025-01-02' },
]);
const handleApprove = (id: string) => {
setRequests(requests.map(req =>
req.id === id ? { ...req, status: 'Approved' as const } : req
));
};
const handleDeny = (id: string) => {
setRequests(requests.map(req =>
req.id === id ? { ...req, status: 'Denied' as const } : req
));
};
const filteredRequests = filterStatus === 'All'
? requests
: requests.filter(req => req.status === filterStatus);
return (
<div className="min-h-screen bg-slate-900 text-white p-6">
<div className="max-w-6xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white flex items-center">
<Clock className="mr-3 h-10 w-10 text-blue-400" />
Time Off Requests
</h1>
{/* Filter */}
<Card>
<div className="flex items-center space-x-4 mb-6">
<Filter className="h-5 w-5 text-slate-400" />
<select
value={filterStatus}
onChange={(e) => setFilterStatus(e.target.value)}
className="px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="All">All Requests</option>
<option value="Pending">Pending</option>
<option value="Approved">Approved</option>
<option value="Denied">Denied</option>
</select>
<span className="text-slate-400 text-sm">
Showing {filteredRequests.length} of {requests.length} requests
</span>
</div>
</Card>
{/* Requests List */}
<div className="mt-8 space-y-4">
{filteredRequests.map((request) => (
<Card key={request.id}>
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center space-x-4 mb-3">
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center text-lg font-bold">
{request.employee.split(' ').map(n => n[0]).join('')}
</div>
<div>
<h3 className="text-lg font-semibold text-white">{request.employee}</h3>
<p className="text-sm text-slate-400">{request.type}</p>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 ml-16">
<div>
<p className="text-xs text-slate-500 mb-1">Dates</p>
<p className="text-white">{request.startDate} to {request.endDate}</p>
<p className="text-sm text-slate-400">{request.days} day{request.days > 1 ? 's' : ''}</p>
</div>
<div>
<p className="text-xs text-slate-500 mb-1">Reason</p>
<p className="text-white">{request.reason}</p>
</div>
<div>
<p className="text-xs text-slate-500 mb-1">Submitted</p>
<p className="text-white">{request.submittedDate}</p>
</div>
</div>
</div>
<div className="flex flex-col items-end space-y-3">
<span className={`px-4 py-2 rounded-full text-sm font-medium ${
request.status === 'Approved' ? 'bg-green-600 text-white' :
request.status === 'Pending' ? 'bg-yellow-600 text-white' :
'bg-red-600 text-white'
}`}>
{request.status}
</span>
{request.status === 'Pending' && (
<div className="flex space-x-2">
<button
onClick={() => handleApprove(request.id)}
className="p-2 bg-green-600 hover:bg-green-700 rounded-lg transition-colors"
title="Approve"
>
<Check className="h-5 w-5 text-white" />
</button>
<button
onClick={() => handleDeny(request.id)}
className="p-2 bg-red-600 hover:bg-red-700 rounded-lg transition-colors"
title="Deny"
>
<X className="h-5 w-5 text-white" />
</button>
</div>
)}
</div>
</div>
</Card>
))}
{filteredRequests.length === 0 && (
<div className="text-center py-12">
<Clock className="h-16 w-16 text-slate-600 mx-auto mb-4" />
<p className="text-slate-400 text-lg">No requests found</p>
</div>
)}
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Time Off Requests - BambooHR</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./App.tsx"></script>
<script type="module">
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
ReactDOM.createRoot(document.getElementById('root')).render(
React.createElement(React.StrictMode, null, React.createElement(App))
);
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3004,
open: true
},
build: {
outDir: 'dist',
sourcemap: true
}
});

View File

@ -0,0 +1,171 @@
import React, { useState } from 'react';
import { BookOpen, Users, Clock, Star } from 'lucide-react';
interface Course {
id: string;
title: string;
category: string;
duration: string;
enrolled: number;
completed: number;
rating: number;
difficulty: 'Beginner' | 'Intermediate' | 'Advanced';
}
const Card: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="bg-slate-800 rounded-lg shadow-lg p-6">{children}</div>
);
export default function App() {
const [filterCategory, setFilterCategory] = useState('All');
const courses: Course[] = [
{ id: '1', title: 'Leadership Essentials', category: 'Leadership', duration: '4 hours', enrolled: 45, completed: 32, rating: 4.8, difficulty: 'Intermediate' },
{ id: '2', title: 'Communication Skills', category: 'Soft Skills', duration: '3 hours', enrolled: 65, completed: 58, rating: 4.6, difficulty: 'Beginner' },
{ id: '3', title: 'Project Management', category: 'Management', duration: '6 hours', enrolled: 38, completed: 25, rating: 4.7, difficulty: 'Advanced' },
{ id: '4', title: 'Data Analytics Basics', category: 'Technical', duration: '5 hours', enrolled: 52, completed: 40, rating: 4.5, difficulty: 'Beginner' },
{ id: '5', title: 'Advanced Excel', category: 'Technical', duration: '4 hours', enrolled: 30, completed: 22, rating: 4.9, difficulty: 'Advanced' },
{ id: '6', title: 'Diversity & Inclusion', category: 'Compliance', duration: '2 hours', enrolled: 85, completed: 75, rating: 4.4, difficulty: 'Beginner' },
{ id: '7', title: 'Agile Methodology', category: 'Development', duration: '5 hours', enrolled: 42, completed: 30, rating: 4.7, difficulty: 'Intermediate' },
{ id: '8', title: 'Customer Service Excellence', category: 'Soft Skills', duration: '3 hours', enrolled: 55, completed: 48, rating: 4.6, difficulty: 'Beginner' },
];
const categories = ['All', ...Array.from(new Set(courses.map(c => c.category)))];
const filteredCourses = filterCategory === 'All'
? courses
: courses.filter(c => c.category === filterCategory);
const stats = {
totalCourses: courses.length,
totalEnrolled: courses.reduce((sum, c) => sum + c.enrolled, 0),
totalCompleted: courses.reduce((sum, c) => sum + c.completed, 0),
avgRating: (courses.reduce((sum, c) => sum + c.rating, 0) / courses.length).toFixed(1)
};
return (
<div className="min-h-screen bg-slate-900 text-white p-6">
<div className="max-w-7xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white flex items-center">
<BookOpen className="mr-3 h-10 w-10 text-blue-400" />
Training Catalog
</h1>
{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Total Courses</p>
<p className="text-3xl font-bold text-white">{stats.totalCourses}</p>
</div>
<BookOpen className="h-10 w-10 text-blue-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Enrolled</p>
<p className="text-3xl font-bold text-white">{stats.totalEnrolled}</p>
</div>
<Users className="h-10 w-10 text-green-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Completed</p>
<p className="text-3xl font-bold text-white">{stats.totalCompleted}</p>
</div>
<Clock className="h-10 w-10 text-purple-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Avg Rating</p>
<p className="text-3xl font-bold text-white">{stats.avgRating}</p>
</div>
<Star className="h-10 w-10 text-yellow-400" />
</div>
</Card>
</div>
{/* Filter */}
<Card>
<select
value={filterCategory}
onChange={(e) => setFilterCategory(e.target.value)}
className="px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
>
{categories.map(cat => (
<option key={cat} value={cat}>{cat === 'All' ? 'All Categories' : cat}</option>
))}
</select>
<span className="ml-4 text-slate-400 text-sm">
Showing {filteredCourses.length} course{filteredCourses.length !== 1 ? 's' : ''}
</span>
</Card>
{/* Courses Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mt-8">
{filteredCourses.map((course) => {
const completionRate = Math.round((course.completed / course.enrolled) * 100);
return (
<Card key={course.id}>
<div className="mb-4">
<div className="flex items-start justify-between mb-2">
<h3 className="text-lg font-semibold text-white flex-1">{course.title}</h3>
<span className={`px-2 py-1 rounded text-xs font-medium ml-2 ${
course.difficulty === 'Beginner' ? 'bg-green-600 text-white' :
course.difficulty === 'Intermediate' ? 'bg-yellow-600 text-white' :
'bg-red-600 text-white'
}`}>
{course.difficulty}
</span>
</div>
<p className="text-sm text-slate-400">{course.category}</p>
</div>
<div className="space-y-3 mb-4">
<div className="flex items-center justify-between text-sm">
<span className="text-slate-400 flex items-center">
<Clock className="h-4 w-4 mr-1" />
{course.duration}
</span>
<span className="text-slate-400 flex items-center">
<Star className="h-4 w-4 mr-1 text-yellow-400" />
{course.rating}
</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-slate-400">Enrolled</span>
<span className="text-white font-medium">{course.enrolled}</span>
</div>
<div>
<div className="flex justify-between text-sm mb-1">
<span className="text-slate-400">Completion Rate</span>
<span className="text-white">{completionRate}%</span>
</div>
<div className="w-full bg-slate-700 rounded-full h-2">
<div
className="bg-gradient-to-r from-blue-500 to-purple-500 h-2 rounded-full"
style={{ width: `${completionRate}%` }}
/>
</div>
</div>
</div>
<button className="w-full py-2 bg-blue-600 hover:bg-blue-700 rounded-lg font-medium transition-colors">
View Details
</button>
</Card>
);
})}
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Training Catalog - BambooHR</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./App.tsx"></script>
<script type="module">
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
ReactDOM.createRoot(document.getElementById('root')).render(
React.createElement(React.StrictMode, null, React.createElement(App))
);
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3011,
open: true
},
build: {
outDir: 'dist',
sourcemap: true
}
});

View File

@ -0,0 +1,166 @@
import React, { useState } from 'react';
import { BookOpen, CheckCircle, Clock, Award, Search } from 'lucide-react';
interface EmployeeProgress {
id: string;
employee: string;
department: string;
coursesCompleted: number;
coursesInProgress: number;
totalCourses: number;
hoursCompleted: number;
lastActivity: string;
}
const Card: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="bg-slate-800 rounded-lg shadow-lg p-6">{children}</div>
);
export default function App() {
const [searchTerm, setSearchTerm] = useState('');
const progress: EmployeeProgress[] = [
{ id: '1', employee: 'Sarah Johnson', department: 'Engineering', coursesCompleted: 8, coursesInProgress: 2, totalCourses: 12, hoursCompleted: 32, lastActivity: '2 days ago' },
{ id: '2', employee: 'Mike Chen', department: 'Product', coursesCompleted: 5, coursesInProgress: 1, totalCourses: 8, hoursCompleted: 20, lastActivity: '1 week ago' },
{ id: '3', employee: 'Emily Davis', department: 'Design', coursesCompleted: 10, coursesInProgress: 0, totalCourses: 10, hoursCompleted: 38, lastActivity: '3 days ago' },
{ id: '4', employee: 'James Wilson', department: 'Engineering', coursesCompleted: 6, coursesInProgress: 3, totalCourses: 12, hoursCompleted: 24, lastActivity: 'Today' },
{ id: '5', employee: 'Lisa Anderson', department: 'Marketing', coursesCompleted: 7, coursesInProgress: 2, totalCourses: 10, hoursCompleted: 28, lastActivity: '4 days ago' },
];
const filteredProgress = progress.filter(p =>
p.employee.toLowerCase().includes(searchTerm.toLowerCase()) ||
p.department.toLowerCase().includes(searchTerm.toLowerCase())
);
const stats = {
totalEmployees: progress.length,
avgCompletion: Math.round((progress.reduce((sum, p) => sum + (p.coursesCompleted / p.totalCourses), 0) / progress.length) * 100),
totalHours: progress.reduce((sum, p) => sum + p.hoursCompleted, 0),
activeEmployees: progress.filter(p => p.coursesInProgress > 0).length
};
return (
<div className="min-h-screen bg-slate-900 text-white p-6">
<div className="max-w-7xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white flex items-center">
<BookOpen className="mr-3 h-10 w-10 text-blue-400" />
Training Progress
</h1>
{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Total Employees</p>
<p className="text-3xl font-bold text-white">{stats.totalEmployees}</p>
</div>
<Award className="h-10 w-10 text-blue-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Avg Completion</p>
<p className="text-3xl font-bold text-white">{stats.avgCompletion}%</p>
</div>
<CheckCircle className="h-10 w-10 text-green-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Total Hours</p>
<p className="text-3xl font-bold text-white">{stats.totalHours}</p>
</div>
<Clock className="h-10 w-10 text-purple-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Active Learners</p>
<p className="text-3xl font-bold text-white">{stats.activeEmployees}</p>
</div>
<BookOpen className="h-10 w-10 text-orange-400" />
</div>
</Card>
</div>
{/* Search */}
<Card>
<div className="relative">
<Search className="absolute left-3 top-3 h-5 w-5 text-slate-400" />
<input
type="text"
placeholder="Search by employee or department..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
</Card>
{/* Progress List */}
<div className="mt-8 space-y-4">
{filteredProgress.map((employee) => {
const completionRate = Math.round((employee.coursesCompleted / employee.totalCourses) * 100);
return (
<Card key={employee.id}>
<div className="flex items-start justify-between">
<div className="flex items-center flex-1">
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center text-lg font-bold mr-4">
{employee.employee.split(' ').map(n => n[0]).join('')}
</div>
<div className="flex-1">
<h3 className="text-lg font-semibold text-white">{employee.employee}</h3>
<p className="text-sm text-slate-400">{employee.department}</p>
<p className="text-xs text-slate-500 mt-1">Last active: {employee.lastActivity}</p>
</div>
</div>
<div className="grid grid-cols-3 gap-6 ml-6">
<div className="text-center">
<p className="text-2xl font-bold text-green-400">{employee.coursesCompleted}</p>
<p className="text-xs text-slate-400">Completed</p>
</div>
<div className="text-center">
<p className="text-2xl font-bold text-yellow-400">{employee.coursesInProgress}</p>
<p className="text-xs text-slate-400">In Progress</p>
</div>
<div className="text-center">
<p className="text-2xl font-bold text-purple-400">{employee.hoursCompleted}h</p>
<p className="text-xs text-slate-400">Hours</p>
</div>
</div>
</div>
<div className="mt-4">
<div className="flex justify-between text-sm mb-2">
<span className="text-slate-400">Overall Progress</span>
<span className="text-white font-medium">
{employee.coursesCompleted} / {employee.totalCourses} ({completionRate}%)
</span>
</div>
<div className="w-full bg-slate-700 rounded-full h-3">
<div
className="bg-gradient-to-r from-blue-500 to-purple-500 h-3 rounded-full transition-all duration-300"
style={{ width: `${completionRate}%` }}
/>
</div>
</div>
</Card>
);
})}
{filteredProgress.length === 0 && (
<div className="text-center py-12">
<BookOpen className="h-16 w-16 text-slate-600 mx-auto mb-4" />
<p className="text-slate-400 text-lg">No employees found</p>
</div>
)}
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Training Progress - BambooHR</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./App.tsx"></script>
<script type="module">
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
ReactDOM.createRoot(document.getElementById('root')).render(
React.createElement(React.StrictMode, null, React.createElement(App))
);
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3012,
open: true
},
build: {
outDir: 'dist',
sourcemap: true
}
});

View File

@ -0,0 +1,184 @@
import React from 'react';
import { TrendingDown, AlertCircle, Users, Calendar } from 'lucide-react';
const Card: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="bg-slate-800 rounded-lg shadow-lg p-6">{children}</div>
);
export default function App() {
const monthlyTurnover = [
{ month: 'Jul 2024', voluntary: 1, involuntary: 1, total: 2, rate: 0.9 },
{ month: 'Aug 2024', voluntary: 2, involuntary: 0, total: 2, rate: 0.9 },
{ month: 'Sep 2024', voluntary: 1, involuntary: 0, total: 1, rate: 0.4 },
{ month: 'Oct 2024', voluntary: 2, involuntary: 1, total: 3, rate: 1.3 },
{ month: 'Nov 2024', voluntary: 1, involuntary: 1, total: 2, rate: 0.8 },
{ month: 'Dec 2024', voluntary: 2, involuntary: 0, total: 2, rate: 0.8 },
{ month: 'Jan 2025', voluntary: 1, involuntary: 0, total: 1, rate: 0.4 }
];
const departmentTurnover = [
{ dept: 'Engineering', terminations: 3, avgTenure: '2.5 years', rate: 3.5, risk: 'Low' },
{ dept: 'Sales', terminations: 5, avgTenure: '1.8 years', rate: 11.1, risk: 'High' },
{ dept: 'Marketing', terminations: 2, avgTenure: '2.2 years', rate: 7.1, risk: 'Medium' },
{ dept: 'Product', terminations: 1, avgTenure: '3.1 years', rate: 3.1, risk: 'Low' },
{ dept: 'Operations', terminations: 2, avgTenure: '1.5 years', rate: 20.0, risk: 'High' }
];
const exitReasons = [
{ reason: 'Better Opportunity', count: 5, percentage: 38 },
{ reason: 'Compensation', count: 3, percentage: 23 },
{ reason: 'Relocation', count: 2, percentage: 15 },
{ reason: 'Career Growth', count: 2, percentage: 15 },
{ reason: 'Other', count: 1, percentage: 8 }
];
const totalTerminations = monthlyTurnover.reduce((sum, m) => sum + m.total, 0);
const avgTurnoverRate = (monthlyTurnover.reduce((sum, m) => sum + m.rate, 0) / monthlyTurnover.length).toFixed(1);
const voluntaryTerminations = monthlyTurnover.reduce((sum, m) => sum + m.voluntary, 0);
const involuntaryTerminations = monthlyTurnover.reduce((sum, m) => sum + m.involuntary, 0);
const maxRate = Math.max(...monthlyTurnover.map(m => m.rate));
return (
<div className="min-h-screen bg-slate-900 text-white p-6">
<div className="max-w-7xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white flex items-center">
<TrendingDown className="mr-3 h-10 w-10 text-red-400" />
Turnover Report
</h1>
{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Total Terminations</p>
<p className="text-3xl font-bold text-white">{totalTerminations}</p>
<p className="text-xs text-slate-500 mt-1">Last 6 months</p>
</div>
<Users className="h-10 w-10 text-red-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Avg Turnover Rate</p>
<p className="text-3xl font-bold text-white">{avgTurnoverRate}%</p>
<p className="text-xs text-slate-500 mt-1">Monthly average</p>
</div>
<TrendingDown className="h-10 w-10 text-orange-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Voluntary</p>
<p className="text-3xl font-bold text-white">{voluntaryTerminations}</p>
<p className="text-xs text-slate-500 mt-1">{((voluntaryTerminations/totalTerminations)*100).toFixed(0)}% of total</p>
</div>
<Calendar className="h-10 w-10 text-yellow-400" />
</div>
</Card>
<Card>
<div className="flex items-center justify-between">
<div>
<p className="text-slate-400 text-sm">Involuntary</p>
<p className="text-3xl font-bold text-white">{involuntaryTerminations}</p>
<p className="text-xs text-slate-500 mt-1">{((involuntaryTerminations/totalTerminations)*100).toFixed(0)}% of total</p>
</div>
<AlertCircle className="h-10 w-10 text-blue-400" />
</div>
</Card>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Monthly Trend */}
<Card>
<h2 className="text-xl font-semibold mb-6 text-white">Monthly Turnover Rate</h2>
<div className="space-y-4">
{monthlyTurnover.map((data, idx) => {
const barHeight = (data.rate / maxRate) * 100;
return (
<div key={idx}>
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-slate-400 w-24">{data.month}</span>
<div className="flex items-center space-x-3">
<span className="text-sm text-slate-400">V:{data.voluntary} I:{data.involuntary}</span>
<span className="text-lg font-bold text-white">{data.rate}%</span>
</div>
</div>
<div className="w-full bg-slate-700 rounded-full h-6">
<div
className="bg-gradient-to-r from-red-500 to-orange-500 h-6 rounded-full transition-all duration-500"
style={{ width: `${barHeight}%` }}
/>
</div>
</div>
);
})}
</div>
</Card>
{/* By Department */}
<Card>
<h2 className="text-xl font-semibold mb-6 text-white">Department Analysis</h2>
<div className="space-y-4">
{departmentTurnover.map((dept, idx) => (
<div key={idx} className="p-4 bg-slate-700 rounded-lg">
<div className="flex items-center justify-between mb-3">
<h3 className="font-semibold text-white">{dept.dept}</h3>
<span className={`px-3 py-1 rounded-full text-xs font-medium ${
dept.risk === 'High' ? 'bg-red-600 text-white' :
dept.risk === 'Medium' ? 'bg-yellow-600 text-white' :
'bg-green-600 text-white'
}`}>
{dept.risk} Risk
</span>
</div>
<div className="grid grid-cols-3 gap-4 text-sm">
<div>
<p className="text-slate-400">Terminations</p>
<p className="text-white font-bold">{dept.terminations}</p>
</div>
<div>
<p className="text-slate-400">Rate</p>
<p className="text-white font-bold">{dept.rate}%</p>
</div>
<div>
<p className="text-slate-400">Avg Tenure</p>
<p className="text-white font-bold">{dept.avgTenure}</p>
</div>
</div>
</div>
))}
</div>
</Card>
</div>
{/* Exit Reasons */}
<Card className="mt-8">
<h2 className="text-xl font-semibold mb-6 text-white">Exit Reasons (Voluntary)</h2>
<div className="space-y-4">
{exitReasons.map((reason, idx) => (
<div key={idx}>
<div className="flex items-center justify-between mb-2">
<span className="text-white font-medium">{reason.reason}</span>
<div className="flex items-center space-x-3">
<span className="text-slate-400">{reason.count} employee{reason.count > 1 ? 's' : ''}</span>
<span className="text-white font-bold">{reason.percentage}%</span>
</div>
</div>
<div className="w-full bg-slate-700 rounded-full h-2">
<div
className="bg-gradient-to-r from-blue-500 to-purple-500 h-2 rounded-full"
style={{ width: `${reason.percentage}%` }}
/>
</div>
</div>
))}
</div>
</Card>
</div>
</div>
);
}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Turnover Report - BambooHR</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./App.tsx"></script>
<script type="module">
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
ReactDOM.createRoot(document.getElementById('root')).render(
React.createElement(React.StrictMode, null, React.createElement(App))
);
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3016,
open: true
},
build: {
outDir: 'dist',
sourcemap: true
}
});