diff --git a/README.md b/README.md index 64bccb9..2ea359a 100644 --- a/README.md +++ b/README.md @@ -95,3 +95,5 @@ LOGIN_METHOD=PortalSSOToken STUDENT_ID_KEY=whateveryouwant COURSES_KEY=whateveryouwant ``` +## Thanks to +[jeongbinboo๐ŸŽพ](https://github.com/jeongbinboo) diff --git a/SejongAttendanceV1/components/attendance/AttendanceCard.js b/SejongAttendanceV1/components/attendance/AttendanceCard.js index a8a690b..fc66cbb 100644 --- a/SejongAttendanceV1/components/attendance/AttendanceCard.js +++ b/SejongAttendanceV1/components/attendance/AttendanceCard.js @@ -21,20 +21,23 @@ const AttendanceCard = ({ const [lectureData, setLectureData] = useState([]); useEffect(() => { - parseXlsxData(deptId, courseId, classId, studentId) + parseXlsxData(deptId, courseId, classId, studentId, setIsParse) .then(data => { console.log(courseId); console.log(data); setLectureData(data); }) - .then(() => setUnpassCount(checkStatusCounter(lectureData))) .catch(error => { console.log('error'); console.log(error); setIsParse(data => data + 1); }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [setLectureData, setUnpassCount, isParse, refreshing]); + }, [isParse, refreshing]); + + useEffect(() => { + setUnpassCount(checkStatusCounter(lectureData)); + }, [lectureData]); return ( diff --git a/SejongAttendanceV1/components/course/UnPassCourseCard.js b/SejongAttendanceV1/components/course/UnPassCourseCard.js index 9a27251..d6897b1 100644 --- a/SejongAttendanceV1/components/course/UnPassCourseCard.js +++ b/SejongAttendanceV1/components/course/UnPassCourseCard.js @@ -3,48 +3,51 @@ import {StyleSheet, View, Text} from 'react-native'; import parseXlsxData from '../../utils/parseXlsxData'; import CourseCard from './CourseCard'; import {height, scale} from '../../config/globalStyles'; +import {useDispatch, useSelector} from 'react-redux'; +import {setIsChecked} from '../../redux/Actions'; const UnPassCourseCard = ({ deptId, courseId, classId, studentId, - isThere, - setIsThere, refreshing, }) => { const [lectureData, setLectureData] = useState([]); const [isParse, setIsParse] = useState(0); const [flag, setFlag] = useState(false); + const courseList = useSelector(state => state.courseList); + const dispatch = useDispatch(); const mounted = useRef(false); + useEffect(() => { + setFlag(false); + }, [courseList]); + useEffect(() => { if (!mounted.current) { mounted.current = true; } else { - if (flag === false) { - setIsThere(true); + if (flag === true) { + dispatch(setIsChecked(1)); } } - }, [isThere]); + }, [courseList, flag]); useEffect(() => { - console.log('unpasscoursecard'); - parseXlsxData(deptId, courseId, classId, studentId) + parseXlsxData(deptId, courseId, classId, studentId, setIsParse) .then(data => { console.log(data); setLectureData([...data]); console.log(lectureData); }) .catch(error => { - console.log('error'); console.log(error); setIsParse(data => data + 1); }); - console.log('lecture'); console.log(lectureData); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [setLectureData, isParse, refreshing]); + }, [setLectureData, isParse, refreshing, setFlag]); return ( diff --git a/SejongAttendanceV1/components/profile/CourseTable.js b/SejongAttendanceV1/components/profile/CourseTable.js index 966d951..4b1b451 100644 --- a/SejongAttendanceV1/components/profile/CourseTable.js +++ b/SejongAttendanceV1/components/profile/CourseTable.js @@ -5,6 +5,8 @@ import {height, width, scale} from '../../config/globalStyles'; import Config from 'react-native-config'; import AsyncStorage from '@react-native-async-storage/async-storage'; +import {useDispatch} from 'react-redux'; +import {setCourseList, setIsChecked} from '../../redux/Actions'; const delCourse = async newCourses => { try { @@ -25,6 +27,8 @@ const CourseTable = ({ navigation, setDelCourse, }) => { + const dispatch = useDispatch(); + return ( data + 1); } }, diff --git a/SejongAttendanceV1/ios/SejongAttendanceV1.xcodeproj/project.pbxproj b/SejongAttendanceV1/ios/SejongAttendanceV1.xcodeproj/project.pbxproj index bff9aef..356a954 100644 --- a/SejongAttendanceV1/ios/SejongAttendanceV1.xcodeproj/project.pbxproj +++ b/SejongAttendanceV1/ios/SejongAttendanceV1.xcodeproj/project.pbxproj @@ -528,7 +528,7 @@ CODE_SIGN_ENTITLEMENTS = SejongAttendanceV1/SejongAttendanceV1.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 1.0.1; DEVELOPMENT_TEAM = PL3MPVK83S; ENABLE_BITCODE = NO; INFOPLIST_FILE = SejongAttendanceV1/Info.plist; @@ -536,6 +536,7 @@ "$(inherited)", "@executable_path/Frameworks", ); + MARKETING_VERSION = 1.0.1; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -561,13 +562,14 @@ CODE_SIGN_ENTITLEMENTS = SejongAttendanceV1/SejongAttendanceV1.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 1.0.1; DEVELOPMENT_TEAM = PL3MPVK83S; INFOPLIST_FILE = SejongAttendanceV1/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); + MARKETING_VERSION = 1.0.1; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", diff --git a/SejongAttendanceV1/ios/SejongAttendanceV1/Info.plist b/SejongAttendanceV1/ios/SejongAttendanceV1/Info.plist index 1680865..a652f8b 100644 --- a/SejongAttendanceV1/ios/SejongAttendanceV1/Info.plist +++ b/SejongAttendanceV1/ios/SejongAttendanceV1/Info.plist @@ -17,26 +17,13 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + $(MARKETING_VERSION) CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS - UIAppFonts AntDesign.ttf diff --git a/SejongAttendanceV1/navigation/profile/ProfileStackNavi.js b/SejongAttendanceV1/navigation/profile/ProfileStackNavi.js index 7e6daa6..5be8556 100644 --- a/SejongAttendanceV1/navigation/profile/ProfileStackNavi.js +++ b/SejongAttendanceV1/navigation/profile/ProfileStackNavi.js @@ -12,7 +12,8 @@ import CreditScreen from '../../screens/profile/CreditScreen'; import HelpScreen from '../../screens/profile/HelpScreen'; import AsyncStorage from '@react-native-async-storage/async-storage'; //Redux -import {useSelector} from 'react-redux'; +import {useSelector, useDispatch} from 'react-redux'; +import {setCourseList} from '../../redux/Actions'; const Stack = createNativeStackNavigator(); @@ -24,6 +25,7 @@ const ProfileStackNavi = ({navigation}) => { let checkCourseNumber = /^\d{6}$/; let checkClassNumber = /^\d{3}$/; let courseData = []; + const dispatch = useDispatch(); const pushCourseToStorage = ( courseName, @@ -47,8 +49,10 @@ const ProfileStackNavi = ({navigation}) => { curValue = JSON.parse(oldValue).courses; curValue.push(courseData[0]); } + dispatch(setCourseList(curValue)); const courses = JSON.stringify({courses: curValue}); await AsyncStorage.setItem(Config.COURSES_KEY, courses); + return courses; } catch (error) { console.log('์ €์žฅ ์‹คํŒจ'); Alert.alert('์ €์žฅ ์‹คํŒจ', '์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค.\n๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”...์ฃ„์†ก', [ @@ -128,7 +132,7 @@ const ProfileStackNavi = ({navigation}) => { courseAddStorage(); Alert.alert( '๊ฐ•์˜ ์ถ”๊ฐ€', - `${courseName}(${courseNum}-${courseClass})\๊ฐ•์˜๋ฅผ ์ถ”๊ฐ€ํ–ˆ์–ด์š”.๐Ÿ‘ป`, + `${courseName}(${courseNum}-${courseClass})\n๊ฐ•์˜๋ฅผ ์ถ”๊ฐ€ํ–ˆ์–ด์š”.๐Ÿ‘ป`, [ { text: '์˜คํ‚ค', diff --git a/SejongAttendanceV1/redux/Actions.js b/SejongAttendanceV1/redux/Actions.js index a25421a..afc79c5 100644 --- a/SejongAttendanceV1/redux/Actions.js +++ b/SejongAttendanceV1/redux/Actions.js @@ -42,3 +42,8 @@ export const setStudentId = data => ({ type: 'SET_STUDENT_ID', payload: data, }); + +export const setIsChecked = data => ({ + type: 'SET_IS_CHECKED', + payload: data, +}); diff --git a/SejongAttendanceV1/redux/RootReducer.js b/SejongAttendanceV1/redux/RootReducer.js index e2d3ac3..f51ed76 100644 --- a/SejongAttendanceV1/redux/RootReducer.js +++ b/SejongAttendanceV1/redux/RootReducer.js @@ -7,6 +7,7 @@ const initialState = { courseCollegeName: '', courseDeptName: '', courseList: [], + isChecked: 0, }; const rootReducer = (state = initialState, action) => { @@ -56,6 +57,11 @@ const rootReducer = (state = initialState, action) => { ...state, studentId: action.payload, }; + case 'SET_IS_CHECKED': + return { + ...state, + isChecked: action.payload, + }; default: return state; } diff --git a/SejongAttendanceV1/screens/analysis/AnalysisScreen.js b/SejongAttendanceV1/screens/analysis/AnalysisScreen.js index 55dfc6e..59d8f58 100644 --- a/SejongAttendanceV1/screens/analysis/AnalysisScreen.js +++ b/SejongAttendanceV1/screens/analysis/AnalysisScreen.js @@ -1,41 +1,26 @@ -import React, {useState} from 'react'; -import { - StyleSheet, - View, - Text, - ScrollView, - StatusBar, - RefreshControl, -} from 'react-native'; +import React, {useEffect, useState} from 'react'; +import {StyleSheet, View, Text, ScrollView, StatusBar} from 'react-native'; import {height, width, scale} from '../../config/globalStyles'; import UnPassCourseCard from '../../components/course/UnPassCourseCard'; import {useSelector} from 'react-redux'; - -const wait = timeout => { - return new Promise(resolve => setTimeout(resolve, timeout)); -}; +import {useIsFocused} from '@react-navigation/native'; const AnalysisScreen = () => { + const isFocused = useIsFocused(); const studentId = useSelector(state => state.studentId); const courseList = useSelector(state => state.courseList); - const [isThere, setIsThere] = useState(false); + const isChecked = useSelector(state => state.isChecked); const [flag, setFlag] = useState(false); - const [refreshing, setRefreshing] = React.useState(false); - const onRefresh = React.useCallback(() => { - setRefreshing(true); - wait(1000).then(() => setRefreshing(false)); - }, []); + useEffect(() => { + setFlag(false); + }, [courseList]); return ( ๊ธ‰ํ•œ๊ฑฐ๐Ÿ”ฅ - - }> + {courseList.length > 0 ? ( courseList.map((course, idx) => { return ( @@ -45,9 +30,7 @@ const AnalysisScreen = () => { courseId={course.course_id} classId={course.class_id} studentId={studentId} - isThere={isThere} - setIsThere={setIsThere} - refreshing={refreshing} + isFocused={isFocused} /> ); }) @@ -57,7 +40,7 @@ const AnalysisScreen = () => { ๊ณผ๋ชฉ์„ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”. )} - {!isThere && !flag ? ( + {isChecked === 0 && !flag ? ( ๋ชจ๋‘ ์™„๋ฃŒํ–ˆ์–ด์š” :) diff --git a/SejongAttendanceV1/screens/attendance/AttendanceScreen.js b/SejongAttendanceV1/screens/attendance/AttendanceScreen.js index db6d5c3..55931de 100644 --- a/SejongAttendanceV1/screens/attendance/AttendanceScreen.js +++ b/SejongAttendanceV1/screens/attendance/AttendanceScreen.js @@ -36,18 +36,16 @@ const AttendanceScreen = ({navigation}) => { const [courses, setCourses] = useState([]); const dispatch = useDispatch(); + const [refreshCount, setRefreshCount] = useState(0); const [refreshing, setRefreshing] = React.useState(false); const onRefresh = React.useCallback(() => { setRefreshing(true); + setRefreshCount(value => value + 1); wait(1000).then(() => setRefreshing(false)); }, []); useEffect(() => { getAsyncStudendtId(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { getAsyncCourses(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [isFocused]); @@ -112,7 +110,8 @@ const AttendanceScreen = ({navigation}) => { studentId={id} thisWeek={thisWeek} navigation={navigation} - refreshing={refreshing} + refreshing={refreshCount} + isFocused={isFocused} /> ); })} diff --git a/SejongAttendanceV1/screens/profile/ProfileScreen.js b/SejongAttendanceV1/screens/profile/ProfileScreen.js index 1398b6d..2f80784 100644 --- a/SejongAttendanceV1/screens/profile/ProfileScreen.js +++ b/SejongAttendanceV1/screens/profile/ProfileScreen.js @@ -111,7 +111,7 @@ const ProfileScreen = ({navigation}) => { options: ['์ทจ์†Œ', '๊ฐ•์˜ ์ดˆ๊ธฐํ™”ํ•˜๊ธฐ'], destructiveButtonIndex: 1, cancelButtonIndex: 0, - title: `์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ชจ๋‘ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค.\๊ฐ•์˜ ์ •๋ณด๋ฅผ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?`, + title: `์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ชจ๋‘ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค.\n๊ฐ•์˜ ์ •๋ณด๋ฅผ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?`, }, buttonIndex => { if (buttonIndex === 1) { diff --git a/SejongAttendanceV1/utils/parseXlsxData.js b/SejongAttendanceV1/utils/parseXlsxData.js index 1c71d3a..e56e3d1 100644 --- a/SejongAttendanceV1/utils/parseXlsxData.js +++ b/SejongAttendanceV1/utils/parseXlsxData.js @@ -1,5 +1,6 @@ import XLSX from 'xlsx'; import * as FileSystem from 'expo-file-system'; +import React, {useState} from 'react'; export const getCurrentDate = () => { let time = new Date(); @@ -51,8 +52,27 @@ const parseLectureStatus = (startDate, endDate, currentDate, isPass) => { return 4; } }; +const readXlsxData = srcXlsFile => { + return new Promise((resolve, reject) => { + const xlsRawObjects = XLSX.read(srcXlsFile, { + type: 'string', + }); + if (!xlsRawObjects) reject(); + else resolve(xlsRawObjects); + }); +}; +const parseXlsxData = async ( + deptId, + courseId, + classId, + studentId, + setIsParse, +) => { + let srcXlsFile, xlsRawObjects; + let lectureData = []; + let parsedResult, lectureStatus; + let currentDate = getCurrentDate(); -const downloadXlsx = async (deptId, courseId, classId, studentId) => { const semester = '20222020'; const ApiUrl = `https://blackboard.sejong.ac.kr/webapps/bbgs-OnlineAttendance-BB5cf774ff89eaf/excel?selectedUserId=${studentId}&crs_batch_uid=${semester}${deptId}${courseId}${classId}&title=${studentId}&column=์‚ฌ์šฉ์ž๋ช…,์œ„์น˜,์ปจํ…์ธ ๋ช…,ํ•™์Šตํ•œ์‹œ๊ฐ„,ํ•™์Šต์ธ์ •์‹œ๊ฐ„,์ปจํ…์ธ ์‹œ๊ฐ„,์˜จ๋ผ์ธ์ถœ์„์ง„๋„์œจ,์˜จ๋ผ์ธ์ถœ์„์ƒํƒœ(P/F)`; @@ -60,70 +80,45 @@ const downloadXlsx = async (deptId, courseId, classId, studentId) => { await FileSystem.downloadAsync( encodeURI(ApiUrl), FileSystem.documentDirectory + courseId, - ); - } catch (e) { - e.reason = 'ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ๋ถˆ๊ฐ€'; - throw e; - } - let srcXlsFile; - try { + ).then(({status}) => { + console.log(status); + // if (status !== 200) throw new Error('helleo'); + }); srcXlsFile = await FileSystem.readAsStringAsync( FileSystem.documentDirectory + courseId, ); - } catch (e) { - e.reason = 'ํŒŒ์ผ ํŒŒ์‹ฑ ๋ถˆ๊ฐ€'; - throw e; - } - - return srcXlsFile; -}; - -const parseXlsxData = async (deptId, courseId, classId, studentId) => { - let srcXlsFile, xlsRawObjects; - let lectureData = []; - let parsedResult, lectureStatus; - let currentDate = getCurrentDate(); - - await downloadXlsx(deptId, courseId, classId, studentId).then( - value => (srcXlsFile = value), - ); - try { - xlsRawObjects = XLSX.read(srcXlsFile, { - type: 'string', + xlsRawObjects = await readXlsxData(srcXlsFile).catch(e => { + console.log(`read error ${courseId}`); + throw new Error(e); }); - } catch (error) { - if ((error.reason = 'Error: Invalid HTML: could not find ')) { - console.log('๋‹ค์šด๋กœ๋“œ ํ•  ํŒŒ์ผ ๋ฌธ์ œ'); - } else { - console.log('๋ชจ๋ฅด๋Š” ๋ฌธ์ œ...'); - } - throw error; + console.log(xlsRawObjects); + let xlsParsedObjects = XLSX.utils.sheet_to_json( + xlsRawObjects.Sheets[xlsRawObjects.SheetNames[0]], + ); + xlsParsedObjects.forEach( + singleObject => ( + ((parsedResult = parseLectureName(singleObject['์ปจํ…์ธ ๋ช…'])), + (lectureStatus = parseLectureStatus( + parsedResult[0], + parsedResult[1], + currentDate, + singleObject['์˜จ๋ผ์ธ์ถœ์„์ƒํƒœ(P/F)'], + ))), + lectureData.push({ + location: singleObject['์œ„์น˜'], + progress: singleObject['์˜จ๋ผ์ธ์ถœ์„์ง„๋„์œจ'], + is_pass: singleObject['์˜จ๋ผ์ธ์ถœ์„์ƒํƒœ(P/F)'] === 'P' ? true : false, + start_date: parsedResult[0], + end_date: parsedResult[1], + lecture_name: parsedResult[2], + lecture_status: lectureStatus, + }) + ), + ); + } catch (e) { + // console.log(setIsParse(data => data + 1)); + throw new Error(`hello error ${e}`); } - - //@todo : catch error 'TypeError: Cannot read property 'Sheets' of undefined' - let xlsParsedObjects = XLSX.utils.sheet_to_json( - xlsRawObjects.Sheets[xlsRawObjects.SheetNames[0]], - ); - xlsParsedObjects.forEach( - singleObject => ( - ((parsedResult = parseLectureName(singleObject['์ปจํ…์ธ ๋ช…'])), - (lectureStatus = parseLectureStatus( - parsedResult[0], - parsedResult[1], - currentDate, - singleObject['์˜จ๋ผ์ธ์ถœ์„์ƒํƒœ(P/F)'], - ))), - lectureData.push({ - location: singleObject['์œ„์น˜'], - progress: singleObject['์˜จ๋ผ์ธ์ถœ์„์ง„๋„์œจ'], - is_pass: singleObject['์˜จ๋ผ์ธ์ถœ์„์ƒํƒœ(P/F)'] === 'P' ? true : false, - start_date: parsedResult[0], - end_date: parsedResult[1], - lecture_name: parsedResult[2], - lecture_status: lectureStatus, - }) - ), - ); return lectureData; };