Update step feature to allow users to jump to any step regardless of completion status

This commit is contained in:
Mochammad Adhi Buchori
2025-06-16 22:09:35 +07:00
parent e2b74911f2
commit 91e93d4c10
6 changed files with 325 additions and 58 deletions

View File

@ -1,25 +1,50 @@
import React from 'react'; import React from 'react';
import {StyleSheet, Text, View} from 'react-native'; import {StyleSheet, Pressable, Text, View} from 'react-native';
import Colors from '../../assets/styles/Colors'; import Colors from '../../assets/styles/Colors';
import FontFamily from '../../assets/styles/FontFamily'; import FontFamily from '../../assets/styles/FontFamily';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
const StepIndicator = ({currentStep, totalSteps, completedSteps}: any) => { const StepIndicator = ({
currentStep,
totalSteps,
completedSteps,
onStepPress,
validationStatus,
}: any) => {
return ( return (
<View style={styles.container}> <View style={styles.container}>
{[...Array(totalSteps)].map((_, index) => { {[...Array(totalSteps)].map((_, index) => {
const stepNumber = index + 1; const stepNumber = index + 1;
const isCompleted = completedSteps.includes(stepNumber); const isCompleted = completedSteps.includes(stepNumber);
const isCurrent = currentStep === stepNumber; const isCurrent = currentStep === stepNumber;
const stepStatus = validationStatus[stepNumber];
const backgroundColor = isCompleted const backgroundColorStyle =
stepStatus === 'completed'
? Colors.indicatorGreen.color
: stepStatus === 'invalid'
? Colors.indicatorOrange.color
: Colors.neutral100.color;
const indicatorLineBackgroundColorStyle = isCompleted
? Colors.secondary30.color ? Colors.secondary30.color
: Colors.neutral100.color; : Colors.neutral100.color;
const textColor = isCompleted const borderColorStyle =
? Colors.neutral100.color stepStatus === 'completed'
: isCurrent ? Colors.indicatorGreen.color
? Colors.secondary30.color : stepStatus === 'invalid'
: Colors.secondary50.color; ? Colors.indicatorOrange.color
: isCurrent
? Colors.secondary30.color
: Colors.neutral100.color;
const textColorStyle =
isCompleted || stepStatus === 'invalid'
? Colors.neutral100.color
: isCurrent
? Colors.secondary30.color
: Colors.secondary50.color;
const textStyle = isCompleted const textStyle = isCompleted
? FontFamily.notoSansBold ? FontFamily.notoSansBold
@ -29,36 +54,53 @@ const StepIndicator = ({currentStep, totalSteps, completedSteps}: any) => {
return ( return (
<React.Fragment key={index}> <React.Fragment key={index}>
<View style={{alignItems: 'center'}}> <Pressable
<View onPress={() => onStepPress?.(stepNumber)}
style={[ style={({pressed}) => ({
styles.stepIndicatorContainer, transform: [{scale: pressed ? 0.97 : 1}],
{ })}>
backgroundColor: backgroundColor, <View style={{alignItems: 'center'}}>
borderColor: isCompleted <View
? Colors.secondary30.color style={[
: Colors.neutral100.color, styles.stepIndicatorContainer,
}, {
]}> backgroundColor: backgroundColorStyle,
<Text borderColor: borderColorStyle,
style={{ },
includeFontPadding: false, ]}>
fontSize: 12, {stepStatus === 'completed' ? (
color: textColor, <Icon
...textStyle, name="check"
}}> color={Colors.neutral100.color}
{stepNumber} size={16}
</Text> />
) : stepStatus === 'invalid' ? (
<Icon
name="alert-circle"
color={Colors.neutral100.color}
size={16}
/>
) : (
<Text
style={{
includeFontPadding: false,
fontSize: 12,
color: textColorStyle,
...textStyle,
}}>
{stepNumber}
</Text>
)}
</View>
</View> </View>
</View> </Pressable>
{index < totalSteps - 1 && ( {index < totalSteps - 1 && (
<View <View
style={[ style={[
styles.stepIndicatorLine, styles.stepIndicatorLine,
{ {
backgroundColor: isCompleted backgroundColor: indicatorLineBackgroundColorStyle,
? Colors.secondary30.color
: Colors.neutral100.color,
}, },
]} ]}
/> />
@ -93,7 +135,7 @@ const styles = StyleSheet.create({
stepIndicatorLine: { stepIndicatorLine: {
flex: 1, flex: 1,
height: 2, height: 2,
} },
}); });
export default StepIndicator; export default StepIndicator;

View File

@ -7,7 +7,7 @@ import Colors from '../../assets/styles/Colors';
import FontFamily from '../../assets/styles/FontFamily'; import FontFamily from '../../assets/styles/FontFamily';
import DateTimePicker from '@react-native-community/datetimepicker'; import DateTimePicker from '@react-native-community/datetimepicker';
import {useState} from 'react'; import {useState} from 'react';
import {Dropdown, SelectCountry} from 'react-native-element-dropdown'; import {Dropdown} from 'react-native-element-dropdown';
type DropdownItem = { type DropdownItem = {
label: string; label: string;
@ -42,6 +42,8 @@ interface TextInputComponentProps {
handleDropdownPressed?: () => void; handleDropdownPressed?: () => void;
countryValue?: string | null; countryValue?: string | null;
setCountryValue?: (country: string) => void; setCountryValue?: (country: string) => void;
value?: string;
onChangeText?: (text: string) => void;
} }
const TextInputComponent = (props: TextInputComponentProps) => { const TextInputComponent = (props: TextInputComponentProps) => {
@ -65,6 +67,8 @@ const TextInputComponent = (props: TextInputComponentProps) => {
handleDropdownPressed, handleDropdownPressed,
countryValue, countryValue,
setCountryValue, setCountryValue,
value,
onChangeText,
} = props; } = props;
const [secureText, setSecureText] = useState(isPassword); const [secureText, setSecureText] = useState(isPassword);
@ -97,6 +101,7 @@ const TextInputComponent = (props: TextInputComponentProps) => {
date.getMonth() + 1, date.getMonth() + 1,
).padStart(2, '0')}/${date.getFullYear()}`; ).padStart(2, '0')}/${date.getFullYear()}`;
setFormattedDate(formatted); setFormattedDate(formatted);
onChangeText?.(formatted);
} }
}; };
@ -189,7 +194,7 @@ const TextInputComponent = (props: TextInputComponentProps) => {
)} )}
</View> </View>
<Dropdown <Dropdown
style={[styles.dropdown, isDisabled && styles.outlineColorDisabled]} style={[styles.dropdown, isDisabled && styles.outlineColorDisabledDropdown]}
placeholderStyle={styles.placeholderDropdownStyle} placeholderStyle={styles.placeholderDropdownStyle}
selectedTextStyle={styles.selectedTextStyle} selectedTextStyle={styles.selectedTextStyle}
iconStyle={styles.iconStyle} iconStyle={styles.iconStyle}
@ -201,6 +206,7 @@ const TextInputComponent = (props: TextInputComponentProps) => {
value={dropdownValue} value={dropdownValue}
onChange={item => { onChange={item => {
setDropdownValue(item.value); setDropdownValue(item.value);
onChangeText?.(item.value);
}} }}
disable={isDisabled} disable={isDisabled}
renderRightIcon={() => <Icon name="arrow-drop-down" size={20} />} renderRightIcon={() => <Icon name="arrow-drop-down" size={20} />}
@ -258,7 +264,7 @@ const TextInputComponent = (props: TextInputComponentProps) => {
</View> </View>
)} )}
<Dropdown <Dropdown
style={[styles.dropdown, isDisabled && styles.outlineColorDisabled]} style={[styles.dropdown, isDisabled && styles.outlineColorDisabledDropdown]}
selectedTextStyle={styles.selectedTextStyle} selectedTextStyle={styles.selectedTextStyle}
placeholderStyle={styles.placeholderDropdownStyle} placeholderStyle={styles.placeholderDropdownStyle}
inputSearchStyle={styles.inputSearchStyle} inputSearchStyle={styles.inputSearchStyle}
@ -305,7 +311,7 @@ const TextInputComponent = (props: TextInputComponentProps) => {
<TextInput <TextInput
mode="outlined" mode="outlined"
placeholder={placeholder} placeholder={placeholder}
style={[inputStyle, isDisabled && styles.outlineColorDisabled]} style={[inputStyle, isDisabled && styles.outlineColorDisabledDropdown]}
theme={{roundness: 12}} theme={{roundness: 12}}
placeholderTextColor={Colors.primary60.color} placeholderTextColor={Colors.primary60.color}
editable={false} editable={false}
@ -355,6 +361,8 @@ const TextInputComponent = (props: TextInputComponentProps) => {
} }
multiline={isMultiline} multiline={isMultiline}
textColor="#48454E" textColor="#48454E"
value={value}
onChangeText={onChangeText}
/> />
{supportText && <Text style={[styles.supportText]}>{supportText}</Text>} {supportText && <Text style={[styles.supportText]}>{supportText}</Text>}
</View> </View>
@ -449,6 +457,13 @@ const styles = StyleSheet.create({
borderRadius: 12, borderRadius: 12,
borderColor: '#e3e3e5', borderColor: '#e3e3e5',
}, },
outlineColorDisabledDropdown: {
height: 58,
backgroundColor: '#F8F9FE',
borderWidth: 1,
borderRadius: 12,
borderColor: '#e3e3e5',
},
imageCountryStyle: { imageCountryStyle: {
width: 32, width: 32,
height: 20, height: 20,

View File

@ -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 {BackHandler, StatusBar, Text, View} from 'react-native';
import styles from './styles'; import styles from './styles';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
@ -12,11 +12,11 @@ import StepIndicator from '../../components/StepIndicator';
import DialogApplicationPassport from '../../components/dialog/DialogApplicationPassport'; import DialogApplicationPassport from '../../components/dialog/DialogApplicationPassport';
import DialogDontHaveYetPassport from '../../components/dialog/DialogDontHaveYetPassport'; import DialogDontHaveYetPassport from '../../components/dialog/DialogDontHaveYetPassport';
import DialogLostOrDamagedPassport from '../../components/dialog/DialogLostOrDamagedPassport'; import DialogLostOrDamagedPassport from '../../components/dialog/DialogLostOrDamagedPassport';
import passportAppointmentData from '../../data/History/PassportAppointmentData';
import Step7ApplicationFeeDetails from './steps/Step7ApplicationFeeDetails/Step7ApplicationFeeDetails'; import Step7ApplicationFeeDetails from './steps/Step7ApplicationFeeDetails/Step7ApplicationFeeDetails';
import Step6ApplicationTypeAndApplicantData from './steps/Step6ApplicationTypeAndApplicantData/Step6ApplicationTypeAndApplicantData'; import Step6ApplicationTypeAndApplicantData from './steps/Step6ApplicationTypeAndApplicantData/Step6ApplicationTypeAndApplicantData';
import Step5ApplicationTypeAndApplicantData from './steps/Step5ApplicationTypeAndApplicantData/Step5ApplicationTypeAndApplicantData'; import Step5ApplicationTypeAndApplicantData from './steps/Step5ApplicationTypeAndApplicantData/Step5ApplicationTypeAndApplicantData';
import Step3UploadDocuments from './steps/Step3UploadDocuments/Step3UploadDocuments'; import Step3UploadDocuments from './steps/Step3UploadDocuments/Step3UploadDocuments';
import {ToastAndroid} from 'react-native';
// Options Data // Options Data
import passportForOptions from '../../data/Options/PassportForOptions'; import passportForOptions from '../../data/Options/PassportForOptions';
@ -74,6 +74,9 @@ type RenderApplicationStepsContentProps = {
showSelectDateSheet: () => void; showSelectDateSheet: () => void;
selectedDestinationCountryOption: string; selectedDestinationCountryOption: string;
setSelectedDestinationCountryOption: (val: string) => void; setSelectedDestinationCountryOption: (val: string) => void;
setStepValidationStatus: React.Dispatch<
React.SetStateAction<Record<number, 'incomplete' | 'completed' | 'invalid'>>
>;
}; };
const RenderApplicationStepsContent = ( const RenderApplicationStepsContent = (
@ -103,6 +106,7 @@ const RenderApplicationStepsContent = (
showEditDataSheet, showEditDataSheet,
showSearchLocationSheet, showSearchLocationSheet,
showSelectDateSheet, showSelectDateSheet,
setStepValidationStatus,
} = props; } = props;
if (step === 1) { if (step === 1) {
@ -113,7 +117,16 @@ const RenderApplicationStepsContent = (
return <Step1VerifyNikSubStep2 setSubStep={setSubStep} />; return <Step1VerifyNikSubStep2 setSubStep={setSubStep} />;
case 3: case 3:
return ( return (
<Step1VerifyNikSubStep3 setStep={setStep} setSubStep={setSubStep} /> <Step1VerifyNikSubStep3
setStep={setStep}
setSubStep={setSubStep}
onSubStepValidation={isValid => {
setStepValidationStatus(prev => ({
...prev,
1: isValid ? 'completed' : 'invalid',
}));
}}
/>
); );
default: default:
return null; return null;
@ -208,6 +221,12 @@ const RenderApplicationStepsContent = (
setSubStep={setSubStep} setSubStep={setSubStep}
selectedOption={selectedOption} selectedOption={selectedOption}
setSelectedOption={setSelectedOption} setSelectedOption={setSelectedOption}
onSubStepValidation={isValid => {
setStepValidationStatus(prev => ({
...prev,
2: isValid ? 'completed' : 'invalid',
}));
}}
/> />
); );
default: default:
@ -246,6 +265,12 @@ const RenderApplicationStepsContent = (
showCivilStatusDocumentsInfoDialog showCivilStatusDocumentsInfoDialog
} }
selectedDestinationCountryOption={selectedDestinationCountryOption} selectedDestinationCountryOption={selectedDestinationCountryOption}
onSubStepValidation={isValid => {
setStepValidationStatus(prev => ({
...prev,
3: isValid ? 'completed' : 'invalid',
}));
}}
/> />
); );
case 5: case 5:
@ -253,7 +278,6 @@ const RenderApplicationStepsContent = (
<Step5ApplicationTypeAndApplicantData <Step5ApplicationTypeAndApplicantData
setStep={setStep} setStep={setStep}
setSubStep={setSubStep} setSubStep={setSubStep}
passportAppointmentData={passportAppointmentData}
showEditDataSheet={showEditDataSheet} showEditDataSheet={showEditDataSheet}
/> />
); );
@ -340,10 +364,24 @@ function RegularPassportScreen() {
useState(false); useState(false);
const [step, setStep] = useState(1); const [step, setStep] = useState(1);
const [subStep, setSubStep] = useState(1); const [subStep, setSubStep] = useState(1);
const [completedSteps, setCompletedSteps] = useState<number[]>( const [completedSteps, setCompletedSteps] = useState<number[]>(
[...Array(step - 1)].map((_, i) => i + 1), [...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 // Dialog visibility states
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [visibleDontHaveYetDialog, setVisibleDontHaveYetDialog] = const [visibleDontHaveYetDialog, setVisibleDontHaveYetDialog] =
@ -416,6 +454,8 @@ function RegularPassportScreen() {
const showSelectDateSheet = () => setVisibleSelectDateSheet(true); const showSelectDateSheet = () => setVisibleSelectDateSheet(true);
const hideSelectDateSheet = () => setVisibleSelectDateSheet(false); const hideSelectDateSheet = () => setVisibleSelectDateSheet(false);
const editedCompletedRef = useRef<Set<number>>(new Set());
const stepTitles: {[key: number]: string} = { const stepTitles: {[key: number]: string} = {
1: 'Verifikasi NIK', 1: 'Verifikasi NIK',
2: 'Kuesioner Permohonan Paspor (PERDIM)', 2: 'Kuesioner Permohonan Paspor (PERDIM)',
@ -481,7 +521,68 @@ function RegularPassportScreen() {
currentStep={step} currentStep={step}
totalSteps={7} totalSteps={7}
completedSteps={completedSteps} 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 <RenderApplicationStepsContent
step={step} step={step}
subStep={subStep} subStep={subStep}
@ -512,6 +613,7 @@ function RegularPassportScreen() {
showEditDataSheet={showEditDataSheet} showEditDataSheet={showEditDataSheet}
showSearchLocationSheet={showSearchLocationSheet} showSearchLocationSheet={showSearchLocationSheet}
showSelectDateSheet={showSelectDateSheet} showSelectDateSheet={showSelectDateSheet}
setStepValidationStatus={setStepValidationStatus}
/> />
</View> </View>

View File

@ -10,12 +10,38 @@ import Colors from '../../../../../assets/styles/Colors';
type Step1VerifyNikSubStep3Props = { type Step1VerifyNikSubStep3Props = {
setStep: (val: number) => void; setStep: (val: number) => void;
setSubStep: (val: number) => void; setSubStep: (val: number) => void;
onSubStepValidation: (isValid: boolean) => void;
}; };
const Step1VerifyNikSubStep3 = ({ const Step1VerifyNikSubStep3 = ({
setStep, setStep,
setSubStep, setSubStep,
onSubStepValidation,
}: Step1VerifyNikSubStep3Props) => { }: 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 ( return (
<ScrollView> <ScrollView>
<View style={styles.subStepContainer}> <View style={styles.subStepContainer}>
@ -28,11 +54,15 @@ const Step1VerifyNikSubStep3 = ({
title="Nama Lengkap Pemohon" title="Nama Lengkap Pemohon"
placeholder="Nama Lengkap Anda" placeholder="Nama Lengkap Anda"
isRequired isRequired
value={fullName}
onChangeText={setFullName}
/> />
<TextInputComponent <TextInputComponent
title="NIK" title="NIK"
placeholder="Nama NIK Anda" placeholder="Nama NIK Anda"
isRequired isRequired
value={nik}
onChangeText={setNik}
/> />
<View style={styles.subStepTextInputRowContainer}> <View style={styles.subStepTextInputRowContainer}>
<View style={styles.subStepTextInputFlex}> <View style={styles.subStepTextInputFlex}>
@ -41,6 +71,8 @@ const Step1VerifyNikSubStep3 = ({
placeholder="DD/MM/YYYY" placeholder="DD/MM/YYYY"
isRequired isRequired
isDate isDate
value={birthDate}
onChangeText={setBirthDate}
/> />
</View> </View>
<View style={styles.subStepTextInputFlex}> <View style={styles.subStepTextInputFlex}>
@ -50,6 +82,8 @@ const Step1VerifyNikSubStep3 = ({
isRequired isRequired
isDropdown isDropdown
dropdownItemData={genderData} dropdownItemData={genderData}
value={gender}
onChangeText={setGender}
/> />
</View> </View>
</View> </View>
@ -59,16 +93,15 @@ const Step1VerifyNikSubStep3 = ({
isRequired isRequired
isDropdown isDropdown
dropdownItemData={civilStatusData} dropdownItemData={civilStatusData}
value={civilStatus}
onChangeText={setCivilStatus}
/> />
</View> </View>
<View style={styles.subStepButtonContainer}> <View style={styles.subStepButtonContainer}>
<Button <Button
mode="contained" mode="contained"
onPress={() => { onPress={onNextPress}
setStep(2);
setSubStep(1);
}}
style={styles.subStepButtonContained} style={styles.subStepButtonContained}
textColor={Colors.neutral100.color}> textColor={Colors.neutral100.color}>
Lanjut Lanjut

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, {useState} from 'react';
import {ScrollView, View, Text, Pressable} from 'react-native'; import {ScrollView, View, Text, Pressable} from 'react-native';
import {Button} from 'react-native-paper'; import {Button} from 'react-native-paper';
import styles from '../styles'; import styles from '../styles';
@ -14,6 +14,7 @@ type Step2PassportApplicationQuestionnaireSubStep11Props = {
setSubStep: (subStep: number) => void; setSubStep: (subStep: number) => void;
selectedOption: string; selectedOption: string;
setSelectedOption: (value: string) => void; setSelectedOption: (value: string) => void;
onSubStepValidation: (isValid: boolean) => void;
}; };
const Step2PassportApplicationQuestionnaireSubStep11 = ({ const Step2PassportApplicationQuestionnaireSubStep11 = ({
@ -21,7 +22,28 @@ const Step2PassportApplicationQuestionnaireSubStep11 = ({
setSubStep, setSubStep,
selectedOption, selectedOption,
setSelectedOption, setSelectedOption,
onSubStepValidation,
}: Step2PassportApplicationQuestionnaireSubStep11Props) => { }: 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 ( return (
<ScrollView> <ScrollView>
<View style={styles.subStepContainer}> <View style={styles.subStepContainer}>
@ -56,11 +78,15 @@ const Step2PassportApplicationQuestionnaireSubStep11 = ({
<TextInputComponent <TextInputComponent
title="Nama Kerabat" title="Nama Kerabat"
placeholder="Masukkan Nama Kerabat Anda" placeholder="Masukkan Nama Kerabat Anda"
value={relativeName}
onChangeText={setRelativeName}
/> />
<TextInputComponent <TextInputComponent
title="Nomor Telepon" title="Nomor Telepon"
placeholder="Contoh: 08513456789" placeholder="Contoh: 08513456789"
value={phoneNumber}
onChangeText={setPhoneNumber}
/> />
<TextInputComponent <TextInputComponent
@ -68,6 +94,8 @@ const Step2PassportApplicationQuestionnaireSubStep11 = ({
placeholder="Pilih Hubungan" placeholder="Pilih Hubungan"
isDropdown isDropdown
dropdownItemData={familyRelationshipData} dropdownItemData={familyRelationshipData}
value={relationship}
onChangeText={setRelationship}
/> />
{destinationFamilyContactOptions.map(options => ( {destinationFamilyContactOptions.map(options => (
@ -84,10 +112,7 @@ const Step2PassportApplicationQuestionnaireSubStep11 = ({
<Button <Button
mode="contained" mode="contained"
onPress={() => { onPress={onNextPress}
setStep(3);
setSubStep(1);
}}
style={styles.subStepButtonContained} style={styles.subStepButtonContained}
textColor={Colors.neutral100.color}> textColor={Colors.neutral100.color}>
Lanjut Lanjut

View File

@ -17,6 +17,8 @@ interface DocumentUploadSectionProps {
isRequired?: boolean; isRequired?: boolean;
isIcon?: boolean; isIcon?: boolean;
showDialogCivilStatusDocumentsInfo?: () => void; showDialogCivilStatusDocumentsInfo?: () => void;
onUploadSuccess?: () => void;
onDelete?: () => void;
} }
interface Step3UploadDocumentsProps { interface Step3UploadDocumentsProps {
@ -25,6 +27,7 @@ interface Step3UploadDocumentsProps {
selectedPassportOption: string; selectedPassportOption: string;
selectedDestinationCountryOption: string; selectedDestinationCountryOption: string;
showCivilStatusDocumentsInfoDialog: () => void; showCivilStatusDocumentsInfoDialog: () => void;
onSubStepValidation: (isValid: boolean) => void;
} }
const BackButton = (props: BackButtonProps) => { const BackButton = (props: BackButtonProps) => {
@ -47,7 +50,15 @@ const BackButton = (props: BackButtonProps) => {
}; };
const DocumentUploadSection = (props: DocumentUploadSectionProps) => { 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 [uploadedFileName, setUploadedFileName] = useState<string | null>(null);
const handleUpload = (p0: string) => { const handleUpload = (p0: string) => {
@ -60,10 +71,12 @@ const DocumentUploadSection = (props: DocumentUploadSectionProps) => {
} }
setUploadedFileName(fileName); setUploadedFileName(fileName);
onUploadSuccess?.();
}; };
const handleDelete = () => { const handleDelete = () => {
setUploadedFileName(null); setUploadedFileName(null);
onDelete?.();
}; };
return ( return (
@ -141,7 +154,31 @@ const Step3UploadDocuments = (props: Step3UploadDocumentsProps) => {
selectedPassportOption, selectedPassportOption,
selectedDestinationCountryOption, selectedDestinationCountryOption,
showCivilStatusDocumentsInfoDialog, showCivilStatusDocumentsInfoDialog,
onSubStepValidation,
} = props; } = 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 ( return (
<ScrollView> <ScrollView>
<View style={styles.subStepContainer}> <View style={styles.subStepContainer}>
@ -242,26 +279,39 @@ const Step3UploadDocuments = (props: Step3UploadDocumentsProps) => {
</View> </View>
<View style={styles.subStepSectionButtonContainer}> <View style={styles.subStepSectionButtonContainer}>
<DocumentUploadSection title="e-KTP" isRequired /> <DocumentUploadSection
<DocumentUploadSection title="Kartu Keluarga" /> title="e-KTP"
isRequired
onUploadSuccess={() => setIsKTPUploaded(true)}
onDelete={() => setIsKTPUploaded(false)}
/>
<DocumentUploadSection
title="Kartu Keluarga"
onUploadSuccess={() => setIsFamilyCardUploaded(true)}
onDelete={() => setIsFamilyCardUploaded(false)}
/>
<DocumentUploadSection <DocumentUploadSection
title="Akta kelahiran/ijazah/akta perkawinan/buku nikah/surat baptis" title="Akta kelahiran/ijazah/akta perkawinan/buku nikah/surat baptis"
isIcon isIcon
showDialogCivilStatusDocumentsInfo={ showDialogCivilStatusDocumentsInfo={
showCivilStatusDocumentsInfoDialog showCivilStatusDocumentsInfoDialog
} }
onUploadSuccess={() => setIsCivilStatusUploaded(true)}
onDelete={() => setIsCivilStatusUploaded(false)}
/> />
{selectedPassportOption === 'already' && ( {selectedPassportOption === 'already' && (
<DocumentUploadSection title="Paspor Lama" isRequired /> <DocumentUploadSection
title="Paspor Lama"
isRequired
onUploadSuccess={() => setIsOldPassportUploaded(true)}
onDelete={() => setIsOldPassportUploaded(false)}
/>
)} )}
</View> </View>
<Button <Button
mode="contained" mode="contained"
onPress={() => { onPress={onNextPress}
setStep(4);
setSubStep(1);
}}
style={styles.subStepButtonContained} style={styles.subStepButtonContained}
textColor={Colors.neutral100.color}> textColor={Colors.neutral100.color}>
Lanjut Lanjut