template-saas/apps/frontend/src/components/commissions/EntriesList.tsx
Adrian Flores Cortes f853c49568
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
feat(frontend): Align documentation with development - complete frontend audit
- Add Sales/Commissions routes to router (11 new routes)
- Complete authStore (refreshTokens, updateProfile methods)
- Add JSDoc documentation to 40+ components across all categories
- Create FRONTEND-ROUTING.md with complete route mapping
- Create FRONTEND-PAGES-SPEC.md with 38 page specifications
- Update FRONTEND_INVENTORY.yml to v4.1.0 with resolved gaps

Components documented: ai/, audit/, commissions/, feature-flags/,
notifications/, sales/, storage/, webhooks/, whatsapp/

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 01:23:50 -06:00

195 lines
7.3 KiB
TypeScript

import { ChevronLeft, ChevronRight } from 'lucide-react';
import { CommissionEntry } from '../../services/commissions/entries.api';
import { EntryStatusBadge } from './EntryStatusBadge';
/**
* Props for the EntriesList component.
*/
interface EntriesListProps {
/** Array of commission entries to display in the table */
entries: CommissionEntry[];
/** Total number of entries across all pages */
total: number;
/** Current page number (1-indexed) */
page: number;
/** Total number of pages available */
totalPages: number;
/** Array of selected entry IDs for bulk operations */
selectedEntries: string[];
/** Callback when selection changes (for bulk approve/reject) */
onSelectionChange: (ids: string[]) => void;
/** Callback when page changes for pagination */
onPageChange: (page: number) => void;
/** Callback to refresh the entries list */
onRefresh: () => void;
}
/**
* Displays a paginated table of commission entries with selection support.
* Allows selecting pending entries for bulk approval/rejection operations.
* Shows entry details including date, user, reference, amounts, and status.
*
* @param props - The component props
* @param props.entries - Commission entries to display
* @param props.total - Total entry count for pagination info
* @param props.page - Current page number
* @param props.totalPages - Total pages for pagination controls
* @param props.selectedEntries - Currently selected entry IDs
* @param props.onSelectionChange - Handler for selection updates
* @param props.onPageChange - Handler for page navigation
* @param props.onRefresh - Handler to refresh data after operations
*
* @example
* ```tsx
* const [selected, setSelected] = useState<string[]>([]);
* const [page, setPage] = useState(1);
*
* <EntriesList
* entries={commissionEntries}
* total={100}
* page={page}
* totalPages={10}
* selectedEntries={selected}
* onSelectionChange={setSelected}
* onPageChange={setPage}
* onRefresh={() => refetch()}
* />
* ```
*/
export function EntriesList({
entries,
total,
page,
totalPages,
selectedEntries,
onSelectionChange,
onPageChange,
}: EntriesListProps) {
const formatCurrency = (amount: number) =>
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount);
const handleSelectAll = () => {
const pendingIds = entries.filter((e) => e.status === 'pending').map((e) => e.id);
if (pendingIds.every((id) => selectedEntries.includes(id))) {
onSelectionChange(selectedEntries.filter((id) => !pendingIds.includes(id)));
} else {
onSelectionChange([...new Set([...selectedEntries, ...pendingIds])]);
}
};
const handleSelectOne = (id: string) => {
if (selectedEntries.includes(id)) {
onSelectionChange(selectedEntries.filter((i) => i !== id));
} else {
onSelectionChange([...selectedEntries, id]);
}
};
const pendingEntries = entries.filter((e) => e.status === 'pending');
const allPendingSelected = pendingEntries.length > 0 && pendingEntries.every((e) => selectedEntries.includes(e.id));
return (
<div className="bg-white rounded-lg shadow">
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b bg-gray-50">
<th className="py-3 px-4 w-10">
<input
type="checkbox"
checked={allPendingSelected}
onChange={handleSelectAll}
className="h-4 w-4 text-blue-600 rounded"
title="Select all pending"
/>
</th>
<th className="text-left py-3 px-4 text-sm font-medium text-gray-500">Date</th>
<th className="text-left py-3 px-4 text-sm font-medium text-gray-500">User</th>
<th className="text-left py-3 px-4 text-sm font-medium text-gray-500">Reference</th>
<th className="text-right py-3 px-4 text-sm font-medium text-gray-500">Base Amount</th>
<th className="text-right py-3 px-4 text-sm font-medium text-gray-500">Rate</th>
<th className="text-right py-3 px-4 text-sm font-medium text-gray-500">Commission</th>
<th className="text-center py-3 px-4 text-sm font-medium text-gray-500">Status</th>
</tr>
</thead>
<tbody>
{entries.length === 0 ? (
<tr>
<td colSpan={8} className="text-center py-8 text-gray-500">
No commission entries found
</td>
</tr>
) : (
entries.map((entry) => (
<tr key={entry.id} className="border-b last:border-0 hover:bg-gray-50">
<td className="py-3 px-4">
{entry.status === 'pending' && (
<input
type="checkbox"
checked={selectedEntries.includes(entry.id)}
onChange={() => handleSelectOne(entry.id)}
className="h-4 w-4 text-blue-600 rounded"
/>
)}
</td>
<td className="py-3 px-4 text-sm">
{new Date(entry.created_at).toLocaleDateString()}
</td>
<td className="py-3 px-4 text-sm">
{entry.user
? `${entry.user.first_name} ${entry.user.last_name}`
: entry.user_id.slice(0, 8)}
</td>
<td className="py-3 px-4 text-sm capitalize">
{entry.reference_type}
</td>
<td className="py-3 px-4 text-sm text-right">
{formatCurrency(entry.base_amount)}
</td>
<td className="py-3 px-4 text-sm text-right">
{entry.rate_applied}%
</td>
<td className="py-3 px-4 text-sm text-right font-medium text-green-600">
{formatCurrency(entry.commission_amount)}
</td>
<td className="py-3 px-4 text-center">
<EntryStatusBadge status={entry.status} />
</td>
</tr>
))
)}
</tbody>
</table>
</div>
{/* Pagination */}
{totalPages > 1 && (
<div className="flex items-center justify-between px-4 py-3 border-t">
<p className="text-sm text-gray-500">
Showing {entries.length} of {total} entries
</p>
<div className="flex items-center gap-2">
<button
onClick={() => onPageChange(page - 1)}
disabled={page === 1}
className="p-1 rounded hover:bg-gray-100 disabled:opacity-50"
>
<ChevronLeft className="h-5 w-5" />
</button>
<span className="text-sm">
Page {page} of {totalPages}
</span>
<button
onClick={() => onPageChange(page + 1)}
disabled={page === totalPages}
className="p-1 rounded hover:bg-gray-100 disabled:opacity-50"
>
<ChevronRight className="h-5 w-5" />
</button>
</div>
</div>
)}
</div>
);
}