restore repo

This commit is contained in:
PanSi21 2024-11-19 02:04:30 +01:00
parent 97e8ee6eb8
commit f2a798ab18
Signed by untrusted user who does not match committer: PanSi21
GPG key ID: 755F8874C65EF462
30 changed files with 1627 additions and 2 deletions

View file

@ -0,0 +1,77 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import { ExternalLink } from './ExternalLink';
import { MonoText } from './StyledText';
import { Text, View } from './Themed';
import Colors from '@/constants/Colors';
export default function EditScreenInfo({ path }: { path: string }) {
return (
<View>
<View style={styles.getStartedContainer}>
<Text
style={styles.getStartedText}
lightColor="rgba(0,0,0,0.8)"
darkColor="rgba(255,255,255,0.8)">
Open up the code for this screen:
</Text>
<View
style={[styles.codeHighlightContainer, styles.homeScreenFilename]}
darkColor="rgba(255,255,255,0.05)"
lightColor="rgba(0,0,0,0.05)">
<MonoText>{path}</MonoText>
</View>
<Text
style={styles.getStartedText}
lightColor="rgba(0,0,0,0.8)"
darkColor="rgba(255,255,255,0.8)">
Change any of the text, save the file, and your app will automatically update.
</Text>
</View>
<View style={styles.helpContainer}>
<ExternalLink
style={styles.helpLink}
href="https://docs.expo.io/get-started/create-a-new-app/#opening-the-app-on-your-phonetablet">
<Text style={styles.helpLinkText} lightColor={Colors.light.tint}>
Tap here if your app doesn't automatically update after making changes
</Text>
</ExternalLink>
</View>
</View>
);
}
const styles = StyleSheet.create({
getStartedContainer: {
alignItems: 'center',
marginHorizontal: 50,
},
homeScreenFilename: {
marginVertical: 7,
},
codeHighlightContainer: {
borderRadius: 3,
paddingHorizontal: 4,
},
getStartedText: {
fontSize: 17,
lineHeight: 24,
textAlign: 'center',
},
helpContainer: {
marginTop: 15,
marginHorizontal: 20,
alignItems: 'center',
},
helpLink: {
paddingVertical: 15,
},
helpLinkText: {
textAlign: 'center',
},
});

View file

@ -0,0 +1,25 @@
import { Link } from 'expo-router';
import * as WebBrowser from 'expo-web-browser';
import React from 'react';
import { Platform } from 'react-native';
export function ExternalLink(
props: Omit<React.ComponentProps<typeof Link>, 'href'> & { href: string }
) {
return (
<Link
target="_blank"
{...props}
// @ts-expect-error: External URLs are not typed.
href={props.href}
onPress={(e) => {
if (Platform.OS !== 'web') {
// Prevent the default behavior of linking to the default browser on native.
e.preventDefault();
// Open the link in an in-app browser.
WebBrowser.openBrowserAsync(props.href as string);
}
}}
/>
);
}

View file

@ -0,0 +1,5 @@
import { Text, TextProps } from './Themed';
export function MonoText(props: TextProps) {
return <Text {...props} style={[props.style, { fontFamily: 'SpaceMono' }]} />;
}

45
components/Themed.tsx Normal file
View file

@ -0,0 +1,45 @@
/**
* Learn more about Light and Dark modes:
* https://docs.expo.io/guides/color-schemes/
*/
import { Text as DefaultText, View as DefaultView } from 'react-native';
import Colors from '@/constants/Colors';
import { useColorScheme } from './useColorScheme';
type ThemeProps = {
lightColor?: string;
darkColor?: string;
};
export type TextProps = ThemeProps & DefaultText['props'];
export type ViewProps = ThemeProps & DefaultView['props'];
export function useThemeColor(
props: { light?: string; dark?: string },
colorName: keyof typeof Colors.light & keyof typeof Colors.dark
) {
const theme = useColorScheme() ?? 'light';
const colorFromProps = props[theme];
if (colorFromProps) {
return colorFromProps;
} else {
return Colors[theme][colorName];
}
}
export function Text(props: TextProps) {
const { style, lightColor, darkColor, ...otherProps } = props;
const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text');
return <DefaultText style={[{ color }, style]} {...otherProps} />;
}
export function View(props: ViewProps) {
const { style, lightColor, darkColor, ...otherProps } = props;
const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background');
return <DefaultView style={[{ backgroundColor }, style]} {...otherProps} />;
}

View file

