diff --git a/android/app/src/main/ic_launcher-playstore.png b/android/app/src/main/ic_launcher-playstore.png index 9ceea19..8fe4db3 100644 Binary files a/android/app/src/main/ic_launcher-playstore.png and b/android/app/src/main/ic_launcher-playstore.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp index d8481cd..54d98d7 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp index be724f3..f6263b7 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp index 2babee9..8932e00 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp index ccc1963..5ad29bb 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp index 961c967..bc27e0d 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp index 6959a8b..e7b3544 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp index b4e44ac..a6de76e 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp index 3b09d20..76927b1 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp index 36b15d2..6dcfa91 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp index cd1e48d..08c7228 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp index 2a80790..ea9279a 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp index acce0ad..8805178 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp index e723bbd..32d8e53 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp index 60b5469..01dfd84 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp index a392476..dd48628 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml index f4d1d22..c5d5899 100644 --- a/android/app/src/main/res/values/ic_launcher_background.xml +++ b/android/app/src/main/res/values/ic_launcher_background.xml @@ -1,4 +1,4 @@ - #2B3A51 + #FFFFFF \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 855c4f2..9767741 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "@babel/core": "^7.25.2", "@babel/preset-env": "^7.25.3", "@babel/runtime": "^7.25.0", - "@react-native-community/cli": "15.0.1", + "@react-native-community/cli": "^15.0.1", "@react-native-community/cli-platform-android": "15.0.1", "@react-native-community/cli-platform-ios": "15.0.1", "@react-native/babel-preset": "0.78.0", diff --git a/package.json b/package.json index 3625145..26b783e 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "@babel/core": "^7.25.2", "@babel/preset-env": "^7.25.3", "@babel/runtime": "^7.25.0", - "@react-native-community/cli": "15.0.1", + "@react-native-community/cli": "^15.0.1", "@react-native-community/cli-platform-android": "15.0.1", "@react-native-community/cli-platform-ios": "15.0.1", "@react-native/babel-preset": "0.78.0", diff --git a/src/components/InfoToast.tsx b/src/components/InfoToast.tsx new file mode 100644 index 0000000..b3ddc81 --- /dev/null +++ b/src/components/InfoToast.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import {StyleSheet, Text, View} from 'react-native'; +import {Snackbar} from 'react-native-paper'; +import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; +import Colors from '../../assets/styles/Colors'; + +type InfoToastProps = { + visible: boolean; + message: string; + onDismiss: () => void; + duration?: number; +}; + +const InfoToast = ({ + visible, + message, + onDismiss, + duration = 2000, +}: InfoToastProps) => { + return ( + + + + {message} + + + ); +}; + +const styles = StyleSheet.create({ + snackbar: { + backgroundColor: Colors.secondary10.color, + borderRadius: 100, + margin: 16, + alignSelf: 'center', + }, + contentContainer: { + flexDirection: 'row', + alignItems: 'center', + }, + icon: { + marginRight: 8, + }, + message: { + color: 'white', + }, +}); + +export default InfoToast; diff --git a/src/components/TextInput.tsx b/src/components/TextInput.tsx index 40a5e30..2aec2bc 100644 --- a/src/components/TextInput.tsx +++ b/src/components/TextInput.tsx @@ -194,7 +194,10 @@ const TextInputComponent = (props: TextInputComponentProps) => { )} { )} { ; type RenderApplicationStepsContentProps = { + navigation: any; step: number; subStep: number; setStep: (step: number) => void; @@ -74,15 +93,16 @@ type RenderApplicationStepsContentProps = { showSelectDateSheet: () => void; selectedDestinationCountryOption: string; setSelectedDestinationCountryOption: (val: string) => void; - setStepValidationStatus: React.Dispatch< - React.SetStateAction> - >; + stepValidationStatus: StepValidationStatus; + setStepValidationStatus: StepValidationStatusSetter; + editedCompletedRef: RefObject>; }; const RenderApplicationStepsContent = ( props: RenderApplicationStepsContentProps, ) => { const { + navigation, step, subStep, setStep, @@ -107,6 +127,7 @@ const RenderApplicationStepsContent = ( showSearchLocationSheet, showSelectDateSheet, setStepValidationStatus, + editedCompletedRef, } = props; if (step === 1) { @@ -118,14 +139,17 @@ const RenderApplicationStepsContent = ( case 3: return ( { setStepValidationStatus(prev => ({ ...prev, 1: isValid ? 'completed' : 'invalid', })); }} + editedCompletedRef={editedCompletedRef} /> ); default: @@ -138,11 +162,14 @@ const RenderApplicationStepsContent = ( case 1: return ( ); case 2: @@ -209,16 +236,27 @@ const RenderApplicationStepsContent = ( case 10: return ( { + setStepValidationStatus(prev => ({ + ...prev, + 2: isValid ? 'completed' : 'invalid', + })); + }} + editedCompletedRef={editedCompletedRef} /> ); case 11: return ( { @@ -227,6 +265,7 @@ const RenderApplicationStepsContent = ( 2: isValid ? 'completed' : 'invalid', })); }} + editedCompletedRef={editedCompletedRef} /> ); default: @@ -239,17 +278,29 @@ const RenderApplicationStepsContent = ( case 1: return ( ); case 2: return ( { + setStepValidationStatus(prev => ({ + ...prev, + 4: isValid ? 'completed' : 'invalid', + })); + }} + editedCompletedRef={editedCompletedRef} /> ); } @@ -258,8 +309,10 @@ const RenderApplicationStepsContent = ( case 3: return ( ); case 5: return ( { + setStepValidationStatus(prev => ({ + ...prev, + 5: 'completed', + })); + }} /> ); case 6: @@ -297,6 +357,12 @@ const RenderApplicationStepsContent = ( { + setStepValidationStatus(prev => ({ + ...prev, + 7: 'completed', + })); + }} /> ); default: @@ -362,9 +428,12 @@ function RegularPassportScreen() { const [checkedOption, setCheckedOption] = useState(false); const [showApplicationStepsContent, setShowApplicationStepsContent] = useState(false); + + const [toastVisible, setToastVisible] = useState(false); + const [toastMessage, setToastMessage] = useState(''); + const [step, setStep] = useState(1); const [subStep, setSubStep] = useState(1); - const [completedSteps, setCompletedSteps] = useState( [...Array(step - 1)].map((_, i) => i + 1), ); @@ -512,6 +581,11 @@ function RegularPassportScreen() { }); }; + const showInfoToast = (msg: string) => { + setToastMessage(msg); + setToastVisible(true); + }; + // Render steps or questionnaire const renderApplicationStepsContent = showApplicationStepsContent ? ( <> @@ -523,67 +597,33 @@ function RegularPassportScreen() { completedSteps={completedSteps} validationStatus={stepValidationStatus} onStepPress={(targetStep: number) => { - const isCurrentStepIn5to7 = step >= 5 && step <= 7; - const isTargetStepIn1to4 = targetStep >= 1 && targetStep <= 4; - const isTargetStepIn5to7 = targetStep >= 5 && targetStep <= 7; + const isCurrentStep7 = step === 7; + const isTargetStepIn1to6 = targetStep >= 1 && targetStep <= 6; - const isStep1to4Completed = [1, 2, 3, 4].every( - s => stepValidationStatus[s] === 'completed', - ); + const toastMessage = isCurrentStep7 + ? 'Tak dapat kembali – langkah terakhir.' + : !isTargetStepIn1to6 + ? 'Lengkapi langkah 1 – 6 dulu' + : null; - if (!isCurrentStepIn5to7 && isTargetStepIn5to7) { - ToastAndroid.show( - 'Lengkapi langkah 1 – 4 dulu', - ToastAndroid.SHORT, - ); + if (toastMessage) { + showInfoToast(toastMessage); return; } - if ( - isCurrentStepIn5to7 && - isStep1to4Completed && - isTargetStepIn1to4 - ) { - ToastAndroid.show( - 'Hanya dapat berpindah di langkah 5 – 7.', - ToastAndroid.SHORT, - ); - return; - } - - setStepValidationStatus(prev => { - const next = {...prev}; - - if (step !== targetStep && editedCompletedRef.current.has(step)) { - next[step] = 'completed'; - editedCompletedRef.current.delete(step); - } - - if (prev[targetStep] === 'completed') { - editedCompletedRef.current.add(targetStep); - } - - next[targetStep] = 'incomplete'; - - if (targetStep > step) { - for (let s = 1; s < targetStep; s++) { - if (next[s] !== 'completed') next[s] = 'invalid'; - } - } else if (targetStep < step) { - for (let s = step; s > targetStep; s--) { - if (next[s] !== 'completed') next[s] = 'invalid'; - } - } - - return next; + changeStep({ + currentStep: step, + targetStep: targetStep, + setStep, + setSubStep: () => setSubStep(1), + setStepValidationStatus, + editedCompletedRef, }); - - setStep(targetStep); - setSubStep(1); }} /> @@ -672,7 +714,19 @@ function RegularPassportScreen() { visible={visibleFinalizationConfirmationDialog} onClose={hideFinalizationConfirmationDialog} onContinue={() => { - setStep(7); + setStepValidationStatus(prev => ({ + ...prev, + 6: 'completed', + })); + + const canProceedToStep7 = [1, 2, 3, 4, 5, 6].every( + s => stepValidationStatus[s] === 'completed', + ); + + !canProceedToStep7 + ? showInfoToast('Lengkapi semua langkah terlebih dahulu.') + : setStep(7); + hideFinalizationConfirmationDialog(); }} /> @@ -753,6 +807,11 @@ function RegularPassportScreen() { setShowApplicationStepsContent(true); }} /> + setToastVisible(false)} + /> ); diff --git a/src/screens/regularPassport/steps/Step1VerifyNik/Step1VerifyNikSubStep3.tsx b/src/screens/regularPassport/steps/Step1VerifyNik/Step1VerifyNikSubStep3.tsx index 26cd7ce..15100d4 100644 --- a/src/screens/regularPassport/steps/Step1VerifyNik/Step1VerifyNikSubStep3.tsx +++ b/src/screens/regularPassport/steps/Step1VerifyNik/Step1VerifyNikSubStep3.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {RefObject, useState} from 'react'; import {ScrollView, View} from 'react-native'; import {Button} from 'react-native-paper'; import styles from '../styles'; @@ -6,23 +6,31 @@ import TextInputComponent from '../../../../components/TextInput'; import genderData from '../../../../data/DropdownData/GenderData'; import civilStatusData from '../../../../data/DropdownData/CivilStatusData'; import Colors from '../../../../../assets/styles/Colors'; +import {changeStep} from '../../../../utils/stepNavigation'; +import {StepValidationStatusSetter} from '../../../../../types/step'; type Step1VerifyNikSubStep3Props = { + step: number; setStep: (val: number) => void; setSubStep: (val: number) => void; + setStepValidationStatus: StepValidationStatusSetter; onSubStepValidation: (isValid: boolean) => void; + editedCompletedRef: RefObject>; }; const Step1VerifyNikSubStep3 = ({ + step, setStep, setSubStep, + setStepValidationStatus, onSubStepValidation, + editedCompletedRef, }: Step1VerifyNikSubStep3Props) => { - const [fullName, setFullName] = React.useState(''); - const [nik, setNik] = React.useState(''); - const [birthDate, setBirthDate] = React.useState(''); - const [gender, setGender] = React.useState(''); - const [civilStatus, setCivilStatus] = React.useState(''); + const [fullName, setFullName] = useState(''); + const [nik, setNik] = useState(''); + const [birthDate, setBirthDate] = useState(''); + const [gender, setGender] = useState(''); + const [civilStatus, setCivilStatus] = useState(''); const onNextPress = () => { const isFormValid = @@ -38,8 +46,14 @@ const Step1VerifyNikSubStep3 = ({ onSubStepValidation(false); } - setStep(2); - setSubStep(1); + changeStep({ + currentStep: step, + targetStep: 2, + setStep, + setSubStep: () => setSubStep(1), + setStepValidationStatus, + editedCompletedRef, + }); }; return ( diff --git a/src/screens/regularPassport/steps/Step2PassportApplicationQuestionnaire/Step2PassportApplicationQuestionnaireSubStep1.tsx b/src/screens/regularPassport/steps/Step2PassportApplicationQuestionnaire/Step2PassportApplicationQuestionnaireSubStep1.tsx index a8eb2e7..00318cb 100644 --- a/src/screens/regularPassport/steps/Step2PassportApplicationQuestionnaire/Step2PassportApplicationQuestionnaireSubStep1.tsx +++ b/src/screens/regularPassport/steps/Step2PassportApplicationQuestionnaire/Step2PassportApplicationQuestionnaireSubStep1.tsx @@ -1,36 +1,52 @@ -import React from 'react'; +import React, {RefObject, useRef} from 'react'; import {View, Pressable, Text} from 'react-native'; import styles from '../styles'; import RadioButtonOptionComponent from '../../../../components/RadioButtonOption'; import hasHadPassportBeforeOptions from '../../../../data/Options/HasHadPassportBeforeOptions'; import {Button} from 'react-native-paper'; import Colors from '../../../../../assets/styles/Colors'; +import {changeStep} from '../../../../utils/stepNavigation'; +import {StepValidationStatusSetter} from '../../../../../types/step'; type Step2PassportApplicationQuestionnaireSubStep1Props = { + step: number; setStep: (step: number) => void; setSubStep: (subStep: number) => void; + setStepValidationStatus: StepValidationStatusSetter; selectedPassportOption: string; setSelectedPassportOption: (value: string) => void; showDontHaveYetDialog: () => void; + editedCompletedRef: RefObject>; }; const Step2PassportApplicationQuestionnaireSubStep1 = ({ + step, setStep, setSubStep, + setStepValidationStatus, selectedPassportOption, setSelectedPassportOption, showDontHaveYetDialog, + editedCompletedRef, }: Step2PassportApplicationQuestionnaireSubStep1Props) => { + const onBackPress = () => { + changeStep({ + currentStep: step, + targetStep: 1, + setStep, + setSubStep: () => setSubStep(3), + setStepValidationStatus, + editedCompletedRef, + }); + }; + return ( ({ transform: [{scale: pressed ? 0.99 : 1}], })} - onPress={() => { - setStep(1); - setSubStep(3); - }}> + onPress={onBackPress}> + Data di bawah ini harus sesuai dengan keterangan pada KTP pemohon. Data yang bertanda ( @@ -58,11 +140,15 @@ const Step4ApplicantAdditionalDataSubStep2 = ({ isRequired isDropdown dropdownItemData={jobData} + value={job} + onChangeText={setJob} /> @@ -78,6 +164,8 @@ const Step4ApplicantAdditionalDataSubStep2 = ({ title="Nama Ibu" placeholder="Masukkan nama lengkap ibu" isRequired + value={motherName} + onChangeText={setMotherName} /> @@ -152,49 +244,7 @@ const Step4ApplicantAdditionalDataSubStep2 = ({ @@ -137,4 +149,4 @@ const DetailRow = ({ ); -export default Step5Content; +export default Step5ApplicationTypeAndApplicantData; diff --git a/src/screens/regularPassport/steps/Step6ApplicationTypeAndApplicantData/Step6ApplicationTypeAndApplicantData.tsx b/src/screens/regularPassport/steps/Step6ApplicationTypeAndApplicantData/Step6ApplicationTypeAndApplicantData.tsx index 30b8cff..678c25d 100644 --- a/src/screens/regularPassport/steps/Step6ApplicationTypeAndApplicantData/Step6ApplicationTypeAndApplicantData.tsx +++ b/src/screens/regularPassport/steps/Step6ApplicationTypeAndApplicantData/Step6ApplicationTypeAndApplicantData.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useState} from 'react'; import {ScrollView, View, Text} from 'react-native'; import {Button, Divider} from 'react-native-paper'; import TextInputComponent from '../../../../components/TextInput'; @@ -15,13 +15,16 @@ type Step6ApplicationTypeAndApplicantDataProps = { showSelectDateSheet: () => void; }; -const Step6ApplicationTypeAndApplicantData = (props: Step6ApplicationTypeAndApplicantDataProps) => { +const Step6ApplicationTypeAndApplicantData = ( + props: Step6ApplicationTypeAndApplicantDataProps, +) => { const { showFinalizationConfirmationDialog, showPassportTypeInfoDialog, showSearchLocationSheet, showSelectDateSheet, } = props; + return ( diff --git a/src/screens/regularPassport/steps/Step7ApplicationFeeDetails/Step7ApplicationFeeDetails.tsx b/src/screens/regularPassport/steps/Step7ApplicationFeeDetails/Step7ApplicationFeeDetails.tsx index b0e93e8..1ed39e4 100644 --- a/src/screens/regularPassport/steps/Step7ApplicationFeeDetails/Step7ApplicationFeeDetails.tsx +++ b/src/screens/regularPassport/steps/Step7ApplicationFeeDetails/Step7ApplicationFeeDetails.tsx @@ -12,10 +12,12 @@ import {getData} from '../../../../helper/asyncStorageHelper'; type Step7ApplicationFeeDetailsProps = { showSubmitSuccessDialog: () => void; setLastCompletedSteps: () => void; + onSubStepValidation: (isValid: boolean) => void; }; const Step7ApplicationFeeDetails = (props: Step7ApplicationFeeDetailsProps) => { - const {showSubmitSuccessDialog, setLastCompletedSteps} = props; + const {showSubmitSuccessDialog, setLastCompletedSteps, onSubStepValidation} = + props; const [lastAppointment, setLastAppointment] = useState(); useEffect(() => { @@ -32,6 +34,12 @@ const Step7ApplicationFeeDetails = (props: Step7ApplicationFeeDetailsProps) => { fetchData(); }, []); + const onNextPress = () => { + onSubStepValidation(true); + showSubmitSuccessDialog(); + setLastCompletedSteps(); + }; + return ( @@ -215,10 +223,7 @@ const Step7ApplicationFeeDetails = (props: Step7ApplicationFeeDetailsProps) => { mode="contained" style={styles.subStepButtonContained} textColor={Colors.neutral100.color} - onPress={() => { - showSubmitSuccessDialog(); - setLastCompletedSteps(); - }}> + onPress={onNextPress}> Kembali ke Halaman Utama diff --git a/src/utils/stepNavigation.ts b/src/utils/stepNavigation.ts new file mode 100644 index 0000000..0ba07fa --- /dev/null +++ b/src/utils/stepNavigation.ts @@ -0,0 +1,53 @@ +import {RefObject} from 'react'; +export type StepStatus = 'completed' | 'incomplete' | 'invalid'; + +interface StepChangeParams { + currentStep: number; + targetStep: number; + setStep: (step: number) => void; + setSubStep?: (sub: number) => void; + setStepValidationStatus: React.Dispatch< + React.SetStateAction> + >; + editedCompletedRef: RefObject>; +} + +export function changeStep({ + currentStep, + targetStep, + setStep, + setSubStep, + setStepValidationStatus, + editedCompletedRef, +}: StepChangeParams) { + setStepValidationStatus(prev => { + const next = {...prev}; + + if (currentStep !== targetStep && + editedCompletedRef.current?.has(currentStep)) { + next[currentStep] = 'completed'; + editedCompletedRef.current.delete(currentStep); + } + + if (prev[targetStep] === 'completed') { + editedCompletedRef.current?.add(targetStep); + } + + next[targetStep] = 'incomplete'; + + if (targetStep > currentStep) { + for (let s = 1; s < targetStep; s++) { + if (next[s] !== 'completed') next[s] = 'invalid'; + } + } else if (targetStep < currentStep) { + for (let s = currentStep; s > targetStep; s--) { + if (next[s] !== 'completed') next[s] = 'invalid'; + } + } + + return next; + }); + + setStep(targetStep); + if (setSubStep) setSubStep(1); +} diff --git a/types/step.ts b/types/step.ts new file mode 100644 index 0000000..67a91ed --- /dev/null +++ b/types/step.ts @@ -0,0 +1,7 @@ +export type StepStatus = 'incomplete' | 'completed' | 'invalid'; + +export type StepValidationStatus = Record; + +export type StepValidationStatusSetter = React.Dispatch< + React.SetStateAction +>;