Update step feature to allow users to jump to any step regardless of completion status
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import {BackHandler, StatusBar, Text, View} from 'react-native';
|
||||
import styles from './styles';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
@ -12,11 +12,11 @@ import StepIndicator from '../../components/StepIndicator';
|
||||
import DialogApplicationPassport from '../../components/dialog/DialogApplicationPassport';
|
||||
import DialogDontHaveYetPassport from '../../components/dialog/DialogDontHaveYetPassport';
|
||||
import DialogLostOrDamagedPassport from '../../components/dialog/DialogLostOrDamagedPassport';
|
||||
import passportAppointmentData from '../../data/History/PassportAppointmentData';
|
||||
import Step7ApplicationFeeDetails from './steps/Step7ApplicationFeeDetails/Step7ApplicationFeeDetails';
|
||||
import Step6ApplicationTypeAndApplicantData from './steps/Step6ApplicationTypeAndApplicantData/Step6ApplicationTypeAndApplicantData';
|
||||
import Step5ApplicationTypeAndApplicantData from './steps/Step5ApplicationTypeAndApplicantData/Step5ApplicationTypeAndApplicantData';
|
||||
import Step3UploadDocuments from './steps/Step3UploadDocuments/Step3UploadDocuments';
|
||||
import {ToastAndroid} from 'react-native';
|
||||
|
||||
// Options Data
|
||||
import passportForOptions from '../../data/Options/PassportForOptions';
|
||||
@ -74,6 +74,9 @@ type RenderApplicationStepsContentProps = {
|
||||
showSelectDateSheet: () => void;
|
||||
selectedDestinationCountryOption: string;
|
||||
setSelectedDestinationCountryOption: (val: string) => void;
|
||||
setStepValidationStatus: React.Dispatch<
|
||||
React.SetStateAction<Record<number, 'incomplete' | 'completed' | 'invalid'>>
|
||||
>;
|
||||
};
|
||||
|
||||
const RenderApplicationStepsContent = (
|
||||
@ -103,6 +106,7 @@ const RenderApplicationStepsContent = (
|
||||
showEditDataSheet,
|
||||
showSearchLocationSheet,
|
||||
showSelectDateSheet,
|
||||
setStepValidationStatus,
|
||||
} = props;
|
||||
|
||||
if (step === 1) {
|
||||
@ -113,7 +117,16 @@ const RenderApplicationStepsContent = (
|
||||
return <Step1VerifyNikSubStep2 setSubStep={setSubStep} />;
|
||||
case 3:
|
||||
return (
|
||||
<Step1VerifyNikSubStep3 setStep={setStep} setSubStep={setSubStep} />
|
||||
<Step1VerifyNikSubStep3
|
||||
setStep={setStep}
|
||||
setSubStep={setSubStep}
|
||||
onSubStepValidation={isValid => {
|
||||
setStepValidationStatus(prev => ({
|
||||
...prev,
|
||||
1: isValid ? 'completed' : 'invalid',
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
@ -208,6 +221,12 @@ const RenderApplicationStepsContent = (
|
||||
setSubStep={setSubStep}
|
||||
selectedOption={selectedOption}
|
||||
setSelectedOption={setSelectedOption}
|
||||
onSubStepValidation={isValid => {
|
||||
setStepValidationStatus(prev => ({
|
||||
...prev,
|
||||
2: isValid ? 'completed' : 'invalid',
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
@ -246,6 +265,12 @@ const RenderApplicationStepsContent = (
|
||||
showCivilStatusDocumentsInfoDialog
|
||||
}
|
||||
selectedDestinationCountryOption={selectedDestinationCountryOption}
|
||||
onSubStepValidation={isValid => {
|
||||
setStepValidationStatus(prev => ({
|
||||
...prev,
|
||||
3: isValid ? 'completed' : 'invalid',
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case 5:
|
||||
@ -253,7 +278,6 @@ const RenderApplicationStepsContent = (
|
||||
<Step5ApplicationTypeAndApplicantData
|
||||
setStep={setStep}
|
||||
setSubStep={setSubStep}
|
||||
passportAppointmentData={passportAppointmentData}
|
||||
showEditDataSheet={showEditDataSheet}
|
||||
/>
|
||||
);
|
||||
@ -340,10 +364,24 @@ function RegularPassportScreen() {
|
||||
useState(false);
|
||||
const [step, setStep] = useState(1);
|
||||
const [subStep, setSubStep] = useState(1);
|
||||
|
||||
const [completedSteps, setCompletedSteps] = useState<number[]>(
|
||||
[...Array(step - 1)].map((_, i) => i + 1),
|
||||
);
|
||||
|
||||
// Step status states
|
||||
const [stepValidationStatus, setStepValidationStatus] = useState<{
|
||||
[key: number]: 'completed' | 'incomplete' | 'invalid';
|
||||
}>({
|
||||
1: 'incomplete',
|
||||
2: 'incomplete',
|
||||
3: 'incomplete',
|
||||
4: 'incomplete',
|
||||
5: 'incomplete',
|
||||
6: 'incomplete',
|
||||
7: 'incomplete',
|
||||
});
|
||||
|
||||
// Dialog visibility states
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [visibleDontHaveYetDialog, setVisibleDontHaveYetDialog] =
|
||||
@ -416,6 +454,8 @@ function RegularPassportScreen() {
|
||||
const showSelectDateSheet = () => setVisibleSelectDateSheet(true);
|
||||
const hideSelectDateSheet = () => setVisibleSelectDateSheet(false);
|
||||
|
||||
const editedCompletedRef = useRef<Set<number>>(new Set());
|
||||
|
||||
const stepTitles: {[key: number]: string} = {
|
||||
1: 'Verifikasi NIK',
|
||||
2: 'Kuesioner Permohonan Paspor (PERDIM)',
|
||||
@ -481,7 +521,68 @@ function RegularPassportScreen() {
|
||||
currentStep={step}
|
||||
totalSteps={7}
|
||||
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 isStep1to4Completed = [1, 2, 3, 4].every(
|
||||
s => stepValidationStatus[s] === 'completed',
|
||||
);
|
||||
|
||||
if (!isCurrentStepIn5to7 && isTargetStepIn5to7) {
|
||||
ToastAndroid.show(
|
||||
'Lengkapi langkah 1 – 4 dulu',
|
||||
ToastAndroid.SHORT,
|
||||
);
|
||||
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;
|
||||
});
|
||||
|
||||
setStep(targetStep);
|
||||
setSubStep(1);
|
||||
}}
|
||||
/>
|
||||
|
||||
<RenderApplicationStepsContent
|
||||
step={step}
|
||||
subStep={subStep}
|
||||
@ -512,6 +613,7 @@ function RegularPassportScreen() {
|
||||
showEditDataSheet={showEditDataSheet}
|
||||
showSearchLocationSheet={showSearchLocationSheet}
|
||||
showSelectDateSheet={showSelectDateSheet}
|
||||
setStepValidationStatus={setStepValidationStatus}
|
||||
/>
|
||||
</View>
|
||||
|
||||
|
@ -10,12 +10,38 @@ import Colors from '../../../../../assets/styles/Colors';
|
||||
type Step1VerifyNikSubStep3Props = {
|
||||
setStep: (val: number) => void;
|
||||
setSubStep: (val: number) => void;
|
||||
onSubStepValidation: (isValid: boolean) => void;
|
||||
};
|
||||
|
||||
const Step1VerifyNikSubStep3 = ({
|
||||
setStep,
|
||||
setSubStep,
|
||||
onSubStepValidation,
|
||||
}: 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 onNextPress = () => {
|
||||
const isFormValid =
|
||||
fullName.trim() !== '' &&
|
||||
nik.trim() !== '' &&
|
||||
birthDate.trim() !== '' &&
|
||||
gender.trim() !== '' &&
|
||||
civilStatus.trim() !== '';
|
||||
|
||||
if (isFormValid) {
|
||||
onSubStepValidation(true);
|
||||
} else {
|
||||
onSubStepValidation(false);
|
||||
}
|
||||
|
||||
setStep(2);
|
||||
setSubStep(1);
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.subStepContainer}>
|
||||
@ -28,11 +54,15 @@ const Step1VerifyNikSubStep3 = ({
|
||||
title="Nama Lengkap Pemohon"
|
||||
placeholder="Nama Lengkap Anda"
|
||||
isRequired
|
||||
value={fullName}
|
||||
onChangeText={setFullName}
|
||||
/>
|
||||
<TextInputComponent
|
||||
title="NIK"
|
||||
placeholder="Nama NIK Anda"
|
||||
isRequired
|
||||
value={nik}
|
||||
onChangeText={setNik}
|
||||
/>
|
||||
<View style={styles.subStepTextInputRowContainer}>
|
||||
<View style={styles.subStepTextInputFlex}>
|
||||
@ -41,6 +71,8 @@ const Step1VerifyNikSubStep3 = ({
|
||||
placeholder="DD/MM/YYYY"
|
||||
isRequired
|
||||
isDate
|
||||
value={birthDate}
|
||||
onChangeText={setBirthDate}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.subStepTextInputFlex}>
|
||||
@ -50,6 +82,8 @@ const Step1VerifyNikSubStep3 = ({
|
||||
isRequired
|
||||
isDropdown
|
||||
dropdownItemData={genderData}
|
||||
value={gender}
|
||||
onChangeText={setGender}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
@ -59,16 +93,15 @@ const Step1VerifyNikSubStep3 = ({
|
||||
isRequired
|
||||
isDropdown
|
||||
dropdownItemData={civilStatusData}
|
||||
value={civilStatus}
|
||||
onChangeText={setCivilStatus}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles.subStepButtonContainer}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => {
|
||||
setStep(2);
|
||||
setSubStep(1);
|
||||
}}
|
||||
onPress={onNextPress}
|
||||
style={styles.subStepButtonContained}
|
||||
textColor={Colors.neutral100.color}>
|
||||
Lanjut
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, {useState} from 'react';
|
||||
import {ScrollView, View, Text, Pressable} from 'react-native';
|
||||
import {Button} from 'react-native-paper';
|
||||
import styles from '../styles';
|
||||
@ -14,6 +14,7 @@ type Step2PassportApplicationQuestionnaireSubStep11Props = {
|
||||
setSubStep: (subStep: number) => void;
|
||||
selectedOption: string;
|
||||
setSelectedOption: (value: string) => void;
|
||||
onSubStepValidation: (isValid: boolean) => void;
|
||||
};
|
||||
|
||||
const Step2PassportApplicationQuestionnaireSubStep11 = ({
|
||||
@ -21,7 +22,28 @@ const Step2PassportApplicationQuestionnaireSubStep11 = ({
|
||||
setSubStep,
|
||||
selectedOption,
|
||||
setSelectedOption,
|
||||
onSubStepValidation,
|
||||
}: Step2PassportApplicationQuestionnaireSubStep11Props) => {
|
||||
const [relativeName, setRelativeName] = useState('');
|
||||
const [phoneNumber, setPhoneNumber] = useState('');
|
||||
const [relationship, setRelationship] = useState('');
|
||||
|
||||
const onNextPress = () => {
|
||||
const isFormValid =
|
||||
relativeName.trim() !== '' &&
|
||||
phoneNumber.trim() !== '' &&
|
||||
relationship.trim() !== '' &&
|
||||
selectedOption.trim() !== '';
|
||||
|
||||
if (isFormValid) {
|
||||
onSubStepValidation(true);
|
||||
} else {
|
||||
onSubStepValidation(false);
|
||||
}
|
||||
|
||||
setStep(3);
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.subStepContainer}>
|
||||
@ -56,11 +78,15 @@ const Step2PassportApplicationQuestionnaireSubStep11 = ({
|
||||
<TextInputComponent
|
||||
title="Nama Kerabat"
|
||||
placeholder="Masukkan Nama Kerabat Anda"
|
||||
value={relativeName}
|
||||
onChangeText={setRelativeName}
|
||||
/>
|
||||
|
||||
<TextInputComponent
|
||||
title="Nomor Telepon"
|
||||
placeholder="Contoh: 08513456789"
|
||||
value={phoneNumber}
|
||||
onChangeText={setPhoneNumber}
|
||||
/>
|
||||
|
||||
<TextInputComponent
|
||||
@ -68,6 +94,8 @@ const Step2PassportApplicationQuestionnaireSubStep11 = ({
|
||||
placeholder="Pilih Hubungan"
|
||||
isDropdown
|
||||
dropdownItemData={familyRelationshipData}
|
||||
value={relationship}
|
||||
onChangeText={setRelationship}
|
||||
/>
|
||||
|
||||
{destinationFamilyContactOptions.map(options => (
|
||||
@ -84,10 +112,7 @@ const Step2PassportApplicationQuestionnaireSubStep11 = ({
|
||||
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => {
|
||||
setStep(3);
|
||||
setSubStep(1);
|
||||
}}
|
||||
onPress={onNextPress}
|
||||
style={styles.subStepButtonContained}
|
||||
textColor={Colors.neutral100.color}>
|
||||
Lanjut
|
||||
|
@ -17,6 +17,8 @@ interface DocumentUploadSectionProps {
|
||||
isRequired?: boolean;
|
||||
isIcon?: boolean;
|
||||
showDialogCivilStatusDocumentsInfo?: () => void;
|
||||
onUploadSuccess?: () => void;
|
||||
onDelete?: () => void;
|
||||
}
|
||||
|
||||
interface Step3UploadDocumentsProps {
|
||||
@ -25,6 +27,7 @@ interface Step3UploadDocumentsProps {
|
||||
selectedPassportOption: string;
|
||||
selectedDestinationCountryOption: string;
|
||||
showCivilStatusDocumentsInfoDialog: () => void;
|
||||
onSubStepValidation: (isValid: boolean) => void;
|
||||
}
|
||||
|
||||
const BackButton = (props: BackButtonProps) => {
|
||||
@ -47,7 +50,15 @@ const BackButton = (props: BackButtonProps) => {
|
||||
};
|
||||
|
||||
const DocumentUploadSection = (props: DocumentUploadSectionProps) => {
|
||||
const {title, isRequired, isIcon, showDialogCivilStatusDocumentsInfo} = props;
|
||||
const {
|
||||
title,
|
||||
isRequired,
|
||||
isIcon,
|
||||
showDialogCivilStatusDocumentsInfo,
|
||||
onUploadSuccess,
|
||||
onDelete,
|
||||
} = props;
|
||||
|
||||
const [uploadedFileName, setUploadedFileName] = useState<string | null>(null);
|
||||
|
||||
const handleUpload = (p0: string) => {
|
||||
@ -60,10 +71,12 @@ const DocumentUploadSection = (props: DocumentUploadSectionProps) => {
|
||||
}
|
||||
|
||||
setUploadedFileName(fileName);
|
||||
onUploadSuccess?.();
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
setUploadedFileName(null);
|
||||
onDelete?.();
|
||||
};
|
||||
|
||||
return (
|
||||
@ -141,7 +154,31 @@ const Step3UploadDocuments = (props: Step3UploadDocumentsProps) => {
|
||||
selectedPassportOption,
|
||||
selectedDestinationCountryOption,
|
||||
showCivilStatusDocumentsInfoDialog,
|
||||
onSubStepValidation,
|
||||
} = props;
|
||||
|
||||
const [isKTPUploaded, setIsKTPUploaded] = useState(false);
|
||||
const [isFamilyCardUploaded, setIsFamilyCardUploaded] = useState(false);
|
||||
const [isCivilStatusUploaded, setIsCivilStatusUploaded] = useState(false);
|
||||
const [isOldPassportUploaded, setIsOldPassportUploaded] = useState(false);
|
||||
|
||||
const onNextPress = () => {
|
||||
const isFormValid =
|
||||
isKTPUploaded &&
|
||||
isFamilyCardUploaded &&
|
||||
isCivilStatusUploaded &&
|
||||
(selectedPassportOption !== 'already' || isOldPassportUploaded);
|
||||
|
||||
if (isFormValid) {
|
||||
onSubStepValidation(true);
|
||||
} else {
|
||||
onSubStepValidation(false);
|
||||
}
|
||||
|
||||
setStep(4);
|
||||
setSubStep(1);
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.subStepContainer}>
|
||||
@ -242,26 +279,39 @@ const Step3UploadDocuments = (props: Step3UploadDocumentsProps) => {
|
||||
</View>
|
||||
|
||||
<View style={styles.subStepSectionButtonContainer}>
|
||||
<DocumentUploadSection title="e-KTP" isRequired />
|
||||
<DocumentUploadSection title="Kartu Keluarga" />
|
||||
<DocumentUploadSection
|
||||
title="e-KTP"
|
||||
isRequired
|
||||
onUploadSuccess={() => setIsKTPUploaded(true)}
|
||||
onDelete={() => setIsKTPUploaded(false)}
|
||||
/>
|
||||
<DocumentUploadSection
|
||||
title="Kartu Keluarga"
|
||||
onUploadSuccess={() => setIsFamilyCardUploaded(true)}
|
||||
onDelete={() => setIsFamilyCardUploaded(false)}
|
||||
/>
|
||||
<DocumentUploadSection
|
||||
title="Akta kelahiran/ijazah/akta perkawinan/buku nikah/surat baptis"
|
||||
isIcon
|
||||
showDialogCivilStatusDocumentsInfo={
|
||||
showCivilStatusDocumentsInfoDialog
|
||||
}
|
||||
onUploadSuccess={() => setIsCivilStatusUploaded(true)}
|
||||
onDelete={() => setIsCivilStatusUploaded(false)}
|
||||
/>
|
||||
{selectedPassportOption === 'already' && (
|
||||
<DocumentUploadSection title="Paspor Lama" isRequired />
|
||||
<DocumentUploadSection
|
||||
title="Paspor Lama"
|
||||
isRequired
|
||||
onUploadSuccess={() => setIsOldPassportUploaded(true)}
|
||||
onDelete={() => setIsOldPassportUploaded(false)}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => {
|
||||
setStep(4);
|
||||
setSubStep(1);
|
||||
}}
|
||||
onPress={onNextPress}
|
||||
style={styles.subStepButtonContained}
|
||||
textColor={Colors.neutral100.color}>
|
||||
Lanjut
|
||||
|
Reference in New Issue
Block a user