@ -0,0 +1,10 @@
import * as React from 'react';
import renderer from 'react-test-renderer';
import { MonoText } from '../StyledText';
it(`renders correctly`, () => {
const tree = renderer.create(<MonoText>Snapshot test!</MonoText>).toJSON();
expect(tree).toMatchSnapshot();
});

View 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,
},
});

View 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,
},
});

112
components/ui/Grafico.tsx Normal file
View file

@ -0,0 +1,112 @@
import React, { useState } from 'react';
import { View, Text, Dimensions, Pressable, StyleSheet, ScrollView } from "react-native";
import { LineChart } from 'react-native-chart-kit';
const screenWidth = Dimensions.get("window").width;
const Grafico = () => {
const [timeFrame, setTimeFrame] = useState("1D");
const data = {
labels: ["Lun", "Mar", "Mer", "Gio", "Ven", "Sab", "Dom"],
datasets: [
{
data: [20, 45, 28, 80, 70, 43, 100, 100],
color: (opacity = 1) => `rgba(134, 65, 244, ${opacity})`,
strokeWidth: 2
}
],
legend: ["Money"]
};
const handleTimeFrameChange = (newTimeFrame: React.SetStateAction<string>) => {
setTimeFrame(newTimeFrame);
};
return (
<View style={styles.container}>
{/* Contenitore con bordo per il grafico */}
<View style={styles.chartContainer}>
<LineChart
data={data}
width={screenWidth - 40}
height={180}
verticalLabelRotation={0}
chartConfig={chartConfig}
bezier
/>
</View>
{/* ScrollView per bottoni */}
<ScrollView horizontal contentContainerStyle={styles.scrollContainer}>
<View style={styles.buttonRow}>
<Pressable onPress={() => handleTimeFrameChange("1D")}>
<Text style={[styles.buttonText, timeFrame === "1D" && styles.activeButton]}>1D</Text>
</Pressable>
<Pressable onPress={() => handleTimeFrameChange("1W")}>
<Text style={[styles.buttonText, timeFrame === "1W" && styles.activeButton]}>1W</Text>
</Pressable>
<Pressable onPress={() => handleTimeFrameChange("1M")}>
<Text style={[styles.buttonText, timeFrame === "1M" && styles.activeButton]}>1M</Text>
</Pressable>
<Pressable onPress={() => handleTimeFrameChange("July")}>
<Text style={[styles.buttonText, timeFrame === "July" && styles.activeButton]}>July</Text>
</Pressable>
<Pressable onPress={() => handleTimeFrameChange("June")}>
<Text style={[styles.buttonText, timeFrame === "June" && styles.activeButton]}>June</Text>
</Pressable>
<Pressable onPress={() => handleTimeFrameChange("May")}>
<Text style={[styles.buttonText, timeFrame === "May" && styles.activeButton]}>May</Text>
</Pressable>
<Pressable onPress={() => handleTimeFrameChange("All")}>
<Text style={[styles.buttonText, timeFrame === "All" && styles.activeButton]}>All</Text>
</Pressable>
</View>
</ScrollView>
</View>
);
};
const chartConfig = {
color: (opacity = 1) => `rgba(255, 255, 255, ${opacity})`,
strokeWidth: 1,
barPercentage: 0.5,
useShadowColorFromDataset: false
};
const styles = StyleSheet.create({
container: {
padding: 20,
},
chartContainer: {
borderWidth: 2,
borderColor: 'black',
borderRadius: 10,
padding: 10,
marginBottom: 20,
alignItems: 'center',
},
scrollContainer: {
paddingVertical: 10,
alignItems: 'center',
},
buttonRow: {
flexDirection: 'row',
justifyContent: 'space-around',
},
buttonText: {
fontSize: 16,
color: 'white', // Cambia il colore del testo per contrasto
backgroundColor: '#1a1a1a', // Aggiungi un background color per i bottoni
padding: 10, // Rendi il bottone più compatto
marginHorizontal: 5, // Spaziatura laterale più piccola
borderRadius: 5, // Arrotonda i bordi
},
activeButton: {
color: 'purple',
fontWeight: 'bold',
backgroundColor: 'lightgray', // Cambia il colore di sfondo per il bottone attivo
}
});
export default Grafico;

View file

