refactor: split app into modular src architecture
Some checks failed
Luggage List Build / release (push) Has been cancelled
Luggage List Build / build-android (push) Has been cancelled
Luggage List Build / build-web (push) Has been cancelled

This commit is contained in:
2026-04-18 12:56:12 +02:00
parent 3323d09dda
commit bdea52b7c6
17 changed files with 1493 additions and 1343 deletions

45
src/tabs/CheckupTab.js Normal file
View File

@@ -0,0 +1,45 @@
import React from 'react';
import { Pressable, Text, View } from 'react-native';
import { styles } from '../styles';
export default function CheckupTab({ checkupSession, answerCheckupYes, openFixModal, createFreshCheckupSession, saveCheckup }) {
return (
<View style={styles.section}>
<View style={styles.sectionRow}>
<Text style={styles.sectionTitle}>Check-Up</Text>
<Pressable style={styles.secondaryBtnTight} onPress={createFreshCheckupSession}>
<Text style={styles.secondaryBtnText}>Restart</Text>
</Pressable>
</View>
{checkupSession.length === 0 ? <Text style={styles.muted}>No items for this trip yet.</Text> : null}
{checkupSession.map((entry) => (
<View key={entry.itemId} style={styles.cardSoft}>
<Text style={styles.cardTitle}>{entry.name}</Text>
<Text style={styles.cardMeta}>{entry.category || 'uncategorized'}</Text>
<Text style={styles.cardMeta}>
{entry.current.status} · {entry.current.placement}
{entry.current.status === 'lent-to' && entry.current.lentTo ? ` · ${entry.current.lentTo}` : ''}
</Text>
<View style={styles.answerRow}>
<Pressable style={styles.answerYes} onPress={() => answerCheckupYes(entry.itemId)}>
<Text style={styles.answerText}>Yes</Text>
</Pressable>
<Pressable style={styles.answerNo} onPress={() => openFixModal(entry.itemId)}>
<Text style={styles.answerText}>No</Text>
</Pressable>
<View style={[styles.answerStateDot, entry.confirmed ? styles.answerStateDotOn : null]} />
</View>
</View>
))}
{!!checkupSession.length && (
<Pressable style={styles.primaryBtn} onPress={saveCheckup}>
<Text style={styles.primaryBtnText}>Save Check-Up Snapshot</Text>
</Pressable>
)}
</View>
);
}

36
src/tabs/HistoryTab.js Normal file
View File

@@ -0,0 +1,36 @@
import React from 'react';
import { Pressable, Text, View } from 'react-native';
import { styles } from '../styles';
export default function HistoryTab({ selectedTripCheckups, selectedCheckupId, setSelectedCheckupId }) {
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>History</Text>
{selectedTripCheckups.length === 0 ? <Text style={styles.muted}>No check-ups saved yet.</Text> : null}
{selectedTripCheckups.map((checkup) => (
<View key={checkup.id} style={styles.cardSoft}>
<Pressable onPress={() => setSelectedCheckupId((prev) => (prev === checkup.id ? null : checkup.id))}>
<Text style={styles.cardTitle}>{new Date(checkup.createdAt).toLocaleString()}</Text>
<Text style={styles.cardMeta}>{checkup.snapshot.length} items</Text>
<Text style={styles.cardMeta}>{selectedCheckupId === checkup.id ? 'Tap to collapse' : 'Tap to open'}</Text>
</Pressable>
{selectedCheckupId === checkup.id ? (
<View style={styles.snapshotWrap}>
{checkup.snapshot.map((entry) => (
<View key={entry.itemId} style={styles.snapshotRow}>
<Text style={styles.snapshotTitle}>{entry.name}</Text>
<Text style={styles.cardMeta}>
{entry.status} · {entry.placement}
{entry.status === 'lent-to' && entry.lentTo ? ` · ${entry.lentTo}` : ''}
</Text>
</View>
))}
</View>
) : null}
</View>
))}
</View>
);
}

24
src/tabs/ItemsTab.js Normal file
View File

@@ -0,0 +1,24 @@
import React from 'react';
import { Pressable, Text, View } from 'react-native';
import ItemCard from '../components/ItemCard';
import { styles } from '../styles';
export default function ItemsTab({ selectedTrip, selectedTripItems, openAddItemModal, openEditItemModal, deleteItem }) {
return (
<View style={styles.section}>
<View style={styles.sectionRow}>
<Text style={styles.sectionTitle}>Luggage Items</Text>
<Pressable style={styles.primaryBtnTight} onPress={openAddItemModal}>
<Text style={styles.primaryBtnText}>+ Add</Text>
</Pressable>
</View>
{!selectedTrip ? <Text style={styles.muted}>Select a trip first.</Text> : null}
{selectedTripItems.length === 0 && selectedTrip ? <Text style={styles.muted}>No items yet.</Text> : null}
{selectedTripItems.map((item) => (
<ItemCard key={item.id} item={item} onEdit={openEditItemModal} onDelete={deleteItem} />
))}
</View>
);
}

