diff --git a/src/components/TransactionTable.tsx b/src/components/TransactionTable.tsx new file mode 100644 index 0000000..2c4f4fc --- /dev/null +++ b/src/components/TransactionTable.tsx @@ -0,0 +1,242 @@ + +import React, { useState } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; +import { Badge } from '@/components/ui/badge'; +import { Input } from '@/components/ui/input'; +import { Transaction } from '@/services/jobService'; +import { formatISK } from '@/utils/priceUtils'; +import { Edit, Save, X, Trash2 } from 'lucide-react'; + +interface TransactionTableProps { + title: string; + transactions: Transaction[]; + type: 'expenditure' | 'income'; + onUpdateTransaction: (transactionId: string, updates: Partial) => void; + onDeleteTransaction: (transactionId: string) => void; +} + +const TransactionTable: React.FC = ({ + title, + transactions, + type, + onUpdateTransaction, + onDeleteTransaction +}) => { + const [editingId, setEditingId] = useState(null); + const [editingTransaction, setEditingTransaction] = useState(null); + + const totalAmount = transactions.reduce((sum, tx) => sum + Math.abs(tx.totalAmount), 0); + + const handleEdit = (transaction: Transaction) => { + setEditingId(transaction.id); + setEditingTransaction({ ...transaction }); + }; + + const handleSave = () => { + if (editingTransaction && editingId) { + onUpdateTransaction(editingId, editingTransaction); + setEditingId(null); + setEditingTransaction(null); + } + }; + + const handleCancel = () => { + setEditingId(null); + setEditingTransaction(null); + }; + + const handleDelete = (transactionId: string) => { + if (confirm('Are you sure you want to delete this transaction?')) { + onDeleteTransaction(transactionId); + } + }; + + const updateEditingField = (field: keyof Transaction, value: any) => { + if (editingTransaction) { + setEditingTransaction({ + ...editingTransaction, + [field]: value + }); + } + }; + + return ( + + +
+ {title} + + Total: {formatISK(totalAmount)} + +
+
+ + {transactions.length === 0 ? ( +

No transactions yet

+ ) : ( +
+ + + + Date + Item + Qty + Unit Price + Total + Buyer/Seller + Location + Actions + + + + {transactions.map((transaction) => ( + + + {editingId === transaction.id ? ( + updateEditingField('date', new Date(e.target.value))} + className="bg-gray-800 border-gray-600 text-white text-xs" + /> + ) : ( + transaction.date.toLocaleString('sv-SE', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + }).replace(' ', ' ') + )} + + + {editingId === transaction.id ? ( + updateEditingField('itemName', e.target.value)} + className="bg-gray-800 border-gray-600 text-white" + /> + ) : ( + transaction.itemName + )} + + + {editingId === transaction.id ? ( + updateEditingField('quantity', parseInt(e.target.value) || 0)} + className="bg-gray-800 border-gray-600 text-white" + /> + ) : ( + transaction.quantity.toLocaleString() + )} + + + {editingId === transaction.id ? ( + updateEditingField('unitPrice', parseFloat(e.target.value) || 0)} + className="bg-gray-800 border-gray-600 text-white" + /> + ) : ( + formatISK(transaction.unitPrice) + )} + + + {editingId === transaction.id ? ( + { + const value = parseFloat(e.target.value) || 0; + updateEditingField('totalAmount', type === 'expenditure' ? -Math.abs(value) : Math.abs(value)); + }} + className="bg-gray-800 border-gray-600 text-white" + /> + ) : ( + formatISK(Math.abs(transaction.totalAmount)) + )} + + + {editingId === transaction.id ? ( + updateEditingField('buyer', e.target.value)} + className="bg-gray-800 border-gray-600 text-white" + /> + ) : ( + transaction.buyer || '-' + )} + + + {editingId === transaction.id ? ( + updateEditingField('location', e.target.value)} + className="bg-gray-800 border-gray-600 text-white" + /> + ) : ( + {transaction.location || '-'} + )} + + +
+ {editingId === transaction.id ? ( + <> + + + + ) : ( + <> + + + + )} +
+
+
+ ))} +
+
+
+ )} +
+
+ ); +}; + +export default TransactionTable; diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index b5a0946..2794b30 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -1,4 +1,3 @@ - import React, { useState, useEffect } from 'react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; @@ -8,6 +7,7 @@ import { formatISK } from '@/utils/priceUtils'; import JobCard from '@/components/JobCard'; import JobForm from '@/components/JobForm'; import TransactionForm from '@/components/TransactionForm'; +import TransactionTable from '@/components/TransactionTable'; const Index = () => { const [jobs, setJobs] = useState([]); @@ -93,6 +93,46 @@ const Index = () => { } }; + const handleUpdateTransaction = async (transactionId: string, updates: Partial) => { + if (!selectedJob) return; + + try { + await jobService.updateTransaction(selectedJob.id, transactionId, updates); + + // Update local state + const updatedJob = { ...selectedJob }; + updatedJob.expenditures = updatedJob.expenditures.map(tx => + tx.id === transactionId ? { ...tx, ...updates } : tx + ); + updatedJob.income = updatedJob.income.map(tx => + tx.id === transactionId ? { ...tx, ...updates } : tx + ); + + setSelectedJob(updatedJob); + setJobs(jobs.map(job => job.id === selectedJob.id ? updatedJob : job)); + } catch (error) { + console.error('Error updating transaction:', error); + } + }; + + const handleDeleteTransaction = async (transactionId: string) => { + if (!selectedJob) return; + + try { + await jobService.deleteTransaction(selectedJob.id, transactionId); + + // Update local state + const updatedJob = { ...selectedJob }; + updatedJob.expenditures = updatedJob.expenditures.filter(tx => tx.id !== transactionId); + updatedJob.income = updatedJob.income.filter(tx => tx.id !== transactionId); + + setSelectedJob(updatedJob); + setJobs(jobs.map(job => job.id === selectedJob.id ? updatedJob : job)); + } catch (error) { + console.error('Error deleting transaction:', error); + } + }; + const totalJobs = jobs.length; const totalProfit = jobs.reduce((sum, job) => { const expenditure = job.expenditures.reduce((sum, tx) => sum + Math.abs(tx.totalAmount), 0); @@ -124,7 +164,7 @@ const Index = () => { if (selectedJob) { return (
-
+

Job Details

@@ -150,6 +190,23 @@ const Index = () => { onTransactionsAdded={handleTransactionsAdded} />
+ +
+ + +
); diff --git a/src/services/jobService.ts b/src/services/jobService.ts index c6e9b77..21121bb 100644 --- a/src/services/jobService.ts +++ b/src/services/jobService.ts @@ -77,5 +77,15 @@ export const jobService = { async addTransaction(jobId: string, transaction: Omit, type: 'expenditure' | 'income'): Promise { // TODO: Implement with PocketBase console.log('Adding transaction:', jobId, transaction, type); + }, + + async updateTransaction(jobId: string, transactionId: string, updates: Partial): Promise { + // TODO: Implement with PocketBase + console.log('Updating transaction:', jobId, transactionId, updates); + }, + + async deleteTransaction(jobId: string, transactionId: string): Promise { + // TODO: Implement with PocketBase + console.log('Deleting transaction:', jobId, transactionId); } };