|
@@ -20,6 +20,8 @@ import {
|
|
|
import { styles } from './styles';
|
|
import { styles } from './styles';
|
|
|
|
|
|
|
|
import { ActivityIndicator } from 'react-native-paper';
|
|
import { ActivityIndicator } from 'react-native-paper';
|
|
|
|
|
+import ActionSheet, { ActionSheetRef } from 'react-native-actions-sheet';
|
|
|
|
|
+import SummarySheet from '../Components/SummarySheet';
|
|
|
|
|
|
|
|
interface DateValue {
|
|
interface DateValue {
|
|
|
year: number | null;
|
|
year: number | null;
|
|
@@ -55,6 +57,17 @@ const AddNewTripScreen = ({ route }: { route: any }) => {
|
|
|
const [calendarVisibleForIndex, setCalendarVisibleForIndex] = useState<number | null>(null);
|
|
const [calendarVisibleForIndex, setCalendarVisibleForIndex] = useState<number | null>(null);
|
|
|
|
|
|
|
|
const [regionErrors, setRegionErrors] = useState<{ [instanceId: string]: string }>({});
|
|
const [regionErrors, setRegionErrors] = useState<{ [instanceId: string]: string }>({});
|
|
|
|
|
+ const [shouldScrollToEmpty, setShouldScrollToEmpty] = useState(false);
|
|
|
|
|
+
|
|
|
|
|
+ const summarySheetRef = React.useRef<ActionSheetRef>(null);
|
|
|
|
|
+
|
|
|
|
|
+ const [summaryData, setSummaryData] = useState({
|
|
|
|
|
+ totalDays: 0,
|
|
|
|
|
+ perRegion: []
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const [pendingAction, setPendingAction] = useState<'save' | 'update' | null>(null);
|
|
|
|
|
+ const isClosingRef = React.useRef<boolean>(null);
|
|
|
|
|
|
|
|
const { mutate: saveNewTrip } = usePostSetNewTripMutation();
|
|
const { mutate: saveNewTrip } = usePostSetNewTripMutation();
|
|
|
const { mutate: updateTrip } = usePostUpdateTripMutation();
|
|
const { mutate: updateTrip } = usePostUpdateTripMutation();
|
|
@@ -72,6 +85,64 @@ const AddNewTripScreen = ({ route }: { route: any }) => {
|
|
|
scrollRef.current?.scrollTo({ y: idx * itemHeight, animated: true });
|
|
scrollRef.current?.scrollTo({ y: idx * itemHeight, animated: true });
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+ const scrollToEmpty = () => {
|
|
|
|
|
+ if (!regions?.length) return;
|
|
|
|
|
+
|
|
|
|
|
+ const idx = regions.findIndex((r) => {
|
|
|
|
|
+ const startOK = isFullDate(r.visitStartDate);
|
|
|
|
|
+ const endOK = isFullDate(r.visitEndDate);
|
|
|
|
|
+ return !(startOK && endOK);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (idx === -1) return;
|
|
|
|
|
+
|
|
|
|
|
+ const itemHeight = 160;
|
|
|
|
|
+ scrollRef.current?.scrollTo({
|
|
|
|
|
+ y: idx * itemHeight,
|
|
|
|
|
+ animated: true
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const computeTripSummary = (list: RegionWithDates[]) => {
|
|
|
|
|
+ const daySet = new Set<string>();
|
|
|
|
|
+ const perRegionMap: Record<number, any> = {};
|
|
|
|
|
+
|
|
|
|
|
+ for (const r of list) {
|
|
|
|
|
+ if (!isFullDate(r.visitStartDate) || !isFullDate(r.visitEndDate)) continue;
|
|
|
|
|
+
|
|
|
|
|
+ const startISO = dateValueToISO(r.visitStartDate)!;
|
|
|
|
|
+ const endISO = dateValueToISO(r.visitEndDate)!;
|
|
|
|
|
+
|
|
|
|
|
+ let cursor = moment(startISO);
|
|
|
|
|
+ const end = moment(endISO);
|
|
|
|
|
+
|
|
|
|
|
+ let regionDays = 0;
|
|
|
|
|
+
|
|
|
|
|
+ while (cursor.isSameOrBefore(end)) {
|
|
|
|
|
+ daySet.add(cursor.format('YYYY-MM-DD'));
|
|
|
|
|
+ regionDays++;
|
|
|
|
|
+ cursor = cursor.add(1, 'day');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!perRegionMap[r.id]) {
|
|
|
|
|
+ perRegionMap[r.id] = {
|
|
|
|
|
+ id: r.id,
|
|
|
|
|
+ name: r.region_name,
|
|
|
|
|
+ days: 0,
|
|
|
|
|
+ flag1: r.flag1,
|
|
|
|
|
+ flag2: r?.flag2
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ perRegionMap[r.id].days += regionDays;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ totalDays: daySet.size,
|
|
|
|
|
+ perRegion: Object.values(perRegionMap)
|
|
|
|
|
+ };
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
const autoFillAfterAppend = (list: RegionWithDates[]) => {
|
|
const autoFillAfterAppend = (list: RegionWithDates[]) => {
|
|
|
if (!list || list.length === 0) return list;
|
|
if (!list || list.length === 0) return list;
|
|
|
|
|
|
|
@@ -121,11 +192,21 @@ const AddNewTripScreen = ({ route }: { route: any }) => {
|
|
|
return updated;
|
|
return updated;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ if (!shouldScrollToEmpty || !regions) return;
|
|
|
|
|
+
|
|
|
|
|
+ requestAnimationFrame(() => {
|
|
|
|
|
+ scrollToEmpty();
|
|
|
|
|
+ setShouldScrollToEmpty(false);
|
|
|
|
|
+ });
|
|
|
|
|
+ }, [shouldScrollToEmpty, regions]);
|
|
|
|
|
+
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
if (route.params?.regionsToSave) {
|
|
if (route.params?.regionsToSave) {
|
|
|
const filled = autoFillAfterAppend(route.params.regionsToSave);
|
|
const filled = autoFillAfterAppend(route.params.regionsToSave);
|
|
|
|
|
|
|
|
setRegions(filled);
|
|
setRegions(filled);
|
|
|
|
|
+ setShouldScrollToEmpty(true);
|
|
|
}
|
|
}
|
|
|
}, [route.params?.regionsToSave]);
|
|
}, [route.params?.regionsToSave]);
|
|
|
|
|
|
|
@@ -348,13 +429,10 @@ const AddNewTripScreen = ({ route }: { route: any }) => {
|
|
|
return { date_from: minStart, date_to: maxEnd };
|
|
return { date_from: minStart, date_to: maxEnd };
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- const handleSaveNewTrip = () => {
|
|
|
|
|
- if (regions) {
|
|
|
|
|
- if (!validateRegionsDates(regions)) {
|
|
|
|
|
- scrollToError(regionErrors);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const performSaveNewTrip = async () => {
|
|
|
|
|
+ if (!regions) return;
|
|
|
|
|
|
|
|
|
|
+ try {
|
|
|
setIsLoading('save');
|
|
setIsLoading('save');
|
|
|
const regionsData = regions.map((region) => {
|
|
const regionsData = regions.map((region) => {
|
|
|
return {
|
|
return {
|
|
@@ -398,16 +476,15 @@ const AddNewTripScreen = ({ route }: { route: any }) => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
);
|
|
);
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.log('Save trip failed', err);
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- const handleUpdateTrip = () => {
|
|
|
|
|
- if (regions) {
|
|
|
|
|
- if (!validateRegionsDates(regions)) {
|
|
|
|
|
- scrollToError(regionErrors);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const performUpdateTrip = async () => {
|
|
|
|
|
+ if (!regions) return;
|
|
|
|
|
|
|
|
|
|
+ try {
|
|
|
setIsLoading('update');
|
|
setIsLoading('update');
|
|
|
const regionsData = regions.map((region) => {
|
|
const regionsData = regions.map((region) => {
|
|
|
return {
|
|
return {
|
|
@@ -452,6 +529,52 @@ const AddNewTripScreen = ({ route }: { route: any }) => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
);
|
|
);
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.log('Update trip failed', err);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleSaveNewTrip = () => {
|
|
|
|
|
+ if (regions) {
|
|
|
|
|
+ if (!validateRegionsDates(regions)) {
|
|
|
|
|
+ scrollToError(regionErrors);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const summary = computeTripSummary(regions);
|
|
|
|
|
+ if (summary) {
|
|
|
|
|
+ setSummaryData({
|
|
|
|
|
+ ...summary,
|
|
|
|
|
+ perRegion: summary.perRegion?.sort((a: any, b: any) => b.days - a.days) || []
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ setSummaryData({ totalDays: 0, perRegion: [] });
|
|
|
|
|
+ }
|
|
|
|
|
+ setPendingAction('save');
|
|
|
|
|
+
|
|
|
|
|
+ summarySheetRef.current?.show();
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleUpdateTrip = () => {
|
|
|
|
|
+ if (regions) {
|
|
|
|
|
+ if (!validateRegionsDates(regions)) {
|
|
|
|
|
+ scrollToError(regionErrors);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const summary = computeTripSummary(regions);
|
|
|
|
|
+ if (summary) {
|
|
|
|
|
+ setSummaryData({
|
|
|
|
|
+ ...summary,
|
|
|
|
|
+ perRegion: summary.perRegion?.sort((a: any, b: any) => b.days - a.days) || []
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ setSummaryData({ totalDays: 0, perRegion: [] });
|
|
|
|
|
+ }
|
|
|
|
|
+ setPendingAction('update');
|
|
|
|
|
+
|
|
|
|
|
+ summarySheetRef.current?.show();
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
@@ -464,12 +587,12 @@ const AddNewTripScreen = ({ route }: { route: any }) => {
|
|
|
showsVerticalScrollIndicator={false}
|
|
showsVerticalScrollIndicator={false}
|
|
|
>
|
|
>
|
|
|
<Input
|
|
<Input
|
|
|
- placeholder="Add description and all interesting moments of your trip"
|
|
|
|
|
|
|
+ // placeholder="Add description and all interesting moments of your trip"
|
|
|
inputMode={'text'}
|
|
inputMode={'text'}
|
|
|
onChange={(text) => setDescription(text)}
|
|
onChange={(text) => setDescription(text)}
|
|
|
value={description}
|
|
value={description}
|
|
|
header="Description"
|
|
header="Description"
|
|
|
- height={54}
|
|
|
|
|
|
|
+ height={36}
|
|
|
multiline={true}
|
|
multiline={true}
|
|
|
/>
|
|
/>
|
|
|
|
|
|
|
@@ -632,6 +755,21 @@ const AddNewTripScreen = ({ route }: { route: any }) => {
|
|
|
}
|
|
}
|
|
|
}}
|
|
}}
|
|
|
/>
|
|
/>
|
|
|
|
|
+
|
|
|
|
|
+ <SummarySheet
|
|
|
|
|
+ ref={summarySheetRef}
|
|
|
|
|
+ summary={summaryData}
|
|
|
|
|
+ onConfirm={async () => {
|
|
|
|
|
+ summarySheetRef.current?.hide();
|
|
|
|
|
+ isClosingRef.current = true;
|
|
|
|
|
+ }}
|
|
|
|
|
+ onClose={async () => {
|
|
|
|
|
+ if (!isClosingRef.current) return;
|
|
|
|
|
+ if (pendingAction === 'save') performSaveNewTrip();
|
|
|
|
|
+ if (pendingAction === 'update') performUpdateTrip();
|
|
|
|
|
+ isClosingRef.current = false;
|
|
|
|
|
+ }}
|
|
|
|
|
+ />
|
|
|
</PageWrapper>
|
|
</PageWrapper>
|
|
|
);
|
|
);
|
|
|
};
|
|
};
|