Compare commits
3 Commits
luggage-li
...
luggage-li
| Author | SHA1 | Date | |
|---|---|---|---|
| fbdae66c9b | |||
| e8ffab355e | |||
| 2ec877362f |
@@ -22,7 +22,6 @@ const emptyTripForm = () => ({
|
||||
location: '',
|
||||
startDate: todayYMD(),
|
||||
endDate: todayYMD(),
|
||||
imageUri: '',
|
||||
copyDefaultTemplate: true,
|
||||
setAsDefaultTemplate: false,
|
||||
});
|
||||
@@ -279,7 +278,7 @@ export default function AppRoot() {
|
||||
location: tripForm.location.trim(),
|
||||
startDate: tripForm.startDate,
|
||||
endDate: tripForm.endDate,
|
||||
imageUri: tripForm.imageUri,
|
||||
imageUri: '',
|
||||
archived: false,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
@@ -344,7 +343,6 @@ export default function AppRoot() {
|
||||
location: patch.location.trim(),
|
||||
startDate: patch.startDate,
|
||||
endDate: patch.endDate,
|
||||
imageUri: patch.imageUri,
|
||||
updatedAt: Date.now(),
|
||||
}
|
||||
: trip
|
||||
@@ -727,6 +725,14 @@ export default function AppRoot() {
|
||||
}, 80);
|
||||
}
|
||||
|
||||
function openQuickAddItemFromNav() {
|
||||
if (!selectedTripId) {
|
||||
showAlert('No trip selected', 'Please select or create a trip first.');
|
||||
return;
|
||||
}
|
||||
openAddItemModal();
|
||||
}
|
||||
|
||||
if (!loaded) {
|
||||
return (
|
||||
<SafeAreaView style={[styles.safe, { paddingTop: topInset }]}>
|
||||
@@ -755,18 +761,6 @@ export default function AppRoot() {
|
||||
<TripsTab
|
||||
tripForm={tripForm}
|
||||
updateTripForm={updateTripForm}
|
||||
pickTripImage={(onPicked) =>
|
||||
pickImage((uri) => {
|
||||
if (typeof onPicked === 'function') onPicked(uri);
|
||||
else updateTripForm('imageUri', uri);
|
||||
})
|
||||
}
|
||||
takeTripImage={(onPicked) =>
|
||||
takeImage((uri) => {
|
||||
if (typeof onPicked === 'function') onPicked(uri);
|
||||
else updateTripForm('imageUri', uri);
|
||||
})
|
||||
}
|
||||
templateTrip={templateTrip}
|
||||
createTrip={createTrip}
|
||||
trips={data.trips}
|
||||
@@ -817,7 +811,7 @@ export default function AppRoot() {
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
|
||||
<BottomTab current={tab} onChange={setTab} />
|
||||
<BottomTab current={tab} onChange={setTab} onAddItem={openQuickAddItemFromNav} canAddItem={!!selectedTripId} />
|
||||
|
||||
<DatePickerModal
|
||||
visible={datePicker.visible}
|
||||
|
||||
@@ -3,10 +3,23 @@ import { Pressable, Text, View } from 'react-native';
|
||||
import Ionicons from '@expo/vector-icons/Ionicons';
|
||||
import { styles } from '../styles';
|
||||
|
||||
export default function BottomTab({ current, onChange }) {
|
||||
const tabs = [
|
||||
function TabBtn({ tab, current, onChange }) {
|
||||
const active = current === tab.key;
|
||||
return (
|
||||
<Pressable onPress={() => onChange(tab.key)} style={styles.tabItem}>
|
||||
<Ionicons name={active ? tab.iconActive : tab.icon} size={18} color={active ? '#dbeafe' : '#94a3b8'} />
|
||||
<Text style={[styles.tabLabel, active && styles.tabLabelActive]}>{tab.label}</Text>
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
export default function BottomTab({ current, onChange, onAddItem, canAddItem }) {
|
||||
const leftTabs = [
|
||||
{ key: 'trips', label: 'Trips', icon: 'airplane-outline', iconActive: 'airplane' },
|
||||
{ key: 'items', label: 'Items', icon: 'briefcase-outline', iconActive: 'briefcase' },
|
||||
];
|
||||
|
||||
const rightTabs = [
|
||||
{ key: 'checkup', label: 'Check-Up', icon: 'checkmark-circle-outline', iconActive: 'checkmark-circle' },
|
||||
{ key: 'history', label: 'History', icon: 'time-outline', iconActive: 'time' },
|
||||
];
|
||||
@@ -14,19 +27,25 @@ export default function BottomTab({ current, onChange }) {
|
||||
return (
|
||||
<View style={styles.tabBarWrap}>
|
||||
<View style={styles.tabBar}>
|
||||
{tabs.map((tab) => {
|
||||
const active = current === tab.key;
|
||||
return (
|
||||
<Pressable key={tab.key} onPress={() => onChange(tab.key)} style={styles.tabItem}>
|
||||
<Ionicons
|
||||
name={active ? tab.iconActive : tab.icon}
|
||||
size={18}
|
||||
color={active ? '#dbeafe' : '#94a3b8'}
|
||||
/>
|
||||
<Text style={[styles.tabLabel, active && styles.tabLabelActive]}>{tab.label}</Text>
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
<View style={styles.tabGroupSide}>
|
||||
{leftTabs.map((tab) => (
|
||||
<TabBtn key={tab.key} tab={tab} current={current} onChange={onChange} />
|
||||
))}
|
||||
</View>
|
||||
|
||||
<Pressable
|
||||
style={[styles.tabAddBtn, !canAddItem && styles.tabAddBtnDisabled]}
|
||||
onPress={onAddItem}
|
||||
disabled={!canAddItem}
|
||||
>
|
||||
<Ionicons name="add" size={26} color="#fff" />
|
||||
</Pressable>
|
||||
|
||||
<View style={styles.tabGroupSide}>
|
||||
{rightTabs.map((tab) => (
|
||||
<TabBtn key={tab.key} tab={tab} current={current} onChange={onChange} />
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -14,6 +14,17 @@ export default function ItemCard({ item, onEdit, onDelete, onQuickPack, onQuickU
|
||||
return (
|
||||
<View style={styles.itemCard}>
|
||||
<View style={[styles.itemAccent, { backgroundColor: statusAccent(item.status) }]} />
|
||||
|
||||
<View style={styles.itemThumbWrap}>
|
||||
{item.imageUri ? (
|
||||
<Image source={{ uri: item.imageUri }} style={styles.itemThumbSmall} />
|
||||
) : (
|
||||
<View style={styles.itemThumbPlaceholder}>
|
||||
<Text style={styles.itemThumbPlaceholderText}>🧳</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View style={styles.itemMain}>
|
||||
<View style={styles.cardRow}>
|
||||
<View style={styles.flex}>
|
||||
@@ -41,8 +52,6 @@ export default function ItemCard({ item, onEdit, onDelete, onQuickPack, onQuickU
|
||||
<Text style={[styles.quickStatusBtnText, isUnpacked && styles.quickStatusBtnTextActive]}>Unpack</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
{item.imageUri ? <Image source={{ uri: item.imageUri }} style={styles.previewImageSmall} /> : null}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -282,13 +282,38 @@ export const styles = StyleSheet.create({
|
||||
borderColor: '#1f2937',
|
||||
overflow: 'hidden',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
itemAccent: {
|
||||
width: 5,
|
||||
alignSelf: 'stretch',
|
||||
},
|
||||
itemThumbWrap: {
|
||||
paddingTop: 10,
|
||||
paddingLeft: 10,
|
||||
},
|
||||
itemThumbSmall: {
|
||||
width: 46,
|
||||
height: 46,
|
||||
borderRadius: 8,
|
||||
backgroundColor: '#0b1220',
|
||||
},
|
||||
itemThumbPlaceholder: {
|
||||
width: 46,
|
||||
height: 46,
|
||||
borderRadius: 8,
|
||||
backgroundColor: '#0b1220',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderWidth: 1,
|
||||
borderColor: '#243244',
|
||||
},
|
||||
itemThumbPlaceholderText: {
|
||||
fontSize: 18,
|
||||
},
|
||||
itemMain: {
|
||||
flex: 1,
|
||||
padding: 12,
|
||||
padding: 10,
|
||||
gap: 8,
|
||||
},
|
||||
itemTitle: {
|
||||
@@ -462,13 +487,34 @@ export const styles = StyleSheet.create({
|
||||
borderColor: '#1f2937',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: 8,
|
||||
},
|
||||
tabGroupSide: {
|
||||
flexDirection: 'row',
|
||||
width: '42%',
|
||||
justifyContent: 'space-around',
|
||||
alignItems: 'center',
|
||||
},
|
||||
tabItem: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 4,
|
||||
minWidth: 62,
|
||||
minWidth: 58,
|
||||
},
|
||||
tabAddBtn: {
|
||||
width: 54,
|
||||
height: 54,
|
||||
borderRadius: 999,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#2563eb',
|
||||
marginTop: -24,
|
||||
borderWidth: 2,
|
||||
borderColor: '#0b1220',
|
||||
},
|
||||
tabAddBtnDisabled: {
|
||||
opacity: 0.45,
|
||||
},
|
||||
tabLabel: {
|
||||
color: '#94a3b8',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { Image, KeyboardAvoidingView, Modal, Platform, Pressable, ScrollView, Text, TextInput, View } from 'react-native';
|
||||
import { KeyboardAvoidingView, Modal, Platform, Pressable, ScrollView, Text, TextInput, View } from 'react-native';
|
||||
import DatePickerModal from '../components/DatePickerModal';
|
||||
import Field from '../components/Field';
|
||||
import { styles } from '../styles';
|
||||
@@ -19,14 +19,11 @@ const emptyEditForm = {
|
||||
location: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
imageUri: '',
|
||||
};
|
||||
|
||||
export default function TripsTab({
|
||||
tripForm,
|
||||
updateTripForm,
|
||||
pickTripImage,
|
||||
takeTripImage,
|
||||
templateTrip,
|
||||
createTrip,
|
||||
trips,
|
||||
@@ -68,7 +65,6 @@ export default function TripsTab({
|
||||
location: trip.location || '',
|
||||
startDate: trip.startDate || '',
|
||||
endDate: trip.endDate || '',
|
||||
imageUri: trip.imageUri || '',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -83,14 +79,6 @@ export default function TripsTab({
|
||||
setEditMode(false);
|
||||
}
|
||||
|
||||
function pickViewTripImage() {
|
||||
pickTripImage((uri) => updateEditForm('imageUri', uri));
|
||||
}
|
||||
|
||||
function takeViewTripImage() {
|
||||
takeTripImage((uri) => updateEditForm('imageUri', uri));
|
||||
}
|
||||
|
||||
function applyTemplateFromView() {
|
||||
if (!viewingTrip) return;
|
||||
setTripAsTemplate(viewingTrip.id);
|
||||
@@ -124,7 +112,6 @@ export default function TripsTab({
|
||||
|
||||
{activeTrip ? (
|
||||
<View style={styles.tripHeroCard}>
|
||||
{activeTrip.imageUri ? <Image source={{ uri: activeTrip.imageUri }} style={styles.tripHeroImage} /> : null}
|
||||
<Text style={styles.tripHeroTitle}>{activeTrip.name}</Text>
|
||||
<Text style={styles.cardMeta}>{activeTrip.location || 'No location'} · {activeTrip.startDate} → {activeTrip.endDate}</Text>
|
||||
<Text style={styles.cardMeta}>{activeTripItemCount} items · {activeTripCheckupCount} check-ups</Text>
|
||||
@@ -155,7 +142,6 @@ export default function TripsTab({
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
{trip.imageUri ? <Image source={{ uri: trip.imageUri }} style={styles.previewImageSmall} /> : null}
|
||||
</View>
|
||||
))}
|
||||
|
||||
@@ -226,17 +212,6 @@ export default function TripsTab({
|
||||
<DateField label="Start Date" value={tripForm.startDate} onPress={() => openDatePicker('startDate')} />
|
||||
<DateField label="End Date" value={tripForm.endDate} onPress={() => openDatePicker('endDate')} />
|
||||
|
||||
<View style={styles.actionRow}>
|
||||
<Pressable style={[styles.secondaryBtnTight, styles.flex]} onPress={takeTripImage}>
|
||||
<Text style={styles.secondaryBtnText}>Take photo</Text>
|
||||
</Pressable>
|
||||
<Pressable style={[styles.secondaryBtnTight, styles.flex]} onPress={pickTripImage}>
|
||||
<Text style={styles.secondaryBtnText}>{tripForm.imageUri ? 'From gallery (change)' : 'From gallery'}</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
{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}>
|
||||
@@ -283,7 +258,6 @@ export default function TripsTab({
|
||||
>
|
||||
{!editMode ? (
|
||||
<>
|
||||
{viewingTrip.imageUri ? <Image source={{ uri: viewingTrip.imageUri }} style={styles.previewImage} /> : null}
|
||||
<Text style={styles.tripHeroTitle}>{viewingTrip.name}</Text>
|
||||
<Text style={styles.cardMeta}>{viewingTrip.location || 'No location'}</Text>
|
||||
<Text style={styles.cardMeta}>{viewingTrip.startDate} → {viewingTrip.endDate}</Text>
|
||||
@@ -333,17 +307,6 @@ export default function TripsTab({
|
||||
<DateField label="Start Date" value={editForm.startDate} onPress={() => setViewDatePicker({ visible: true, field: 'startDate' })} />
|
||||
<DateField label="End Date" value={editForm.endDate} onPress={() => setViewDatePicker({ visible: true, field: 'endDate' })} />
|
||||
|
||||
<View style={styles.actionRow}>
|
||||
<Pressable style={[styles.secondaryBtnTight, styles.flex]} onPress={takeViewTripImage}>
|
||||
<Text style={styles.secondaryBtnText}>Take photo</Text>
|
||||
</Pressable>
|
||||
<Pressable style={[styles.secondaryBtnTight, styles.flex]} onPress={pickViewTripImage}>
|
||||
<Text style={styles.secondaryBtnText}>{editForm.imageUri ? 'From gallery (change)' : 'From gallery'}</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
{editForm.imageUri ? <Image source={{ uri: editForm.imageUri }} style={styles.previewImage} /> : null}
|
||||
|
||||
<Pressable style={styles.primaryBtn} onPress={saveEditFromView}>
|
||||
<Text style={styles.primaryBtnText}>Save Trip</Text>
|
||||
</Pressable>
|
||||
|
||||
Reference in New Issue
Block a user