Add screens for Regular Passport, Express Passport, Guidebook, Eazy Passport, and Applicant Detail. Then, add several components in the Applicant Detail screen by parsing data from the History screen

This commit is contained in:
Mochammad Adhi Buchori
2025-04-22 08:06:07 +07:00
parent 4e21c213fc
commit 2c77afb399
17 changed files with 557 additions and 38 deletions

View File

@ -6,7 +6,7 @@ import Colors from '../../assets/styles/Colors';
type PassportAppointmentCardProps = {
applicantName: string;
applicantCount: number;
applicantCode: string;
appointmentDate: string;
appointmentTime: string;
serviceUnit: string;
@ -53,7 +53,7 @@ const renderStatusContent = (status: string) => {
const PassportAppointmentCard: React.FC<PassportAppointmentCardProps> = ({
applicantName,
applicantCount,
applicantCode,
appointmentDate,
appointmentTime,
serviceUnit,
@ -64,9 +64,7 @@ const PassportAppointmentCard: React.FC<PassportAppointmentCardProps> = ({
<View style={styles.topCardContainer}>
<View style={styles.topCardContentTextWrapper}>
<Text style={styles.applicantNameText}>{applicantName}</Text>
<Text style={styles.applicantCountText}>
{applicantCount} Pemohon
</Text>
<Text style={styles.applicantCodeText}>{applicantCode}</Text>
</View>
<Icon name="menu-right" size={24} />
</View>
@ -139,7 +137,7 @@ const styles = StyleSheet.create({
textTransform: 'uppercase',
color: Colors.secondary30.color,
},
applicantCountText: {
applicantCodeText: {
fontSize: 12,
...FontFamily.notoSansRegular,
color: Colors.primary30.color,

View File

@ -2,47 +2,142 @@ const passportAppointmentData = [
{
id: '1',
applicantName: 'Irma Wahyudini',
applicantCount: 1,
applicantCode: '1038000001112223',
appointmentDate: 'Kamis, 17 April 2025',
appointmentTime: '10.00 - 11.00 WIB',
appointmentTime: '10:00 - 11:00 WIB',
serviceUnit: 'Unit Layanan Paspor I Jakarta Selatan (Pondok Pinang)',
status: 'Menunggu Pembayaran',
submissionDate: 'Senin, 14 April 2025 19:15',
serviceCode: 'EH-PL8XQM',
applicationDetails: {
nationalIdNumber: '3271234560009120001',
gender: 'Wanita',
applicationType: 'Baru',
replacementReason: '',
applicationPurpose: 'Ibadah Umrah',
passportType: 'PASPOR BIASA NON ELEKTRONIK',
fee: '350.000',
},
},
{
id: '2',
applicantName: 'Salwa Aisyah Adhani',
applicantCount: 2,
applicantCode: '1038000002223344',
appointmentDate: 'Senin, 14 April 2025',
appointmentTime: '08:00 - 09:00 WIB',
serviceUnit: 'Kantor Imigrasi Depok',
status: 'Sudah Terbayar',
submissionDate: 'Minggu, 13 April 2025 22:45',
serviceCode: 'EH-KS9BNV',
applicationDetails: {
nationalIdNumber: '3271234560009120002',
gender: 'Wanita',
applicationType: 'Penggantian Paspor',
replacementReason: 'Hilang',
applicationPurpose: 'Sekolah di Luar Negeri',
passportType: 'PASPOR BIASA ELEKTRONIK POLIKARBONAT 10 TAHUN',
fee: '950.000',
},
},
{
id: '3',
applicantName: 'Salwa Aisyah Adhani',
applicantCount: 2,
applicantCode: '1038000002223344',
appointmentDate: 'Senin, 14 April 2025',
appointmentTime: '08:00 - 09:00 WIB',
serviceUnit: 'Kantor Imigrasi Depok',
status: 'Menunggu Pembayaran',
submissionDate: 'Sabtu, 12 April 2025 18:30',
serviceCode: 'EH-GT4JWR',
applicationDetails: {
nationalIdNumber: '3271234560009120003',
gender: 'Wanita',
applicationType: 'Baru',
replacementReason: '',
applicationPurpose: 'Wisata/Liburan',
passportType: 'PASPOR BIASA NON ELEKTRONIK',
fee: '350.000',
},
},
{
id: '4',
applicantName: 'Salwa Aisyah Adhani',
applicantCount: 2,
applicantCode: '1038000002223344',
appointmentDate: 'Senin, 23 September 2024',
appointmentTime: '10:00 - 11:00 WIB',
serviceUnit: 'Kantor Imigrasi Depok',
status: 'Permohonan Kadaluarsa',
submissionDate: 'Kamis, 19 September 2024 20:05',
serviceCode: 'EH-RN8XLC',
applicationDetails: {
nationalIdNumber: '3271234560009120004',
gender: 'Wanita',
applicationType: 'Penggantian Paspor',
replacementReason: 'Rusak',
applicationPurpose: 'Kerja di Luar Negeri',
passportType: 'PASPOR BIASA ELEKTRONIK 5 TAHUN',
fee: '650.000',
},
},
{
id: '5',
applicantName: 'Yulfarisa Hasnah',
applicantCount: 2,
applicantCode: '1038000009971388',
appointmentDate: 'Senin, 14 April 2025',
appointmentTime: '08:00 - 09:00 WIB',
serviceUnit: 'Kantor Imigrasi Depok',
status: 'Sudah Terbayar',
submissionDate: 'Rabu, 16 April 2025 21:30',
serviceCode: 'EH-LP7RNC',
applicationDetails: {
nationalIdNumber: '3271234560009123456',
gender: 'Wanita',
applicationType: 'Penggantian Paspor',
replacementReason: 'Penuh/Halaman Penuh',
applicationPurpose: 'Wisata/Liburan',
passportType: 'PASPOR BIASA ELEKTRONIK POLIKARBONAT 10 TAHUN',
fee: '950.000',
},
},
{
id: '6',
applicantName: 'Fadlan Ramadhan',
applicantCode: '1038000008885566',
appointmentDate: 'Selasa, 18 April 2025',
appointmentTime: '09:00 - 10:00 WIB',
serviceUnit: 'Kantor Imigrasi Jakarta Barat',
status: 'Sudah Terbayar',
submissionDate: 'Senin, 14 April 2025 20:00',
serviceCode: 'EH-QZ5TVN',
applicationDetails: {
nationalIdNumber: '3271234560009120006',
gender: 'Pria',
applicationType: 'Baru',
replacementReason: '',
applicationPurpose: 'Tugas Kantor',
passportType: 'PASPOR BIASA NON ELEKTRONIK',
fee: '350.000',
},
},
{
id: '7',
applicantName: 'Nabila Khairunisa',
applicantCode: '1038000007773344',
appointmentDate: 'Rabu, 19 April 2025',
appointmentTime: '13:00 - 14:00 WIB',
serviceUnit: 'Kantor Imigrasi Bekasi',
status: 'Menunggu Pembayaran',
submissionDate: 'Senin, 14 April 2025 23:45',
serviceCode: 'EH-MK2YPQ',
applicationDetails: {
nationalIdNumber: '3271234560009120007',
gender: 'Wanita',
applicationType: 'Penggantian Paspor',
replacementReason: 'Hilang',
applicationPurpose: 'Kuliah di Luar Negeri',
passportType: 'PASPOR BIASA ELEKTRONIK 5 TAHUN',
fee: '650.000',
},
},
];

22
src/model/model.ts Normal file
View File

@ -0,0 +1,22 @@
export type ApplicationDetails = {
nationalIdNumber: string;
gender: string;
applicationType: string;
replacementReason: string;
applicationPurpose: string;
passportType: string;
fee: string;
};
export type PassportAppointmentData = {
id: string;
applicantName: string;
applicantCode: string;
appointmentDate: string;
appointmentTime: string;
serviceUnit: string;
status: string;
submissionDate: string;
serviceCode: string;
applicationDetails: ApplicationDetails;
};

View File

@ -13,6 +13,11 @@ import {RootStackParamList} from './type';
import TermsAndConnditionsScreen from '../screens/termsAndConditions';
import NavigationRouteScreen from '../screens/navigationRoute';
import SetPasswordScreen from '../screens/setPassword';
import RegularPassportScreen from '../screens/regularPassport';
import ExpressPassportScreen from '../screens/expressPassport';
import GuidebookScreen from '../screens/guidebook';
import EazyPassportScreen from '../screens/eazyPassport';
import ApplicationDetailScreen from '../screens/applicationDetail';
const Stack = createNativeStackNavigator<RootStackParamList>();
@ -79,6 +84,31 @@ function RootStack() {
component={SetPasswordScreen}
options={{headerShown: false}}
/>
<Stack.Screen
name="RegularPassport"
component={RegularPassportScreen}
options={{headerShown: false}}
/>
<Stack.Screen
name="ExpressPassport"
component={ExpressPassportScreen}
options={{headerShown: false}}
/>
<Stack.Screen
name="Guidebook"
component={GuidebookScreen}
options={{headerShown: false}}
/>
<Stack.Screen
name="EazyPassport"
component={EazyPassportScreen}
options={{headerShown: false}}
/>
<Stack.Screen
name="ApplicationDetail"
component={ApplicationDetailScreen}
options={{headerShown: false}}
/>
</Stack.Navigator>
);
}

View File

@ -1,3 +1,5 @@
import {PassportAppointmentData} from '../model/model';
export type RootStackParamList = {
Login: undefined;
NavigationRoute: undefined;
@ -11,4 +13,9 @@ export type RootStackParamList = {
EditProfile: undefined;
CloseAccount: undefined;
SetPassword: undefined;
RegularPassport: undefined;
ExpressPassport: undefined;
Guidebook: undefined;
EazyPassport: undefined;
ApplicationDetail: {data: PassportAppointmentData};
};

View File

@ -0,0 +1,137 @@
import React from 'react';
import {ScrollView, StatusBar, Text, View} from 'react-native';
import styles from './styles';
import {useNavigation, useRoute} from '@react-navigation/native';
import {PassportAppointmentData} from '../../model/model';
import Colors from '../../../assets/styles/Colors';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import {NativeStackNavigationProp} from '@react-navigation/native-stack';
import {RootStackParamList} from '../../navigation/type';
import {Divider} from 'react-native-paper';
type ApplicationDetailScreenNavigationProp = NativeStackNavigationProp<
RootStackParamList,
'ApplicationDetail'
>;
const renderStatusContent = (status: string) => {
const statusConfig: {
[key: string]: {
backgroundColor: string;
iconName: string;
title: string;
desc: string;
};
} = {
'Permohonan Kadaluarsa': {
backgroundColor: Colors.indicatorRed.color,
iconName: 'close-circle',
title: 'Permohonan Anda telah kadaluarsa',
desc: 'Silakan ajukan permohonan baru',
},
'Sudah Terbayar': {
backgroundColor: Colors.indicatorGreen.color,
iconName: 'check-circle',
title: 'Pembayaran Anda telah selesai',
desc: 'Cek dokumen yang diperlukan selama kunjungan',
},
'Menunggu Pembayaran': {
backgroundColor: Colors.indicatorOrange.color,
iconName: 'alert-circle',
title: '16 April 2025 23:30',
desc: 'Selesaikan pembayaran Anda sebelum',
},
};
const {backgroundColor, iconName, title, desc} =
statusConfig[status] || statusConfig['Menunggu Pembayaran'];
return (
<View style={[styles.statusContentWrapper, {backgroundColor}]}>
<Icon name={iconName} size={24} color={Colors.neutral100.color} />
<View style={styles.textStatusContentWrapper}>
{status === 'Menunggu Pembayaran' ? (
<>
<Text style={styles.textStatusDesc}>{desc}</Text>
<Text style={styles.textStatusTitle}>{title}</Text>
</>
) : (
<>
<Text style={styles.textStatusTitle}>{title}</Text>
<Text style={styles.textStatusDesc}>{desc}</Text>
</>
)}
</View>
</View>
);
};
function ApplicationDetailScreen() {
const route = useRoute();
const {data} = route.params as {data: PassportAppointmentData};
const navigation = useNavigation<ApplicationDetailScreenNavigationProp>();
return (
<View style={styles.container}>
<StatusBar
backgroundColor={Colors.secondary30.color}
barStyle="light-content"
/>
<View style={styles.appBarContainer}>
<Icon
name="arrow-left"
size={24}
style={styles.appBarIcon}
color={Colors.neutral100.color}
onPress={() => navigation.goBack()}
/>
<Text style={styles.appBarTitle}>Detail Permohonan</Text>
</View>
<ScrollView>
<View style={styles.topContainer} />
<View style={styles.statusContentContainer}>
{renderStatusContent(data.status)}
</View>
<View style={styles.midContainer}>
<Text style={styles.midTextTitle}>Jadwal Kedatangan</Text>
<View style={styles.midIconContainer}>
<View style={styles.midIconContentWrapper}>
<Icon
name="calendar-today"
size={24}
color={Colors.secondary30.color}
/>
<Text style={styles.midIconContentTextStyle}>
{data.appointmentDate}
</Text>
</View>
<View style={styles.midIconContentWrapper}>
<Icon
name="clock-outline"
size={24}
color={Colors.secondary30.color}
/>
<Text style={styles.midIconContentTextStyle}>
{data.appointmentTime}
</Text>
</View>
<View style={styles.midIconContentWrapper}>
<Icon
name="map-marker-outline"
size={24}
color={Colors.secondary30.color}
/>
<Text style={styles.midIconContentTextStyle}>
{data.serviceUnit}
</Text>
</View>
</View>
<Divider />
</View>
</ScrollView>
</View>
);
}
export default ApplicationDetailScreen;

View File

@ -0,0 +1,82 @@
import {StyleSheet} from 'react-native';
import Colors from '../../../assets/styles/Colors';
import FontFamily from '../../../assets/styles/FontFamily';
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.secondary70.color,
},
appBarContainer: {
height: 64,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: Colors.secondary30.color,
},
appBarTitle: {
color: Colors.neutral100.color,
...FontFamily.notoSansRegular,
fontSize: 22,
marginStart: 16,
},
appBarIcon: {
marginLeft: 16,
},
topContainer: {
backgroundColor: Colors.secondary30.color,
height: 85,
},
statusContentWrapper: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
paddingVertical: 16,
paddingHorizontal: 24,
borderRadius: 8,
},
statusContentContainer: {
marginTop: -64,
marginHorizontal: 16,
zIndex: 10,
},
textStatusContentWrapper: {
gap: 4,
},
textStatusTitle: {
...FontFamily.notoSansExtraBold,
fontSize: 13,
color: Colors.neutral100.color,
},
textStatusDesc: {
...FontFamily.notoSansMedium,
fontSize: 10,
color: Colors.neutral100.color,
},
midContainer: {
marginTop: -16,
padding: 16,
backgroundColor: Colors.neutral100.color,
},
midTextTitle: {
...FontFamily.notoSansExtraBold,
fontSize: 18,
marginVertical: 12,
color: Colors.primary30.color,
},
midIconContainer: {
gap: 8,
marginVertical: 8,
},
midIconContentWrapper: {
flexDirection: 'row',
gap: 6,
},
midIconContentTextStyle: {
fontSize: 12,
...FontFamily.notoSansRegular,
color: Colors.primary30.color,
flex: 1,
},
});
export default styles;

View File

@ -0,0 +1,13 @@
import React from 'react';
import {Text, View} from 'react-native';
import styles from './styles';
function EazyPassportScreen() {
return (
<View style={styles.container}>
<Text>Eazy Passport Screen</Text>
</View>
);
}
export default EazyPassportScreen;

View File

@ -0,0 +1,13 @@
import {StyleSheet} from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
alignContent: 'center',
alignItems: 'center',
backgroundColor: 'white',
justifyContent: 'center',
},
});
export default styles;

View File

@ -0,0 +1,13 @@
import React from 'react';
import {Text, View} from 'react-native';
import styles from './styles';
function ExpressPassportScreen() {
return (
<View style={styles.container}>
<Text>Express Passport Screen</Text>
</View>
);
}
export default ExpressPassportScreen;

View File

@ -0,0 +1,13 @@
import {StyleSheet} from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
alignContent: 'center',
alignItems: 'center',
backgroundColor: 'white',
justifyContent: 'center',
},
});
export default styles;

