restore repo
This commit is contained in:
parent
97e8ee6eb8
commit
f2a798ab18
30 changed files with 1627 additions and 2 deletions
205
components/transaction/TransactionAdd.jsx
Normal file
205
components/transaction/TransactionAdd.jsx
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { View, TextInput, StyleSheet, Text, TouchableOpacity } from 'react-native';
|
||||
import * as SQLite from 'expo-sqlite';
|
||||
import { Picker } from '@react-native-picker/picker';
|
||||
import DateTimePicker from '@react-native-community/datetimepicker';
|
||||
|
||||
export default function TransactionAdd({ onTransactionAdded }) {
|
||||
const [description, setDescription] = useState('');
|
||||
const [amount, setAmount] = useState('');
|
||||
const [category, setCategory] = useState(''); // Default category
|
||||
const [transactionType, setTransactionType] = useState('expense'); // Default to expense
|
||||
const [date, setDate] = useState(new Date());
|
||||
const [showDatePicker, setShowDatePicker] = useState(false);
|
||||
const [datePlaceholder, setDatePlaceholder] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
async function setupDatabase() {
|
||||
const db = await SQLite.openDatabaseAsync('moneyAppDB');
|
||||
// await db.execAsync('DROP TABLE IF EXISTS transactions'); // Elimina la tabella esistente
|
||||
await db.execAsync(`
|
||||
CREATE TABLE IF NOT EXISTS transactions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
description TEXT NOT NULL,
|
||||
amount REAL NOT NULL,
|
||||
category TEXT NOT NULL,
|
||||
date TEXT NOT NULL,
|
||||
type TEXT NOT NULL
|
||||
)
|
||||
`);
|
||||
}
|
||||
setupDatabase();
|
||||
|
||||
const currentDate = new Date();
|
||||
const formattedDate = `${currentDate.getDate().toString().padStart(2, '0')}-${(currentDate.getMonth() + 1).toString().padStart(2, '0')}-${currentDate.getFullYear()}`;
|
||||
setDatePlaceholder(formattedDate);
|
||||
}, []);
|
||||
|
||||
const addTransaction = async () => {
|
||||
if (description.trim() === '' || amount.trim() === '') {
|
||||
alert('Descrizione e/o Importo mancanti');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate amount format
|
||||
const amountRegex = /^\d+(\.\d{1,2})?$/;
|
||||
if (!amountRegex.test(amount)) {
|
||||
alert('Formato importo non valido. Usa il formato 0.00');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get selected date in DD-MM-YYYY format
|
||||
const formattedDate = `${date.getDate().toString().padStart(2, '0')}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getFullYear()}`;
|
||||
|
||||
try {
|
||||
const db = await SQLite.openDatabaseAsync('moneyAppDB');
|
||||
await db.runAsync(
|
||||
'INSERT INTO transactions (description, amount, category, date, type) VALUES (?, ?, ?, ?, ?)',
|
||||
[description, parseFloat(amount), category, formattedDate, transactionType]
|
||||
);
|
||||
alert('Transazione aggiunta con successo.');
|
||||
setDescription('');
|
||||
setAmount('');
|
||||
setCategory(''); // Reset to default category
|
||||
setTransactionType('expense'); // Reset to default type
|
||||
if (onTransactionAdded) {
|
||||
onTransactionAdded();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error adding transaction:', error);
|
||||
alert('Errore. Inserisci correttamente i dati.');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.testo}>Descrizione:</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
value={description}
|
||||
onChangeText={setDescription}
|
||||
/>
|
||||
<Text style={styles.testo}>Importo:</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
value={amount}
|
||||
onChangeText={setAmount}
|
||||
keyboardType="numeric"
|
||||
/>
|
||||
<Text style={styles.testo}>Categoria:</Text>
|
||||
|
||||
<View style={{borderColor: 'gray', borderWidth: 1, marginBottom: 20}}>
|
||||
<Picker
|
||||
selectedValue={category}
|
||||
style={styles.picker}
|
||||
onValueChange={(itemValue) => setCategory(itemValue)}
|
||||
>
|
||||
<Picker.Item label="Spesa" value="spesa" />
|
||||
<Picker.Item label="Svago" value="svago" />
|
||||
<Picker.Item label="Trasporti" value="trasporti" />
|
||||
<Picker.Item label="Cibo" value="cibo" />
|
||||
<Picker.Item label="Varie" value="varie" />
|
||||
</Picker>
|
||||
</View>
|
||||
|
||||
<Text style={styles.testo}>Data:</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
value={datePlaceholder}
|
||||
onFocus={() => setShowDatePicker(true)}
|
||||
/>
|
||||
{showDatePicker && (
|
||||
<DateTimePicker
|
||||
value={date}
|
||||
mode="date"
|
||||
display="default"
|
||||
onChange={(event, selectedDate) => {
|
||||
const currentDate = selectedDate || date;
|
||||
setShowDatePicker(false);
|
||||
setDate(currentDate);
|
||||
const formattedDate = `${currentDate.getDate().toString().padStart(2, '0')}-${(currentDate.getMonth() + 1).toString().padStart(2, '0')}-${currentDate.getFullYear()}`;
|
||||
setDatePlaceholder(formattedDate);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<View style={{marginTop: 10}}>
|
||||
<Text style={styles.testo}>Tipo di Transazione:</Text>
|
||||
</View>
|
||||
<View style={styles.buttonContainer}>
|
||||
<TouchableOpacity
|
||||
style={[styles.button, transactionType === 'income' && styles.selectedButton]}
|
||||
onPress={() => setTransactionType('income')}
|
||||
>
|
||||
<Text style={styles.buttonText}>Entrata (+)</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[styles.button, transactionType === 'expense' && styles.selectedButton]}
|
||||
onPress={() => setTransactionType('expense')}
|
||||
>
|
||||
<Text style={styles.buttonText}>Uscita (-)</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<TouchableOpacity style={styles.addButton} onPress={addTransaction}>
|
||||
<Text style={styles.addButtonText}>Aggiungi</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
padding: 20,
|
||||
},
|
||||
input: {
|
||||
color: '#fff',
|
||||
height: 50,
|
||||
borderColor: 'gray',
|
||||
borderWidth: 1,
|
||||
marginBottom: 10,
|
||||
paddingHorizontal: 15,
|
||||
fontSize: 16
|
||||
},
|
||||
picker: {
|
||||
color: '#fff',
|
||||
borderColor: 'gray',
|
||||
borderWidth: 2,
|
||||
},
|
||||
testo: {
|
||||
color: '#fff',
|
||||
fontSize: 16,
|
||||
fontWeight: '700',
|
||||
paddingBottom: 10,
|
||||
},
|
||||
buttonContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 20,
|
||||
},
|
||||
button: {
|
||||
flex: 1,
|
||||
padding: 15,
|
||||
backgroundColor: '#3c3c3c',
|
||||
borderRadius: 5,
|
||||
alignItems: 'center',
|
||||
marginHorizontal: 3,
|
||||
},
|
||||
selectedButton: {
|
||||
backgroundColor: '#f57242',
|
||||
},
|
||||
buttonText: {
|
||||
color: '#fff',
|
||||
fontSize: 16,
|
||||
},
|
||||
addButton: {
|
||||
backgroundColor: '#fff',
|
||||
padding: 15, // Aumenta l'altezza del pulsante
|
||||
borderRadius: 5,
|
||||
alignItems: 'center',
|
||||
},
|
||||
addButtonText: {
|
||||
color: '#000',
|
||||
fontSize: 16,
|
||||
},
|
||||
});
|
||||
341
components/transaction/TransactionItem.jsx
Normal file
341
components/transaction/TransactionItem.jsx
Normal file
|
|
@ -0,0 +1,341 @@
|
|||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { View, Text, FlatList, StyleSheet, Modal, TouchableOpacity, TextInput, Alert } from 'react-native';
|
||||
import * as SQLite from 'expo-sqlite';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import { Picker } from '@react-native-picker/picker';
|
||||
import { format } from 'date-fns';
|
||||
import { it } from 'date-fns/locale';
|
||||
import DateTimePicker from '@react-native-community/datetimepicker';
|
||||
|
||||
export default function TransactionItem() {
|
||||
const [transactions, setTransactions] = useState([]);
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [selectedTransaction, setSelectedTransaction] = useState(null);
|
||||
const [editDescription, setEditDescription] = useState('');
|
||||
const [editAmount, setEditAmount] = useState('');
|
||||
const [editCategory, setEditCategory] = useState('');
|
||||
const [editDate, setEditDate] = useState(new Date());
|
||||
const [editType, setEditType] = useState('expense');
|
||||
const [showDatePicker, setShowDatePicker] = useState(false);
|
||||
|
||||
const loadTransactions = useCallback(async () => {
|
||||
try {
|
||||
const db = await SQLite.openDatabaseAsync('moneyAppDB');
|
||||
const result = await db.getAllAsync('SELECT * FROM transactions ORDER BY date DESC, id DESC');
|
||||
setTransactions(result);
|
||||
} catch (error) {
|
||||
console.error('Error fetching transactions:', error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const sortedTransactions = transactions.sort((a, b) => {
|
||||
const dateA = new Date(a.date.split('-').reverse().join('-'));
|
||||
const dateB = new Date(b.date.split('-').reverse().join('-'));
|
||||
return dateB - dateA;
|
||||
});
|
||||
|
||||
const groupedTransactions = sortedTransactions.reduce((acc, transaction) => {
|
||||
const date = new Date(transaction.date.split('-').reverse().join('-'));
|
||||
const formattedDate = format(date, 'dd MMMM yyyy', { locale: it });
|
||||
if (!acc[formattedDate]) {
|
||||
acc[formattedDate] = [];
|
||||
}
|
||||
acc[formattedDate].push(transaction);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
loadTransactions();
|
||||
}, [loadTransactions]);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
loadTransactions();
|
||||
}, [loadTransactions])
|
||||
);
|
||||
|
||||
const openModal = (transaction) => {
|
||||
setSelectedTransaction(transaction);
|
||||
setEditDescription(transaction.description);
|
||||
setEditAmount(transaction.amount.toString());
|
||||
setEditCategory(transaction.category);
|
||||
setEditDate(new Date(transaction.date.split('-').reverse().join('-')));
|
||||
setEditType(transaction.type);
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
const updateTransaction = async () => {
|
||||
try {
|
||||
const db = await SQLite.openDatabaseAsync('moneyAppDB');
|
||||
await db.runAsync(
|
||||
'UPDATE transactions SET description = ?, amount = ?, category = ?, date = ?, type = ? WHERE id = ?',
|
||||
[editDescription, parseFloat(editAmount), editCategory, format(editDate, 'dd-MM-yyyy'), editType, selectedTransaction.id]
|
||||
);
|
||||
setModalVisible(false);
|
||||
loadTransactions();
|
||||
Alert.alert('Successo', 'Transazione aggiornata con successo');
|
||||
} catch (error) {
|
||||
console.error('Error updating transaction:', error);
|
||||
Alert.alert('Errore', 'Impossibile aggiornare la transazione');
|
||||
}
|
||||
};
|
||||
|
||||
const deleteTransaction = async () => {
|
||||
Alert.alert(
|
||||
'Conferma eliminazione',
|
||||
'Sei sicuro di voler eliminare questa transazione?',
|
||||
[
|
||||
{ text: 'Annulla', style: 'cancel' },
|
||||
{
|
||||
text: 'Elimina',
|
||||
style: 'destructive',
|
||||
onPress: async () => {
|
||||
try {
|
||||
const db = await SQLite.openDatabaseAsync('moneyAppDB');
|
||||
await db.runAsync('DELETE FROM transactions WHERE id = ?', [selectedTransaction.id]);
|
||||
setModalVisible(false);
|
||||
loadTransactions();
|
||||
Alert.alert('Successo', 'Transazione eliminata con successo');
|
||||
} catch (error) {
|
||||
console.error('Error deleting transaction:', error);
|
||||
Alert.alert('Errore', 'Impossibile eliminare la transazione');
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
const [day, month, year] = dateString.split('-');
|
||||
const date = new Date(`${year}-${month}-${day}`);
|
||||
return format(date, 'dd MMMM yyyy', { locale: it });
|
||||
};
|
||||
|
||||
const sortedGroupedTransactions = Object.keys(groupedTransactions)
|
||||
.sort((a, b) => new Date(b.split(' ').reverse().join(' ')) - new Date(a.split(' ').reverse().join(' ')))
|
||||
.reduce((acc, key) => {
|
||||
acc[key] = groupedTransactions[key];
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<TouchableOpacity onPress={() => openModal(item)} style={styles.item}>
|
||||
<View>
|
||||
<Text style={styles.description}>{item.description}</Text>
|
||||
<Text style={styles.date}>{item.date}</Text>
|
||||
</View>
|
||||
<Text style={[styles.amount, { color: item.type === 'expense' ? 'red' : 'green' }]}>
|
||||
{item.type === 'expense' ? '-' : '+'}{Math.abs(item.amount).toFixed(2)} €
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
return (
|
||||
<><FlatList
|
||||
data={Object.keys(groupedTransactions)}
|
||||
renderItem={({ item: date }) => (
|
||||
<View key={date}>
|
||||
<Text style={styles.dateHeader}>{date}</Text>
|
||||
<FlatList
|
||||
data={groupedTransactions[date]}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={item => item.id.toString()} />
|
||||
</View>
|
||||
)}
|
||||
keyExtractor={item => item} /><Modal
|
||||
animationType="slide"
|
||||
transparent={true}
|
||||
visible={modalVisible}
|
||||
onRequestClose={() => setModalVisible(false)}
|
||||
>
|
||||
<View style={styles.centeredView}>
|
||||
<View style={styles.modalView}>
|
||||
<Text style={styles.modalTitle}>Dettagli Transazione</Text>
|
||||
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
value={editDescription}
|
||||
onChangeText={setEditDescription}
|
||||
placeholder="Descrizione" />
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
value={editAmount}
|
||||
onChangeText={setEditAmount}
|
||||
placeholder="Importo"
|
||||
placeholderTextColor="#fff"
|
||||
keyboardType="decimal-pad" />
|
||||
<View style={{ borderColor: 'gray', borderWidth: 1, marginBottom: 10 }}>
|
||||
<Picker
|
||||
selectedValue={editCategory}
|
||||
style={styles.picker}
|
||||
onValueChange={(itemValue) => setEditCategory(itemValue)}
|
||||
>
|
||||
<Picker.Item label="Spesa" value="spesa" />
|
||||
<Picker.Item label="Svago" value="svago" />
|
||||
<Picker.Item label="Trasporti" value="trasporti" />
|
||||
<Picker.Item label="Cibo" value="cibo" />
|
||||
<Picker.Item label="Varie" value="varie" />
|
||||
</Picker>
|
||||
</View>
|
||||
<View style={{ borderColor: 'gray', borderWidth: 1, marginBottom: 10 }}>
|
||||
<Picker
|
||||
selectedValue={editType}
|
||||
style={styles.picker}
|
||||
onValueChange={(itemValue) => setEditType(itemValue)}
|
||||
>
|
||||
<Picker.Item label="Entrata" value="income" />
|
||||
<Picker.Item label="Uscita" value="expense" />
|
||||
</Picker>
|
||||
</View>
|
||||
<TouchableOpacity onPress={() => setShowDatePicker(true)}>
|
||||
<Text style={styles.input}>{format(editDate, 'dd-MM-yyyy')}</Text>
|
||||
</TouchableOpacity>
|
||||
{showDatePicker && (
|
||||
<DateTimePicker
|
||||
value={editDate}
|
||||
mode="date"
|
||||
display="default"
|
||||
onChange={(event, selectedDate) => {
|
||||
const currentDate = selectedDate || editDate;
|
||||
setShowDatePicker(false);
|
||||
setEditDate(currentDate);
|
||||
} } />
|
||||
)}
|
||||
|
||||
<View style={styles.buttonContainer}>
|
||||
<TouchableOpacity style={styles.button} onPress={updateTransaction}>
|
||||
<Text style={styles.buttonText}>Aggiorna</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={styles.button} onPress={deleteTransaction}>
|
||||
<Text style={styles.buttonText}>Elimina</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={styles.button} onPress={() => setModalVisible(false)}>
|
||||
<Text style={styles.buttonText}>Chiudi</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Modal></>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
paddingHorizontal: 10,
|
||||
marginBottom: 20,
|
||||
},
|
||||
title: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 10,
|
||||
color: '',
|
||||
},
|
||||
item: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: 10,
|
||||
marginVertical: 5,
|
||||
borderRadius: 10,
|
||||
backgroundColor: '#1a1a1a',
|
||||
},
|
||||
description: {
|
||||
color: '#fff',
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
amount: {
|
||||
color: 'red',
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
date: {
|
||||
color: '#fff',
|
||||
fontSize: 14,
|
||||
},
|
||||
centeredView: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
modalView: {
|
||||
margin: 20,
|
||||
backgroundColor: '#000',
|
||||
borderRadius: 20,
|
||||
padding: 35,
|
||||
alignItems: 'center',
|
||||
shadowColor: '#000',
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 2
|
||||
},
|
||||
shadowOpacity: 0.25,
|
||||
shadowRadius: 4,
|
||||
elevation: 5,
|
||||
},
|
||||
modalTitle: {
|
||||
color: '#fff',
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 15,
|
||||
},
|
||||
input: {
|
||||
height: 60,
|
||||
width: 302,
|
||||
borderColor: 'gray',
|
||||
borderWidth: 1,
|
||||
marginBottom: 10,
|
||||
paddingHorizontal: 15,
|
||||
paddingVertical: 15,
|
||||
color: '#fff',
|
||||
fontSize: 16,
|
||||
backgroundColor: '#1a1a1a'
|
||||
},
|
||||
picker: {
|
||||
height: 50,
|
||||
width: 300,
|
||||
color: '#fff',
|
||||
borderColor: 'gray',
|
||||
borderWidth: 1,
|
||||
backgroundColor: '#1a1a1a'
|
||||
},
|
||||
buttonContainer: {
|
||||
paddingTop: 20,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
width: '100%',
|
||||
},
|
||||
button: {
|
||||
backgroundColor: '#2196F3',
|
||||
borderRadius: 20,
|
||||
padding: 10,
|
||||
elevation: 2,
|
||||
marginTop: 10,
|
||||
minWidth: 100,
|
||||
},
|
||||
deleteButton: {
|
||||
backgroundColor: '#FF0000',
|
||||
},
|
||||
closeButton: {
|
||||
marginTop: 20,
|
||||
},
|
||||
buttonText: {
|
||||
color: '#fff',
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
},
|
||||
date: {
|
||||
color: '#8c8c8c',
|
||||
fontSize: 14,
|
||||
},
|
||||
dateHeader: {
|
||||
fontSize: 16,
|
||||
color: '#fff',
|
||||
marginTop: 20,
|
||||
marginBottom: 10,
|
||||
marginLeft: 5,
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue