Finalized all of the passport application flow feature and and adding a feature to add data
This commit is contained in:
110
package-lock.json
generated
110
package-lock.json
generated
@ -8,13 +8,16 @@
|
||||
"name": "mpaspor",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@react-native-async-storage/async-storage": "^2.1.2",
|
||||
"@react-native-community/datetimepicker": "^8.3.0",
|
||||
"@react-navigation/elements": "^2.3.8",
|
||||
"@react-navigation/native": "^7.1.6",
|
||||
"@react-navigation/native-stack": "^7.3.10",
|
||||
"dayjs": "^1.11.13",
|
||||
"moment": "^2.30.1",
|
||||
"react": "19.0.0",
|
||||
"react-native": "0.78.0",
|
||||
"react-native-calendars": "^1.1311.1",
|
||||
"react-native-element-dropdown": "^2.12.4",
|
||||
"react-native-paper": "^5.13.2",
|
||||
"react-native-safe-area-context": "^5.4.0",
|
||||
@ -2609,6 +2612,18 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native-async-storage/async-storage": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.1.2.tgz",
|
||||
"integrity": "sha512-dvlNq4AlGWC+ehtH12p65+17V0Dx7IecOWl6WanF2ja38O1Dcjjvn7jVzkUHJ5oWkQBlyASurTPlTHgKXyYiow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"merge-options": "^3.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react-native": "^0.0.0-0 || >=0.65 <1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native-community/cli": {
|
||||
"version": "15.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-15.0.1.tgz",
|
||||
@ -7576,6 +7591,15 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-plain-obj": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
|
||||
"integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-plain-object": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
||||
@ -9182,6 +9206,18 @@
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
|
||||
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
|
||||
},
|
||||
"node_modules/merge-options": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz",
|
||||
"integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-plain-obj": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
@ -9678,6 +9714,15 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/moment": {
|
||||
"version": "2.30.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
||||
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@ -10557,6 +10602,38 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-calendars": {
|
||||
"version": "1.1311.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-calendars/-/react-native-calendars-1.1311.1.tgz",
|
||||
"integrity": "sha512-/he1+4irh/643SOnnWlEfoAsHhKq2v8WFRMbcXr+dGgf5hcl/TeVgUINw3Vb2SsAxYM2dV+mVCaNJTbMjS025Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hoist-non-react-statics": "^3.3.1",
|
||||
"lodash": "^4.17.15",
|
||||
"memoize-one": "^5.2.1",
|
||||
"prop-types": "^15.5.10",
|
||||
"react-native-safe-area-context": "4.5.0",
|
||||
"react-native-swipe-gestures": "^1.0.5",
|
||||
"recyclerlistview": "^4.0.0",
|
||||
"xdate": "^0.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"moment": "^2.29.4"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-calendars/node_modules/react-native-safe-area-context": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.5.0.tgz",
|
||||
"integrity": "sha512-0WORnk9SkREGUg2V7jHZbuN5x4vcxj/1B0QOcXJjdYWrzZHgLcUzYWWIUecUPJh747Mwjt/42RZDOaFn3L8kPQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-element-dropdown": {
|
||||
"version": "2.12.4",
|
||||
"resolved": "https://registry.npmjs.org/react-native-element-dropdown/-/react-native-element-dropdown-2.12.4.tgz",
|
||||
@ -10662,6 +10739,12 @@
|
||||
"react-native-svg": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-swipe-gestures": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native-swipe-gestures/-/react-native-swipe-gestures-1.0.5.tgz",
|
||||
"integrity": "sha512-Ns7Bn9H/Tyw278+5SQx9oAblDZ7JixyzeOczcBK8dipQk2pD7Djkcfnf1nB/8RErAmMLL9iXgW0QHqiII8AhKw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-native-vector-icons": {
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.2.0.tgz",
|
||||
@ -10832,6 +10915,21 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/recyclerlistview": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/recyclerlistview/-/recyclerlistview-4.2.3.tgz",
|
||||
"integrity": "sha512-STR/wj/FyT8EMsBzzhZ1l2goYirMkIgfV3gYEPxI3Kf3lOnu6f7Dryhyw7/IkQrgX5xtTcDrZMqytvteH9rL3g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"lodash.debounce": "4.0.8",
|
||||
"prop-types": "15.8.1",
|
||||
"ts-object-utils": "0.0.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 15.2.1",
|
||||
"react-native": ">= 0.30.0"
|
||||
}
|
||||
},
|
||||
"node_modules/reflect.getprototypeof": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
||||
@ -12058,6 +12156,12 @@
|
||||
"typescript": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-object-utils": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/ts-object-utils/-/ts-object-utils-0.0.5.tgz",
|
||||
"integrity": "sha512-iV0GvHqOmilbIKJsfyfJY9/dNHCs969z3so90dQWsO1eMMozvTpnB1MEaUbb3FYtZTGjv5sIy/xmslEz0Rg2TA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
@ -12569,6 +12673,12 @@
|
||||
"async-limiter": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xdate": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/xdate/-/xdate-0.8.3.tgz",
|
||||
"integrity": "sha512-1NhJWPJwN+VjbkACT9XHbQK4o6exeSVtS2CxhMPwUE7xQakoEFTlwra9YcqV/uHQVyeEUYoYC46VGDJ+etnIiw==",
|
||||
"license": "(MIT OR GPL-2.0)"
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
|
@ -10,13 +10,16 @@
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-native-async-storage/async-storage": "^2.1.2",
|
||||
"@react-native-community/datetimepicker": "^8.3.0",
|
||||
"@react-navigation/elements": "^2.3.8",
|
||||
"@react-navigation/native": "^7.1.6",
|
||||
"@react-navigation/native-stack": "^7.3.10",
|
||||
"dayjs": "^1.11.13",
|
||||
"moment": "^2.30.1",
|
||||
"react": "19.0.0",
|
||||
"react-native": "0.78.0",
|
||||
"react-native-calendars": "^1.1311.1",
|
||||
"react-native-element-dropdown": "^2.12.4",
|
||||
"react-native-paper": "^5.13.2",
|
||||
"react-native-safe-area-context": "^5.4.0",
|
||||
|
@ -5,15 +5,15 @@ import FontFamily from '../../assets/styles/FontFamily';
|
||||
import Colors from '../../assets/styles/Colors';
|
||||
|
||||
type PassportAppointmentCardProps = {
|
||||
applicantName: string;
|
||||
applicantCode: string;
|
||||
appointmentDate: string;
|
||||
appointmentTime: string;
|
||||
serviceUnit: string;
|
||||
status: string;
|
||||
applicantName: string | undefined;
|
||||
applicantCode: string | undefined;
|
||||
appointmentDate: string | undefined;
|
||||
appointmentTime: string | undefined;
|
||||
serviceUnit: string | undefined;
|
||||
status: string | undefined;
|
||||
};
|
||||
|
||||
const renderStatusContent = (status: string) => {
|
||||
const renderStatusContent = (status: string | undefined) => {
|
||||
let backgroundColor;
|
||||
let IconComponent;
|
||||
|
||||
|
@ -38,8 +38,8 @@ interface TextInputComponentProps {
|
||||
supportText?: string;
|
||||
containerHeight?: any;
|
||||
isMultiline?: boolean;
|
||||
isDropdownSearchLocation?: boolean;
|
||||
handlePressSearchLocation?: () => void;
|
||||
isDropdownPressedSheet?: boolean;
|
||||
handleDropdownPressed?: () => void;
|
||||
}
|
||||
|
||||
const TextInputComponent = (props: TextInputComponentProps) => {
|
||||
@ -59,8 +59,8 @@ const TextInputComponent = (props: TextInputComponentProps) => {
|
||||
supportText,
|
||||
containerHeight,
|
||||
isMultiline = false,
|
||||
isDropdownSearchLocation = false,
|
||||
handlePressSearchLocation,
|
||||
isDropdownPressedSheet = false,
|
||||
handleDropdownPressed,
|
||||
} = props;
|
||||
|
||||
const [secureText, setSecureText] = useState(isPassword);
|
||||
@ -281,7 +281,7 @@ const TextInputComponent = (props: TextInputComponentProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (isDropdownSearchLocation) {
|
||||
if (isDropdownPressedSheet) {
|
||||
return (
|
||||
<View>
|
||||
{title && (
|
||||
@ -291,7 +291,7 @@ const TextInputComponent = (props: TextInputComponentProps) => {
|
||||
</View>
|
||||
)}
|
||||
<Pressable
|
||||
onPress={handlePressSearchLocation}
|
||||
onPress={handleDropdownPressed}
|
||||
style={({pressed}) => ({
|
||||
transform: [{scale: pressed ? 0.99 : 1}],
|
||||
})}>
|
||||
@ -303,7 +303,14 @@ const TextInputComponent = (props: TextInputComponentProps) => {
|
||||
placeholderTextColor={Colors.primary60.color}
|
||||
editable={false}
|
||||
value={formattedDate}
|
||||
right={<TextInput.Icon icon="menu-down" color="#48454E" size={20} style={{marginLeft: 24}}/>}
|
||||
right={
|
||||
<TextInput.Icon
|
||||
icon="menu-down"
|
||||
color="#48454E"
|
||||
size={20}
|
||||
style={{marginLeft: 24}}
|
||||
/>
|
||||
}
|
||||
multiline={false}
|
||||
textColor="#48454E"
|
||||
disabled={isDisabled}
|
||||
|
79
src/components/dialog/DialogLogout.tsx
Normal file
79
src/components/dialog/DialogLogout.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
import React from 'react';
|
||||
import {View, Text, StyleSheet} from 'react-native';
|
||||
import {Dialog, Portal, Button} from 'react-native-paper';
|
||||
import Colors from '../../../assets/styles/Colors';
|
||||
import FontFamily from '../../../assets/styles/FontFamily';
|
||||
|
||||
interface DialogLogoutProps {
|
||||
visible: boolean;
|
||||
hideDialog: () => void;
|
||||
onNavigate: () => void;
|
||||
}
|
||||
|
||||
const DialogLogout = (props: DialogLogoutProps) => {
|
||||
const {visible, hideDialog, onNavigate} = props;
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Dialog visible={visible} style={styles.container}>
|
||||
<Dialog.Title style={styles.title}>
|
||||
Apakah Anda yakin akan menutup akun?
|
||||
</Dialog.Title>
|
||||
<View style={styles.content}>
|
||||
<Button
|
||||
style={styles.buttonContained}
|
||||
mode="contained"
|
||||
textColor={Colors.neutral100.color}
|
||||
onPress={() => {
|
||||
hideDialog();
|
||||
onNavigate();
|
||||
}}>
|
||||
Ya, lanjut tutup akun
|
||||
</Button>
|
||||
<Button
|
||||
style={styles.buttonOutlined}
|
||||
mode="outlined"
|
||||
textColor={Colors.indicatorRed.color}
|
||||
onPress={() => {
|
||||
hideDialog();
|
||||
}}>
|
||||
Tidak, jangan tutup akun
|
||||
</Button>
|
||||
</View>
|
||||
</Dialog>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DialogLogout;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: 'white',
|
||||
elevation: 0,
|
||||
shadowColor: 'transparent',
|
||||
borderRadius: 20,
|
||||
},
|
||||
title: {
|
||||
fontSize: 22,
|
||||
color: Colors.indicatorRed.color,
|
||||
},
|
||||
content: {
|
||||
marginHorizontal: 24,
|
||||
marginBottom: 24,
|
||||
gap: 16,
|
||||
},
|
||||
message: {
|
||||
fontSize: 14,
|
||||
...FontFamily.notoSansRegular,
|
||||
includeFontPadding: false,
|
||||
lineHeight: 22,
|
||||
color: Colors.primary30.color,
|
||||
},
|
||||
buttonContained: {
|
||||
backgroundColor: Colors.indicatorRed.color,
|
||||
},
|
||||
buttonOutlined: {
|
||||
borderColor: Colors.indicatorRed.color,
|
||||
},
|
||||
});
|
78
src/components/dialog/DialogWarningApplication.tsx
Normal file
78
src/components/dialog/DialogWarningApplication.tsx
Normal file
@ -0,0 +1,78 @@
|
||||
import React from 'react';
|
||||
import {View, Text, StyleSheet} from 'react-native';
|
||||
import {Dialog, Portal, Button} from 'react-native-paper';
|
||||
import Colors from '../../../assets/styles/Colors';
|
||||
import FontFamily from '../../../assets/styles/FontFamily';
|
||||
|
||||
interface DialogWarningApplicationProps {
|
||||
visible: boolean;
|
||||
hideDialog: () => void;
|
||||
onNavigate: () => void;
|
||||
}
|
||||
|
||||
const DialogWarningApplication = (props: DialogWarningApplicationProps) => {
|
||||
const {visible, hideDialog, onNavigate} = props;
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Dialog visible={visible} style={styles.container}>
|
||||
<Dialog.Title style={styles.title}>Peringatan</Dialog.Title>
|
||||
<View style={styles.content}>
|
||||
<Text style={styles.message}>
|
||||
Silakan melakukan pengisian kuesioner.
|
||||
</Text>
|
||||
<Text style={[styles.message, {...FontFamily.notoSansBold}]}>
|
||||
Pastikan data dan jawaban yang Anda berikan benar.
|
||||
</Text>
|
||||
<Text style={styles.message}>
|
||||
Pemberian keterangan yang tidak benar merupakan pelanggaran
|
||||
keimigrasian sebagaimana ketentuan Pasal 126 huruf c UU No. 6 tahun
|
||||
2011 tentang Keimigrasian dan akan mengakibatkan permohonan paspor
|
||||
Anda ditolak dan pembayaran tidak dapat dikembalikan.
|
||||
</Text>
|
||||
<Button
|
||||
style={styles.button}
|
||||
mode="contained"
|
||||
textColor={Colors.neutral100.color}
|
||||
onPress={() => {
|
||||
hideDialog();
|
||||
onNavigate();
|
||||
}}>
|
||||
Lanjut
|
||||
</Button>
|
||||
</View>
|
||||
</Dialog>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DialogWarningApplication;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: 'white',
|
||||
elevation: 0,
|
||||
shadowColor: 'transparent',
|
||||
borderRadius: 20,
|
||||
},
|
||||
title: {
|
||||
fontSize: 22,
|
||||
color: Colors.secondary30.color,
|
||||
},
|
||||
content: {
|
||||
marginHorizontal: 24,
|
||||
marginBottom: 24,
|
||||
gap: 16,
|
||||
},
|
||||
message: {
|
||||
fontSize: 14,
|
||||
...FontFamily.notoSansRegular,
|
||||
includeFontPadding: false,
|
||||
lineHeight: 22,
|
||||
color: Colors.primary30.color,
|
||||
},
|
||||
button: {
|
||||
backgroundColor: Colors.primary30.color,
|
||||
marginTop: 12,
|
||||
},
|
||||
});
|
@ -1,95 +1,353 @@
|
||||
import {StyleSheet, Text, View} from 'react-native';
|
||||
import {Dialog, Portal, Button} from 'react-native-paper';
|
||||
import {
|
||||
FlatList,
|
||||
Modal,
|
||||
Pressable,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {Portal, Button, Divider, RadioButton} from 'react-native-paper';
|
||||
import Colors from '../../../assets/styles/Colors';
|
||||
import FontFamily from '../../../assets/styles/FontFamily';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import {useState} from 'react';
|
||||
import {Calendar} from 'react-native-calendars';
|
||||
import moment from 'moment';
|
||||
import markedDatesData from '../../data/Steps/MarkedDatesData';
|
||||
import arrivalTimesData from '../../data/Steps/ArrivalTimesData';
|
||||
|
||||
type Props = {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
onContinue: () => void;
|
||||
};
|
||||
|
||||
const LegendItem = ({color, label, border}: any) => (
|
||||
<View style={styles.legendItemContainer}>
|
||||
<View
|
||||
style={[
|
||||
styles.legendColorBox,
|
||||
{backgroundColor: color},
|
||||
border && styles.legendColorBoxBorder,
|
||||
]}
|
||||
/>
|
||||
<Text style={styles.legendLabel}>{label}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
const SheetSelectDate = (props: Props) => {
|
||||
const {visible, onClose} = props;
|
||||
const {visible, onClose, onContinue} = props;
|
||||
const [selectedDate, setSelectedDate] = useState('');
|
||||
const [currentMonth, setCurrentMonth] = useState('2025-05-01');
|
||||
const [selectedTimeSlot, setSelectedTimeSlot] = useState('');
|
||||
|
||||
const markedDates = {
|
||||
...markedDatesData,
|
||||
[selectedDate]: {
|
||||
selected: true,
|
||||
selectedColor: Colors.indicatorOrange.color,
|
||||
selectedTextColor: Colors.neutral100.color,
|
||||
},
|
||||
};
|
||||
|
||||
const renderHeaderCalendar = () => {
|
||||
const headerText = moment(currentMonth).format('MMMM YYYY');
|
||||
|
||||
const handleMonthChange = (amount: number) => {
|
||||
const updatedMonth = moment(currentMonth)
|
||||
.add(amount, 'months')
|
||||
.format('YYYY-MM-DD');
|
||||
setCurrentMonth(updatedMonth);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.calendarHeaderContainer}>
|
||||
<Text style={styles.calendarHeaderText}>{headerText}</Text>
|
||||
<View style={styles.calendarNavigation}>
|
||||
<TouchableOpacity onPress={() => handleMonthChange(-1)}>
|
||||
<Icon
|
||||
name="chevron-left"
|
||||
size={24}
|
||||
color={Colors.secondary30.color}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => handleMonthChange(1)}>
|
||||
<Icon
|
||||
name="chevron-right"
|
||||
size={24}
|
||||
color={Colors.secondary30.color}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const renderTimeSlotItem = ({item}: any) => {
|
||||
return (
|
||||
<View>
|
||||
<Text style={styles.timeSlotTitle}>Jam Kedatangan</Text>
|
||||
<View style={styles.timeSlotRadioGroup}>
|
||||
<View style={styles.timeSlotItem}>
|
||||
<RadioButton
|
||||
value={item.id}
|
||||
status={
|
||||
selectedTimeSlot === item.id ? 'checked' : 'unchecked'
|
||||
}
|
||||
onPress={() => setSelectedTimeSlot(item.id)}
|
||||
color={Colors.secondary30.color}
|
||||
uncheckedColor={Colors.secondary30.color}
|
||||
/>
|
||||
<Text style={styles.timeSlotLabel}>{item.arrivalTime}</Text>
|
||||
</View>
|
||||
<View style={styles.timeSlotQueueInfo}>
|
||||
<View style={styles.queueDetail}>
|
||||
<Text style={styles.queueDetailTitle}>Jumlah Antrian</Text>
|
||||
<Text style={styles.queueDetailValue}>{item.queueCount}</Text>
|
||||
</View>
|
||||
<View style={styles.queueDetail}>
|
||||
<Text style={styles.queueDetailTitle}>Sisa Kuota</Text>
|
||||
<Text style={styles.queueDetailValue}>{item.remainingQuota}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Dialog visible={visible} style={styles.dialogContainer}>
|
||||
<Dialog.Title style={styles.dialogTitle}>
|
||||
Jenis dan Manfaat Paspor
|
||||
</Dialog.Title>
|
||||
<View style={styles.dialogContentContainer}>
|
||||
<Text style={[styles.dialogDesc, {...FontFamily.notoSansBold}]}>
|
||||
1. Paspor Biasa
|
||||
</Text>
|
||||
<Text style={styles.dialogDesc}>
|
||||
Biaya pemrosesan lebih murah, tersedia di seluruh lokasi Kanim.
|
||||
{'\n'}Biaya pembuatan: Rp350.000
|
||||
</Text>
|
||||
<Text style={[styles.dialogDesc, {...FontFamily.notoSansBold}]}>
|
||||
2. Paspor Elektronik
|
||||
</Text>
|
||||
<Text style={styles.dialogDesc}>
|
||||
Terdapat chip pada cover paspor yang menyimpan data pemegang paspor
|
||||
sehingga meningkatkan fitur keamanan, tersedia di 35 Kanim.{'\n'}
|
||||
Biaya pembuatan: Rp650.000
|
||||
</Text>
|
||||
<Text style={[styles.dialogDesc, {...FontFamily.notoSansBold}]}>
|
||||
3. Paspor Elektronik Polikarbonat
|
||||
</Text>
|
||||
<Text style={styles.dialogDesc}>
|
||||
Berbahan polikarbonat (PC) pada halaman data pemegang paspor
|
||||
(halaman 2) yang memiliki fitur keamanan tinggi dan lebih kuat,
|
||||
tersedia di Kanim Jakarta Selatan, Kanim Jakarta Barat, dan Kanim
|
||||
Soekarno Hatta.{'\n'}Biaya pembuatan: Rp650.000
|
||||
</Text>
|
||||
<View>
|
||||
<Button
|
||||
style={styles.buttonContinue}
|
||||
mode="contained"
|
||||
textColor={Colors.neutral100.color}
|
||||
onPress={() => {
|
||||
onClose();
|
||||
}}>
|
||||
Lanjut
|
||||
</Button>
|
||||
</View>
|
||||
<Modal visible={visible} transparent animationType="slide">
|
||||
<Pressable style={styles.modalBackdrop} onPress={onClose} />
|
||||
<View style={styles.modalContentContainer}>
|
||||
<ScrollView
|
||||
keyboardShouldPersistTaps="handled"
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={styles.modalScrollViewContent}>
|
||||
<View style={styles.modalCloseButtonContainer}>
|
||||
<Pressable
|
||||
style={({pressed}) => [
|
||||
styles.closeButton,
|
||||
pressed && styles.closeButtonPressed,
|
||||
]}
|
||||
onPress={onClose}>
|
||||
<Icon name="close" size={24} color={Colors.primary30.color} />
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<View style={styles.calendarContainer}>
|
||||
<Calendar
|
||||
key={currentMonth}
|
||||
theme={{
|
||||
textDayFontSize: 14,
|
||||
textDayStyle: {
|
||||
includeFontPadding: false,
|
||||
marginTop: 6,
|
||||
...FontFamily.notoSansRegular,
|
||||
},
|
||||
textDayHeaderFontFamily: 'NotoSans-Medium',
|
||||
textDayHeaderFontSize: 14,
|
||||
textSectionTitleColor: Colors.primary30.color,
|
||||
arrowColor: Colors.secondary30.color,
|
||||
}}
|
||||
onDayPress={day => setSelectedDate(day.dateString)}
|
||||
markedDates={markedDates}
|
||||
hideArrows={true}
|
||||
current={currentMonth}
|
||||
onMonthChange={month => {
|
||||
const newMonth = `${month.year}-${String(
|
||||
month.month,
|
||||
).padStart(2, '0')}-01`;
|
||||
setCurrentMonth(newMonth);
|
||||
}}
|
||||
renderHeader={renderHeaderCalendar}
|
||||
/>
|
||||
|
||||
<Divider style={styles.divider} />
|
||||
|
||||
<View style={styles.legendMainContainer}>
|
||||
<View style={styles.legendColumn}>
|
||||
<LegendItem
|
||||
color={Colors.indicatorGreen.color}
|
||||
label="Kuota tersedia"
|
||||
/>
|
||||
<LegendItem
|
||||
color={Colors.indicatorRed.color}
|
||||
label="Kuota penuh"
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.legendColumn}>
|
||||
<LegendItem
|
||||
color={Colors.indicatorOrange.color}
|
||||
label="Tanggal terpilih"
|
||||
/>
|
||||
<LegendItem
|
||||
color={Colors.neutral100.color}
|
||||
border
|
||||
label="Kuota belum dibuka"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Divider style={styles.divider} />
|
||||
</View>
|
||||
|
||||
<FlatList
|
||||
data={arrivalTimesData}
|
||||
keyExtractor={item => item.id}
|
||||
scrollEnabled={false}
|
||||
renderItem={renderTimeSlotItem}
|
||||
/>
|
||||
|
||||
<View style={styles.continueButtonWrapper}>
|
||||
<Button
|
||||
style={styles.continueButton}
|
||||
mode="contained"
|
||||
textColor={Colors.neutral100.color}
|
||||
onPress={onContinue}>
|
||||
Simpan
|
||||
</Button>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
dialogContainer: {
|
||||
backgroundColor: 'white',
|
||||
elevation: 0,
|
||||
shadowColor: 'transparent',
|
||||
borderRadius: 20,
|
||||
modalBackdrop: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0,0,0,0.3)',
|
||||
},
|
||||
dialogContentContainer: {
|
||||
marginHorizontal: 24,
|
||||
marginBottom: 24,
|
||||
modalContentContainer: {
|
||||
backgroundColor: 'white',
|
||||
borderTopLeftRadius: 20,
|
||||
borderTopRightRadius: 20,
|
||||
paddingTop: 24,
|
||||
paddingHorizontal: 16,
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
height: '90%',
|
||||
width: '100%',
|
||||
maxHeight: '90%',
|
||||
},
|
||||
modalScrollViewContent: {
|
||||
paddingBottom: 24,
|
||||
},
|
||||
modalCloseButtonContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
closeButton: {
|
||||
transform: [{scale: 1}],
|
||||
},
|
||||
closeButtonPressed: {
|
||||
transform: [{scale: 0.925}],
|
||||
},
|
||||
calendarContainer: {
|
||||
marginTop: 8,
|
||||
},
|
||||
calendarHeaderContainer: {
|
||||
width: '100%',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 16,
|
||||
},
|
||||
calendarHeaderText: {
|
||||
fontSize: 14,
|
||||
...FontFamily.notoSansBold,
|
||||
color: Colors.secondary30.color,
|
||||
includeFontPadding: false,
|
||||
},
|
||||
calendarNavigation: {
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
},
|
||||
divider: {
|
||||
marginVertical: 16,
|
||||
height: 1,
|
||||
backgroundColor: Colors.primary70.color,
|
||||
},
|
||||
legendMainContainer: {
|
||||
flexDirection: 'row',
|
||||
gap: 32,
|
||||
},
|
||||
legendColumn: {
|
||||
gap: 16,
|
||||
},
|
||||
dialogTitle: {
|
||||
fontSize: 22,
|
||||
color: Colors.secondary30.color,
|
||||
legendItemContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
},
|
||||
dialogDesc: {
|
||||
fontSize: 14,
|
||||
legendColorBox: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
marginRight: 6,
|
||||
borderRadius: 8,
|
||||
},
|
||||
legendColorBoxBorder: {
|
||||
borderWidth: 1,
|
||||
borderColor: '#ccc',
|
||||
},
|
||||
legendLabel: {
|
||||
fontSize: 11,
|
||||
...FontFamily.notoSansRegular,
|
||||
includeFontPadding: false,
|
||||
color: Colors.primary30.color,
|
||||
lineHeight: 22,
|
||||
},
|
||||
dialogDescRed: {
|
||||
...FontFamily.notoSansBold,
|
||||
color: Colors.indicatorRed.color,
|
||||
includeFontPadding: false,
|
||||
continueButtonWrapper: {
|
||||
marginTop: 16,
|
||||
},
|
||||
buttonContinue: {
|
||||
continueButton: {
|
||||
backgroundColor: Colors.primary30.color,
|
||||
marginTop: 12,
|
||||
},
|
||||
timeSlotTitle: {
|
||||
color: Colors.primary30.color,
|
||||
includeFontPadding: false,
|
||||
fontSize: 12,
|
||||
...FontFamily.notoSansSemiBold,
|
||||
},
|
||||
timeSlotRadioGroup: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
timeSlotItem: {
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
alignItems: 'center',
|
||||
marginVertical: 8,
|
||||
},
|
||||
timeSlotLabel: {
|
||||
...FontFamily.notoSansMedium,
|
||||
fontSize: 12,
|
||||
includeFontPadding: false,
|
||||
color: Colors.secondary30.color,
|
||||
marginStart: -8,
|
||||
},
|
||||
timeSlotQueueInfo: {
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
alignItems: 'center',
|
||||
},
|
||||
queueDetail: {
|
||||
gap: 6,
|
||||
},
|
||||
queueDetailTitle: {
|
||||
fontSize: 10.5,
|
||||
includeFontPadding: false,
|
||||
color: Colors.primary30.color,
|
||||
...FontFamily.notoSansBold,
|
||||
},
|
||||
queueDetailValue: {
|
||||
fontSize: 10.5,
|
||||
includeFontPadding: false,
|
||||
color: Colors.primary30.color,
|
||||
...FontFamily.notoSansRegular,
|
||||
},
|
||||
});
|
||||
|
||||
|
7
src/data/DropdownData/PassportTypeData.tsx
Normal file
7
src/data/DropdownData/PassportTypeData.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
const passportTypeData = [
|
||||
{label: 'Paspor Biasa', value: '1'},
|
||||
{label: 'Paspor Elektronik', value: '2'},
|
||||
{label: 'Paspor Elektronik Polikarbonat', value: '3'},
|
||||
];
|
||||
|
||||
export default passportTypeData;
|
@ -41,17 +41,17 @@ const passportAppointmentData = [
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
applicantName: 'Salwa Aisyah Adhani',
|
||||
applicantName: 'Mochammad Adhi Buchori',
|
||||
applicantCode: '1038000002223344',
|
||||
appointmentDate: 'Senin, 14 April 2025',
|
||||
appointmentTime: '08:00 - 09:00 WIB',
|
||||
serviceUnit: 'Kantor Imigrasi Depok',
|
||||
serviceUnit: 'Kantor Imigrasi Jakarta',
|
||||
status: 'Menunggu Pembayaran',
|
||||
submissionDate: 'Sabtu, 12 April 2025 18:30',
|
||||
serviceCode: 'EH-GT4JWR',
|
||||
applicationDetails: {
|
||||
nationalIdNumber: '3271234560009120003',
|
||||
gender: 'Wanita',
|
||||
gender: 'Pria',
|
||||
applicationType: 'Baru',
|
||||
replacementReason: 'Sekolah di Luar Negeri',
|
||||
applicationPurpose: 'Wisata/Liburan',
|
||||
@ -101,26 +101,6 @@ const passportAppointmentData = [
|
||||
},
|
||||
{
|
||||
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: 'Hilang',
|
||||
applicationPurpose: 'Tugas Kantor',
|
||||
passportType: 'PASPOR BIASA NON ELEKTRONIK',
|
||||
fee: '350.000',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
applicantName: 'Nabila Khairunisa',
|
||||
applicantCode: '1038000007773344',
|
||||
appointmentDate: 'Rabu, 19 April 2025',
|
||||
@ -139,6 +119,26 @@ const passportAppointmentData = [
|
||||
fee: '650.000',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
applicantName: 'Ayaka Haishima',
|
||||
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: 'Wanita',
|
||||
applicationType: 'Baru',
|
||||
replacementReason: 'Hilang',
|
||||
applicationPurpose: 'Kuliah di Luar Negeri',
|
||||
passportType: 'PASPOR BIASA NON ELEKTRONIK',
|
||||
fee: '350.000',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default passportAppointmentData;
|
||||
|
64
src/data/Steps/ArrivalTimesData.tsx
Normal file
64
src/data/Steps/ArrivalTimesData.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
const arrivalTimesData = [
|
||||
{
|
||||
id: '1',
|
||||
arrivalTime: '09:01-10:00',
|
||||
queueCount: '4 people',
|
||||
remainingQuota: '1',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
arrivalTime: '10:01-11:00',
|
||||
queueCount: '4 people',
|
||||
remainingQuota: '1',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
arrivalTime: '11:01-12:00',
|
||||
queueCount: '3 people',
|
||||
remainingQuota: '2',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
arrivalTime: '13:01-14:00',
|
||||
queueCount: '1 person',
|
||||
remainingQuota: '4',
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
arrivalTime: '14:01-15:00',
|
||||
queueCount: '2 people',
|
||||
remainingQuota: '3',
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
arrivalTime: '15:01-16:00',
|
||||
queueCount: '5 people',
|
||||
remainingQuota: '0',
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
arrivalTime: '16:01-17:00',
|
||||
queueCount: '3 people',
|
||||
remainingQuota: '2',
|
||||
},
|
||||
{
|
||||
id: '8',
|
||||
arrivalTime: '17:01-18:00',
|
||||
queueCount: '2 people',
|
||||
remainingQuota: '3',
|
||||
},
|
||||
{
|
||||
id: '9',
|
||||
arrivalTime: '18:01-19:00',
|
||||
queueCount: '1 person',
|
||||
remainingQuota: '4',
|
||||
},
|
||||
{
|
||||
id: '10',
|
||||
arrivalTime: '19:01-20:00',
|
||||
queueCount: '0 people',
|
||||
remainingQuota: '5',
|
||||
},
|
||||
];
|
||||
|
||||
export default arrivalTimesData;
|
129
src/data/Steps/MarkedDatesData.tsx
Normal file
129
src/data/Steps/MarkedDatesData.tsx
Normal file
@ -0,0 +1,129 @@
|
||||
import Colors from '../../../assets/styles/Colors';
|
||||
|
||||
const markedDatesData = {
|
||||
// Kuota penuh (merah)
|
||||
'2025-05-02': {
|
||||
selectedColor: Colors.indicatorRed.color,
|
||||
disableTouchEvent: true,
|
||||
selected: true,
|
||||
textColor: Colors.neutral100.color,
|
||||
},
|
||||
'2025-05-03': {
|
||||
selectedColor: Colors.indicatorRed.color,
|
||||
disableTouchEvent: true,
|
||||
selected: true,
|
||||
textColor: Colors.neutral100.color,
|
||||
},
|
||||
'2025-05-04': {
|
||||
selectedColor: Colors.indicatorRed.color,
|
||||
disableTouchEvent: true,
|
||||
selected: true,
|
||||
textColor: Colors.neutral100.color,
|
||||
},
|
||||
'2025-05-08': {
|
||||
selectedColor: Colors.indicatorRed.color,
|
||||
disableTouchEvent: true,
|
||||
selected: true,
|
||||
textColor: Colors.neutral100.color,
|
||||
},
|
||||
'2025-05-09': {
|
||||
selectedColor: Colors.indicatorRed.color,
|
||||
disableTouchEvent: true,
|
||||
selected: true,
|
||||
textColor: Colors.neutral100.color,
|
||||
},
|
||||
'2025-05-10': {
|
||||
selectedColor: Colors.indicatorRed.color,
|
||||
disableTouchEvent: true,
|
||||
selected: true,
|
||||
textColor: Colors.neutral100.color,
|
||||
},
|
||||
'2025-05-11': {
|
||||
selectedColor: Colors.indicatorRed.color,
|
||||
disableTouchEvent: true,
|
||||
selected: true,
|
||||
textColor: Colors.neutral100.color,
|
||||
},
|
||||
'2025-05-17': {
|
||||
selectedColor: Colors.indicatorRed.color,
|
||||
disableTouchEvent: true,
|
||||
selected: true,
|
||||
textColor: Colors.neutral100.color,
|
||||
},
|
||||
'2025-05-18': {
|
||||
selectedColor: Colors.indicatorRed.color,
|
||||
disableTouchEvent: true,
|
||||
selected: true,
|
||||
textColor: Colors.neutral100.color,
|
||||
},
|
||||
'2025-05-24': {
|
||||
selectedColor: Colors.indicatorRed.color,
|
||||
disableTouchEvent: true,
|
||||
selected: true,
|
||||
textColor: Colors.neutral100.color,
|
||||
},
|
||||
'2025-05-25': {
|
||||
selectedColor: Colors.indicatorRed.color,
|
||||
disableTouchEvent: true,
|
||||
selected: true,
|
||||
textColor: Colors.neutral100.color,
|
||||
},
|
||||
'2025-05-30': {
|
||||
selectedColor: Colors.indicatorRed.color,
|
||||
disableTouchEvent: true,
|
||||
selected: true,
|
||||
textColor: Colors.neutral100.color,
|
||||
},
|
||||
'2025-05-31': {
|
||||
selectedColor: Colors.indicatorRed.color,
|
||||
disableTouchEvent: true,
|
||||
selected: true,
|
||||
textColor: Colors.neutral100.color,
|
||||
},
|
||||
|
||||
// Kuota tersedia (hijau)
|
||||
'2025-05-05': {
|
||||
selectedColor: Colors.indicatorGreen.color,
|
||||
disableTouchEvent: true,
|
||||
selected: true,
|
||||
textColor: Colors.neutral100.color,
|
||||
},
|
||||
'2025-05-14': {
|
||||
selectedColor: Colors.indicatorGreen.color,
|
||||
disableTouchEvent: true,
|
||||
selected: true,
|
||||
textColor: Colors.neutral100.color,
|
||||
},
|
||||
'2025-05-15': {
|
||||
selectedColor: Colors.indicatorGreen.color,
|
||||
disableTouchEvent: true,
|
||||
selected: true,
|
||||
textColor: Colors.neutral100.color,
|
||||
},
|
||||
'2025-05-16': {
|
||||
selectedColor: Colors.indicatorGreen.color,
|
||||
disableTouchEvent: true,
|
||||
selected: true,
|
||||
textColor: Colors.neutral100.color,
|
||||
},
|
||||
'2025-05-20': {
|
||||
selectedColor: Colors.indicatorGreen.color,
|
||||
disableTouchEvent: true,
|
||||
selected: true,
|
||||
textColor: Colors.neutral100.color,
|
||||
},
|
||||
'2025-05-28': {
|
||||
selectedColor: Colors.indicatorGreen.color,
|
||||
disableTouchEvent: true,
|
||||
selected: true,
|
||||
textColor: Colors.neutral100.color,
|
||||
},
|
||||
'2025-05-29': {
|
||||
selectedColor: Colors.indicatorGreen.color,
|
||||
disableTouchEvent: true,
|
||||
selected: true,
|
||||
textColor: Colors.neutral100.color,
|
||||
},
|
||||
};
|
||||
|
||||
export default markedDatesData;
|
40
src/helper/asyncStorageHelper.tsx
Normal file
40
src/helper/asyncStorageHelper.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
// Fungsi untuk menyimpan data
|
||||
export const storeData = async <T,>(key: string, value: T): Promise<void> => {
|
||||
try {
|
||||
await AsyncStorage.setItem(key, JSON.stringify(value));
|
||||
console.log('Data berhasil disimpan!');
|
||||
} catch (e) {
|
||||
console.error('Gagal menyimpan data:', e);
|
||||
}
|
||||
};
|
||||
|
||||
// Fungsi untuk mengambil data
|
||||
export const getData = async <T,>(key: string): Promise<T | null> => {
|
||||
try {
|
||||
const storedData = await AsyncStorage.getItem(key);
|
||||
if (storedData !== null) {
|
||||
return JSON.parse(storedData) as T;
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
console.error('Gagal mengambil data:', e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Fungsi untuk menambah data
|
||||
export const addData = async <T,>(key: string, newData: T): Promise<void> => {
|
||||
try {
|
||||
const storedData = await AsyncStorage.getItem(key);
|
||||
if (storedData !== null) {
|
||||
const parsedData: T[] = JSON.parse(storedData);
|
||||
parsedData.push(newData);
|
||||
await AsyncStorage.setItem(key, JSON.stringify(parsedData));
|
||||
console.log('Data berhasil ditambahkan!');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Gagal menambah data:', e);
|
||||
}
|
||||
};
|
@ -54,7 +54,9 @@ function RootStack() {
|
||||
options={{headerShown: false}}
|
||||
/>
|
||||
<Stack.Screen name="Home" options={{headerShown: false}}>
|
||||
{() => <HomeScreen showDialog={() => console.log('Show dialog!')} visible />}
|
||||
{() => (
|
||||
<HomeScreen showDialog={() => {}} visible />
|
||||
)}
|
||||
</Stack.Screen>
|
||||
<Stack.Screen
|
||||
name="History"
|
||||
@ -66,11 +68,11 @@ function RootStack() {
|
||||
component={NotificationScreen}
|
||||
options={{headerShown: false}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="Profile"
|
||||
component={ProfileScreen}
|
||||
options={{headerShown: false}}
|
||||
/>
|
||||
<Stack.Screen name="Profile" options={{headerShown: false}}>
|
||||
{() => (
|
||||
<ProfileScreen showDialog={() => {}} visible />
|
||||
)}
|
||||
</Stack.Screen>
|
||||
<Stack.Screen
|
||||
name="EditProfile"
|
||||
component={EditProfileScreen}
|
||||
|
@ -23,3 +23,26 @@ export type RootStackParamList = {
|
||||
OtherMethod: undefined;
|
||||
BillingCode: undefined;
|
||||
};
|
||||
|
||||
export interface ApplicationDetails {
|
||||
nationalIdNumber?: string | undefined;
|
||||
gender?: string | undefined;
|
||||
applicationType?: string | undefined;
|
||||
replacementReason?: string | undefined;
|
||||
applicationPurpose?: string | undefined;
|
||||
passportType?: string | undefined;
|
||||
fee?: string | undefined;
|
||||
}
|
||||
|
||||
export interface PassportAppointment {
|
||||
id?: string | undefined;
|
||||
applicantName?: string | undefined;
|
||||
applicantCode?: string | undefined;
|
||||
appointmentDate?: string | undefined;
|
||||
appointmentTime?: string | undefined;
|
||||
serviceUnit?: string | undefined;
|
||||
status?: string | undefined;
|
||||
submissionDate?: string | undefined;
|
||||
serviceCode?: string | undefined;
|
||||
applicationDetails?: ApplicationDetails;
|
||||
}
|
||||
|
@ -154,6 +154,7 @@ const styles = StyleSheet.create({
|
||||
fontSize: 14,
|
||||
...FontFamily.notoSansBold,
|
||||
color: Colors.primary30.color,
|
||||
includeFontPadding: false,
|
||||
},
|
||||
applicantDetailContentChildButton: {
|
||||
marginTop: 8,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, {useCallback} from 'react';
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import {FlatList, Pressable, StatusBar, Text, View} from 'react-native';
|
||||
import styles from './styles';
|
||||
import Colors from '../../../assets/styles/Colors';
|
||||
@ -10,8 +10,10 @@ import {
|
||||
useNavigationState,
|
||||
} from '@react-navigation/native';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import {RootStackParamList} from '../../navigation/type';
|
||||
import {PassportAppointment, RootStackParamList} from '../../navigation/type';
|
||||
import {NativeStackNavigationProp} from '@react-navigation/native-stack';
|
||||
import {getData} from '../../helper/asyncStorageHelper';
|
||||
import {ActivityIndicator} from 'react-native-paper';
|
||||
|
||||
type HistoryScreenNavigationProp = NativeStackNavigationProp<
|
||||
RootStackParamList,
|
||||
@ -29,6 +31,28 @@ function HistoryScreen() {
|
||||
|
||||
const showNavBackAppBar = previousRoute === 'NavigationRoute';
|
||||
|
||||
const [appointments, setAppointments] = useState<PassportAppointment[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
setIsLoading(true); // mulai loading
|
||||
const data = await getData('passportAppointments');
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
setAppointments(data);
|
||||
} else {
|
||||
setAppointments([]); // kosongin kalau tidak ada
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching data: ', error);
|
||||
} finally {
|
||||
setIsLoading(false); // selesai loading
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
StatusBar.setBackgroundColor(Colors.secondary30.color);
|
||||
@ -60,31 +84,37 @@ function HistoryScreen() {
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.topBackground} />
|
||||
<View style={styles.cardWrapper}>
|
||||
<FlatList
|
||||
data={passportAppointmentData}
|
||||
renderItem={({item}) => (
|
||||
<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>
|
||||
{isLoading ? (
|
||||
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
|
||||
<ActivityIndicator size="large" color={Colors.secondary30.color} />
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.cardWrapper}>
|
||||
<FlatList
|
||||
data={appointments}
|
||||
renderItem={({item}: any) => (
|
||||
<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>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
@ -13,15 +13,15 @@ import {
|
||||
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 {PassportAppointment, RootStackParamList} from '../../navigation/type';
|
||||
import {useFocusEffect, useNavigation} from '@react-navigation/native';
|
||||
import RegularPassportIcon from '../../../assets/icons/regular_passport.svg';
|
||||
import ExpressPassportIcon from '../../../assets/icons/express_passport.svg';
|
||||
import GuidebookIcon from '../../../assets/icons/guidebook.svg';
|
||||
import EazyPassportIcon from '../../../assets/icons/eazy_passport.svg';
|
||||
import passportAppointmentData from '../../data/History/PassportAppointmentData';
|
||||
import PassportAppointmentCard from '../../components/PassportAppointmentCard';
|
||||
import {useCallback, useRef} from 'react';
|
||||
import {useCallback, useEffect, useRef, useState} from 'react';
|
||||
import {getData} from '../../helper/asyncStorageHelper';
|
||||
|
||||
type HomeScreenNavigationProp = NativeStackNavigationProp<
|
||||
RootStackParamList,
|
||||
@ -32,6 +32,7 @@ const ItemSeparator = () => <View style={styles.flatllistGap} />;
|
||||
|
||||
type RenderContentProps = {
|
||||
showDialog: () => void;
|
||||
lastTwoAppointments: PassportAppointment[];
|
||||
};
|
||||
|
||||
const RenderBanner = () => {
|
||||
@ -147,7 +148,10 @@ const RenderBanner = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const RenderContent = ({showDialog}: RenderContentProps) => {
|
||||
const RenderContent = ({
|
||||
showDialog,
|
||||
lastTwoAppointments,
|
||||
}: RenderContentProps) => {
|
||||
const navigation = useNavigation<HomeScreenNavigationProp>();
|
||||
|
||||
return (
|
||||
@ -223,8 +227,8 @@ const RenderContent = ({showDialog}: RenderContentProps) => {
|
||||
</View>
|
||||
<View style={styles.cardWrapper}>
|
||||
<FlatList
|
||||
data={passportAppointmentData.slice(-2)}
|
||||
renderItem={({item}) => (
|
||||
data={lastTwoAppointments}
|
||||
renderItem={({item}: any) => (
|
||||
<Pressable
|
||||
onPress={() =>
|
||||
navigation.navigate('ApplicationDetail', {data: item})
|
||||
@ -242,7 +246,7 @@ const RenderContent = ({showDialog}: RenderContentProps) => {
|
||||
/>
|
||||
</Pressable>
|
||||
)}
|
||||
keyExtractor={item => item.id}
|
||||
keyExtractor={item => item.id ?? ''}
|
||||
ItemSeparatorComponent={ItemSeparator}
|
||||
/>
|
||||
</View>
|
||||
@ -256,9 +260,28 @@ type HomeScreenProps = {
|
||||
readonly visible: boolean;
|
||||
};
|
||||
|
||||
function HomeScreen({showDialog, visible}: HomeScreenProps) {
|
||||
function HomeScreen(props: HomeScreenProps) {
|
||||
const {showDialog, visible} = props;
|
||||
const navigation = useNavigation<HomeScreenNavigationProp>();
|
||||
|
||||
const [lastTwoAppointments, setLastTwoAppointments] = useState<
|
||||
PassportAppointment[]
|
||||
>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const data = await getData('passportAppointments');
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
setLastTwoAppointments(data.slice(-2));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching data: ', error);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
StatusBar.setBackgroundColor(
|
||||
@ -276,7 +299,9 @@ function HomeScreen({showDialog, visible}: HomeScreenProps) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.appBarContainer}>
|
||||
<Text style={styles.appBarTitle}>Halo, X!</Text>
|
||||
<Text style={styles.appBarTitle} numberOfLines={1} ellipsizeMode="tail">
|
||||
Halo, Salwa Aisyah Adhani!
|
||||
</Text>
|
||||
<Icon
|
||||
name="bell-outline"
|
||||
size={24}
|
||||
@ -286,7 +311,12 @@ function HomeScreen({showDialog, visible}: HomeScreenProps) {
|
||||
</View>
|
||||
<FlatList
|
||||
data={[{}]}
|
||||
renderItem={() => <RenderContent showDialog={showDialog} />}
|
||||
renderItem={() => (
|
||||
<RenderContent
|
||||
showDialog={showDialog}
|
||||
lastTwoAppointments={lastTwoAppointments}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
@ -16,6 +16,8 @@ const styles = StyleSheet.create({
|
||||
fontSize: 28,
|
||||
marginVertical: 14,
|
||||
includeFontPadding: false,
|
||||
flex: 1,
|
||||
marginEnd: 16,
|
||||
},
|
||||
appBarContainer: {
|
||||
height: 72,
|
||||
|
@ -13,8 +13,10 @@ import TextInputComponent from '../../components/TextInput';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import {useNavigation} from '@react-navigation/native';
|
||||
import {NativeStackNavigationProp} from '@react-navigation/native-stack';
|
||||
import {RootStackParamList} from '../../navigation/type';
|
||||
import {PassportAppointment, RootStackParamList} from '../../navigation/type';
|
||||
import Colors from '../../../assets/styles/Colors';
|
||||
import {getData, storeData} from '../../helper/asyncStorageHelper';
|
||||
import passportAppointmentData from '../../data/History/PassportAppointmentData';
|
||||
|
||||
type LoginScreenNavigationProp = NativeStackNavigationProp<
|
||||
RootStackParamList,
|
||||
@ -48,12 +50,18 @@ function LoginScreen() {
|
||||
<Button
|
||||
style={styles.loginButton}
|
||||
mode="contained"
|
||||
onPress={() =>
|
||||
onPress={async () => {
|
||||
storeData<PassportAppointment[]>(
|
||||
'passportAppointments',
|
||||
passportAppointmentData,
|
||||
);
|
||||
const storedData = await getData('passportAppointments');
|
||||
console.log('Data yang tersimpan:', storedData);
|
||||
navigation.reset({
|
||||
index: 0,
|
||||
routes: [{name: 'NavigationRoute'}],
|
||||
})
|
||||
}>
|
||||
});
|
||||
}}>
|
||||
Masuk
|
||||
</Button>
|
||||
<View style={styles.registerAccountContainer}>
|
||||
|
@ -1,23 +1,16 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
BottomNavigation,
|
||||
Button,
|
||||
Dialog,
|
||||
PaperProvider,
|
||||
Portal,
|
||||
Text,
|
||||
} from 'react-native-paper';
|
||||
import {BottomNavigation, PaperProvider, Text} from 'react-native-paper';
|
||||
import Colors from '../../../assets/styles/Colors';
|
||||
import ProfileScreen from '../profile';
|
||||
import styles from './styles';
|
||||
import HomeScreen from '../home';
|
||||
import HistoryScreen from '../history';
|
||||
import {View} from 'react-native';
|
||||
import {useState} from 'react';
|
||||
import {NativeStackNavigationProp} from '@react-navigation/native-stack';
|
||||
import {RootStackParamList} from '../../navigation/type';
|
||||
import {useNavigation} from '@react-navigation/native';
|
||||
import FontFamily from '../../../assets/styles/FontFamily';
|
||||
import DialogWarningApplication from '../../components/dialog/DialogWarningApplication';
|
||||
import DialogLogout from '../../components/dialog/DialogLogout';
|
||||
|
||||
type NavigationRouteScreenNavigationProp = NativeStackNavigationProp<
|
||||
RootStackParamList,
|
||||
@ -26,7 +19,11 @@ type NavigationRouteScreenNavigationProp = NativeStackNavigationProp<
|
||||
|
||||
function NavigationRouteScreen() {
|
||||
const navigation = useNavigation<NavigationRouteScreenNavigationProp>();
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const [visibleWarningApplicationDialog, setVisibleWarningApplicationDialog] =
|
||||
useState(false);
|
||||
const [visibleLogoutDialog, setVisibleLogoutDialog] = useState(false);
|
||||
|
||||
const [index, setIndex] = useState(0);
|
||||
const [routes] = useState([
|
||||
{
|
||||
@ -48,17 +45,32 @@ function NavigationRouteScreen() {
|
||||
},
|
||||
]);
|
||||
|
||||
const showDialog = () => setVisible(true);
|
||||
const hideDialog = () => setVisible(false);
|
||||
const showWarningApplicationDialog = () =>
|
||||
setVisibleWarningApplicationDialog(true);
|
||||
const hideWarningApplicationDialog = () =>
|
||||
setVisibleWarningApplicationDialog(false);
|
||||
|
||||
const showLogoutDialog = () => setVisibleLogoutDialog(true);
|
||||
const hideLogoutDialog = () => setVisibleLogoutDialog(false);
|
||||
|
||||
const renderScene = ({route}: {route: {key: string}}) => {
|
||||
switch (route.key) {
|
||||
case 'home':
|
||||
return <HomeScreen showDialog={showDialog} visible={visible} />;
|
||||
return (
|
||||
<HomeScreen
|
||||
visible={visibleWarningApplicationDialog}
|
||||
showDialog={showWarningApplicationDialog}
|
||||
/>
|
||||
);
|
||||
case 'history':
|
||||
return <HistoryScreen />;
|
||||
case 'profile':
|
||||
return <ProfileScreen />;
|
||||
return (
|
||||
<ProfileScreen
|
||||
visible={visibleLogoutDialog}
|
||||
showDialog={showLogoutDialog}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@ -82,35 +94,25 @@ function NavigationRouteScreen() {
|
||||
<Text style={styles.bottomNavLabel}>{route.title}</Text>
|
||||
)}
|
||||
/>
|
||||
<Portal>
|
||||
<Dialog visible={visible} style={styles.dialogContainer}>
|
||||
<Dialog.Title style={styles.dialogTitle}>Peringatan</Dialog.Title>
|
||||
<View style={styles.dialogContentContainer}>
|
||||
<Text style={styles.dialogDesc}>
|
||||
Silakan melakukan pengisian kuesioner.
|
||||
</Text>
|
||||
<Text style={[styles.dialogDesc, {...FontFamily.notoSansBold}]}>
|
||||
Pastikan data dan jawaban yang Anda berikan benar.
|
||||
</Text>
|
||||
<Text style={styles.dialogDesc}>
|
||||
Pemberian keterangan yang tidak benar merupakan pelanggaran
|
||||
keimigrasian sebagaimana ketentuan Pasal 126 huruf c UU No. 6
|
||||
tahun 2011 tentang Keimigrasian dan akan mengakibatkan permohonan
|
||||
paspor Anda ditolak dan pembayaran tidak dapat dikembalikan.
|
||||
</Text>
|
||||
<Button
|
||||
style={styles.buttonContinue}
|
||||
mode="contained"
|
||||
textColor={Colors.neutral100.color}
|
||||
onPress={() => {
|
||||
hideDialog();
|
||||
navigation.navigate('RegularPassport');
|
||||
}}>
|
||||
Lanjut
|
||||
</Button>
|
||||
</View>
|
||||
</Dialog>
|
||||
</Portal>
|
||||
{visibleWarningApplicationDialog && (
|
||||
<DialogWarningApplication
|
||||
visible={visibleWarningApplicationDialog}
|
||||
hideDialog={hideWarningApplicationDialog}
|
||||
onNavigate={() => navigation.navigate('RegularPassport')}
|
||||
/>
|
||||
)}
|
||||
{visibleLogoutDialog && (
|
||||
<DialogLogout
|
||||
visible={visibleLogoutDialog}
|
||||
hideDialog={hideLogoutDialog}
|
||||
onNavigate={() =>
|
||||
navigation.reset({
|
||||
index: 0,
|
||||
routes: [{name: 'Login'}],
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</PaperProvider>
|
||||
);
|
||||
}
|
||||
|
@ -10,32 +10,6 @@ const styles = StyleSheet.create({
|
||||
fontSize: 12,
|
||||
position: 'absolute',
|
||||
},
|
||||
dialogContainer: {
|
||||
backgroundColor: 'white',
|
||||
elevation: 0,
|
||||
shadowColor: 'transparent',
|
||||
borderRadius: 20,
|
||||
},
|
||||
dialogTitle: {
|
||||
fontSize: 22,
|
||||
color: Colors.secondary30.color,
|
||||
},
|
||||
dialogContentContainer: {
|
||||
marginHorizontal: 24,
|
||||
marginBottom: 24,
|
||||
gap: 16,
|
||||
},
|
||||
dialogDesc: {
|
||||
fontSize: 14,
|
||||
...FontFamily.notoSansRegular,
|
||||
includeFontPadding: false,
|
||||
lineHeight: 22,
|
||||
color: Colors.primary30.color,
|
||||
},
|
||||
buttonContinue: {
|
||||
backgroundColor: Colors.primary30.color,
|
||||
marginTop: 12,
|
||||
},
|
||||
});
|
||||
|
||||
export default styles;
|
||||
|
@ -13,20 +13,29 @@ type ProfileScreenNavigationProp = NativeStackNavigationProp<
|
||||
'Profile'
|
||||
>;
|
||||
|
||||
function ProfileScreen() {
|
||||
type ProfileScreenProps = {
|
||||
readonly showDialog: () => void;
|
||||
readonly visible: boolean;
|
||||
};
|
||||
|
||||
function ProfileScreen(props: ProfileScreenProps) {
|
||||
const {showDialog, visible} = props;
|
||||
|
||||
const placeholderProfileImage = require('../../../assets/images/placeholderProfileImage.png');
|
||||
const navigation = useNavigation<ProfileScreenNavigationProp>();
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
StatusBar.setBackgroundColor(Colors.secondary30.color);
|
||||
StatusBar.setBackgroundColor(
|
||||
visible ? '#295E70' : Colors.secondary30.color,
|
||||
);
|
||||
StatusBar.setBarStyle('light-content');
|
||||
|
||||
return () => {
|
||||
StatusBar.setBackgroundColor(Colors.secondary30.color);
|
||||
StatusBar.setBarStyle('light-content');
|
||||
};
|
||||
}, []),
|
||||
}, [visible]),
|
||||
);
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
@ -35,7 +44,7 @@ function ProfileScreen() {
|
||||
</View>
|
||||
<View style={styles.topContainer}>
|
||||
<Image source={placeholderProfileImage} style={styles.profileImage} />
|
||||
<Text style={styles.accountName}>X</Text>
|
||||
<Text style={styles.accountName}>Salwa Aisyah Adhani</Text>
|
||||
<Text style={styles.accountNumber}>3271234560009123456</Text>
|
||||
</View>
|
||||
<View style={styles.sectionProfileField}>
|
||||
@ -80,12 +89,7 @@ function ProfileScreen() {
|
||||
mode="outlined"
|
||||
textColor={Colors.indicatorRed.color}
|
||||
style={styles.logoutButton}
|
||||
onPress={() =>
|
||||
navigation.reset({
|
||||
index: 0,
|
||||
routes: [{name: 'Login'}],
|
||||
})
|
||||
}>
|
||||
onPress={showDialog}>
|
||||
Keluar
|
||||
</Button>
|
||||
</View>
|
||||
|
@ -43,6 +43,7 @@ import DialogPassportConditionInfo from '../../components/dialog/DialogPassportC
|
||||
import DialogPassportTypeInfo from '../../components/dialog/DialogPassportTypeInfo';
|
||||
import SheetEditData from '../../components/sheet/SheetEditData';
|
||||
import SheetSearchLocation from '../../components/sheet/SheetSearchLocation';
|
||||
import SheetSelectDate from '../../components/sheet/SheetSelectDate';
|
||||
|
||||
type RegularPassportScreenNavigationProp = NativeStackNavigationProp<
|
||||
RootStackParamList,
|
||||
@ -70,6 +71,7 @@ type RenderApplicationStepsContentProps = {
|
||||
showPassportTypeInfoDialog: () => void;
|
||||
showEditDataSheet: () => void;
|
||||
showSearchLocationSheet: () => void;
|
||||
showSelectDateSheet: () => void;
|
||||
};
|
||||
|
||||
const RenderApplicationStepsContent = (
|
||||
@ -96,6 +98,7 @@ const RenderApplicationStepsContent = (
|
||||
showPassportTypeInfoDialog,
|
||||
showEditDataSheet,
|
||||
showSearchLocationSheet,
|
||||
showSelectDateSheet,
|
||||
} = props;
|
||||
|
||||
if (step === 1) {
|
||||
@ -235,6 +238,7 @@ const RenderApplicationStepsContent = (
|
||||
}
|
||||
showPassportTypeInfoDialog={showPassportTypeInfoDialog}
|
||||
showSearchLocationSheet={showSearchLocationSheet}
|
||||
showSelectDateSheet={showSelectDateSheet}
|
||||
/>
|
||||
);
|
||||
case 7:
|
||||
@ -336,6 +340,7 @@ function RegularPassportScreen() {
|
||||
const [visibleEditDataSheet, setVisibleEditDataSheet] = useState(false);
|
||||
const [visibleSearchLocationSheet, setVisibleSearchLocationSheet] =
|
||||
useState(false);
|
||||
const [visibleSelectDateSheet, setVisibleSelectDateSheet] = useState(false);
|
||||
|
||||
// Dialog visibility function
|
||||
const showDialog = () => setVisible(true);
|
||||
@ -377,6 +382,9 @@ function RegularPassportScreen() {
|
||||
const showSearchLocationSheet = () => setVisibleSearchLocationSheet(true);
|
||||
const hideSearchLocationSheet = () => setVisibleSearchLocationSheet(false);
|
||||
|
||||
const showSelectDateSheet = () => setVisibleSelectDateSheet(true);
|
||||
const hideSelectDateSheet = () => setVisibleSelectDateSheet(false);
|
||||
|
||||
const stepTitles: {[key: number]: string} = {
|
||||
1: 'Informasi Pribadi',
|
||||
2: 'Dokumen Pendukung',
|
||||
@ -457,6 +465,7 @@ function RegularPassportScreen() {
|
||||
showPassportTypeInfoDialog={showPassportTypeInfoDialog}
|
||||
showEditDataSheet={showEditDataSheet}
|
||||
showSearchLocationSheet={showSearchLocationSheet}
|
||||
showSelectDateSheet={showSelectDateSheet}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@ -501,7 +510,11 @@ function RegularPassportScreen() {
|
||||
<DialogSubmitSuccess
|
||||
visible={visibleSubmitSuccessDialog}
|
||||
onSubmitSuccess={() => {
|
||||
navigation.goBack(), hideSubmitSuccessDialog();
|
||||
navigation.reset({
|
||||
index: 0,
|
||||
routes: [{name: 'NavigationRoute'}],
|
||||
});
|
||||
hideSubmitSuccessDialog();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@ -542,6 +555,14 @@ function RegularPassportScreen() {
|
||||
onClose={hideSearchLocationSheet}
|
||||
/>
|
||||
)}
|
||||
|
||||
{visibleSelectDateSheet && (
|
||||
<SheetSelectDate
|
||||
visible={visibleSelectDateSheet}
|
||||
onClose={hideSelectDateSheet}
|
||||
onContinue={hideSelectDateSheet}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Questionnaire
|
||||
@ -566,7 +587,9 @@ function RegularPassportScreen() {
|
||||
visibleFinalizationConfirmationDialog ||
|
||||
visiblePassportTypeInfoDialog
|
||||
? '#295E70'
|
||||
: visibleEditDataSheet || visibleSearchLocationSheet
|
||||
: visibleEditDataSheet ||
|
||||
visibleSearchLocationSheet ||
|
||||
visibleSelectDateSheet
|
||||
? '#185769'
|
||||
: Colors.secondary30.color
|
||||
}
|
||||
|
@ -7,6 +7,12 @@ import {Button} from 'react-native-paper';
|
||||
import Colors from '../../../../../assets/styles/Colors';
|
||||
import jobData from '../../../../data/DropdownData/JobData';
|
||||
import nationalityData from '../../../../data/DropdownData/NationalityData';
|
||||
import {PassportAppointment} from '../../../../navigation/type';
|
||||
import {
|
||||
addData,
|
||||
getData,
|
||||
storeData,
|
||||
} from '../../../../helper/asyncStorageHelper';
|
||||
|
||||
const Step4DataConfirmationSubStep2 = ({
|
||||
setStep,
|
||||
@ -148,7 +154,49 @@ const Step4DataConfirmationSubStep2 = ({
|
||||
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => setStep(5)}
|
||||
onPress={async () => {
|
||||
// Ambil data appointment yang sudah tersimpan
|
||||
const storedAppointments: PassportAppointment[] =
|
||||
(await getData('passportAppointments')) || [];
|
||||
|
||||
// Ambil ID terakhir dan hitung ID baru
|
||||
const lastId = storedAppointments.length
|
||||
? Math.max(...storedAppointments.map(item => Number(item.id)))
|
||||
: 0;
|
||||
const nextId = (lastId + 1).toString();
|
||||
|
||||
// Buat appointment baru dengan ID yang sudah dihitung
|
||||
const newAppointment: PassportAppointment = {
|
||||
id: nextId,
|
||||
applicantName: 'Salwa Aisyah Adhani',
|
||||
applicantCode: '1038000008887777',
|
||||
appointmentDate: 'Selasa, 20 Mei 2025',
|
||||
appointmentTime: '10:00-11:00 WIB',
|
||||
serviceUnit: 'Kantor Imigrasi Depok',
|
||||
status: 'Menunggu Pembayaran',
|
||||
submissionDate: 'Kamis, 15 Mei 2025 21:30',
|
||||
serviceCode: 'EH-LP7RNC',
|
||||
applicationDetails: {
|
||||
nationalIdNumber: '3271234560009123456',
|
||||
gender: 'Wanita',
|
||||
applicationType: 'Penggantian Paspor',
|
||||
replacementReason: 'Penuh/Halaman Penuh',
|
||||
applicationPurpose: 'Wisata/Liburan',
|
||||
passportType: 'PASPOR ELEKTRONIK POLIKARBONAT 5 TAHUN',
|
||||
fee: '650.000',
|
||||
},
|
||||
};
|
||||
|
||||
// Simpan appointment baru
|
||||
await addData<PassportAppointment>(
|
||||
'passportAppointments',
|
||||
newAppointment,
|
||||
);
|
||||
|
||||
const updatedAppointments = await getData('passportAppointments');
|
||||
console.log('Data yang berhasil ditambahkan:', updatedAppointments);
|
||||
setStep(5);
|
||||
}}
|
||||
style={[styles.subStepButtonContained, {marginBottom: 8}]}
|
||||
textColor={Colors.neutral100.color}>
|
||||
Simpan Draft
|
||||
|
@ -1,9 +1,11 @@
|
||||
import React from 'react';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {View, Text, Pressable} from 'react-native';
|
||||
import {Button} from 'react-native-paper';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import Colors from '../../../../../assets/styles/Colors';
|
||||
import styles from '../styles';
|
||||
import {getData} from '../../../../helper/asyncStorageHelper';
|
||||
import {PassportAppointment} from '../../../../navigation/type';
|
||||
|
||||
type Step5VerificationProps = {
|
||||
setStep: (step: number) => void;
|
||||
@ -15,8 +17,23 @@ type Step5VerificationProps = {
|
||||
const Step5Content = (props: Step5VerificationProps) => {
|
||||
const {setStep, setSubStep, passportAppointmentData, showEditDataSheet} =
|
||||
props;
|
||||
const lastAppointment =
|
||||
passportAppointmentData[passportAppointmentData.length - 1];
|
||||
|
||||
const [lastAppointment, setLastAppointment] =
|
||||
useState<PassportAppointment>();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const data = await getData('passportAppointments');
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
setLastAppointment(data[data.length - 1]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching data: ', error);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View style={styles.subStepContainer}>
|
||||
@ -36,7 +53,7 @@ const Step5Content = (props: Step5VerificationProps) => {
|
||||
styles.applicantDetailTextDesc,
|
||||
{textTransform: 'uppercase', flex: 0},
|
||||
]}>
|
||||
{lastAppointment.applicantName}
|
||||
{lastAppointment?.applicantName}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.applicantDetailIconContentWrapper}>
|
||||
@ -63,27 +80,27 @@ const Step5Content = (props: Step5VerificationProps) => {
|
||||
<View style={styles.applicantDetailContentChildContainer}>
|
||||
<DetailRow
|
||||
label="NIK"
|
||||
value={lastAppointment.applicationDetails.nationalIdNumber}
|
||||
value={lastAppointment?.applicationDetails?.nationalIdNumber}
|
||||
/>
|
||||
<DetailRow
|
||||
label="Jenis Kelamin"
|
||||
value={lastAppointment.applicationDetails.gender}
|
||||
value={lastAppointment?.applicationDetails?.gender}
|
||||
/>
|
||||
<DetailRow
|
||||
label="Jenis Permohonan"
|
||||
value={lastAppointment.applicationDetails.applicationType}
|
||||
value={lastAppointment?.applicationDetails?.applicationType}
|
||||
/>
|
||||
<DetailRow
|
||||
label="Alasan Penggantian"
|
||||
value={lastAppointment.applicationDetails.replacementReason}
|
||||
value={lastAppointment?.applicationDetails?.replacementReason}
|
||||
/>
|
||||
<DetailRow
|
||||
label="Tujuan Permohonan"
|
||||
value={lastAppointment.applicationDetails.applicationPurpose}
|
||||
value={lastAppointment?.applicationDetails?.applicationPurpose}
|
||||
/>
|
||||
<DetailRow
|
||||
label="Jenis Paspor"
|
||||
value={lastAppointment.applicationDetails.passportType}
|
||||
value={lastAppointment?.applicationDetails?.passportType}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
@ -110,7 +127,7 @@ const Step5Content = (props: Step5VerificationProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const DetailRow = ({label, value}: {label: string; value: string}) => (
|
||||
const DetailRow = ({label, value}: {label: string; value: string | undefined}) => (
|
||||
<View style={styles.applicantDetailTextContentWrapper}>
|
||||
<Text style={styles.applicantDetailTextTitle}>{label}</Text>
|
||||
<Text style={styles.applicantDetailTextDesc}>{value}</Text>
|
||||
|
@ -6,16 +6,22 @@ import styles from '../styles';
|
||||
import Colors from '../../../../../assets/styles/Colors';
|
||||
import FontFamily from '../../../../../assets/styles/FontFamily';
|
||||
import arrivalDateGuidelinesData from '../../../../data/Steps/ArrivalDateGuidelinesData';
|
||||
import passportTypeData from '../../../../data/DropdownData/PassportTypeData';
|
||||
|
||||
type Step6ProcessingProps = {
|
||||
showFinalizationConfirmationDialog: () => void;
|
||||
showPassportTypeInfoDialog: () => void;
|
||||
showSearchLocationSheet: () => void;
|
||||
showSelectDateSheet: () => void;
|
||||
};
|
||||
|
||||
const Step6Processing = (props: Step6ProcessingProps) => {
|
||||
const {showFinalizationConfirmationDialog, showPassportTypeInfoDialog, showSearchLocationSheet} =
|
||||
props;
|
||||
const {
|
||||
showFinalizationConfirmationDialog,
|
||||
showPassportTypeInfoDialog,
|
||||
showSearchLocationSheet,
|
||||
showSelectDateSheet,
|
||||
} = props;
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.subStepContainer}>
|
||||
@ -30,13 +36,12 @@ const Step6Processing = (props: Step6ProcessingProps) => {
|
||||
</View>
|
||||
|
||||
<View style={[styles.subStepTextInputContainer, {marginVertical: 16}]}>
|
||||
{/* Trigger Search Location Bottom Sheet */}
|
||||
<TextInputComponent
|
||||
title="Lokasi Kantor Imigrasi"
|
||||
placeholder="Pilih lokasi kantor imigrasi"
|
||||
isRequired
|
||||
isDropdownSearchLocation
|
||||
handlePressSearchLocation={showSearchLocationSheet}
|
||||
isDropdownPressedSheet
|
||||
handleDropdownPressed={showSearchLocationSheet}
|
||||
/>
|
||||
<TextInputComponent
|
||||
title="Jenis Paspor"
|
||||
@ -44,6 +49,7 @@ const Step6Processing = (props: Step6ProcessingProps) => {
|
||||
placeholder="Pilih satu jenis paspor"
|
||||
isRequired
|
||||
isDropdown
|
||||
dropdownItemData={passportTypeData}
|
||||
onIconButtonPress={showPassportTypeInfoDialog}
|
||||
/>
|
||||
</View>
|
||||
@ -80,12 +86,12 @@ const Step6Processing = (props: Step6ProcessingProps) => {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* TODO: Add calendar functionality here. */}
|
||||
<TextInputComponent
|
||||
title="Tanggal dan Waktu Kedatangan"
|
||||
placeholder="Pilih tanggal dan waktu kedatangan"
|
||||
isRequired
|
||||
isDate
|
||||
isDropdownPressedSheet
|
||||
handleDropdownPressed={showSelectDateSheet}
|
||||
/>
|
||||
|
||||
<Button
|
||||
|
@ -1,12 +1,13 @@
|
||||
import React from 'react';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {ScrollView, View} from 'react-native';
|
||||
import {Button, Divider, Text} from 'react-native-paper';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import Colors from '../../../../../assets/styles/Colors';
|
||||
import styles from '../styles';
|
||||
import passportAppointmentData from '../../../../data/History/PassportAppointmentData';
|
||||
import Accordion from '../../../../components/Accordion';
|
||||
import termsAndConditionsData from '../../../../data/Steps/TermsAndContionsData';
|
||||
import {PassportAppointment} from '../../../../navigation/type';
|
||||
import {getData} from '../../../../helper/asyncStorageHelper';
|
||||
|
||||
type Step7CompletionProps = {
|
||||
showSubmitSuccessDialog: () => void;
|
||||
@ -15,8 +16,21 @@ type Step7CompletionProps = {
|
||||
|
||||
const Step7Completion = (props: Step7CompletionProps) => {
|
||||
const {showSubmitSuccessDialog, setLastCompletedSteps} = props;
|
||||
const lastAppointment =
|
||||
passportAppointmentData[passportAppointmentData.length - 1];
|
||||
const [lastAppointment, setLastAppointment] = useState<PassportAppointment>();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const data = await getData('passportAppointments');
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
setLastAppointment(data[data.length - 1]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching data: ', error);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
@ -31,7 +45,7 @@ const Step7Completion = (props: Step7CompletionProps) => {
|
||||
color={Colors.secondary30.color}
|
||||
/>
|
||||
<Text style={styles.midIconContentTextStyle}>
|
||||
{lastAppointment.appointmentDate}
|
||||
{lastAppointment?.appointmentDate}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.midIconContentWrapper}>
|
||||
@ -41,7 +55,7 @@ const Step7Completion = (props: Step7CompletionProps) => {
|
||||
color={Colors.secondary30.color}
|
||||
/>
|
||||
<Text style={styles.midIconContentTextStyle}>
|
||||
{lastAppointment.appointmentTime}
|
||||
{lastAppointment?.appointmentTime}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.midIconContentWrapper}>
|
||||
@ -51,7 +65,7 @@ const Step7Completion = (props: Step7CompletionProps) => {
|
||||
color={Colors.secondary30.color}
|
||||
/>
|
||||
<Text style={styles.midIconContentTextStyle}>
|
||||
{lastAppointment.serviceUnit}
|
||||
{lastAppointment?.serviceUnit}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
@ -60,13 +74,13 @@ const Step7Completion = (props: Step7CompletionProps) => {
|
||||
<View style={styles.midTextContentWrapper}>
|
||||
<Text style={styles.midTextContentTitle}>Tanggal Pengajuan</Text>
|
||||
<Text style={styles.midTextContentData}>
|
||||
{lastAppointment.submissionDate}
|
||||
{lastAppointment?.submissionDate}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.midTextContentWrapper}>
|
||||
<Text style={styles.midTextContentTitle}>Kode Layanan</Text>
|
||||
<Text style={styles.midTextContentData}>
|
||||
{lastAppointment.serviceCode}
|
||||
{lastAppointment?.serviceCode}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
@ -130,7 +144,7 @@ const Step7Completion = (props: Step7CompletionProps) => {
|
||||
styles.applicantDetailTextDesc,
|
||||
styles.applicantDetailTexDescName,
|
||||
]}>
|
||||
{lastAppointment.applicantName}
|
||||
{lastAppointment?.applicantName}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.applicantDetailTextContentWrapper}>
|
||||
@ -140,20 +154,20 @@ const Step7Completion = (props: Step7CompletionProps) => {
|
||||
styles.applicantDetailTextDesc,
|
||||
styles.applicantDetailTexDescCode,
|
||||
]}>
|
||||
{lastAppointment.applicantCode}
|
||||
{lastAppointment?.applicantCode}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.applicantDetailContentChildContainer}>
|
||||
<View style={styles.applicantDetailTextContentWrapper}>
|
||||
<Text style={styles.applicantDetailTextTitle}>NIK</Text>
|
||||
<Text style={styles.applicantDetailTextDesc}>
|
||||
{lastAppointment.applicationDetails.nationalIdNumber}
|
||||
{lastAppointment?.applicationDetails?.nationalIdNumber}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.applicantDetailTextContentWrapper}>
|
||||
<Text style={styles.applicantDetailTextTitle}>Jenis Kelamin</Text>
|
||||
<Text style={styles.applicantDetailTextDesc}>
|
||||
{lastAppointment.applicationDetails.gender}
|
||||
{lastAppointment?.applicationDetails?.gender}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.applicantDetailTextContentWrapper}>
|
||||
@ -161,7 +175,7 @@ const Step7Completion = (props: Step7CompletionProps) => {
|
||||
Jenis Permohonan
|
||||
</Text>
|
||||
<Text style={styles.applicantDetailTextDesc}>
|
||||
{lastAppointment.applicationDetails.applicationType}
|
||||
{lastAppointment?.applicationDetails?.applicationType}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.applicantDetailTextContentWrapper}>
|
||||
@ -169,7 +183,7 @@ const Step7Completion = (props: Step7CompletionProps) => {
|
||||
Alasan Penggantian
|
||||
</Text>
|
||||
<Text style={styles.applicantDetailTextDesc}>
|
||||
{lastAppointment.applicationDetails.replacementReason}
|
||||
{lastAppointment?.applicationDetails?.replacementReason}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.applicantDetailTextContentWrapper}>
|
||||
@ -177,16 +191,23 @@ const Step7Completion = (props: Step7CompletionProps) => {
|
||||
Tujuan Permohonan
|
||||
</Text>
|
||||
<Text style={styles.applicantDetailTextDesc}>
|
||||
{lastAppointment.applicationDetails.applicationPurpose}
|
||||
{lastAppointment?.applicationDetails?.applicationPurpose}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.applicantDetailTextContentWrapper}>
|
||||
<Text style={styles.applicantDetailTextTitle}>Jenis Paspor</Text>
|
||||
<Text style={styles.applicantDetailTextDesc}>
|
||||
{lastAppointment.applicationDetails.passportType}
|
||||
{lastAppointment?.applicationDetails?.passportType}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Divider style={styles.applicantDetailDividerMargin} />
|
||||
<View style={styles.applicantDetailBottomContentWrapper}>
|
||||
<Text style={styles.applicantDetailBottomText}>Biaya</Text>
|
||||
<Text style={styles.applicantDetailBottomText}>
|
||||
{lastAppointment?.applicationDetails?.fee}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View style={{margin: 16}}>
|
||||
|
@ -200,6 +200,15 @@ const styles = StyleSheet.create({
|
||||
applicantDetailTexDescCode: {
|
||||
textAlign: 'right',
|
||||
},
|
||||
applicantDetailBottomText: {
|
||||
fontSize: 14,
|
||||
...FontFamily.notoSansBold,
|
||||
color: Colors.primary30.color,
|
||||
includeFontPadding: false,
|
||||
},
|
||||
applicantDetailDividerMargin: {
|
||||
marginVertical: 4,
|
||||
},
|
||||
midContainer: {
|
||||
backgroundColor: Colors.neutral100.color,
|
||||
},
|
||||
|
Reference in New Issue
Block a user