template-saas/apps/frontend/src/components/commissions/CommissionsDashboard.tsx
Adrian Flores Cortes 2e6ecee8ea
Some checks are pending
CI / Backend CI (push) Waiting to run
CI / Frontend CI (push) Waiting to run
CI / Security Scan (push) Waiting to run
CI / CI Summary (push) Blocked by required conditions
[SAAS-018/020] feat: Complete Sales and Commissions modules implementation
Sprint 2 - Sales Frontend:
- Add frontend services, hooks, and pages for Sales module
- Update router with Sales routes

Sprint 3 - Commissions Backend:
- Add Commissions module with entities, DTOs, services, controllers
- Add DDL schema for commissions tables
- Register CommissionsModule in AppModule

Sprint 4 - Commissions Frontend:
- Add frontend services, hooks, and pages for Commissions module
- Add dashboard, schemes, entries, periods, and my-earnings pages
- Update router with Commissions routes

Update submodule references: backend, database, frontend

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 22:51:30 -06:00

135 lines
5.8 KiB
TypeScript

import { DollarSign, Users, Calendar, TrendingUp, Clock, CheckCircle } from 'lucide-react';
import { DashboardSummary, TopEarner, PeriodEarnings } from '../../services/commissions/dashboard.api';
interface CommissionsDashboardProps {
summary: DashboardSummary;
topEarners: TopEarner[];
periodEarnings: PeriodEarnings[];
}
export function CommissionsDashboard({ summary, topEarners, periodEarnings }: CommissionsDashboardProps) {
const formatCurrency = (amount: number) =>
new Intl.NumberFormat('en-US', { style: 'currency', currency: summary.currency }).format(amount);
return (
<div className="space-y-6">
{/* Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="bg-white rounded-lg shadow p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-500">Pending Commissions</p>
<p className="text-2xl font-bold text-yellow-600">{formatCurrency(summary.total_pending)}</p>
<p className="text-xs text-gray-400">{summary.pending_count} entries</p>
</div>
<div className="p-3 bg-yellow-100 rounded-full">
<Clock className="h-6 w-6 text-yellow-600" />
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-500">Approved</p>
<p className="text-2xl font-bold text-green-600">{formatCurrency(summary.total_approved)}</p>
<p className="text-xs text-gray-400">{summary.approved_count} entries</p>
</div>
<div className="p-3 bg-green-100 rounded-full">
<CheckCircle className="h-6 w-6 text-green-600" />
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-500">Total Paid</p>
<p className="text-2xl font-bold text-blue-600">{formatCurrency(summary.total_paid)}</p>
<p className="text-xs text-gray-400">{summary.paid_count} entries</p>
</div>
<div className="p-3 bg-blue-100 rounded-full">
<DollarSign className="h-6 w-6 text-blue-600" />
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-500">Active Schemes</p>
<p className="text-2xl font-bold text-purple-600">{summary.active_schemes}</p>
<p className="text-xs text-gray-400">{summary.active_assignments} assignments</p>
</div>
<div className="p-3 bg-purple-100 rounded-full">
<TrendingUp className="h-6 w-6 text-purple-600" />
</div>
</div>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Top Earners */}
<div className="bg-white rounded-lg shadow p-6">
<h3 className="text-lg font-semibold mb-4">Top Earners</h3>
{topEarners.length === 0 ? (
<p className="text-gray-500 text-center py-4">No earners data</p>
) : (
<div className="space-y-3">
{topEarners.map((earner) => (
<div key={earner.user_id} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-blue-500 text-white rounded-full flex items-center justify-center font-bold">
{earner.rank}
</div>
<div>
<p className="font-medium">{earner.user_name}</p>
<p className="text-sm text-gray-500">{earner.entries_count} sales</p>
</div>
</div>
<p className="font-bold text-green-600">{formatCurrency(earner.total_earned)}</p>
</div>
))}
</div>
)}
</div>
{/* Recent Periods */}
<div className="bg-white rounded-lg shadow p-6">
<h3 className="text-lg font-semibold mb-4">Recent Periods</h3>
{periodEarnings.length === 0 ? (
<p className="text-gray-500 text-center py-4">No periods data</p>
) : (
<div className="space-y-3">
{periodEarnings.slice(0, 5).map((period) => (
<div key={period.period_id} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div>
<p className="font-medium">{period.period_name}</p>
<p className="text-sm text-gray-500">
{new Date(period.starts_at).toLocaleDateString()} - {new Date(period.ends_at).toLocaleDateString()}
</p>
</div>
<div className="text-right">
<p className="font-bold">{formatCurrency(period.total_amount)}</p>
<span
className={`text-xs px-2 py-1 rounded-full ${
period.status === 'paid'
? 'bg-green-100 text-green-700'
: period.status === 'open'
? 'bg-blue-100 text-blue-700'
: 'bg-gray-100 text-gray-700'
}`}
>
{period.status}
</span>
</div>
</div>
))}
</div>
)}
</div>
</div>
</div>
);
}