diff --git a/package-lock.json b/package-lock.json
index 5969817..3ebedf1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index 1bfc8fa..a0a0ef7 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/components/PassportAppointmentCard.tsx b/src/components/PassportAppointmentCard.tsx
index 5be15bc..c0cd9ac 100644
--- a/src/components/PassportAppointmentCard.tsx
+++ b/src/components/PassportAppointmentCard.tsx
@@ -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;
diff --git a/src/components/TextInput.tsx b/src/components/TextInput.tsx
index e4b0056..24eaa9b 100644
--- a/src/components/TextInput.tsx
+++ b/src/components/TextInput.tsx
@@ -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 (
{title && (
@@ -291,7 +291,7 @@ const TextInputComponent = (props: TextInputComponentProps) => {
)}
({
transform: [{scale: pressed ? 0.99 : 1}],
})}>
@@ -303,7 +303,14 @@ const TextInputComponent = (props: TextInputComponentProps) => {
placeholderTextColor={Colors.primary60.color}
editable={false}
value={formattedDate}
- right={}
+ right={
+
+ }
multiline={false}
textColor="#48454E"
disabled={isDisabled}
diff --git a/src/components/dialog/DialogLogout.tsx b/src/components/dialog/DialogLogout.tsx
new file mode 100644
index 0000000..04fd906
--- /dev/null
+++ b/src/components/dialog/DialogLogout.tsx
@@ -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 (
+
+
+
+ );
+};
+
+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,
+ },
+});
diff --git a/src/components/dialog/DialogWarningApplication.tsx b/src/components/dialog/DialogWarningApplication.tsx
new file mode 100644
index 0000000..471c16f
--- /dev/null
+++ b/src/components/dialog/DialogWarningApplication.tsx
@@ -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 (
+
+
+
+ );
+};
+
+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,
+ },
+});
diff --git a/src/components/sheet/SheetSelectDate.tsx b/src/components/sheet/SheetSelectDate.tsx
index 569cff1..dab5adb 100644
--- a/src/components/sheet/SheetSelectDate.tsx
+++ b/src/components/sheet/SheetSelectDate.tsx
@@ -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) => (
+
+
+ {label}
+
+);
+
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 (
+
+ {headerText}
+
+ handleMonthChange(-1)}>
+
+
+ handleMonthChange(1)}>
+
+
+
+
+ );
+ };
+
+ const renderTimeSlotItem = ({item}: any) => {
+ return (
+
+ Jam Kedatangan
+
+
+ setSelectedTimeSlot(item.id)}
+ color={Colors.secondary30.color}
+ uncheckedColor={Colors.secondary30.color}
+ />
+ {item.arrivalTime}
+
+
+
+ Jumlah Antrian
+ {item.queueCount}
+
+
+ Sisa Kuota
+ {item.remainingQuota}
+
+
+
+
+ );
+ };
return (
-
+
);
};
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,
},
});
diff --git a/src/data/DropdownData/PassportTypeData.tsx b/src/data/DropdownData/PassportTypeData.tsx
new file mode 100644
index 0000000..907d0e2
--- /dev/null
+++ b/src/data/DropdownData/PassportTypeData.tsx
@@ -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;
diff --git a/src/data/History/PassportAppointmentData.tsx b/src/data/History/PassportAppointmentData.tsx
index a8372ea..eb47c91 100644
--- a/src/data/History/PassportAppointmentData.tsx
+++ b/src/data/History/PassportAppointmentData.tsx
@@ -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;
diff --git a/src/data/Steps/ArrivalTimesData.tsx b/src/data/Steps/ArrivalTimesData.tsx
new file mode 100644
index 0000000..eabb710
--- /dev/null
+++ b/src/data/Steps/ArrivalTimesData.tsx
@@ -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;
diff --git a/src/data/Steps/MarkedDatesData.tsx b/src/data/Steps/MarkedDatesData.tsx
new file mode 100644
index 0000000..8a58018
--- /dev/null
+++ b/src/data/Steps/MarkedDatesData.tsx
@@ -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;
diff --git a/src/helper/asyncStorageHelper.tsx b/src/helper/asyncStorageHelper.tsx
new file mode 100644
index 0000000..8a516d3
--- /dev/null
+++ b/src/helper/asyncStorageHelper.tsx
@@ -0,0 +1,40 @@
+import AsyncStorage from '@react-native-async-storage/async-storage';
+
+// Fungsi untuk menyimpan data
+export const storeData = async (key: string, value: T): Promise => {
+ 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 (key: string): Promise => {
+ 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 (key: string, newData: T): Promise => {
+ 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);
+ }
+};
diff --git a/src/navigation/RootStack.tsx b/src/navigation/RootStack.tsx
index 6d9c660..b5fa938 100644
--- a/src/navigation/RootStack.tsx
+++ b/src/navigation/RootStack.tsx
@@ -54,7 +54,9 @@ function RootStack() {
options={{headerShown: false}}
/>
- {() => console.log('Show dialog!')} visible />}
+ {() => (
+ {}} visible />
+ )}
-
+
+ {() => (
+ {}} visible />
+ )}
+
([]);
+ 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() {
)}
-
- (
-
- navigation.navigate('ApplicationDetail', {data: item})
- }
- style={({pressed}) => ({
- transform: [{scale: pressed ? 0.975 : 1}],
- })}>
-
-
- )}
- keyExtractor={item => item.id}
- ItemSeparatorComponent={ItemSeparator}
- />
-
+ {isLoading ? (
+
+
+
+ ) : (
+
+ (
+
+ navigation.navigate('ApplicationDetail', {data: item})
+ }
+ style={({pressed}) => ({
+ transform: [{scale: pressed ? 0.975 : 1}],
+ })}>
+
+
+ )}
+ keyExtractor={item => item.id ?? ''}
+ ItemSeparatorComponent={ItemSeparator}
+ />
+
+ )}
);
}
diff --git a/src/screens/home/index.tsx b/src/screens/home/index.tsx
index 89dcbd2..32846fd 100644
--- a/src/screens/home/index.tsx
+++ b/src/screens/home/index.tsx
@@ -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 = () => ;
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();
return (
@@ -223,8 +227,8 @@ const RenderContent = ({showDialog}: RenderContentProps) => {
(
+ data={lastTwoAppointments}
+ renderItem={({item}: any) => (
navigation.navigate('ApplicationDetail', {data: item})
@@ -242,7 +246,7 @@ const RenderContent = ({showDialog}: RenderContentProps) => {
/>
)}
- keyExtractor={item => item.id}
+ keyExtractor={item => item.id ?? ''}
ItemSeparatorComponent={ItemSeparator}
/>
@@ -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();
+ 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 (
- Halo, X!
+
+ Halo, Salwa Aisyah Adhani!
+
}
+ renderItem={() => (
+
+ )}
/>
);
diff --git a/src/screens/home/styles.tsx b/src/screens/home/styles.tsx
index 213ea4b..9039cc6 100644
--- a/src/screens/home/styles.tsx
+++ b/src/screens/home/styles.tsx
@@ -16,6 +16,8 @@ const styles = StyleSheet.create({
fontSize: 28,
marginVertical: 14,
includeFontPadding: false,
+ flex: 1,
+ marginEnd: 16,
},
appBarContainer: {
height: 72,
diff --git a/src/screens/login/index.tsx b/src/screens/login/index.tsx
index e635039..1550720 100644
--- a/src/screens/login/index.tsx
+++ b/src/screens/login/index.tsx
@@ -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() {
diff --git a/src/screens/navigationRoute/index.tsx b/src/screens/navigationRoute/index.tsx
index 302eeef..b2d47b6 100644
--- a/src/screens/navigationRoute/index.tsx
+++ b/src/screens/navigationRoute/index.tsx
@@ -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();
- 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 ;
+ return (
+
+ );
case 'history':
return ;
case 'profile':
- return ;
+ return (
+
+ );
default:
return null;
}
@@ -82,35 +94,25 @@ function NavigationRouteScreen() {
{route.title}
)}
/>
-
-
-
+ {visibleWarningApplicationDialog && (
+ navigation.navigate('RegularPassport')}
+ />
+ )}
+ {visibleLogoutDialog && (
+
+ navigation.reset({
+ index: 0,
+ routes: [{name: 'Login'}],
+ })
+ }
+ />
+ )}
);
}
diff --git a/src/screens/navigationRoute/styles.tsx b/src/screens/navigationRoute/styles.tsx
index efe44d1..6a38d34 100644
--- a/src/screens/navigationRoute/styles.tsx
+++ b/src/screens/navigationRoute/styles.tsx
@@ -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;
diff --git a/src/screens/profile/index.tsx b/src/screens/profile/index.tsx
index 56360e4..e1fdfad 100644
--- a/src/screens/profile/index.tsx
+++ b/src/screens/profile/index.tsx
@@ -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();
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 (
@@ -35,7 +44,7 @@ function ProfileScreen() {
- X
+ Salwa Aisyah Adhani
3271234560009123456
@@ -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
diff --git a/src/screens/regularPassport/index.tsx b/src/screens/regularPassport/index.tsx
index 4934ee5..e1e2609 100644
--- a/src/screens/regularPassport/index.tsx
+++ b/src/screens/regularPassport/index.tsx
@@ -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}
/>
@@ -501,7 +510,11 @@ function RegularPassportScreen() {
{
- navigation.goBack(), hideSubmitSuccessDialog();
+ navigation.reset({
+ index: 0,
+ routes: [{name: 'NavigationRoute'}],
+ });
+ hideSubmitSuccessDialog();
}}
/>
)}
@@ -542,6 +555,14 @@ function RegularPassportScreen() {
onClose={hideSearchLocationSheet}
/>
)}
+
+ {visibleSelectDateSheet && (
+
+ )}
>
) : (
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(
+ '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
diff --git a/src/screens/regularPassport/steps/Step5Verification/Step5Verification.tsx b/src/screens/regularPassport/steps/Step5Verification/Step5Verification.tsx
index e09f374..ec54f6c 100644
--- a/src/screens/regularPassport/steps/Step5Verification/Step5Verification.tsx
+++ b/src/screens/regularPassport/steps/Step5Verification/Step5Verification.tsx
@@ -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();
+
+ 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 (
@@ -36,7 +53,7 @@ const Step5Content = (props: Step5VerificationProps) => {
styles.applicantDetailTextDesc,
{textTransform: 'uppercase', flex: 0},
]}>
- {lastAppointment.applicantName}
+ {lastAppointment?.applicantName}
@@ -63,27 +80,27 @@ const Step5Content = (props: Step5VerificationProps) => {
@@ -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}) => (
{label}
{value}
diff --git a/src/screens/regularPassport/steps/Step6Processing/Step6Processing.tsx b/src/screens/regularPassport/steps/Step6Processing/Step6Processing.tsx
index 0adb0f9..b00a12f 100644
--- a/src/screens/regularPassport/steps/Step6Processing/Step6Processing.tsx
+++ b/src/screens/regularPassport/steps/Step6Processing/Step6Processing.tsx
@@ -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 (
@@ -30,13 +36,12 @@ const Step6Processing = (props: Step6ProcessingProps) => {
- {/* Trigger Search Location Bottom Sheet */}
{
placeholder="Pilih satu jenis paspor"
isRequired
isDropdown
+ dropdownItemData={passportTypeData}
onIconButtonPress={showPassportTypeInfoDialog}
/>
@@ -80,12 +86,12 @@ const Step6Processing = (props: Step6ProcessingProps) => {
- {/* TODO: Add calendar functionality here. */}