View File

@ -0,0 +1,13 @@
import React from 'react';
import {Text, View} from 'react-native';
import styles from './styles';
function GuidebookScreen() {
return (
<View style={styles.container}>
<Text>Guidebook Screen</Text>
</View>
);
}
export default GuidebookScreen;

View File

@ -0,0 +1,13 @@
import {StyleSheet} from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
alignContent: 'center',
alignItems: 'center',
backgroundColor: 'white',
justifyContent: 'center',
},
});
export default styles;

View File

@ -1,5 +1,5 @@
import React from 'react';
import {FlatList, StatusBar, Text, View} from 'react-native';
import {FlatList, Pressable, StatusBar, Text, View} from 'react-native';
import styles from './styles';
import Colors from '../../../assets/styles/Colors';
import passportAppointmentData from '../../data/History/PassportAppointmentData';
@ -52,14 +52,22 @@ function HistoryScreen() {
<FlatList
data={passportAppointmentData}
renderItem={({item}) => (
<PassportAppointmentCard
applicantName={item.applicantName}
applicantCount={item.applicantCount}
appointmentDate={item.appointmentDate}
appointmentTime={item.appointmentTime}
serviceUnit={item.serviceUnit}
status={item.status}
/>
<Pressable
onPress={() =>
navigation.navigate('ApplicationDetail', {data: item})
}
style={({pressed}) => ({
transform: [{scale: pressed ? 0.975 : 1}],
})}>
<PassportAppointmentCard
applicantName={item.applicantName}
applicantCode={item.applicantCode}
appointmentDate={item.appointmentDate}
appointmentTime={item.appointmentTime}
serviceUnit={item.serviceUnit}
status={item.status}
/>
</Pressable>
)}
keyExtractor={item => item.id}
ItemSeparatorComponent={ItemSeparator}

View File

@ -130,27 +130,55 @@ const RenderContent = () => {
<Text style={styles.serviceText}>Layanan</Text>
<View style={styles.serviceOptionWrapper}>
<View style={styles.serviceOptionContainer}>
<View style={styles.serviceIcon}>
<Pressable
onPress={() => navigation.navigate('RegularPassport')}
style={({pressed}) => [
styles.serviceIcon,
{
transform: [{scale: pressed ? 0.925 : 1}],
},
]}>
<RegularPassportIcon />
</View>
</Pressable>
<Text style={styles.serviceDesc}>Paspor Reguler</Text>
</View>
<View style={styles.serviceOptionContainer}>
<View style={styles.serviceIcon}>
<Pressable
onPress={() => navigation.navigate('ExpressPassport')}
style={({pressed}) => [
styles.serviceIcon,
{
transform: [{scale: pressed ? 0.925 : 1}],
},
]}>
<ExpressPassportIcon />
</View>
</Pressable>
<Text style={styles.serviceDesc}>Paspor Percepatan</Text>
</View>
<View style={styles.serviceOptionContainer}>
<View style={styles.serviceIcon}>
<Pressable
onPress={() => navigation.navigate('Guidebook')}
style={({pressed}) => [
styles.serviceIcon,
{
transform: [{scale: pressed ? 0.925 : 1}],
},
]}>
<GuidebookIcon />
</View>
</Pressable>
<Text style={styles.serviceDesc}>Buku Panduan</Text>
</View>
<View style={styles.serviceOptionContainer}>
<View style={styles.serviceIcon}>
<Pressable
onPress={() => navigation.navigate('EazyPassport')}
style={({pressed}) => [
styles.serviceIcon,
{
transform: [{scale: pressed ? 0.925 : 1}],
},
]}>
<EazyPassportIcon />
</View>
</Pressable>
<Text style={styles.serviceDesc}>EAZY Pasport</Text>
</View>
</View>
@ -168,16 +196,24 @@ const RenderContent = () => {
</View>
<View style={styles.cardWrapper}>
<FlatList
data={passportAppointmentData.slice(0, 2)}
data={passportAppointmentData.slice(-2)}
renderItem={({item}) => (
<PassportAppointmentCard
applicantName={item.applicantName}
applicantCount={item.applicantCount}
appointmentDate={item.appointmentDate}
appointmentTime={item.appointmentTime}
serviceUnit={item.serviceUnit}
status={item.status}
/>
<Pressable
onPress={() =>
navigation.navigate('ApplicationDetail', {data: item})
}
style={({pressed}) => ({
transform: [{scale: pressed ? 0.975 : 1}],
})}>
<PassportAppointmentCard
applicantName={item.applicantName}
applicantCode={item.applicantCode}
appointmentDate={item.appointmentDate}
appointmentTime={item.appointmentTime}
serviceUnit={item.serviceUnit}
status={item.status}
/>
</Pressable>
)}
keyExtractor={item => item.id}
ItemSeparatorComponent={ItemSeparator}

View File

@ -0,0 +1,13 @@
import React from 'react';
import {Text, View} from 'react-native';
import styles from './styles';
function RegularPassportScreen() {
return (
<View style={styles.container}>
<Text>Regular Passport Screen</Text>
</View>
);
}
export default RegularPassportScreen;

View File

@ -0,0 +1,13 @@
import {StyleSheet} from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
alignContent: 'center',
alignItems: 'center',
backgroundColor: 'white',
justifyContent: 'center',
},
});
export default styles;