Update step feature to allow users to jump to any step regardless of completion status
This commit is contained in:
@ -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;
|
||||||
|
@ -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,
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user