diff --git a/apis/MyAccessListApi.js b/apis/MyAccessListApi.js
index 9b4ff2a..79d2d81 100644
--- a/apis/MyAccessListApi.js
+++ b/apis/MyAccessListApi.js
@@ -1,8 +1,8 @@
import axios from './AxiosInstance';
// 출입증 목록 조회
-export const getAccessList = () => {
- const response = axios.get('/passes');
+export const getAccessList = async () => {
+ const response = await axios.get('/passes');
- return response;
+ return response.data.data;
};
diff --git a/app.config.js b/app.config.js
index 23490b4..9b9e9d9 100644
--- a/app.config.js
+++ b/app.config.js
@@ -26,7 +26,7 @@ export default {
},
extra: {
//BASE_URL: 'http://keywe.site', // EKS 사용시
- BASE_URL: 'http://192.168.0.225:8081', // 도커 사용시 - 본인 pc IPv4 주소로 수정하세용
+ BASE_URL: 'http://192.168.0.111:8081', // 도커 사용시 - 본인 pc IPv4 주소로 수정하세용
},
},
};
diff --git a/assets/images/logoGreen.png b/assets/images/logoGreen.png
index e75478d..1617d81 100644
Binary files a/assets/images/logoGreen.png and b/assets/images/logoGreen.png differ
diff --git a/assets/images/logoIcon.png b/assets/images/logoIcon.png
index de5bc7a..9101560 100644
Binary files a/assets/images/logoIcon.png and b/assets/images/logoIcon.png differ
diff --git a/assets/images/logoLoading.png b/assets/images/logoLoading.png
new file mode 100644
index 0000000..de5bc7a
Binary files /dev/null and b/assets/images/logoLoading.png differ
diff --git a/assets/images/logoText.png b/assets/images/logoText.png
index 31c8cdf..d65b4d4 100644
Binary files a/assets/images/logoText.png and b/assets/images/logoText.png differ
diff --git a/assets/images/logoWhite.png b/assets/images/logoWhite.png
index cf0cb6b..a1cec9f 100644
Binary files a/assets/images/logoWhite.png and b/assets/images/logoWhite.png differ
diff --git a/components/cards/Dot.js b/components/cards/Dot.js
index ede8791..38e631f 100644
--- a/components/cards/Dot.js
+++ b/components/cards/Dot.js
@@ -15,7 +15,7 @@ const Dot = ({ active }) => {
useEffect(() => {
Animated.timing(animatedWidth, {
toValue: active ? DOT_ACTIVE_WIDTH : DOT_WIDTH,
- duration: 300,
+ duration: 200,
useNativeDriver: false,
}).start();
}, [active, animatedWidth]);
diff --git a/components/cards/DotPagination.js b/components/cards/DotPagination.js
new file mode 100644
index 0000000..4bcfacd
--- /dev/null
+++ b/components/cards/DotPagination.js
@@ -0,0 +1,81 @@
+import React, { useMemo, useEffect, useRef } from 'react';
+import { View, TouchableOpacity, Text, Animated } from 'react-native';
+import Dot from './Dot';
+import { styles } from './styles/DotPagination.styles';
+
+// 한번에 보이는 최대 DOT 개수
+const MAX_DOTS = 8;
+
+const DotPagination = ({ total, currentIndex, onPress }) => {
+ const groupCount = Math.ceil(total / MAX_DOTS); //전체 도트 수를 MAX_DOTS로 나눈 그룹 개수
+ const currentGroup = Math.floor(currentIndex / MAX_DOTS); //현재 인덱스가 속한 그룹 번호
+ const start = currentGroup * MAX_DOTS; // 현재 그룹에서 첫번째 도트 인덱스
+ const end = Math.min(start + MAX_DOTS, total); // 현재 그룹에서 마지막 도트 다음 인덱스
+ // 현재 그룹에서 보여줄 도트 인덱스
+ // useMemo를 통해 최적화(start, end가 변하지 않으면 이전에 만든 배열을 재사용)
+ const dotIndexes = useMemo(
+ () => Array.from({ length: end - start }, (_, i) => start + i), // 각 요소의 인덱스 i에 start를 더함
+ [start, end],
+ );
+
+ // 왼쪽/오른쪽 화살표의 애니메이션 위치값 (초기값: 첫그룹이면 왼쪽은 숨김, 마지막그룹이면 오른쪽은 숨김)
+ const leftArrowTranslate = useRef(new Animated.Value(currentGroup > 0 ? 0 : -30)).current;
+ const rightArrowTranslate = useRef(
+ new Animated.Value(currentGroup < groupCount - 1 ? 0 : 30),
+ ).current;
+
+ // 그룹이 바뀔 때마다 왼쪽 화살표 애니메이션
+ useEffect(() => {
+ Animated.timing(leftArrowTranslate, {
+ toValue: currentGroup > 0 ? 0 : -30, // 첫그룹이면 숨김(-30), 아니면 보임(0)
+ duration: 250,
+ useNativeDriver: true,
+ }).start();
+ }, [currentGroup, leftArrowTranslate]);
+
+ // 그룹이 바뀔 때마다 오른쪽 화살표 애니메이션
+ useEffect(() => {
+ Animated.timing(rightArrowTranslate, {
+ toValue: currentGroup < groupCount - 1 ? 0 : 30, // 마지막그룹이면 숨김(30), 아니면 보임(0)
+ duration: 250,
+ useNativeDriver: true,
+ }).start();
+ }, [currentGroup, rightArrowTranslate, groupCount]);
+
+ // 왼쪽 화살표 클릭: 첫 그룹으로 이동
+ const goToFirstGroup = () => onPress(0);
+ // 오른쪽 화살표 클릭: 마지막 그룹의 첫 도트로 이동
+ const goToLastGroup = () => onPress((groupCount - 1) * MAX_DOTS);
+
+ return (
+
+
+
+ {currentGroup > 0 && (
+
+ {'<'}
+
+ )}
+
+
+
+ {dotIndexes.map((idx) => (
+ onPress(idx)}>
+
+
+ ))}
+
+
+
+ {currentGroup < groupCount - 1 && (
+
+ {'>'}
+
+ )}
+
+
+
+ );
+};
+
+export default DotPagination;
diff --git a/components/cards/QrCard.js b/components/cards/QrCard.js
index 3ee8a62..b99ce3d 100644
--- a/components/cards/QrCard.js
+++ b/components/cards/QrCard.js
@@ -9,7 +9,7 @@ import { colors } from '../../constants/colors';
import { hospitalName } from '../../mocks/hospitalData';
// hasAccessAuthority: 출입 권한 여부, userVC : VC에 담을 사용자 정보, qrData : QR에 담을 JSON 문자열
-const QrCard = ({ hasAccessAuthority, did, userName, hospitalName }) => {
+const QrCard = ({ hasAccessAuthority, did, userName, hospitalName, startDate, expireDate }) => {
// 해당 QR의 상세 페이지로 이동 (아직 미구현)
//const navigation = useNavigation();
// const navigateToAccessListDeatail = () => {
@@ -17,7 +17,7 @@ const QrCard = ({ hasAccessAuthority, did, userName, hospitalName }) => {
// };
// 임시: QR에 담을 JSON 문자열
- const qrData = JSON.stringify({ did, userName, hospitalName });
+ const qrData = JSON.stringify({ did, userName, hospitalName, startDate, expireDate });
return (
@@ -32,6 +32,8 @@ const QrCard = ({ hasAccessAuthority, did, userName, hospitalName }) => {
{userName}
{hospitalName}
+ 시작일: {startDate}
+ 만료일: {expireDate}
>
) : (
<>
diff --git a/components/cards/QrCards.js b/components/cards/QrCards.js
index 5dfc9b1..2aeb5b8 100644
--- a/components/cards/QrCards.js
+++ b/components/cards/QrCards.js
@@ -2,12 +2,11 @@ import React, { useRef, useState } from 'react';
import { FlatList, View, Dimensions, TouchableOpacity } from 'react-native';
import QrCard from './QrCard';
import styles from './styles/QrCards.styles';
-import Dot from './Dot';
+import DotPagination from './DotPagination';
// 화면의 가로 길이 가져오기
const { width } = Dimensions.get('window');
const CARD_WIDTH = width;
-const SIDE_PADDING = (width - CARD_WIDTH) / 2; // 좌우 패딩 계산
const OVERLAP = 80; // 카드가 겹치는 정도
const QrCards = ({ userVC, hasAccessAuthority }) => {
@@ -32,7 +31,8 @@ const QrCards = ({ userVC, hasAccessAuthority }) => {
// dot(인디케이터) 클릭 시 해당 카드로 이동
const handleDotPress = (index) => {
- flatListRef.current?.scrollToIndex({ index, animated: true });
+ const offset = index * (CARD_WIDTH - OVERLAP);
+ flatListRef.current?.scrollToOffset({ offset, animated: true });
};
// 카드 리스트
@@ -47,9 +47,11 @@ const QrCards = ({ userVC, hasAccessAuthority }) => {
pagingEnabled={false} // snapToInterval을 사용하므로 false
snapToInterval={CARD_WIDTH - OVERLAP} // 카드 단위로 스냅
decelerationRate="fast" // 빠른 스냅 효과
- contentContainerStyle={{
- paddingHorizontal: SIDE_PADDING, // 양쪽에 패딩 추가로 옆 카드 살짝 보이게
- }}
+ getItemLayout={(data, index) => ({
+ length: CARD_WIDTH - OVERLAP,
+ offset: (CARD_WIDTH - OVERLAP) * index,
+ index,
+ })}
renderItem={({ item, index }) => (
{
did={item.did}
userName={item.userName}
hospitalName={item.hospitalName}
+ startDate={item.startDate}
+ expireDate={item.expireDate}
hasAccessAuthority={true}
/>
@@ -70,11 +74,7 @@ const QrCards = ({ userVC, hasAccessAuthority }) => {
onMomentumScrollEnd={onMomentumScrollEnd}
/>
- {userVC.map((_, i) => (
- handleDotPress(i)}>
-
-
- ))}
+
);
diff --git a/components/cards/styles/DotPagination.styles.js b/components/cards/styles/DotPagination.styles.js
new file mode 100644
index 0000000..59032b7
--- /dev/null
+++ b/components/cards/styles/DotPagination.styles.js
@@ -0,0 +1,37 @@
+import { StyleSheet } from 'react-native';
+import { colors } from '../../../constants/colors';
+// TODO: 도트 상수 문서화 필요할지도.. 통일
+export const DOT_SIZE = 12; // 도트 크기
+export const DOT_MARGIN = 8; // 도트 사이 간격
+export const MAX_DOTS = 8; // 한 그룹 최대 도트 수
+
+export const styles = StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ alignSelf: 'center',
+ marginVertical: 16,
+ },
+ arrowContainer: {
+ width: 40,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ arrow: {
+ paddingHorizontal: 8,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ arrowText: {
+ fontSize: 24,
+ fontWeight: 'bold',
+ color: colors.darkGray,
+ },
+ dotsWrapper: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ flex: 0,
+ minWidth: DOT_SIZE * MAX_DOTS + DOT_MARGIN * (MAX_DOTS - 1),
+ },
+});
diff --git a/components/cards/styles/QrCard.styles.js b/components/cards/styles/QrCard.styles.js
index 7f13ae1..83a5187 100644
--- a/components/cards/styles/QrCard.styles.js
+++ b/components/cards/styles/QrCard.styles.js
@@ -11,7 +11,7 @@ export const styles = StyleSheet.create({
elevation: 7, // Android용
borderRadius: 15,
width: '70%',
- height: '80%',
+ height: '85%', //TODO : 기존에는 80이었는데, 추후 카드 높이 변경 필요할듯함
marginVertical: '10%',
},
cardContainer: {
diff --git a/components/lists/NormalListDeep.js b/components/lists/NormalListDeep.js
index f0ba30e..17ea908 100644
--- a/components/lists/NormalListDeep.js
+++ b/components/lists/NormalListDeep.js
@@ -5,16 +5,16 @@ import { styles } from './styles/NormalListDeep.styles'; //노말리스트와
// TODO: Pass-Service 구현 완료 시, 실제 데이터로 변경 필요
//리스트안에 노말리스트가 있는 컴포넌트
-const NormalListDeep = ({ sections = [], onItemPress, renderItem }) => {
+const NormalListDeep = ({ sections = [], onItemPress, renderItem, cardStyle }) => {
return (
{sections.map((section, idx) => (
- //console.log('Section', section),
{section.contentTitle}
{
if (onItemPress) onItemPress(section, item, index);
diff --git a/components/lists/styles/NormalListDeep.styles.js b/components/lists/styles/NormalListDeep.styles.js
index 78c9380..cde9716 100644
--- a/components/lists/styles/NormalListDeep.styles.js
+++ b/components/lists/styles/NormalListDeep.styles.js
@@ -8,7 +8,6 @@ export const styles = StyleSheet.create({
contentContainer: { paddingBottom: 60 },
// 각 아이템 박스 스타일
itemBox: {
- marginBottom: 10,
marginBottom: -40,
},
// 각 아이템 텍스트 스타일
diff --git a/components/loadings/KiwiSpinner.js b/components/loadings/KiwiSpinner.js
index fcb6d84..b63a307 100644
--- a/components/loadings/KiwiSpinner.js
+++ b/components/loadings/KiwiSpinner.js
@@ -27,7 +27,7 @@ const KiwiSpinner = () => {
return (
diff --git a/constants/colors.js b/constants/colors.js
index de22e3a..f301457 100644
--- a/constants/colors.js
+++ b/constants/colors.js
@@ -2,6 +2,8 @@ export const colors = {
background: '#F0F0F0',
white: '#FFFFFF',
black: '#464646',
+ moreLightGreen: '#E7EDE8',
+ moreLightGray: '#DFDFDF',
lightGray: '#B7B7B7',
darkGray: '#7E7E7E',
primary: '#24562B', // 메인 포인트 색상
diff --git a/modals/MyAccessDetailModal.js b/modals/MyAccessDetailModal.js
index 5dd83b3..97385dd 100644
--- a/modals/MyAccessDetailModal.js
+++ b/modals/MyAccessDetailModal.js
@@ -14,14 +14,15 @@ const MyAccessDetailModal = ({ isVisible, onClose, onConfirm, data }) => {
{data.area}
방문자: {data.visitorType}
+ 시작일: {data.startDate}
만료일: {data.expireDate}
승인 여부: {data.approval}
환자 번호: {data.patientNumber}
내 보호자
- {`김OO\t|\t010-0000-0000`}
- {`김OO\t|\t010-0000-0000`}
+ {`김지수\t|\t010-0000-0000`}
+ {`손민지\t|\t010-1111-1111`}
diff --git a/modals/styles/MyAccessDetailModal.styles.js b/modals/styles/MyAccessDetailModal.styles.js
index 9dd394b..8a457cc 100644
--- a/modals/styles/MyAccessDetailModal.styles.js
+++ b/modals/styles/MyAccessDetailModal.styles.js
@@ -13,14 +13,14 @@ export const styles = StyleSheet.create({
alignSelf: 'center',
},
modalTitle: {
- fontSize: 25,
+ fontSize: 22,
fontWeight: '600',
color: colors.black,
marginTop: '7%',
alignSelf: 'center',
},
modalContentTitle: {
- fontSize: 20,
+ fontSize: 17,
fontWeight: '500',
color: colors.black,
marginTop: '5%',
@@ -31,10 +31,10 @@ export const styles = StyleSheet.create({
marginBottom: '7%',
},
modalText: {
- fontSize: 18,
+ fontSize: 17,
fontWeight: '500',
color: colors.darkGray,
- marginTop: 10,
+ marginTop: 2,
lineHeight: 30,
},
buttonRow: {
diff --git a/pages/MainPage.js b/pages/MainPage.js
index 967dbe3..dd811f8 100644
--- a/pages/MainPage.js
+++ b/pages/MainPage.js
@@ -10,28 +10,170 @@ import QrCards from '../components/cards/QrCards';
const MainPage = () => {
// 임시: 상태변수로 출입 권한 제어
const [hasAccessAuthority, setHasAccessAuthority] = useState(true);
-
- // 임시: VC에 담을 사용자 정보
const userVC = [
{
- did: 'did:example:123456789abcdefghi',
- userName: '김짱구',
- hospitalName: '짱구병원',
+ did: 'did:example:123456789abcdefg01',
+ userName: '김엘지',
+ hospitalName: '강북삼성병원',
+ startDate: '2025-05-17T06:35:05',
+ expireDate: '2025-05-18T06:33:09',
+ issuedAt: Date.now(),
+ },
+ {
+ did: 'did:example:123456789abcdefg02',
+ userName: '김엘지',
+ hospitalName: '강북삼성병원',
+ startDate: '2025-05-15T08:06:27',
+ expireDate: '2025-05-17T08:06:00',
+ issuedAt: Date.now(),
+ },
+ {
+ did: 'did:example:123456789abcdefg03',
+ userName: '김엘지',
+ hospitalName: '건국대학교병원',
+ startDate: '2025-05-15T08:08:15',
+ expireDate: '2025-05-17T08:06:54',
issuedAt: Date.now(),
},
{
- did: 'did:example:123456789abcdefdhi',
- userName: '김짱구',
- hospitalName: '흰둥이병원',
+ did: 'did:example:123456789abcdefg04',
+ userName: '김엘지',
+ hospitalName: '건국대학교병원',
+ startDate: '2025-05-15T08:08:15',
+ expireDate: '2025-05-17T08:06:54',
issuedAt: Date.now(),
},
{
- did: 'did:example:123456789abcdeffhi',
- userName: '김짱구',
- hospitalName: '오수병원',
+ did: 'did:example:123456789abcdefg05',
+ userName: '김엘지',
+ hospitalName: '건국대학교병원',
+ startDate: '2025-05-15T08:08:15',
+ expireDate: '2025-05-17T08:06:54',
issuedAt: Date.now(),
},
+ {
+ did: 'did:example:123456789abcdefg06',
+ userName: '김엘지',
+ hospitalName: '건국대학교병원',
+ startDate: '2025-05-15T08:08:15',
+ expireDate: '2025-05-17T08:06:54',
+ issuedAt: Date.now(),
+ },
+ {
+ did: 'did:example:123456789abcdefg07',
+ userName: '김엘지',
+ hospitalName: '건국대학교병원',
+ startDate: '2025-05-15T08:08:15',
+ expireDate: '2025-05-17T08:06:54',
+ issuedAt: Date.now(),
+ },
+ {
+ did: 'did:example:123456789abcdefg08',
+ userName: '김엘지',
+ hospitalName: '건국대학교병원',
+ startDate: '2025-05-15T08:08:15',
+ expireDate: '2025-05-17T08:06:54',
+ issuedAt: Date.now(),
+ },
+ {
+ did: 'did:example:123456789abcdefg09',
+ userName: '김엘지',
+ hospitalName: '건국대학교병원',
+ startDate: '2025-05-15T08:08:15',
+ expireDate: '2025-05-17T08:06:54',
+ issuedAt: Date.now(),
+ },
+ {
+ did: 'did:example:123456789abcdefg10',
+ userName: '김엘지',
+ hospitalName: '건국대학교병원',
+ startDate: '2025-05-15T08:08:15',
+ expireDate: '2025-05-17T08:06:54',
+ issuedAt: Date.now(),
+ },
+ {
+ did: 'did:example:123456789abcdefg11',
+ userName: '김엘지',
+ hospitalName: '건국대학교병원',
+ startDate: '2025-05-15T08:08:15',
+ expireDate: '2025-05-17T08:06:54',
+ issuedAt: Date.now(),
+ },
+ {
+ did: 'did:example:123456789abcdefg12',
+ userName: '김엘지',
+ hospitalName: '건국대학교병원',
+ startDate: '2025-05-15T08:08:15',
+ expireDate: '2025-05-17T08:06:54',
+ issuedAt: Date.now(),
+ },
+ {
+ did: 'did:example:123456789abcdefg13',
+ userName: '김엘지',
+ hospitalName: '건국대학교병원',
+ startDate: '2025-05-15T08:08:15',
+ expireDate: '2025-05-17T08:06:54',
+ issuedAt: Date.now(),
+ },
+ {
+ did: 'did:example:123456789abcdefg14',
+ userName: '김엘지',
+ hospitalName: '건국대학교병원',
+ startDate: '2025-05-15T08:08:15',
+ expireDate: '2025-05-17T08:06:54',
+ issuedAt: Date.now(),
+ },
+ {
+ did: 'did:example:123456789abcdefg15',
+ userName: '김엘지',
+ hospitalName: '건국대학교병원',
+ startDate: '2025-05-15T08:08:15',
+ expireDate: '2025-05-17T08:06:54',
+ issuedAt: Date.now(),
+ },
+ {
+ did: 'did:example:123456789abcdefg16',
+ userName: '김엘지',
+ hospitalName: '건국대학교병원',
+ startDate: '2025-05-15T08:08:15',
+ expireDate: '2025-05-17T08:06:54',
+ issuedAt: Date.now(),
+ },
+ {
+ did: 'did:example:123456789abcdefg17',
+ userName: '김엘지',
+ hospitalName: '건국대학교병원',
+ startDate: '2025-05-15T08:08:15',
+ expireDate: '2025-05-17T08:06:54',
+ issuedAt: Date.now(),
+ },
+ {
+ did: 'did:example:123456789abcdefg18',
+ userName: '김엘지',
+ hospitalName: '건국대학교병원',
+ startDate: '2025-05-15T08:08:15',
+ expireDate: '2025-05-17T08:06:54',
+ issuedAt: Date.now(),
+ },
+ {
+ did: 'did:example:123456789abcdefg19',
+ userName: '김엘지',
+ hospitalName: '건국대학교병원',
+ startDate: '2025-05-15T08:08:15',
+ expireDate: '2025-05-17T08:06:54',
+ issuedAt: Date.now(),
+ },
+ {
+ did: 'did:example:123456789abcdefg20',
+ userName: '김엘지',
+ hospitalName: '건국대학교병원',
+ startDate: '2025-05-15T08:08:15',
+ expireDate: '2025-05-17T08:06:54',
+ issuedAt: Date.now(),
+ },
+ // ... (이런 식으로 계속 01, 02, 03 ... 40까지 did 값을 고유하게 부여)
];
+
// 임시: QR에 담을 JSON 문자열
//const qrData = JSON.stringify(userVC);
diff --git a/pages/MyAccessListPage.js b/pages/MyAccessListPage.js
index 2cbb139..e89787a 100644
--- a/pages/MyAccessListPage.js
+++ b/pages/MyAccessListPage.js
@@ -1,84 +1,243 @@
import React, { useEffect, useState } from 'react';
import { View, Text } from 'react-native';
import NormalListDeep from '../components/lists/NormalListDeep';
-import { MyAccessList } from '../mocks/MyAccessListSample'; //예시 데이터
import { styles } from './styles/MyAccessListPage.styles';
import { getAccessList } from '../apis/MyAccessListApi';
+import { getHospitalList } from '../apis/AccessRequestApi';
import MyAccessDetailModal from '../modals/MyAccessDetailModal';
import { useAuthStore } from '../stores/authStore';
+// 더미 데이터 (로컬 테스트 용)
+const mockAccessList = [
+ {
+ passId: 4,
+ memberId: 1,
+ hospitalId: 1,
+ accessAreaNames: [
+ '본관 5층 내과병동 501호',
+ '본관 5층 내과병동 502호',
+ '본관 5층 내과병동 503호',
+ ],
+ visitCategory: 'PATIENT',
+ patientId: 9,
+ startedAt: '2025-05-16T06:35:05',
+ expiredAt: '2025-05-18T06:33:09',
+ },
+ {
+ passId: 5,
+ memberId: 1,
+ hospitalId: 1,
+ accessAreaNames: ['신관 3층 외과병동 301호', '신관 4층 외과병동 402호'],
+ visitCategory: 'PATIENT',
+ patientId: 9,
+ startedAt: '2025-05-15T08:06:27',
+ expiredAt: '2025-05-17T08:06:00',
+ },
+ {
+ passId: 6,
+ memberId: 1,
+ hospitalId: 2,
+ accessAreaNames: ['암센터 7층 항암치료실 701호'],
+ visitCategory: 'GUARDIAN',
+ patientId: 10,
+ startedAt: '2025-05-15T08:08:15',
+ expiredAt: '2025-05-17T08:06:54',
+ },
+ {
+ passId: 7,
+ memberId: 1,
+ hospitalId: 3,
+ accessAreaNames: ['본관 3층 내과병동 305호', '본관 4층 외과병동 410호'],
+ visitCategory: 'GUARDIAN',
+ patientId: 10,
+ startedAt: '2025-05-17T08:08:15',
+ expiredAt: '2025-05-18T08:06:54',
+ },
+ // {
+ // passId: 8,
+ // memberId: 1,
+ // hospitalId: 3,
+ // accessAreaNames: ['소망관 3층 응급의학과 외상전용 수술실 320호'],
+ // visitCategory: 'GUARDIAN',
+ // patientId: 10,
+ // startedAt: '2025-05-15T08:08:15',
+ // expiredAt: '2025-06-16T08:06:54',
+ // },
+];
+
const MyAccessListPage = () => {
const { setLoading } = useAuthStore();
const [myAccessList, setMyAccessList] = useState([]);
+ const [hospitalNameList, setHospitalNameList] = useState([]);
// Alert 관리 상태변수
const [showModal, setShowModal] = useState(false); // 모달 표시 여부
const [selectedAccess, setSelectedAccess] = useState(null); // 클릭된 출입증
- // 출입증 목록 불러오기
+ // 병원 목록 불러오기
useEffect(() => {
- const getMyAccessList = async () => {
+ const getHospitalsName = async () => {
setLoading(true);
try {
- const data = await getAccessList();
- console.log(data.data.data);
- setMyAccessList(data.data.data);
+ const data = await getHospitalList();
+ setHospitalNameList(data); // [{ hospitalId, hospitalName }]
} catch (error) {
- console.error('출입증 목록 불러오기 실패: ', error);
+ console.error('병원 목록 불러오기 실패:', error);
} finally {
setLoading(false);
}
};
- getMyAccessList();
+ getHospitalsName();
+ }, [setLoading]);
+
+ // 출입증 목록 불러오기
+ // useEffect(() => {
+ // const getMyAccessList = async () => {
+ // setLoading(true);
+ // try {
+ // const data = await getAccessList();
+ // console.log(data);
+ // setMyAccessList(data);
+ // } catch (error) {
+ // console.error('출입증 목록 불러오기 실패: ', error);
+ // } finally {
+ // setLoading(false);
+ // }
+ // };
+ // getMyAccessList();
+ // }, [setLoading]);
+
+ // 임시 데이터 적용
+ useEffect(() => {
+ setMyAccessList(mockAccessList); // 위에서 만든 mock 데이터로 대체
}, []);
// 출입 권한 클릭 시 모달 띄우기
- // TODO: Pass-Service 구현 완료 시, 실제 데이터로 변경 필요
const handleItemPress = (section, item, index) => {
const access = item.data;
setSelectedAccess({
hospitalName: section.contentTitle,
- area: `${access.building_name} ${access.area_name}`,
- visitorType: access.visitor_category,
- expireDate: access.validate_to,
- approval: access.expired ? '출입 대기' : '유효',
- patientNumber: access.PatientID,
- issuer: access.requester_category,
+ // area: (access.accessAreaNames || []).join(',\n'),
+ area: (access.accessAreaNames || []).map((name) => `${name}`).join('\n'),
+ visitorType: getVisitCategoryLabel(access.visitCategory),
+ startDate: formatDateTime(access.startedAt),
+ expireDate: formatDateTime(access.expiredAt),
+ approval: getApprovalStatus(access.startedAt, access.expiredAt),
+ patientNumber: access.patientId,
+ issuer: access.memberId,
});
setShowModal(true);
};
+ // visitCategory 변환 함수
+ const getVisitCategoryLabel = (category) => {
+ switch (category) {
+ case 'PATIENT':
+ return '환자';
+ case 'GUARDIAN':
+ return '보호자';
+ default:
+ return category; // 혹시 모르는 값은 그대로 표기
+ }
+ };
+
+ // 날짜 포맷 함수 (YYYY-MM-DD HH:mm)
+ const formatDateTime = (date) => {
+ if (!date) return '';
+ const d = new Date(date);
+ const yyyy = d.getFullYear();
+ const mm = String(d.getMonth() + 1).padStart(2, '0');
+ const dd = String(d.getDate()).padStart(2, '0');
+ const hh = String(d.getHours()).padStart(2, '0');
+ const min = String(d.getMinutes()).padStart(2, '0');
+ return `${yyyy}-${mm}-${dd} ${hh}:${min}`;
+ };
+
+ // 출입 가능 상태 함수
+ const getApprovalStatus = (startedAt, expiredAt) => {
+ const now = new Date();
+ const start = new Date(startedAt);
+ const end = new Date(expiredAt);
+
+ if (start > now) {
+ return '출입 대기';
+ } else if (end < now) {
+ return '만료';
+ } else {
+ return '출입 가능';
+ }
+ };
+
+ //병원 Id로 병원 이름 찾기
+ const getHospitalName = (hospitalId) => {
+ const hospital = hospitalNameList.find((hospital) => hospital.hospitalId === hospitalId);
+ // 병원 목록에 없으면 병원 #id로 표시
+ return hospital ? hospital.hospitalName : `병원명 로딩 중 . . .병원: #${hospitalId}`;
+ };
+
// NormalListDeep에 넘길 데이터 가공
- const sections = myAccessList.map((section) => ({
- contentTitle: section.hospital_name,
- accessList: section.accessList,
- }));
+ const sections = myAccessList.reduce((acc, cur) => {
+ //acc - accumulator(누적값, 병원별로 묶안 배열), cur - current(현재 배열에서 처리중인 값)
+ const hospitalId = cur.hospitalId;
+ let hospitalName = getHospitalName(hospitalId); //id로 이름 찾아서 저장
+ let section = acc.find((sec) => sec.hospitalId === hospitalId); //현재 hospitalId와 같은 section이 있는지 찾는다.
+ if (!section) {
+ //섹션이 없으면 새로운 섹션 객체를 만들어 acc에 추가한다.
+ section = {
+ hospitalId,
+ contentTitle: hospitalName,
+ accessList: [],
+ };
+ acc.push(section);
+ }
+ section.accessList.push(cur); //해당 병원 그룹의 accessList 배열에 현재 출입증 추가
+ return acc; //누적값 반환해서 다음 루프에 이어감
+ }, []);
return (
<>
- {myAccessList.length > 0 ? (
+ {sections.length > 0 ? (
({
+ ...section,
+ accessList: section.accessList.map((item) => ({ data: item })),
+ }))}
onItemPress={handleItemPress}
- renderItem={(item, idx, selected) => (
-
-
-
- {item.data.building_name} {item.data.area_name} - {item.data.visitor_category}
-
+ renderItem={(itemObj, idx, selected) => {
+ const item = itemObj.data;
+ return (
+
+
+
+
+ {'[ ' + getVisitCategoryLabel(item.visitCategory) + ' ]'}
+
+ {(item.accessAreaNames || []).map((area, idx) => (
+
+ {area}
+
+ ))}
+
+
+
+ {getApprovalStatus(item.startedAt, item.expiredAt)}
+
+
+
- {'\n'}({item.data.validate_to}까지)
-
-
-
-
- {'\n'} {item.data.expired ? '출입 대기' : '유효'}
+ 시작일: {formatDateTime(item.startedAt)}
+ {'\n'}만료일: {formatDateTime(item.expiredAt)}
+ {'\n'}
-
- )}
+ );
+ }}
/>
) : (
유효한 출입증이 존재하지 않습니다.
diff --git a/pages/styles/MyAccessListPage.styles.js b/pages/styles/MyAccessListPage.styles.js
index 83a9ab7..8be0d6b 100644
--- a/pages/styles/MyAccessListPage.styles.js
+++ b/pages/styles/MyAccessListPage.styles.js
@@ -3,27 +3,62 @@ import { colors } from '../../constants/colors';
export const styles = StyleSheet.create({
container: {
+ justifyContent: 'space-between',
+ borderRadius: 5,
+ borderWidth: 1,
+ borderColor: colors.moreLightGray,
+ },
+ // 권한 구역 + 출입 가능 여부 (패딩)
+ infoTextPadding: {
+ padding: '5%',
flexDirection: 'row',
justifyContent: 'space-between',
+ borderBottomWidth: 1,
+ borderColor: colors.moreLightGray,
+ backgroundColor: colors.moreLightGreen,
},
+ // 권한 구역 (패딩)
+ areaTextPadding: {
+ width: '75%',
+ },
+ // 권한 구역 - 환자, 보호자 여부
textTitle: {
+ width: '70%',
fontSize: 20,
fontWeight: 600,
color: colors.black,
+ lineHeight: 30,
},
- text: {
- fontSize: 19,
+ // 권한 구역 - 구역 이름
+ areaText: {
+ fontSize: 18,
color: colors.black,
+ lineHeight: 28,
+ marginVertical: 2,
},
+ // 출입 가능 여부 (패딩)
+ validateTextPadding: {
+ maxWidth: '20%',
+ alignSelf: 'center',
+ marginLeft: 'auto',
+ },
+ // 출입 가능 여부 글자
validateText: {
fontSize: 20,
fontWeight: 600,
color: colors.tertiary,
},
+ // 출입증 존재하지 않을 시 텍스트
infoText: {
marginTop: '10%',
fontSize: 20,
textAlign: 'center',
color: colors.darkGray,
},
+ // 시작일, 만료일 정보
+ text: {
+ padding: '5%',
+ fontSize: 15,
+ color: colors.black,
+ },
});