3 Commits

Author SHA1 Message Date
354a13e9a9 fix(#14,#15): reduce UI clutter and rename status actions
All checks were successful
Luggage List Build / build-web (push) Successful in 29s
Luggage List Build / build-android (push) Successful in 6m27s
Luggage List Build / release (push) Successful in 18s
2026-04-18 21:32:12 +02:00
4018e97476 feat(#13): open item images in full-screen preview modal
All checks were successful
Luggage List Build / build-web (push) Successful in 29s
Luggage List Build / build-android (push) Successful in 5m42s
Luggage List Build / release (push) Successful in 11s
2026-04-18 19:45:53 +02:00
1e0eb7aee9 fix(#12): remove Select action from trips list
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
2026-04-18 19:45:46 +02:00
5 changed files with 93 additions and 56 deletions

View File

@@ -877,7 +877,6 @@ export default function AppRoot() {
createTrip={createTrip} createTrip={createTrip}
trips={data.trips} trips={data.trips}
selectedTripId={selectedTripId} selectedTripId={selectedTripId}
chooseTrip={setSelectedTripId}
setTripAsTemplate={setTripAsTemplate} setTripAsTemplate={setTripAsTemplate}
saveTripEdits={saveTripEdits} saveTripEdits={saveTripEdits}
setTripArchived={setTripArchived} setTripArchived={setTripArchived}

View File

@@ -8,7 +8,7 @@ function statusAccent(status) {
return STATUS_COLORS[status] || '#64748b'; return STATUS_COLORS[status] || '#64748b';
} }
export default function ItemCard({ item, onEdit, onDelete, onQuickPack, onQuickUnpack }) { export default function ItemCard({ item, onEdit, onDelete, onQuickPack, onQuickUnpack, onOpenImage }) {
const isPacked = item.status === 'packed'; const isPacked = item.status === 'packed';
const isUnpacked = item.status === 'unpacked'; const isUnpacked = item.status === 'unpacked';
@@ -18,7 +18,9 @@ export default function ItemCard({ item, onEdit, onDelete, onQuickPack, onQuickU
<View style={styles.itemThumbWrap}> <View style={styles.itemThumbWrap}>
{item.imageUri ? ( {item.imageUri ? (
<Pressable onPress={() => onOpenImage?.(item.imageUri)}>
<Image source={{ uri: item.imageUri }} style={styles.itemThumbSmall} /> <Image source={{ uri: item.imageUri }} style={styles.itemThumbSmall} />
</Pressable>
) : ( ) : (
<View style={styles.itemThumbPlaceholder}> <View style={styles.itemThumbPlaceholder}>
<Text style={styles.itemThumbPlaceholderText}>🧳</Text> <Text style={styles.itemThumbPlaceholderText}>🧳</Text>
@@ -29,11 +31,11 @@ export default function ItemCard({ item, onEdit, onDelete, onQuickPack, onQuickU
<View style={styles.itemMain}> <View style={styles.itemMain}>
<View style={styles.cardRow}> <View style={styles.cardRow}>
<View style={styles.flex}> <View style={styles.flex}>
<Text style={styles.itemTitle}>{item.name}</Text> <Text style={styles.itemTitle} numberOfLines={1}>{item.name}</Text>
<Text style={styles.itemMeta}>{item.category || 'uncategorized'} · {formatStatusLabel(item.status, item.lentTo)}</Text> <Text style={styles.itemMeta} numberOfLines={1}>{item.category || 'uncategorized'} · {formatStatusLabel(item.status, item.lentTo)}</Text>
<Text style={styles.itemMeta}>Location: {item.placement}</Text> <Text style={styles.itemMeta}>Location: {item.placement}</Text>
{item.status === 'lent-to' && !!item.lentTo ? <Text style={styles.itemMeta}>Borrower: {item.lentTo}</Text> : null} {item.status === 'lent-to' && !!item.lentTo ? <Text style={styles.itemMeta} numberOfLines={1}>Borrower: {item.lentTo}</Text> : null}
{!!item.description ? <Text style={styles.itemMeta}>{item.description}</Text> : null} {!!item.description ? <Text style={styles.itemMeta} numberOfLines={2}>{item.description}</Text> : null}
</View> </View>
<View style={styles.stackButtons}> <View style={styles.stackButtons}>
<Pressable style={styles.miniBtn} onPress={() => onEdit(item)}> <Pressable style={styles.miniBtn} onPress={() => onEdit(item)}>
@@ -47,10 +49,10 @@ export default function ItemCard({ item, onEdit, onDelete, onQuickPack, onQuickU
<View style={styles.quickStatusRow}> <View style={styles.quickStatusRow}>
<Pressable style={[styles.quickStatusBtn, isPacked && styles.quickStatusBtnActive]} onPress={onQuickPack}> <Pressable style={[styles.quickStatusBtn, isPacked && styles.quickStatusBtnActive]} onPress={onQuickPack}>
<Text style={[styles.quickStatusBtnText, isPacked && styles.quickStatusBtnTextActive]}>Pack</Text> <Text style={[styles.quickStatusBtnText, isPacked && styles.quickStatusBtnTextActive]}>Packed</Text>
</Pressable> </Pressable>
<Pressable style={[styles.quickStatusBtn, isUnpacked && styles.quickStatusBtnActive]} onPress={onQuickUnpack}> <Pressable style={[styles.quickStatusBtn, isUnpacked && styles.quickStatusBtnActive]} onPress={onQuickUnpack}>
<Text style={[styles.quickStatusBtnText, isUnpacked && styles.quickStatusBtnTextActive]}>Unpack</Text> <Text style={[styles.quickStatusBtnText, isUnpacked && styles.quickStatusBtnTextActive]}>Unpacked</Text>
</Pressable> </Pressable>
</View> </View>
</View> </View>

View File

@@ -10,10 +10,10 @@ export const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
paddingHorizontal: 16, paddingHorizontal: 14,
paddingTop: 12, paddingTop: 10,
paddingBottom: TAB_BAR_HEIGHT + 22, paddingBottom: TAB_BAR_HEIGHT + 20,
gap: 14, gap: 10,
}, },
statusSpacer: { statusSpacer: {
height: Platform.OS === 'android' ? 8 : 0, height: Platform.OS === 'android' ? 8 : 0,
@@ -86,7 +86,7 @@ export const styles = StyleSheet.create({
}, },
section: { section: {
gap: 12, gap: 10,
}, },
sectionTitle: { sectionTitle: {
color: '#f1f5f9', color: '#f1f5f9',
@@ -105,8 +105,8 @@ export const styles = StyleSheet.create({
borderRadius: 16, borderRadius: 16,
borderWidth: 1, borderWidth: 1,
borderColor: '#1f2937', borderColor: '#1f2937',
padding: 12, padding: 10,
gap: 8, gap: 6,
}, },
cardActive: { cardActive: {
borderColor: '#60a5fa', borderColor: '#60a5fa',
@@ -120,12 +120,12 @@ export const styles = StyleSheet.create({
borderRadius: 16, borderRadius: 16,
borderWidth: 1, borderWidth: 1,
borderColor: '#1e293b', borderColor: '#1e293b',
padding: 12, padding: 10,
gap: 10, gap: 8,
}, },
cardRow: { cardRow: {
flexDirection: 'row', flexDirection: 'row',
gap: 10, gap: 8,
}, },
cardTitle: { cardTitle: {
color: '#f8fafc', color: '#f8fafc',
@@ -134,8 +134,8 @@ export const styles = StyleSheet.create({
}, },
cardMeta: { cardMeta: {
color: '#94a3b8', color: '#94a3b8',
marginTop: 3, marginTop: 2,
fontSize: 13, fontSize: 12,
}, },
tripHistoryLabel: { tripHistoryLabel: {
color: '#93c5fd', color: '#93c5fd',
@@ -148,8 +148,8 @@ export const styles = StyleSheet.create({
borderRadius: 18, borderRadius: 18,
borderWidth: 1, borderWidth: 1,
borderColor: '#334155', borderColor: '#334155',
padding: 12, padding: 10,
gap: 8, gap: 6,
}, },
tripHeroImage: { tripHeroImage: {
width: '100%', width: '100%',
@@ -160,7 +160,7 @@ export const styles = StyleSheet.create({
tripHeroTitle: { tripHeroTitle: {
color: '#f8fafc', color: '#f8fafc',
fontWeight: '800', fontWeight: '800',
fontSize: 22, fontSize: 20,
}, },
tripListTitle: { tripListTitle: {
color: '#cbd5e1', color: '#cbd5e1',
@@ -290,19 +290,19 @@ export const styles = StyleSheet.create({
}, },
stackButtons: { stackButtons: {
gap: 7, gap: 6,
}, },
miniBtn: { miniBtn: {
backgroundColor: '#1e293b', backgroundColor: '#1e293b',
borderRadius: 8, borderRadius: 8,
paddingVertical: 7, paddingVertical: 6,
paddingHorizontal: 10, paddingHorizontal: 9,
}, },
miniBtnDanger: { miniBtnDanger: {
backgroundColor: '#3b1d22', backgroundColor: '#3b1d22',
borderRadius: 8, borderRadius: 8,
paddingVertical: 7, paddingVertical: 6,
paddingHorizontal: 10, paddingHorizontal: 9,
}, },
miniBtnText: { miniBtnText: {
color: '#e2e8f0', color: '#e2e8f0',
@@ -324,18 +324,18 @@ export const styles = StyleSheet.create({
alignSelf: 'stretch', alignSelf: 'stretch',
}, },
itemThumbWrap: { itemThumbWrap: {
paddingTop: 10, paddingTop: 8,
paddingLeft: 10, paddingLeft: 8,
}, },
itemThumbSmall: { itemThumbSmall: {
width: 46, width: 42,
height: 46, height: 42,
borderRadius: 8, borderRadius: 8,
backgroundColor: '#0b1220', backgroundColor: '#0b1220',
}, },
itemThumbPlaceholder: { itemThumbPlaceholder: {
width: 46, width: 42,
height: 46, height: 42,
borderRadius: 8, borderRadius: 8,
backgroundColor: '#0b1220', backgroundColor: '#0b1220',
alignItems: 'center', alignItems: 'center',
@@ -344,12 +344,12 @@ export const styles = StyleSheet.create({
borderColor: '#243244', borderColor: '#243244',
}, },
itemThumbPlaceholderText: { itemThumbPlaceholderText: {
fontSize: 18, fontSize: 16,
}, },
itemMain: { itemMain: {
flex: 1, flex: 1,
padding: 10, padding: 8,
gap: 8, gap: 6,
}, },
itemTitle: { itemTitle: {
color: '#f8fafc', color: '#f8fafc',
@@ -358,17 +358,17 @@ export const styles = StyleSheet.create({
}, },
itemMeta: { itemMeta: {
color: '#94a3b8', color: '#94a3b8',
marginTop: 2, marginTop: 1,
fontSize: 13, fontSize: 12,
}, },
quickStatusRow: { quickStatusRow: {
flexDirection: 'row', flexDirection: 'row',
gap: 8, gap: 6,
marginTop: 2, marginTop: 1,
}, },
quickStatusBtn: { quickStatusBtn: {
paddingVertical: 6, paddingVertical: 5,
paddingHorizontal: 12, paddingHorizontal: 10,
borderRadius: 999, borderRadius: 999,
borderWidth: 1, borderWidth: 1,
borderColor: '#334155', borderColor: '#334155',
@@ -381,7 +381,7 @@ export const styles = StyleSheet.create({
quickStatusBtnText: { quickStatusBtnText: {
color: '#cbd5e1', color: '#cbd5e1',
fontWeight: '700', fontWeight: '700',
fontSize: 12, fontSize: 11,
}, },
quickStatusBtnTextActive: { quickStatusBtnTextActive: {
color: '#dbeafe', color: '#dbeafe',
@@ -517,6 +517,35 @@ export const styles = StyleSheet.create({
borderRadius: 10, borderRadius: 10,
backgroundColor: '#111827', backgroundColor: '#111827',
}, },
imagePreviewBackdrop: {
flex: 1,
backgroundColor: 'rgba(2,6,23,0.88)',
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 14,
},
imagePreviewCard: {
width: '100%',
maxWidth: 460,
borderRadius: 14,
borderWidth: 1,
borderColor: '#334155',
backgroundColor: '#0f172a',
padding: 10,
gap: 8,
},
imagePreviewImage: {
width: '100%',
height: 360,
borderRadius: 10,
backgroundColor: '#0b1220',
},
imagePreviewHint: {
color: '#93c5fd',
textAlign: 'center',
fontSize: 12,
fontWeight: '600',
},
tabBarWrap: { tabBarWrap: {
position: 'absolute', position: 'absolute',
@@ -547,8 +576,8 @@ export const styles = StyleSheet.create({
tabItem: { tabItem: {
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
gap: 4, gap: 3,
minWidth: 58, minWidth: 56,
}, },
tabAddBtn: { tabAddBtn: {
width: 54, width: 54,
@@ -566,7 +595,7 @@ export const styles = StyleSheet.create({
}, },
tabLabel: { tabLabel: {
color: '#94a3b8', color: '#94a3b8',
fontSize: 12, fontSize: 11,
fontWeight: '600', fontWeight: '600',
}, },
tabLabelActive: { tabLabelActive: {
@@ -644,8 +673,8 @@ export const styles = StyleSheet.create({
borderRadius: 20, borderRadius: 20,
borderWidth: 1, borderWidth: 1,
borderColor: '#1e293b', borderColor: '#1e293b',
padding: 16, padding: 14,
gap: 10, gap: 8,
}, },
closeText: { closeText: {
color: '#93c5fd', color: '#93c5fd',

View File

@@ -1,5 +1,5 @@
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { Pressable, Text, View } from 'react-native'; import { Image, Modal, Pressable, Text, View } from 'react-native';
import ItemCard from '../components/ItemCard'; import ItemCard from '../components/ItemCard';
import { ITEM_STATUSES } from '../constants'; import { ITEM_STATUSES } from '../constants';
import { styles } from '../styles'; import { styles } from '../styles';
@@ -16,6 +16,7 @@ export default function ItemsTab({
}) { }) {
const [statusFilter, setStatusFilter] = useState('all'); const [statusFilter, setStatusFilter] = useState('all');
const [categoryFilter, setCategoryFilter] = useState('all'); const [categoryFilter, setCategoryFilter] = useState('all');
const [imagePreviewUri, setImagePreviewUri] = useState('');
const categories = useMemo( const categories = useMemo(
() => Array.from(new Set(selectedTripItems.map((item) => item.category?.trim()).filter(Boolean))).sort((a, b) => a.localeCompare(b)), () => Array.from(new Set(selectedTripItems.map((item) => item.category?.trim()).filter(Boolean))).sort((a, b) => a.localeCompare(b)),
@@ -100,10 +101,20 @@ export default function ItemsTab({
onDelete={deleteItem} onDelete={deleteItem}
onQuickPack={() => quickSetItemStatus(item.id, 'packed')} onQuickPack={() => quickSetItemStatus(item.id, 'packed')}
onQuickUnpack={() => quickSetItemStatus(item.id, 'unpacked')} onQuickUnpack={() => quickSetItemStatus(item.id, 'unpacked')}
onOpenImage={(uri) => setImagePreviewUri(uri)}
/> />
))} ))}
{selectedTripItems.length > 0 && filteredItems.length === 0 ? <Text style={styles.muted}>No items match the current filters.</Text> : null} {selectedTripItems.length > 0 && filteredItems.length === 0 ? <Text style={styles.muted}>No items match the current filters.</Text> : null}
<Modal visible={!!imagePreviewUri} transparent animationType="fade">
<Pressable style={styles.imagePreviewBackdrop} onPress={() => setImagePreviewUri('')}>
<Pressable style={styles.imagePreviewCard} onPress={() => {}}>
{imagePreviewUri ? <Image source={{ uri: imagePreviewUri }} style={styles.imagePreviewImage} resizeMode="contain" /> : null}
<Text style={styles.imagePreviewHint}>Tap outside to close</Text>
</Pressable>
</Pressable>
</Modal>
</View> </View>
); );
} }

View File

@@ -28,7 +28,6 @@ export default function TripsTab({
createTrip, createTrip,
trips, trips,
selectedTripId, selectedTripId,
chooseTrip,
setTripAsTemplate, setTripAsTemplate,
saveTripEdits, saveTripEdits,
setTripArchived, setTripArchived,
@@ -137,12 +136,9 @@ export default function TripsTab({
<View style={styles.flex}> <View style={styles.flex}>
<Text style={styles.cardTitle}>{trip.name}</Text> <Text style={styles.cardTitle}>{trip.name}</Text>
<Text style={styles.cardMeta}>{trip.location || 'No location'} · {trip.startDate} {trip.endDate}</Text> <Text style={styles.cardMeta}>{trip.location || 'No location'} · {trip.startDate} {trip.endDate}</Text>
<Text style={styles.cardMeta}>{defaultTemplateTripId === trip.id ? 'Default template' : ' '}</Text> {defaultTemplateTripId === trip.id ? <Text style={styles.cardMeta}>Default template</Text> : null}
</View> </View>
<View style={styles.stackButtons}> <View style={styles.stackButtons}>
<Pressable style={styles.miniBtn} onPress={() => chooseTrip(trip.id)}>
<Text style={styles.miniBtnText}>Select</Text>
</Pressable>
<Pressable style={styles.miniBtn} onPress={() => openView(trip.id)}> <Pressable style={styles.miniBtn} onPress={() => openView(trip.id)}>
<Text style={styles.miniBtnText}>View</Text> <Text style={styles.miniBtnText}>View</Text>
</Pressable> </Pressable>