semillero-special-hotel/src/app/history/page.tsx

277 lines
10 KiB
TypeScript

'use client';
import React, { useState, useEffect } from 'react';
import Header from '@/components/Header';
import { useLocale } from '@/lib/i18n/LocaleContext';
import { useUser } from '@/lib/auth/UserContext';
import styles from './page.module.css';
interface Settlement {
id: number;
period: string;
salesAmount: string | number;
goalAmount: string | number;
achievementPercentage: string | number;
calculatedCommission: string | number;
calculatedBonus: string | number;
adjustmentAmount: string | number;
totalPayout: string | number;
status: string;
aiAuditNotes: any;
colaborador: {
username: string;
email: string;
hotel: {
name: string;
};
};
plan: {
name: string;
code: string;
};
}
interface UserOption {
id: number;
username: string;
}
export default function HistoryPage() {
const { t, locale } = useLocale();
const { role, user } = useUser();
const [settlements, setSettlements] = useState<Settlement[]>([]);
const [users, setUsers] = useState<UserOption[]>([]);
const [isLoading, setIsLoading] = useState(true);
// Filters state
const [period, setPeriod] = useState('');
const [status, setStatus] = useState('');
const [selectedUserId, setSelectedUserId] = useState('');
const [expandedRowId, setExpandedRowId] = useState<number | null>(null);
const fetchSettlements = async () => {
setIsLoading(true);
try {
const queryParams = new URLSearchParams();
if (period) queryParams.append('period', period);
if (status) queryParams.append('status', status);
if (selectedUserId) queryParams.append('userId', selectedUserId);
const res = await fetch(`/api/settlements?${queryParams.toString()}`);
if (res.ok) {
const data = await res.json();
setSettlements(data.settlements || []);
}
} catch (err) {
console.error('Failed to fetch settlements', err);
} finally {
setIsLoading(false);
}
};
const fetchUsers = async () => {
if (role !== 'admin' && role !== 'director') return;
try {
const res = await fetch('/api/users');
if (res.ok) {
const data = await res.json();
setUsers(data.users || []);
}
} catch (err) {
console.error('Failed to fetch users', err);
}
};
useEffect(() => {
fetchSettlements();
}, [period, status, selectedUserId]);
useEffect(() => {
fetchUsers();
}, [role]);
const toggleRow = (id: number) => {
if (expandedRowId === id) {
setExpandedRowId(null);
} else {
setExpandedRowId(id);
}
};
const formatCurrency = (val: string | number) => {
const num = typeof val === 'string' ? parseFloat(val) : val;
if (isNaN(num)) return '$0.00';
return new Intl.NumberFormat(locale === 'es' ? 'es-CO' : 'en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2
}).format(num);
};
const formatPercent = (val: string | number) => {
const num = typeof val === 'string' ? parseFloat(val) : val;
if (isNaN(num)) return '0%';
return `${(num * 100).toFixed(1)}%`;
};
const getAuditNotes = (notes: any) => {
if (!notes) return null;
if (typeof notes === 'string') return notes;
if (notes[locale]) return notes[locale];
if (notes.es) return notes.es;
if (notes.en) return notes.en;
return JSON.stringify(notes);
};
const handlePrint = () => {
window.print();
};
const showUserFilter = role === 'admin' || role === 'director';
return (
<div className={styles.container}>
<Header activeTab="none" />
<main className={styles.main}>
<div className={styles.titleSection}>
<h1 className={styles.title}>{t('history.title')}</h1>
<button onClick={handlePrint} className={styles.btnPrimary} id="btn-print-pdf">
{t('history.download_pdf')}
</button>
</div>
{/* Filter Card */}
<div className={styles.filterCard}>
<div className={styles.formGroup}>
<label className={styles.label}>{t('history.filters')}</label>
<input
type="text"
placeholder="YYYY-MM (e.g. 2026-06)"
className={styles.input}
value={period}
onChange={(e) => setPeriod(e.target.value)}
id="filter-period"
/>
</div>
<div className={styles.formGroup}>
<label className={styles.label}>{t('history.status')}</label>
<select
className={styles.select}
value={status}
onChange={(e) => setStatus(e.target.value)}
id="filter-status"
>
<option value="">{t('history.all_statuses')}</option>
<option value="APPROVED">{t('status.APPROVED')}</option>
<option value="PENDING">{t('status.PENDING')}</option>
<option value="REJECTED">{t('status.REJECTED')}</option>
<option value="SIMULATED">{t('status.SIMULATED')}</option>
</select>
</div>
{showUserFilter && (
<div className={styles.formGroup}>
<label className={styles.label}>{t('simulation.thCollaborator')}</label>
<select
className={styles.select}
value={selectedUserId}
onChange={(e) => setSelectedUserId(e.target.value)}
id="filter-collaborator"
>
<option value="">{t('history.all_statuses')}</option>
{users.map((u) => (
<option key={u.id} value={u.id}>
{u.username}
</option>
))}
</select>
</div>
)}
</div>
{/* History Table */}
{isLoading ? (
<div className={styles.loading}>{t('history.loading')}</div>
) : settlements.length === 0 ? (
<div className={styles.emptyState}>{t('history.empty')}</div>
) : (
<div className={styles.tableContainer}>
<table className={styles.table}>
<thead>
<tr>
<th className={styles.th}>{t('history.period')}</th>
{showUserFilter && <th className={styles.th}>{t('simulation.thCollaborator')}</th>}
<th className={styles.th}>{t('simulation.thPlan')}</th>
<th className={styles.th + ' ' + styles.thAmount}>{t('history.goal')}</th>
<th className={styles.th + ' ' + styles.thAmount}>{t('history.sales')}</th>
<th className={styles.th + ' ' + styles.thAmount}>%</th>
<th className={styles.th + ' ' + styles.thAmount}>{t('history.commission')}</th>
<th className={styles.th + ' ' + styles.thAmount}>{t('history.adjustment')}</th>
<th className={styles.th + ' ' + styles.thAmount}>{t('history.payout')}</th>
<th className={styles.th}>{t('history.status')}</th>
<th className={styles.th}>{t('simulation.thAiAudit')}</th>
</tr>
</thead>
<tbody>
{settlements.map((item) => (
<React.Fragment key={item.id}>
<tr className={styles.tr} data-settlement-id={item.id}>
<td className={styles.td}>{item.period}</td>
{showUserFilter && (
<td className={styles.td}>
<div><strong>{item.colaborador.username}</strong></div>
<div style={{ fontSize: '0.75rem', opacity: 0.7 }}>
{item.colaborador.hotel.name}
</div>
</td>
)}
<td className={styles.td}>
<div>{item.plan.name}</div>
<div style={{ fontSize: '0.75rem', opacity: 0.7 }}>{item.plan.code}</div>
</td>
<td className={styles.td + ' ' + styles.amount}>{formatCurrency(item.goalAmount)}</td>
<td className={styles.td + ' ' + styles.amount}>{formatCurrency(item.salesAmount)}</td>
<td className={styles.td + ' ' + styles.amount}>{formatPercent(item.achievementPercentage)}</td>
<td className={styles.td + ' ' + styles.amount}>{formatCurrency(item.calculatedCommission)}</td>
<td className={styles.td + ' ' + styles.amount}>{formatCurrency(item.adjustmentAmount)}</td>
<td className={styles.td + ' ' + styles.amount}><strong>{formatCurrency(item.totalPayout)}</strong></td>
<td className={styles.td}>
<span className={`${styles.badge} ${styles['badge' + item.status]}`}>
{t(`status.${item.status}`)}
</span>
</td>
<td className={styles.td}>
{item.aiAuditNotes ? (
<button
onClick={() => toggleRow(item.id)}
className={styles.expandBtn}
id={`btn-toggle-notes-${item.id}`}
>
{expandedRowId === item.id ? t('history.hideNotes') : t('history.showNotes')}
</button>
) : (
<span style={{ fontSize: '0.75rem', opacity: 0.5 }}>-</span>
)}
</td>
</tr>
{expandedRowId === item.id && item.aiAuditNotes && (
<tr className={styles.expandedRow}>
<td colSpan={showUserFilter ? 11 : 10}>
<div className={styles.auditNotesBox}>
<h4 className={styles.auditNotesTitle}>{t('history.audit_notes')}</h4>
<p className={styles.auditNotesText}>{getAuditNotes(item.aiAuditNotes)}</p>
</div>
</td>
</tr>
)}
</React.Fragment>
))}
</tbody>
</table>
</div>
)}
</main>
</div>
);
}