import React, { useState, useMemo } from 'react'; import { Association, Balance, Operation, OperationType } from '../types'; import { api } from '../api'; import Header from './Header'; import BalanceCard from './BalanceCard'; import OperationsChart from './OperationsChart'; import OperationsTable from './OperationsTable'; import AddOperationModal from './AddOperationModal'; import AddBalanceModal from './AddBalanceModal'; import { format, subDays, startOfMonth, endOfMonth } from 'date-fns'; interface DashboardProps { association: Association; onLogout: () => void; onUpdateAssociation: (association: Association) => void; } const Dashboard: React.FC = ({ association, onLogout, onUpdateAssociation }) => { const today = new Date(); const [dateRange, setDateRange] = useState<{ start: Date, end: Date }>({ start: startOfMonth(today), end: endOfMonth(today) }); const [selectedBalanceId, setSelectedBalanceId] = useState(association.balances[0]?.id ?? null); const [isModalOpen, setIsModalOpen] = useState(false); const [isAddBalanceModalOpen, setIsAddBalanceModalOpen] = useState(false); const [balanceToEdit, setBalanceToEdit] = useState(null); const [draggedBalanceIndex, setDraggedBalanceIndex] = useState(null); const [operationToEdit, setOperationToEdit] = useState(null); const scrollContainerRef = React.useRef(null); const scroll = (direction: 'left' | 'right') => { if (scrollContainerRef.current) { const scrollAmount = 320; const newScrollLeft = scrollContainerRef.current.scrollLeft + (direction === 'right' ? scrollAmount : -scrollAmount); scrollContainerRef.current.scrollTo({ left: newScrollLeft, behavior: 'smooth' }); } }; const filteredOperations = useMemo(() => { return association.operations.filter(op => { const opDate = new Date(op.date); return opDate >= dateRange.start && opDate <= dateRange.end; }); }, [association.operations, dateRange]); const selectedBalance = useMemo(() => { return association.balances.find(b => b.id === selectedBalanceId) ?? null; }, [association.balances, selectedBalanceId]); const operationsForSelectedBalance = useMemo(() => { return filteredOperations.filter(op => op.balanceId === selectedBalanceId); }, [filteredOperations, selectedBalanceId]); const incomesForSelectedBalance = operationsForSelectedBalance.filter(op => op.type === OperationType.INCOME); const expensesForSelectedBalance = operationsForSelectedBalance.filter(op => op.type === OperationType.EXPENSE); const handleAddOperation = async (newOperationData: Omit) => { try { const newOperation = await api.createOperation({ ...newOperationData, balance_id: newOperationData.balanceId, date: newOperationData.date, }); const updatedAssociation = { ...association, operations: [...association.operations, newOperation] }; onUpdateAssociation(updatedAssociation); setIsModalOpen(false); } catch (error) { console.error("Failed to add operation", error); alert("Failed to add operation"); } }; const handleUpdateOperation = async (updatedOperation: Operation) => { try { const result = await api.updateOperation({ ...updatedOperation, balanceId: updatedOperation.balanceId, }); const updatedAssociation = { ...association, operations: association.operations.map(op => op.id === result.id ? result : op) }; onUpdateAssociation(updatedAssociation); setIsModalOpen(false); setOperationToEdit(null); } catch (error) { console.error("Failed to update operation", error); alert("Failed to update operation"); } }; const handleDeleteOperation = async (operationId: string) => { if (window.confirm('Are you sure you want to delete this operation?')) { try { await api.deleteOperation(operationId); const updatedAssociation = { ...association, operations: association.operations.filter(op => op.id !== operationId) }; onUpdateAssociation(updatedAssociation); } catch (error) { console.error("Failed to delete operation", error); alert("Failed to delete operation"); } } }; const handleEditOperation = (operation: Operation) => { setOperationToEdit(operation); setIsModalOpen(true); }; const handleCloseModal = () => { setIsModalOpen(false); setOperationToEdit(null); }; const handleAddBalance = async (newBalanceData: Omit) => { try { const newBalance = await api.addBalance(newBalanceData.name, newBalanceData.initialAmount, association.id); const updatedAssociation = { ...association, balances: [...association.balances, newBalance] }; onUpdateAssociation(updatedAssociation); setIsAddBalanceModalOpen(false); setSelectedBalanceId(newBalance.id); } catch (error) { console.error("Failed to add balance", error); alert("Failed to add balance"); } }; const handleEditBalance = (balance: Balance) => { setBalanceToEdit(balance); setIsAddBalanceModalOpen(true); }; const handleDeleteBalance = async (balanceId: string) => { if (window.confirm('Are you sure you want to delete this balance? All associated operations will be lost.')) { try { await api.deleteBalance(balanceId); const updatedAssociation = { ...association, balances: association.balances.filter(b => b.id !== balanceId) }; onUpdateAssociation(updatedAssociation); if (selectedBalanceId === balanceId) { setSelectedBalanceId(updatedAssociation.balances[0]?.id ?? null); } } catch (error) { console.error("Failed to delete balance", error); alert("Failed to delete balance"); } } }; const handleUpdateBalance = async (updatedBalance: Balance) => { try { const result = await api.updateBalance(updatedBalance); const updatedAssociation = { ...association, balances: association.balances.map(b => b.id === result.id ? result : b) }; onUpdateAssociation(updatedAssociation); setIsAddBalanceModalOpen(false); setBalanceToEdit(null); } catch (error) { console.error("Failed to update balance", error); alert("Failed to update balance"); } }; const handleDragStart = (index: number) => { setDraggedBalanceIndex(index); }; const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); }; const handleDrop = async (dropIndex: number) => { if (draggedBalanceIndex === null || draggedBalanceIndex === dropIndex) return; const newBalances = [...association.balances]; const [draggedBalance] = newBalances.splice(draggedBalanceIndex, 1); newBalances.splice(dropIndex, 0, draggedBalance); const updatedBalances = newBalances.map((b, index) => ({ ...b, position: index })); onUpdateAssociation({ ...association, balances: updatedBalances }); try { await Promise.all(updatedBalances.map(b => api.updateBalance(b))); } catch (error) { console.error("Failed to update balance positions", error); alert("Failed to save new order"); } setDraggedBalanceIndex(null); }; return (
{/* ... Header ... */}

Balances

{association.balances .sort((a, b) => (a.position - b.position)) .map((balance, index) => (
handleDragStart(index)} onDragOver={handleDragOver} onDrop={() => handleDrop(index)} > op.balanceId === balance.id)} isSelected={selectedBalanceId === balance.id} onClick={() => setSelectedBalanceId(balance.id)} onEdit={handleEditBalance} onDelete={handleDeleteBalance} />
))}

Balances Variation

{selectedBalance && ( <>

Operations for {selectedBalance.name}

)}
{ setIsAddBalanceModalOpen(false); setBalanceToEdit(null); }} onAddBalance={handleAddBalance} onUpdateBalance={handleUpdateBalance} balanceToEdit={balanceToEdit} />
); }; export default Dashboard;