Compare commits
1 Commits
luggage-li
...
luggage-li
| Author | SHA1 | Date | |
|---|---|---|---|
| e1bfbdbf1e |
@@ -109,6 +109,35 @@ export default function AppRoot() {
|
|||||||
return (data.checkupsByTrip[selectedTripId] || []).slice().sort((a, b) => b.createdAt - a.createdAt);
|
return (data.checkupsByTrip[selectedTripId] || []).slice().sort((a, b) => b.createdAt - a.createdAt);
|
||||||
}, [data.checkupsByTrip, selectedTripId]);
|
}, [data.checkupsByTrip, selectedTripId]);
|
||||||
|
|
||||||
|
const previousCustomPlacements = useMemo(() => {
|
||||||
|
const seen = new Set();
|
||||||
|
|
||||||
|
function takeFrom(items = [], bucket = []) {
|
||||||
|
items
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => (b.updatedAt || b.createdAt || 0) - (a.updatedAt || a.createdAt || 0))
|
||||||
|
.forEach((item) => {
|
||||||
|
const location = item.placement?.trim();
|
||||||
|
if (!location || ITEM_PLACEMENTS.includes(location) || seen.has(location)) return;
|
||||||
|
seen.add(location);
|
||||||
|
bucket.push(location);
|
||||||
|
});
|
||||||
|
return bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
const collected = [];
|
||||||
|
if (selectedTripId) {
|
||||||
|
takeFrom(data.itemsByTrip[selectedTripId] || [], collected);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.entries(data.itemsByTrip).forEach(([tripId, items]) => {
|
||||||
|
if (tripId === selectedTripId) return;
|
||||||
|
takeFrom(items || [], collected);
|
||||||
|
});
|
||||||
|
|
||||||
|
return collected;
|
||||||
|
}, [data.itemsByTrip, selectedTripId]);
|
||||||
|
|
||||||
const templateTrip = useMemo(
|
const templateTrip = useMemo(
|
||||||
() => data.trips.find((trip) => trip.id === data.defaultTemplateTripId) || null,
|
() => data.trips.find((trip) => trip.id === data.defaultTemplateTripId) || null,
|
||||||
[data.trips, data.defaultTemplateTripId]
|
[data.trips, data.defaultTemplateTripId]
|
||||||
@@ -229,7 +258,18 @@ export default function AppRoot() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateItemForm(field, value) {
|
function updateItemForm(field, value) {
|
||||||
setItemForm((prev) => ({ ...prev, [field]: value }));
|
setItemForm((prev) => {
|
||||||
|
if (field === 'placement' && value === 'other') {
|
||||||
|
const fallbackLocation = previousCustomPlacements[0] || '';
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
placement: value,
|
||||||
|
placementCustom: prev.placementCustom?.trim() ? prev.placementCustom : fallbackLocation,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...prev, [field]: value };
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function openDatePicker(field) {
|
function openDatePicker(field) {
|
||||||
@@ -959,6 +999,7 @@ export default function AppRoot() {
|
|||||||
<ItemModal
|
<ItemModal
|
||||||
visible={itemModalVisible}
|
visible={itemModalVisible}
|
||||||
itemForm={itemForm}
|
itemForm={itemForm}
|
||||||
|
previousCustomPlacements={previousCustomPlacements}
|
||||||
setItemModalVisible={setItemModalVisible}
|
setItemModalVisible={setItemModalVisible}
|
||||||
updateItemForm={updateItemForm}
|
updateItemForm={updateItemForm}
|
||||||
pickItemImage={(options) => pickImage((uri) => updateItemForm('imageUri', uri), options)}
|
pickItemImage={(options) => pickImage((uri) => updateItemForm('imageUri', uri), options)}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ function qualityValue(level) {
|
|||||||
export default function ItemModal({
|
export default function ItemModal({
|
||||||
visible,
|
visible,
|
||||||
itemForm,
|
itemForm,
|
||||||
|
previousCustomPlacements,
|
||||||
setItemModalVisible,
|
setItemModalVisible,
|
||||||
updateItemForm,
|
updateItemForm,
|
||||||
pickItemImage,
|
pickItemImage,
|
||||||
@@ -90,6 +91,15 @@ export default function ItemModal({
|
|||||||
placeholder="bath-kit"
|
placeholder="bath-kit"
|
||||||
placeholderTextColor="#6b7280"
|
placeholderTextColor="#6b7280"
|
||||||
/>
|
/>
|
||||||
|
{previousCustomPlacements?.length ? (
|
||||||
|
<View style={styles.chipGroup}>
|
||||||
|
{previousCustomPlacements.slice(0, 6).map((location) => (
|
||||||
|
<Pressable key={location} style={styles.chip} onPress={() => updateItemForm('placementCustom', location)}>
|
||||||
|
<Text style={styles.chipText}>{location}</Text>
|
||||||
|
</Pressable>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
</Field>
|
</Field>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
@@ -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 [locationFilter, setLocationFilter] = useState('all');
|
||||||
const [imagePreviewUri, setImagePreviewUri] = useState('');
|
const [imagePreviewUri, setImagePreviewUri] = useState('');
|
||||||
|
|
||||||
const categories = useMemo(
|
const categories = useMemo(
|
||||||
@@ -23,19 +24,27 @@ export default function ItemsTab({
|
|||||||
[selectedTripItems]
|
[selectedTripItems]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const locations = useMemo(
|
||||||
|
() => Array.from(new Set(selectedTripItems.map((item) => item.placement?.trim()).filter(Boolean))).sort((a, b) => a.localeCompare(b)),
|
||||||
|
[selectedTripItems]
|
||||||
|
);
|
||||||
|
|
||||||
const filteredItems = useMemo(
|
const filteredItems = useMemo(
|
||||||
() =>
|
() =>
|
||||||
selectedTripItems.filter((item) => {
|
selectedTripItems.filter((item) => {
|
||||||
const matchStatus = statusFilter === 'all' || item.status === statusFilter;
|
const matchStatus = statusFilter === 'all' || item.status === statusFilter;
|
||||||
const itemCategory = item.category?.trim() || '';
|
const itemCategory = item.category?.trim() || '';
|
||||||
|
const itemLocation = item.placement?.trim() || '';
|
||||||
const matchCategory = categoryFilter === 'all' || itemCategory === categoryFilter;
|
const matchCategory = categoryFilter === 'all' || itemCategory === categoryFilter;
|
||||||
return matchStatus && matchCategory;
|
const matchLocation = locationFilter === 'all' || itemLocation === locationFilter;
|
||||||
|
return matchStatus && matchCategory && matchLocation;
|
||||||
}),
|
}),
|
||||||
[selectedTripItems, statusFilter, categoryFilter]
|
[selectedTripItems, statusFilter, categoryFilter, locationFilter]
|
||||||
);
|
);
|
||||||
|
|
||||||
const filterStatusOptions = ['all', ...ITEM_STATUSES];
|
const filterStatusOptions = ['all', ...ITEM_STATUSES];
|
||||||
const filterCategoryOptions = ['all', ...categories];
|
const filterCategoryOptions = ['all', ...categories];
|
||||||
|
const filterLocationOptions = ['all', ...locations];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
@@ -90,6 +99,18 @@ export default function ItemsTab({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<Text style={styles.cardMeta}>Location</Text>
|
||||||
|
<View style={styles.chipGroup}>
|
||||||
|
{filterLocationOptions.map((location) => {
|
||||||
|
const active = locationFilter === location;
|
||||||
|
return (
|
||||||
|
<Pressable key={location} style={[styles.chip, active && styles.chipActive]} onPress={() => setLocationFilter(location)}>
|
||||||
|
<Text style={[styles.chipText, active && styles.chipTextActive]}>{formatFilterLabel(location)}</Text>
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user