118
src/tabs/TripsTab.js Normal file
View File

@@ -0,0 +1,118 @@
import React from 'react';
import { Image, Pressable, Text, TextInput, View } from 'react-native';
import Field from '../components/Field';
import { styles } from '../styles';
export default function TripsTab({
tripForm,
updateTripForm,
pickTripImage,
templateTrip,
createTrip,
trips,
selectedTripId,
chooseTrip,
setTripAsTemplate,
deleteTrip,
focusToEnd,
defaultTemplateTripId,
}) {
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Trips</Text>
<View style={styles.cardSoft}>
<Field label="Name">
<TextInput
style={styles.input}
value={tripForm.name}
onChangeText={(v) => updateTripForm('name', v)}
placeholder="Summer Weekend"
placeholderTextColor="#6b7280"
onFocus={focusToEnd}
/>
</Field>
<Field label="Location">
<TextInput
style={styles.input}
value={tripForm.location}
onChangeText={(v) => updateTripForm('location', v)}
placeholder="Berlin"
placeholderTextColor="#6b7280"
onFocus={focusToEnd}
/>
</Field>
<Field label="Start Date (YYYY-MM-DD)">
<TextInput
style={styles.input}
value={tripForm.startDate}
onChangeText={(v) => updateTripForm('startDate', v)}
placeholderTextColor="#6b7280"
onFocus={focusToEnd}
/>
</Field>
<Field label="End Date (YYYY-MM-DD)">
<TextInput
style={styles.input}
value={tripForm.endDate}
onChangeText={(v) => updateTripForm('endDate', v)}
placeholderTextColor="#6b7280"
onFocus={focusToEnd}
/>
</Field>
<Pressable style={styles.secondaryBtn} onPress={pickTripImage}>
<Text style={styles.secondaryBtnText}>{tripForm.imageUri ? 'Change trip image' : 'Add trip image'}</Text>
</Pressable>
{tripForm.imageUri ? <Image source={{ uri: tripForm.imageUri }} style={styles.previewImage} /> : null}
{templateTrip ? (
<Pressable style={styles.inlineToggle} onPress={() => updateTripForm('copyDefaultTemplate', !tripForm.copyDefaultTemplate)}>
<Text style={styles.inlineToggleText}>
{tripForm.copyDefaultTemplate ? '☑' : '☐'} Copy items from template ({templateTrip.name})
</Text>
</Pressable>
) : null}
<Pressable style={styles.inlineToggle} onPress={() => updateTripForm('setAsDefaultTemplate', !tripForm.setAsDefaultTemplate)}>
<Text style={styles.inlineToggleText}>{tripForm.setAsDefaultTemplate ? '☑' : '☐'} Set as default template</Text>
</Pressable>
<Pressable style={styles.primaryBtn} onPress={createTrip}>
<Text style={styles.primaryBtnText}>Create Trip</Text>
</Pressable>
</View>
{trips
.slice()
.sort((a, b) => b.startDate.localeCompare(a.startDate))
.map((trip) => (
<View key={trip.id} style={[styles.card, selectedTripId === trip.id && styles.cardActive]}>
<View style={styles.cardRow}>
<View style={styles.flex}>
<Text style={styles.cardTitle}>{trip.name}</Text>
<Text style={styles.cardMeta}>{trip.location || 'No location'} · {trip.startDate} {trip.endDate}</Text>
<Text style={styles.cardMeta}>{defaultTemplateTripId === trip.id ? 'Default template' : ' '}</Text>
</View>
<View style={styles.stackButtons}>
<Pressable style={styles.miniBtn} onPress={() => chooseTrip(trip.id)}>
<Text style={styles.miniBtnText}>Select</Text>
</Pressable>
<Pressable style={styles.miniBtn} onPress={() => setTripAsTemplate(trip.id)}>
<Text style={styles.miniBtnText}>Template</Text>
</Pressable>
<Pressable style={styles.miniBtnDanger} onPress={() => deleteTrip(trip.id)}>
<Text style={styles.miniBtnText}>Delete</Text>
</Pressable>
</View>
</View>
{trip.imageUri ? <Image source={{ uri: trip.imageUri }} style={styles.previewImageSmall} /> : null}
</View>
))}
</View>
);
}