feat: move trip creation and check-up into guided modals
This commit is contained in:
187
src/AppRoot.js
187
src/AppRoot.js
@@ -7,7 +7,7 @@ import BottomTab from './components/BottomTab';
|
||||
import TripPicker from './components/TripPicker';
|
||||
import DatePickerModal from './components/DatePickerModal';
|
||||
import ItemModal from './modals/ItemModal';
|
||||
import CheckupFixModal from './modals/CheckupFixModal';
|
||||
import CheckupFlowModal from './modals/CheckupFlowModal';
|
||||
import TripsTab from './tabs/TripsTab';
|
||||
import ItemsTab from './tabs/ItemsTab';
|
||||
import CheckupTab from './tabs/CheckupTab';
|
||||
@@ -37,6 +37,28 @@ const emptyItemForm = () => ({
|
||||
imageUri: '',
|
||||
});
|
||||
|
||||
const emptyCheckupNoForm = () => ({
|
||||
status: 'unpacked',
|
||||
placement: 'suitcase',
|
||||
lentTo: '',
|
||||
updateMasterList: false,
|
||||
});
|
||||
|
||||
function buildCheckupSession(items) {
|
||||
return items.map((item) => ({
|
||||
itemId: item.id,
|
||||
name: item.name,
|
||||
category: item.category,
|
||||
current: {
|
||||
status: item.status,
|
||||
placement: item.placement,
|
||||
lentTo: item.lentTo || '',
|
||||
},
|
||||
confirmed: false,
|
||||
result: 'pending',
|
||||
}));
|
||||
}
|
||||
|
||||
export default function AppRoot() {
|
||||
const scrollRef = useRef(null);
|
||||
|
||||
@@ -52,14 +74,10 @@ export default function AppRoot() {
|
||||
const [itemForm, setItemForm] = useState(emptyItemForm());
|
||||
|
||||
const [checkupSession, setCheckupSession] = useState([]);
|
||||
const [checkupFixModalVisible, setCheckupFixModalVisible] = useState(false);
|
||||
const [checkupFixTargetId, setCheckupFixTargetId] = useState(null);
|
||||
const [checkupFixForm, setCheckupFixForm] = useState({
|
||||
status: 'unpacked',
|
||||
placement: 'suitcase',
|
||||
lentTo: '',
|
||||
updateMasterList: false,
|
||||
});
|
||||
const [checkupFlowVisible, setCheckupFlowVisible] = useState(false);
|
||||
const [checkupFlowIndex, setCheckupFlowIndex] = useState(0);
|
||||
const [checkupFlowMode, setCheckupFlowMode] = useState('question');
|
||||
const [checkupNoForm, setCheckupNoForm] = useState(emptyCheckupNoForm());
|
||||
|
||||
const [selectedCheckupId, setSelectedCheckupId] = useState(null);
|
||||
|
||||
@@ -90,6 +108,12 @@ export default function AppRoot() {
|
||||
return { total, correct, bad, pending };
|
||||
}, [checkupSession]);
|
||||
|
||||
const checkupCurrentEntry = useMemo(() => {
|
||||
if (!checkupFlowVisible) return null;
|
||||
if (checkupFlowIndex >= checkupSession.length) return null;
|
||||
return checkupSession[checkupFlowIndex] || null;
|
||||
}, [checkupFlowVisible, checkupFlowIndex, checkupSession]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
@@ -189,7 +213,7 @@ export default function AppRoot() {
|
||||
function createTrip() {
|
||||
if (!tripForm.name.trim()) {
|
||||
Alert.alert('Missing name', 'Trip name is required.');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const start = parseYMD(tripForm.startDate);
|
||||
@@ -197,12 +221,12 @@ export default function AppRoot() {
|
||||
|
||||
if (!start || !end) {
|
||||
Alert.alert('Invalid dates', 'Please select valid trip dates.');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (start > end) {
|
||||
Alert.alert('Invalid dates', 'Start date cannot be after end date.');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
@@ -247,6 +271,7 @@ export default function AppRoot() {
|
||||
|
||||
setSelectedTripId(tripId);
|
||||
setTripForm(emptyTripForm());
|
||||
return true;
|
||||
}
|
||||
|
||||
function setTripAsTemplate(tripId) {
|
||||
@@ -401,67 +426,85 @@ export default function AppRoot() {
|
||||
setCheckupSession([]);
|
||||
return;
|
||||
}
|
||||
setCheckupSession(buildCheckupSession(selectedTripItems));
|
||||
}
|
||||
|
||||
const fresh = selectedTripItems.map((item) => ({
|
||||
itemId: item.id,
|
||||
name: item.name,
|
||||
category: item.category,
|
||||
current: {
|
||||
status: item.status,
|
||||
placement: item.placement,
|
||||
lentTo: item.lentTo || '',
|
||||
},
|
||||
confirmed: false,
|
||||
result: 'pending',
|
||||
}));
|
||||
function startCheckupFlow() {
|
||||
if (!selectedTripId) {
|
||||
Alert.alert('No trip selected', 'Please select a trip first.');
|
||||
return;
|
||||
}
|
||||
if (!selectedTripItems.length) {
|
||||
Alert.alert('No items', 'Add items before starting a check-up.');
|
||||
return;
|
||||
}
|
||||
|
||||
const fresh = buildCheckupSession(selectedTripItems);
|
||||
setCheckupSession(fresh);
|
||||
setCheckupFlowIndex(0);
|
||||
setCheckupFlowMode('question');
|
||||
setCheckupNoForm(emptyCheckupNoForm());
|
||||
setCheckupFlowVisible(true);
|
||||
}
|
||||
|
||||
function answerCheckupYes(itemId) {
|
||||
setCheckupSession((prev) =>
|
||||
prev.map((entry) => (entry.itemId === itemId ? { ...entry, confirmed: true, result: 'correct' } : entry))
|
||||
);
|
||||
function closeCheckupFlow() {
|
||||
setCheckupFlowVisible(false);
|
||||
setCheckupFlowMode('question');
|
||||
setCheckupNoForm(emptyCheckupNoForm());
|
||||
}
|
||||
|
||||
function openFixModal(itemId) {
|
||||
const entry = checkupSession.find((x) => x.itemId === itemId);
|
||||
function goNextInCheckup() {
|
||||
setCheckupFlowIndex((prev) => prev + 1);
|
||||
setCheckupFlowMode('question');
|
||||
setCheckupNoForm(emptyCheckupNoForm());
|
||||
}
|
||||
|
||||
function answerCurrentCheckupYes() {
|
||||
const entry = checkupCurrentEntry;
|
||||
if (!entry) return;
|
||||
|
||||
setCheckupFixTargetId(itemId);
|
||||
setCheckupFixForm({
|
||||
setCheckupSession((prev) =>
|
||||
prev.map((x) => (x.itemId === entry.itemId ? { ...x, confirmed: true, result: 'correct' } : x))
|
||||
);
|
||||
goNextInCheckup();
|
||||
}
|
||||
|
||||
function openCurrentCheckupNo() {
|
||||
const entry = checkupCurrentEntry;
|
||||
if (!entry) return;
|
||||
setCheckupNoForm({
|
||||
status: entry.current.status || 'unpacked',
|
||||
placement: entry.current.placement || 'suitcase',
|
||||
lentTo: entry.current.lentTo || '',
|
||||
updateMasterList: false,
|
||||
});
|
||||
setCheckupFixModalVisible(true);
|
||||
setCheckupFlowMode('edit');
|
||||
}
|
||||
|
||||
function saveFixModal() {
|
||||
if (!checkupFixTargetId) return;
|
||||
function saveCurrentCheckupNo() {
|
||||
const entry = checkupCurrentEntry;
|
||||
if (!entry) return;
|
||||
|
||||
const targetId = checkupFixTargetId;
|
||||
const patch = {
|
||||
status: checkupFixForm.status,
|
||||
placement: checkupFixForm.placement,
|
||||
lentTo: checkupFixForm.status === 'lent-to' ? checkupFixForm.lentTo.trim() : '',
|
||||
status: checkupNoForm.status,
|
||||
placement: checkupNoForm.placement,
|
||||
lentTo: checkupNoForm.status === 'lent-to' ? checkupNoForm.lentTo.trim() : '',
|
||||
};
|
||||
|
||||
setCheckupSession((prev) =>
|
||||
prev.map((entry) =>
|
||||
entry.itemId === targetId
|
||||
prev.map((x) =>
|
||||
x.itemId === entry.itemId
|
||||
? {
|
||||
...entry,
|
||||
...x,
|
||||
current: patch,
|
||||
confirmed: true,
|
||||
result: 'bad',
|
||||
}
|
||||
: entry
|
||||
: x
|
||||
)
|
||||
);
|
||||
|
||||
if (checkupFixForm.updateMasterList && selectedTripId) {
|
||||
if (checkupNoForm.updateMasterList && selectedTripId) {
|
||||
setData((prev) => {
|
||||
const items = prev.itemsByTrip[selectedTripId] || [];
|
||||
return {
|
||||
@@ -469,7 +512,7 @@ export default function AppRoot() {
|
||||
itemsByTrip: {
|
||||
...prev.itemsByTrip,
|
||||
[selectedTripId]: items.map((item) =>
|
||||
item.id === targetId
|
||||
item.id === entry.itemId
|
||||
? {
|
||||
...item,
|
||||
status: patch.status,
|
||||
@@ -484,28 +527,27 @@ export default function AppRoot() {
|
||||
});
|
||||
}
|
||||
|
||||
setCheckupFixModalVisible(false);
|
||||
setCheckupFixTargetId(null);
|
||||
goNextInCheckup();
|
||||
}
|
||||
|
||||
function saveCheckup() {
|
||||
function saveCheckupSnapshot(sessionToSave) {
|
||||
if (!selectedTripId) {
|
||||
Alert.alert('No trip selected', 'Please select a trip first.');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!checkupSession.length) {
|
||||
if (!sessionToSave.length) {
|
||||
Alert.alert('No items', 'Add items before creating a check-up.');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const pending = checkupSession.filter((entry) => !entry.confirmed).length;
|
||||
const pending = sessionToSave.filter((entry) => !entry.confirmed).length;
|
||||
if (pending > 0) {
|
||||
Alert.alert('Incomplete', `Please confirm all items first (${pending} remaining).`);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const snapshot = checkupSession.map((entry) => ({
|
||||
const snapshot = sessionToSave.map((entry) => ({
|
||||
itemId: entry.itemId,
|
||||
name: entry.name,
|
||||
category: entry.category,
|
||||
@@ -537,7 +579,15 @@ export default function AppRoot() {
|
||||
};
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function finishCheckupFlow() {
|
||||
const ok = saveCheckupSnapshot(checkupSession);
|
||||
if (!ok) return;
|
||||
|
||||
Alert.alert('Saved', 'Check-up snapshot saved.');
|
||||
closeCheckupFlow();
|
||||
createFreshCheckupSession();
|
||||
}
|
||||
|
||||
@@ -592,6 +642,8 @@ export default function AppRoot() {
|
||||
onInputFocus={onInputFocus}
|
||||
defaultTemplateTripId={data.defaultTemplateTripId}
|
||||
openDatePicker={openDatePicker}
|
||||
activeTripItemCount={selectedTripItems.length}
|
||||
activeTripCheckupCount={selectedTripCheckups.length}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -608,12 +660,10 @@ export default function AppRoot() {
|
||||
|
||||
{tab === 'checkup' && (
|
||||
<CheckupTab
|
||||
checkupSession={checkupSession}
|
||||
selectedTrip={selectedTrip}
|
||||
selectedTripItems={selectedTripItems}
|
||||
checkupStats={checkupStats}
|
||||
answerCheckupYes={answerCheckupYes}
|
||||
openFixModal={openFixModal}
|
||||
createFreshCheckupSession={createFreshCheckupSession}
|
||||
saveCheckup={saveCheckup}
|
||||
startCheckupFlow={startCheckupFlow}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -649,12 +699,19 @@ export default function AppRoot() {
|
||||
saveItemFromModal={saveItemFromModal}
|
||||
/>
|
||||
|
||||
<CheckupFixModal
|
||||
visible={checkupFixModalVisible}
|
||||
checkupFixForm={checkupFixForm}
|
||||
setCheckupFixForm={setCheckupFixForm}
|
||||
setCheckupFixModalVisible={setCheckupFixModalVisible}
|
||||
saveFixModal={saveFixModal}
|
||||
<CheckupFlowModal
|
||||
visible={checkupFlowVisible}
|
||||
entry={checkupCurrentEntry}
|
||||
stepIndex={checkupFlowIndex}
|
||||
total={checkupSession.length}
|
||||
mode={checkupFlowMode}
|
||||
noForm={checkupNoForm}
|
||||
setNoForm={setCheckupNoForm}
|
||||
onClose={closeCheckupFlow}
|
||||
onYes={answerCurrentCheckupYes}
|
||||
onNo={openCurrentCheckupNo}
|
||||
onSaveNo={saveCurrentCheckupNo}
|
||||
onFinish={finishCheckupFlow}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user