Fix batch expenditure form and add autofocus

Fixed the batch expenditure form to correctly recognize transactions. Added autofocus to the input forms in both batch income and expenditure forms.
This commit is contained in:
gpt-engineer-app[bot]
2025-07-09 19:26:31 +00:00
committed by PhatPhuckDave
parent fb130799a9
commit 73ccee5dd3
3 changed files with 104 additions and 26 deletions

View File

@@ -1,5 +1,4 @@
import { useState } from 'react';
import { useState, useRef, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea';
@@ -28,6 +27,14 @@ const BatchExpenditureForm: React.FC<BatchExpenditureFormProps> = ({ onClose, on
const [pastedData, setPastedData] = useState('');
const [transactionGroups, setTransactionGroups] = useState<TransactionGroup[]>([]);
const [duplicatesFound, setDuplicatesFound] = useState(0);
const textareaRef = useRef<HTMLTextAreaElement>(null);
// Auto focus the textarea when component mounts
useEffect(() => {
if (textareaRef.current) {
textareaRef.current.focus();
}
}, []);
// Filter jobs that are in acquisition status
const eligibleJobs = jobs.filter(job => job.status === IndJobStatusOptions.Acquisition);
@@ -84,19 +91,35 @@ const BatchExpenditureForm: React.FC<BatchExpenditureFormProps> = ({ onClose, on
};
const handlePaste = (value: string) => {
console.log('Handling paste with value:', value);
setPastedData(value);
const lines = value.trim().split('\n');
const lines = value.trim().split('\n').filter(line => line.trim().length > 0);
console.log('Processing lines:', lines);
const pasteTransactionMap = new Map<string, PastedTransaction>();
// STEP 1: Combine identical transactions within the pasted data
lines.forEach((line) => {
lines.forEach((line, index) => {
console.log(`Processing line ${index}:`, line);
const parsed: PastedTransaction | null = parseTransactionLine(line);
if (parsed && parsed.totalPrice < 0) { // Only process expenditures (negative amounts)
// Convert to positive values for expenditures
parsed.totalPrice = Math.abs(parsed.totalPrice);
parsed.unitPrice = Math.abs(parsed.unitPrice);
if (parsed) {
console.log('Parsed transaction:', parsed);
// For expenditures, we expect negative amounts, but handle both cases
const isExpenditure = parsed.totalPrice < 0;
if (isExpenditure) {
// Convert to positive values for expenditures
parsed.totalPrice = Math.abs(parsed.totalPrice);
parsed.unitPrice = Math.abs(parsed.unitPrice);
} else {
// If it's positive, we might still want to treat it as expenditure
// based on context, but let's keep it as is for now
console.log('Transaction has positive amount, treating as expenditure anyway');
}
const transactionKey: string = createTransactionKey(parsed);
console.log('Transaction key:', transactionKey);
if (pasteTransactionMap.has(transactionKey)) {
const existing = pasteTransactionMap.get(transactionKey)!;
@@ -108,9 +131,13 @@ const BatchExpenditureForm: React.FC<BatchExpenditureFormProps> = ({ onClose, on
} else {
pasteTransactionMap.set(transactionKey, parsed);
}
} else {
console.log('Failed to parse line:', line);
}
});
console.log('Parsed transactions map:', pasteTransactionMap);
// STEP 2: Identify which jobs these transactions belong to
const relevantJobIds = new Set<string>();
pasteTransactionMap.forEach((transaction) => {
@@ -147,6 +174,7 @@ const BatchExpenditureForm: React.FC<BatchExpenditureFormProps> = ({ onClose, on
});
const transactionList = Array.from(pasteTransactionMap.values());
console.log('Final transaction list:', transactionList);
setDuplicatesFound(duplicates);
// Create individual transaction groups
@@ -157,6 +185,7 @@ const BatchExpenditureForm: React.FC<BatchExpenditureFormProps> = ({ onClose, on
totalValue: tx.totalPrice
}));
console.log('Transaction groups:', groups);
setTransactionGroups(groups);
};
@@ -216,6 +245,7 @@ const BatchExpenditureForm: React.FC<BatchExpenditureFormProps> = ({ onClose, on
Paste EVE expenditure data (negative amounts):
</label>
<Textarea
ref={textareaRef}
value={pastedData}
onChange={(e) => handlePaste(e.target.value)}
placeholder="Paste your EVE expenditure transaction data here..."

View File

@@ -1,5 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useRef, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea';
@@ -19,6 +18,14 @@ const TransactionForm: React.FC<TransactionFormProps> = ({ jobId, onTransactions
const [pastedData, setPastedData] = useState('');
const [parsedTransactions, setParsedTransactions] = useState<IndTransactionRecordNoId[]>([]);
const [transactionType, setTransactionType] = useState<'expenditure' | 'income'>('expenditure');
const textareaRef = useRef<HTMLTextAreaElement>(null);
// Auto focus the textarea when component mounts or when transaction type changes
useEffect(() => {
if (textareaRef.current) {
textareaRef.current.focus();
}
}, [transactionType]);
const handlePaste = (value: string) => {
setPastedData(value);
@@ -69,6 +76,7 @@ const TransactionForm: React.FC<TransactionFormProps> = ({ jobId, onTransactions
Paste EVE transaction data (Ctrl+V):
</label>
<Textarea
ref={textareaRef}
value={pastedData}
onChange={(e) => handlePaste(e.target.value)}
placeholder="Paste your EVE transaction data here..."

View File

@@ -34,45 +34,85 @@ export type PastedTransaction = IndTransactionRecordNoId & {
export const parseTransactionLine = (line: string): PastedTransaction | null => {
try {
console.log('Parsing transaction line:', line);
const parts = line.split('\t');
if (parts.length < 6) return null;
console.log('Split parts:', parts, 'Length:', parts.length);
let dateStr, quantityStr, itemName, unitPriceStr, totalAmountStr, buyer, location, corporation, wallet;
if (parts.length === 8) {
[dateStr, quantityStr, itemName, unitPriceStr, totalAmountStr, buyer, location, corporation, wallet] = parts;
} else {
[dateStr, quantityStr, itemName, unitPriceStr, totalAmountStr, buyer, location] = parts;
if (parts.length < 6) {
console.log('Not enough parts, skipping line');
return null;
}
let dateStr, quantityStr, itemName, unitPriceStr, totalAmountStr, buyer, location, corporation, wallet;
// Handle both 7 and 8+ column formats
if (parts.length >= 7) {
[dateStr, quantityStr, itemName, unitPriceStr, totalAmountStr, buyer, location] = parts;
if (parts.length >= 8) {
corporation = parts[7];
}
if (parts.length >= 9) {
wallet = parts[8];
}
} else {
console.log('Unexpected number of columns:', parts.length);
return null;
}
console.log('Extracted values:', { dateStr, quantityStr, itemName, unitPriceStr, totalAmountStr, buyer, location });
// Parse date (YYYY.MM.DD HH:mm format)
const [datePart, timePart] = dateStr.split(' ');
if (!datePart || !timePart) {
console.log('Invalid date format:', dateStr);
return null;
}
const [year, month, day] = datePart.split('.').map(Number);
const [hour, minute] = timePart.split(':').map(Number);
if (isNaN(year) || isNaN(month) || isNaN(day) || isNaN(hour) || isNaN(minute)) {
console.log('Invalid date components:', { year, month, day, hour, minute });
return null;
}
const date = new Date(year, month - 1, day, hour, minute);
// Parse quantity (remove commas)
const quantity = parseInt(quantityStr.replace(/,/g, ''));
if (isNaN(quantity)) {
console.log('Invalid quantity:', quantityStr);
return null;
}
// Parse prices
let unitPrice = parseISKAmount(unitPriceStr);
let totalPrice = parseISKAmount(totalAmountStr);
if (totalPrice < 0) {
totalPrice = -totalPrice;
console.log('Parsed prices:', { unitPrice, totalPrice });
if (isNaN(unitPrice) || isNaN(totalPrice)) {
console.log('Invalid price values:', { unitPrice, totalPrice });
return null;
}
return {
// Keep the original sign for expenditures (negative values)
// The batch expenditure form will handle the conversion
const result = {
date: date.toISOString(),
quantity,
itemName,
itemName: itemName.trim(),
unitPrice,
totalPrice,
buyer,
location,
corporation,
wallet
buyer: buyer?.trim() || '',
location: location?.trim() || '',
corporation: corporation?.trim(),
wallet: wallet?.trim()
};
console.log('Successfully parsed transaction:', result);
return result;
} catch (error) {
console.error('Error parsing transaction line:', line, error);
return null;