@ -0,0 +1,324 @@
import React, { useState, useEffect } from 'react';
import { View, StyleSheet, Dimensions, Text, FlatList } from 'react-native';
import * as SQLite from 'expo-sqlite';
import { LineChart } from 'react-native-chart-kit';
import { Modal, TouchableOpacity } from 'react-native';
import moment from 'moment';
import 'moment/locale/it'; // Import Italian locale for moment
const VisualizzaDati = () => {
const [data, setData] = useState([]);
const [selectedTransactions, setSelectedTransactions] = useState([]);
const [modalVisible, setModalVisible] = useState(false);
const [selectedDate, setSelectedDate] = useState('');
const [currentWeek, setCurrentWeek] = useState(moment().startOf('week'));
useEffect(() => {
const interval = setInterval(() => {
fetchData();
}, 1000); // Aggiorna ogni 1 secondi
return () => clearInterval(interval); // Pulisce l'intervallo quando il componente viene smontato
}, []);
async function fetchData() {
try {
const db = await SQLite.openDatabaseAsync('moneyAppDB');
const result = await db.getAllAsync('SELECT * FROM transactions');
const fetchedData = [];
for (const row of result) {
fetchedData.push(row);
}
// Raggruppa i dati per data e calcola la somma degli importi per il grafico
const groupedData = fetchedData.reduce((acc, curr) => {
const date = curr.date;
if (!acc[date]) {
acc[date] = 0;
}
acc[date] += curr.amount;
return acc;
}, {});
// Trasforma l'oggetto raggruppato in un array di oggetti e ordina per data
const chartDataArray = Object.keys(groupedData)
.map(date => ({
date,
amount: groupedData[date],
}))
.sort((a, b) => new Date(a.date.split('-').reverse().join('-')) - new Date(b.date.split('-').reverse().join('-')));
setData(chartDataArray);
} catch (error) {
console.error('Error fetching data:', error);
}
}
const getWeekData = () => {
const startOfWeek = currentWeek.clone().startOf('week');
const endOfWeek = currentWeek.clone().endOf('week');
const weekDates = [];
for (let i = 0; i < 7; i++) {
weekDates.push(startOfWeek.clone().add(i, 'days').format('DD-MM-YYYY'));
}
const weekData = weekDates.map(date => {
const item = data.find(d => d.date === date);
return {
date,
amount: item ? item.amount : 0,
};
});
return weekData;
};
const getCurrentWeekText = () => {
moment.locale('it'); // Set moment locale to Italian
const startOfWeek = currentWeek.clone().startOf('week').format('DD MMMM');
const endOfWeek = currentWeek.clone().endOf('week').format('DD MMMM');
return `${startOfWeek} al ${endOfWeek}`;
};
const chartData = {
labels: getWeekData().map(item => item.date),
datasets: [
{
data: getWeekData().map(item => item.amount),
},
],
};
const handleDataPointClick = async (dataPoint) => {
const date = chartData.labels[dataPoint.index];
setSelectedDate(date);
try {
const db = await SQLite.openDatabaseAsync('moneyAppDB');
const result = await db.getAllAsync(`SELECT * FROM transactions WHERE date = ?`, [date]);
setSelectedTransactions(result);
setModalVisible(true);
} catch (error) {
console.error('Error fetching transactions for selected date:', error);
}
};
const handlePrevWeek = () => {
setCurrentWeek(currentWeek.clone().subtract(1, 'week'));
};
const handleNextWeek = () => {
setCurrentWeek(currentWeek.clone().add(1, 'week'));
};
const getTotalForWeek = () => {
const weekData = getWeekData();
const total = weekData.reduce((acc, curr) => acc + curr.amount, 0);
return total;
};
return (
<FlatList
data={[{ key: 'content' }]}
renderItem={() => (
<View style={styles.container}>
{data.length === 0 ? (
<Text style={styles.noDataText}>Nessun dato disponibile</Text>
) : (
<>
<Text style={styles.weekText}>{getCurrentWeekText()}</Text>
<Text style={[styles.totalText, { color: getTotalForWeek() >= 0 ? 'green' : 'red' }]}>
Totale: {getTotalForWeek().toFixed(2)}
</Text>
<LineChart
bezier
data={chartData}
width={Dimensions.get('window').width - 20} // from react-native
height={300}
verticalLabelRotation={30}
yAxisLabel="€"
chartConfig={{
backgroundColor: '#fff',
backgroundGradientFrom: '#000',
backgroundGradientTo: '#000',
decimalPlaces: 2, // optional, defaults to 2dp
color: (opacity = 1) => `#f57242`,
labelColor: (opacity = 1) => `rgba(255, 255, 255, ${opacity})`,
style: {
borderRadius: 10,
},
propsForDots: {
r: '6',
strokeWidth: '3',
stroke: '#3956e6',
},
}}
style={{
borderRadius: 15,
}}
onDataPointClick={handleDataPointClick}
/>
<View style={styles.buttonContainer}>
<TouchableOpacity
style={styles.navButton}
onPress={handlePrevWeek}
>
<Text style={styles.navButtonText}>Settimana Precedente</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.navButton}
onPress={handleNextWeek}
>
<Text style={styles.navButtonText}>Settimana Successiva</Text>
</TouchableOpacity>
</View>
<Modal
animationType="slide"
transparent={true}
visible={modalVisible}
onRequestClose={() => setModalVisible(false)}
>
<View style={styles.centeredView}>
<View style={styles.modalView}>
<Text style={styles.modalTitle}>Transazioni del {selectedDate}</Text>
<FlatList
data={selectedTransactions}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => (
<View style={styles.transactionItem}>
<View><Text style={styles.transactionDescription}>{item.description}</Text></View>
<View>
<Text style={[styles.transactionAmount, { color: item.type === 'expense' ? 'red' : 'green' }]}>
{item.type === 'expense' ? '-' : '+'}{Math.abs(item.amount).toFixed(2)}
</Text>
</View>
</View>
)}
/>
<TouchableOpacity
style={styles.closeButton}
onPress={() => setModalVisible(false)}
>
<Text style={styles.closeButtonText}>Chiudi</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
</>
)}
</View>
)}
keyExtractor={item => item.key}
/>
);
};
const styles = StyleSheet.create({
container: {
marginTop: 20,
padding: 10,
},
buttonContainer: {
justifyContent: 'space-between',
flexDirection: 'row',
marginVertical: 10,
},
noDataText: {
color: '#000',
fontSize: 16,
textAlign: 'center',
marginTop: 20,
},
transactionsContainer: {
},
transactionsTitle: {
fontSize: 18,
fontWeight: 'bold',
color: '#fff',
marginVertical: 15,
textAlign: 'center',
},
transactionItem: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 15,
marginVertical: 5,
borderRadius: 10,
width: '100%',
backgroundColor: '#1a1a1a',
},
transactionDate: {
color: '#fff',
fontSize: 14,
},
transactionDescription: {
color: '#fff',
fontSize: 16,
},
transactionAmount: {
color: 'red',
fontSize: 16,
fontWeight: 'bold',
},
centeredView: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
marginTop: 22,
},
modalView: {
margin: 20,
height: 500,
backgroundColor: '#000',
borderRadius: 20,
borderColor: '#3c3c3c',
borderWidth: 2,
padding: 20,
alignItems: 'center',
},
modalTitle: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 15,
textAlign: 'center',
color: '#fff'
},
navButton: {
padding: 10,
borderRadius: 5,
margin: 2,
backgroundColor: '#f57242',
},
navButtonText: {
color: '#000',
fontSize: 16,
},
closeButton: {
marginTop: 20,
padding: 10,
borderRadius: 5,
backgroundColor: '#fff',
},
closeButtonText: {
color: '#000',
fontSize: 16,
},
weekText: {
color: '#fff',
fontSize: 22,
textAlign: 'center',
paddingBottom: 20
},
totalText: {
fontSize: 20,
textAlign: 'center',
marginBottom: 20,
},
});
export default VisualizzaDati;

View file

@ -0,0 +1,11 @@
import { View, Text } from 'react-native'
import React from 'react'
export default function TransactionCard() {
return (
<View>
<Text>TransactionCard</Text>
</View>
)
}

View file

@ -0,0 +1,4 @@
// This function is web-only as native doesn't currently support server (or build-time) rendering.
export function useClientOnlyValue<S, C>(server: S, client: C): S | C {
return client;
}

View file

@ -0,0 +1,12 @@
import React from 'react';
// `useEffect` is not invoked during server rendering, meaning
// we can use this to determine if we're on the server or not.
export function useClientOnlyValue<S, C>(server: S, client: C): S | C {
const [value, setValue] = React.useState<S | C>(server);
React.useEffect(() => {
setValue(client);
}, [client]);
return value;
}

View file

@ -0,0 +1 @@
export { useColorScheme } from 'react-native';

View file

@ -0,0 +1,8 @@
// NOTE: The default React Native styling doesn't support server rendering.
// Server rendered styles should not change between the first render of the HTML
// and the first render on the client. Typically, web developers will use CSS media queries
// to render different styles on the client and server, these aren't directly supported in React Native
// but can be achieved using a styling library like Nativewind.
export function useColorScheme() {
return 'dark';
}