From c6df1636634802652ac7119d9a0b9ee629c282c7 Mon Sep 17 00:00:00 2001 From: PJW Date: Tue, 16 Sep 2025 00:13:33 +0900 Subject: [PATCH 1/8] =?UTF-8?q?front-24=20=EC=9D=BC=EC=A0=95=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/screens/ScheduleAddModal.js | 152 +++++++++++++++++++++++--------- app/screens/ScheduleList.js | 39 ++++++-- 2 files changed, 142 insertions(+), 49 deletions(-) diff --git a/app/screens/ScheduleAddModal.js b/app/screens/ScheduleAddModal.js index 6b5a677..3685bc6 100644 --- a/app/screens/ScheduleAddModal.js +++ b/app/screens/ScheduleAddModal.js @@ -1,4 +1,4 @@ -import React, { useRef, useState, useMemo } from "react"; +import React, { useRef, useState, useMemo, useEffect } from "react"; import { View, Text, @@ -29,19 +29,12 @@ export default function ScheduleAddModal({ onClose, selectedDate, onSave, - isEditing, + isEditing = false, + editData = null, saving, }) { const modalAddRef = useRef(null); - React.useEffect(() => { - if (visible) { - modalAddRef.current?.open(); - } else { - modalAddRef.current?.close(); - } - }, [visible]); - const [title, setTitle] = useState(""); const [hour, setHour] = useState("10"); const [minute, setMinute] = useState("00"); @@ -63,10 +56,48 @@ export default function ScheduleAddModal({ [] ); + useEffect(() => { + if (visible) { + if (isEditing && editData) { + setTitle(editData.title || ""); + setHour(editData.hour || "10"); + setMinute(editData.minute || "00"); + // priority: "S" | "I" | "N"으로 변환 + setPriority( + editData.priority === "매우 중요" + ? "S" + : editData.priority === "중요하지 않음" + ? "N" + : "I" + ); + setMemo(editData.memo || ""); + } else { + setTitle(""); + setHour("10"); + setMinute("00"); + setPriority("I"); + setMemo(""); + } + setSaveError(""); + } + }, [visible, isEditing, editData]); + + // **Modal open/close - modalize** + useEffect(() => { + if (visible) { + modalAddRef.current?.open(); + } else { + modalAddRef.current?.close(); + } + }, [visible]); + // API 요구 포맷으로 반환 (예: 2025-09-13 10:00:00) const getApiDateTime = () => { // selectedDate가 JS Date 객체이어야 함! - const dateObj = new Date(selectedDate); + let dateObj = new Date(selectedDate); + if (isNaN(dateObj.getTime())) { + dateObj = new Date(); + } const yyyy = dateObj.getFullYear(); const mm = pad(dateObj.getMonth() + 1); const dd = pad(dateObj.getDate()); @@ -88,42 +119,67 @@ export default function ScheduleAddModal({ AsyncStorage.getItem("userId") .then((userId) => { - return axios.get(`${process.env.EXPO_PUBLIC_API_URL}/calendar/add`, { - params: { - userId, - title, - time: getApiDateTime(), - importance: priority, // 반드시 "S" | "I" | "N" - memo, - }, - withCredentials: true - }); - }) - .then((res) => { - const calendarId = res.data.calendarId || res.data.id; - if (calendarId) { - AsyncStorage.setItem("calendarId", calendarId.toString()); - } - console.log("[schedule add] Response:", res.data); - onSave && - onSave({ - title, - hour, - minute, - priority, - memo, - calendarId - }); - handleCancel(); + if (!userId) throw new Error("사용자 정보가 없습니다."); + + const params = { + userId, + title, + time: getApiDateTime(), + importance: priority, + memo, + }; + + if (isEditing && editData?.id) { + return axios + .get(`${process.env.EXPO_PUBLIC_API_URL}/calendar/update`, { + params: { ...params, calendar_id: editData.id }, + withCredentials: true, + }) + .then((res) => { + console.log("[일정 수정 성공]", res.data); + onSave && onSave(); + handleCancel(); + }) + .catch((err) => { + setSaveError( + "일정 수정 실패: " + + (err?.response?.data?.message || err.message) + ); + console.error("[일정 수정 실패]", err?.response?.data || err); + }); + } else { + return axios + .get(`${process.env.EXPO_PUBLIC_API_URL}/calendar/add`, { + params, + withCredentials: true, + }) + .then((res) => { + console.log("[일정 추가 성공]", res.data); + onSave && onSave(); + handleCancel(); + }) + .catch((err) => { + setSaveError( + "일정 저장 실패: " + + (err?.response?.data?.message || err.message) + ); + console.error("[일정 추가 실패]", err?.response?.data || err); + }); + } }) .catch((err) => { setSaveError( - "일정 저장 실패: " + (err?.response?.data?.message || err.message) + (isEditing ? "일정 수정 실패: " : "일정 저장 실패: ") + + (err?.response?.data?.message || err.message) ); - console.error("[schedule add] Error:", err?.response?.data || err); + console.error("[일정 작업 실패]", err?.response?.data || err); }); + + if(onSave) onSave(); + handleCancel(); }; + const handleCancel = () => { setTitle(""); setHour("10"); @@ -141,12 +197,13 @@ export default function ScheduleAddModal({ return ( {titleRequired && ( @@ -233,6 +291,7 @@ export default function ScheduleAddModal({ multiline value={memo} onChangeText={setMemo} + maxLength={100} placeholder="메모를 입력하세요" /> @@ -251,7 +310,10 @@ export default function ScheduleAddModal({ {showTimePicker && ( - + setShowTimePicker(false)}> ({ label: `${h}시`, value: h, @@ -277,6 +340,7 @@ export default function ScheduleAddModal({ ({ label: `${m}분`, value: m, diff --git a/app/screens/ScheduleList.js b/app/screens/ScheduleList.js index 32c28dc..6cdd9d5 100644 --- a/app/screens/ScheduleList.js +++ b/app/screens/ScheduleList.js @@ -43,7 +43,6 @@ export default function ScheduleList({ modalRef, schedules, selectedDate, - onOpenEditModal, onOpenDeleteModal, onModalOpen, onModalClose, @@ -53,7 +52,10 @@ export default function ScheduleList({ }) { const [addModalVisible, setAddModalVisible] = useState(false); + const [editModalVisible, setEditModalVisible] = useState(false); const [hideFAB, setHideFAB] = useState(false); + const [editTarget, setEditTarget] = useState(null); + const handleOpenAddModal = () => { setAddModalVisible(true); setHideFAB(true); @@ -62,6 +64,17 @@ export default function ScheduleList({ setAddModalVisible(false); setHideFAB(false); }; + const handleOpenEditModal = (item) => { + setEditTarget(item); + setEditModalVisible(true); + setHideFAB(true); + }; + const handleCloseEditModal = () => { + setEditModalVisible(false); + setEditTarget(null); + setHideFAB(false); + }; + const scheduleArr = schedules[selectedDate] || []; @@ -93,8 +106,8 @@ export default function ScheduleList({ })} - {schedules[selectedDate]?.length ? ( - schedules[selectedDate] + {scheduleArr.length ? ( + scheduleArr .sort((a, b) => `${a.hour}${a.minute}`.localeCompare(`${b.hour}${b.minute}`) ) @@ -112,7 +125,7 @@ export default function ScheduleList({ onOpenEditModal(item, idx)} + onPress={() => handleOpenEditModal(item)} > {item.title} @@ -159,7 +172,23 @@ export default function ScheduleList({ visible={addModalVisible} onClose={handleCloseAddModal} selectedDate={selectedDate} - onSave={onSave} + onSave={() => { + onSave && onSave(); + handleCloseAddModal(); + }} + isEditing={false} + /> + + { + onSave && onSave(); + handleCloseAddModal(); + }} + isEditing={true} + editData={editTarget} /> {showFAB && !hideFAB && ( From 15fed426c5b03940ab316a01ebab5cd795249b73 Mon Sep 17 00:00:00 2001 From: PJW Date: Tue, 16 Sep 2025 18:00:46 +0900 Subject: [PATCH 2/8] Update ScheduleAddModal.js --- app/screens/ScheduleAddModal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/screens/ScheduleAddModal.js b/app/screens/ScheduleAddModal.js index 3685bc6..3d1d46f 100644 --- a/app/screens/ScheduleAddModal.js +++ b/app/screens/ScheduleAddModal.js @@ -291,7 +291,7 @@ export default function ScheduleAddModal({ multiline value={memo} onChangeText={setMemo} - maxLength={100} + maxLength={50} placeholder="메모를 입력하세요" /> From bfd74ec3762fb3f7aa41114e684f2bcb68eae74d Mon Sep 17 00:00:00 2001 From: PJW Date: Tue, 16 Sep 2025 22:39:06 +0900 Subject: [PATCH 3/8] Merge branch 'main' into front-24! --- app/screens/ScheduleList.js | 174 ++++++++++++++++++++++++++++-------- 1 file changed, 135 insertions(+), 39 deletions(-) diff --git a/app/screens/ScheduleList.js b/app/screens/ScheduleList.js index 6cdd9d5..fc53ac4 100644 --- a/app/screens/ScheduleList.js +++ b/app/screens/ScheduleList.js @@ -1,8 +1,11 @@ -import { View, Text, Pressable, StyleSheet } from "react-native"; +import { View, Text, Pressable, StyleSheet, Dimensions, ScrollView, Modal, Image } from "react-native"; import React, { useState, useEffect, useRef } from "react"; import { Modalize } from "react-native-modalize"; import Ionicons from "@expo/vector-icons/Ionicons"; import ScheduleAddModal from "../screens/ScheduleAddModal"; +import axios from "axios"; + +const SCREEN_HEIGHT = Dimensions.get("window").height; export const importanceIn = (val) => { switch (val) { @@ -43,6 +46,7 @@ export default function ScheduleList({ modalRef, schedules, selectedDate, + onOpenEditModal, onOpenDeleteModal, onModalOpen, onModalClose, @@ -51,10 +55,10 @@ export default function ScheduleList({ onSave }) { + const [deleteIdx, setDeleteIdx] = useState(null); + const [deleteModalVisible, setDeleteModalVisible] = useState(false); const [addModalVisible, setAddModalVisible] = useState(false); - const [editModalVisible, setEditModalVisible] = useState(false); const [hideFAB, setHideFAB] = useState(false); - const [editTarget, setEditTarget] = useState(null); const handleOpenAddModal = () => { setAddModalVisible(true); @@ -64,26 +68,43 @@ export default function ScheduleList({ setAddModalVisible(false); setHideFAB(false); }; - const handleOpenEditModal = (item) => { - setEditTarget(item); - setEditModalVisible(true); - setHideFAB(true); - }; - const handleCloseEditModal = () => { - setEditModalVisible(false); - setEditTarget(null); - setHideFAB(false); - }; - const scheduleArr = schedules[selectedDate] || []; + const handleConfirmDelete = () => { + if (deleteIdx == null) return; + const item = schedules[selectedDate][deleteIdx]; + if (!item || !item.id) return; + + axios + .get(`${process.env.EXPO_PUBLIC_API_URL}/calendar/delete`, { + params: { calendar_id: item.id }, + withCredentials: true, + }) + .then((res) => { + console.log("[삭제 요청 결과]", res.data); + + if (res.data.success) { + console.log("일정 삭제 성공! 리스트 갱신."); + onSave && onSave(); + } else { + console.log("삭제 실패:", res.data.message); + } + setDeleteModalVisible(false); + setDeleteIdx(null); + }) + .catch((err) => { + console.log("[일정 삭제 에러]", err); + setDeleteModalVisible(false); + setDeleteIdx(null); + }); + }; return ( <> - - {scheduleArr.length ? ( - scheduleArr + + {schedules[selectedDate]?.length ? ( + schedules[selectedDate] .sort((a, b) => `${a.hour}${a.minute}`.localeCompare(`${b.hour}${b.minute}`) ) @@ -125,7 +149,7 @@ export default function ScheduleList({ handleOpenEditModal(item)} + onPress={() => onOpenEditModal(item, idx)} > {item.title} @@ -156,7 +180,10 @@ export default function ScheduleList({ onOpenDeleteModal(idx)} + onPress={() =>{ + setDeleteIdx(idx); + setDeleteModalVisible(true); + }} style={styles.trashBtn} > @@ -166,46 +193,60 @@ export default function ScheduleList({ ) : ( 일정이 없습니다. )} + { - onSave && onSave(); - handleCloseAddModal(); - }} - isEditing={false} - /> - - { - onSave && onSave(); - handleCloseAddModal(); - }} - isEditing={true} - editData={editTarget} + onSave={onSave} /> {showFAB && !hideFAB && ( )} + {deleteModalVisible && ( + setDeleteModalVisible(false)} + > + + + + 정말 삭제 하시겠습니까? + 삭제하시면 복구가 불가합니다. + + setDeleteModalVisible(false)}> + + 취소 + + + + + 삭제 + + + + + + + )} ); } const styles = StyleSheet.create({ modalContent: { + flex: 1, paddingHorizontal: 32, paddingTop: 16, paddingBottom: 32, backgroundColor: "#fff", - minHeight: 1000 * 0.5, + minHeight: 0, }, modalDate: { color: "#191919", @@ -251,4 +292,59 @@ const styles = StyleSheet.create({ zIndex: 9999, elevation: 20, }, + modalOverlay: { + flex: 1, + backgroundColor: "rgba(0, 0, 0, 0.5)", + justifyContent: "center", + alignItems: "center", + }, + deleteModal: { + width: 350, + padding: 20, + backgroundColor: "white", + borderRadius: 10, + elevation: 5, + alignItems: "center", + top: -40 + }, + deleteIcon: { + width: 50, + height: 50, + marginBottom: 10, + }, + deleteTitle: { + fontSize: 20, + fontWeight: "600", + marginTop: 10, + }, + deleteSubtitle: { + fontSize: 14, + color: "#808080", + marginTop: 5, + }, + buttonContainer: { + flexDirection: "row", + marginTop: 15, + }, + modalBtn: { + width: 140, + height: 45, + borderRadius: 10, + justifyContent: "center", + alignItems: "center", + marginHorizontal: 7.5, + }, + cancelBtn: { + backgroundColor: "#DDDDDD", + }, + deleteBtn: { + backgroundColor: "#FF3B30", + }, + btnText: { + fontSize: 16, + }, + saveText: { + color: "white", + }, + }); From 880d250442bd20edb856e026a59cf038e9123e7c Mon Sep 17 00:00:00 2001 From: PJW Date: Wed, 17 Sep 2025 00:05:15 +0900 Subject: [PATCH 4/8] =?UTF-8?q?front-24=20=EC=BB=A8=ED=94=8C=EB=A6=AD?= =?UTF-8?q?=ED=8A=B8=20=EC=98=A4=EB=A5=98=20=EB=B0=8F=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/screens/ScheduleList.js | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/app/screens/ScheduleList.js b/app/screens/ScheduleList.js index fc53ac4..2e9f3ea 100644 --- a/app/screens/ScheduleList.js +++ b/app/screens/ScheduleList.js @@ -46,8 +46,6 @@ export default function ScheduleList({ modalRef, schedules, selectedDate, - onOpenEditModal, - onOpenDeleteModal, onModalOpen, onModalClose, showFAB, @@ -60,15 +58,31 @@ export default function ScheduleList({ const [addModalVisible, setAddModalVisible] = useState(false); const [hideFAB, setHideFAB] = useState(false); + const [isEditing, setIsEditing] = useState(false); // 수정 모드 상태 + const [editData, setEditData] = useState(null); // 수정 대상 일정 + + const scheduleArr = schedules[selectedDate] || []; + + const onOpenEditModal = (item, idx) => { + setIsEditing(true); + setEditData(item); + setAddModalVisible(true); + setHideFAB(true); + }; + const handleOpenAddModal = () => { + setIsEditing(false); + setEditData(null); setAddModalVisible(true); setHideFAB(true); }; const handleCloseAddModal = () => { + setIsEditing(false); + setEditData(null); setAddModalVisible(false); setHideFAB(false); }; - const scheduleArr = schedules[selectedDate] || []; + const handleConfirmDelete = () => { if (deleteIdx == null) return; @@ -130,8 +144,8 @@ export default function ScheduleList({ contentContainerStyle={{ paddingBottom: 24 }} showsVerticalScrollIndicator={false} > - {schedules[selectedDate]?.length ? ( - schedules[selectedDate] + {scheduleArr.length ? ( + scheduleArr .sort((a, b) => `${a.hour}${a.minute}`.localeCompare(`${b.hour}${b.minute}`) ) @@ -200,7 +214,12 @@ export default function ScheduleList({ visible={addModalVisible} onClose={handleCloseAddModal} selectedDate={selectedDate} - onSave={onSave} + onSave={() => { + onSave && onSave(); + handleCloseAddModal(); + }} + isEditing={isEditing} + editData={editData} /> {showFAB && !hideFAB && ( From 6183a806d18758926b7c801f3960beac4b1b01e3 Mon Sep 17 00:00:00 2001 From: PJW Date: Wed, 17 Sep 2025 00:35:02 +0900 Subject: [PATCH 5/8] =?UTF-8?q?front-24=20=EC=BB=A8=ED=94=8C=EB=A6=AD?= =?UTF-8?q?=ED=8A=B8=20=ED=95=B4=EA=B2=B0=20=ED=9B=84=20=EC=98=A4=EB=A5=98?= =?UTF-8?q?=20=ED=95=B4=EA=B2=B0=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/screens/ScheduleList.js | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/app/screens/ScheduleList.js b/app/screens/ScheduleList.js index b133b22..6b5faaa 100644 --- a/app/screens/ScheduleList.js +++ b/app/screens/ScheduleList.js @@ -113,35 +113,6 @@ export default function ScheduleList({ }); }; - const handleConfirmDelete = () => { - if (deleteIdx == null) return; - const item = schedules[selectedDate][deleteIdx]; - if (!item || !item.id) return; - - axios - .get(`${process.env.EXPO_PUBLIC_API_URL}/calendar/delete`, { - params: { calendar_id: item.id }, - withCredentials: true, - }) - .then((res) => { - console.log("[삭제 요청 결과]", res.data); - - if (res.data.success) { - console.log("일정 삭제 성공! 리스트 갱신."); - onSave && onSave(); - } else { - console.log("삭제 실패:", res.data.message); - } - setDeleteModalVisible(false); - setDeleteIdx(null); - }) - .catch((err) => { - console.log("[일정 삭제 에러]", err); - setDeleteModalVisible(false); - setDeleteIdx(null); - }); - }; - return ( <> Date: Wed, 24 Sep 2025 14:18:04 +0900 Subject: [PATCH 6/8] Merge branch 'main' into front-24! --- app.json | 16 +- app/(auth)/Login.js | 75 ------ app/(auth)/SignUp.js | 83 ++----- app/(auth)/SignUpForm.js | 83 +++++-- app/(auth)/Start.js | 59 ----- app/(tabs)/_layout.js | 14 +- app/(tabs)/feedback.js | 34 ++- app/(tabs)/home.js | 5 +- app/(tabs)/myPage.js | 7 - app/screens/Feedback_result.js | 231 +++++++++---------- app/screens/ScheduleAddModal.js | 281 ++++++++++++----------- app/screens/change-nickname.js | 2 +- components/Modal/EditListModal.js | 57 +++-- components/Modal/Feedback_resultModal.js | 2 +- components/main/MainCalendar.js | 31 ++- components/main/MainFeedback.js | 36 +-- components/main/MainQuestion.js | 3 +- components/modal/Logout.js | 6 +- package-lock.json | 64 ++++-- package.json | 8 +- 20 files changed, 514 insertions(+), 583 deletions(-) diff --git a/app.json b/app.json index 9ec6c46..06ca935 100644 --- a/app.json +++ b/app.json @@ -22,7 +22,7 @@ }, "android": { - "package": "com.dana7259.ainterviewfront", + "package": "com.example.ainterviewfront", "adaptiveIcon": { "foregroundImage": "./assets/images/adaptive-icon.png", "backgroundColor": "#ffffff" @@ -36,16 +36,6 @@ "favicon": "./assets/images/favicon.png" }, - "plugins": [ - [ - "@react-native-seoul/kakao-login", - { - "kakaoAppKey": "44682a4b6329aa1fc5f9ddf575f9bf2a" - } - ], - "expo-router" - ], - "experiments": { "typedRoutes": true }, @@ -55,8 +45,6 @@ "eas": { "projectId": "76a53025-9fcf-4966-8b97-149ba97debf4" } - }, - - "owner": "dana7259" + } } } diff --git a/app/(auth)/Login.js b/app/(auth)/Login.js index 3709168..7b9a446 100644 --- a/app/(auth)/Login.js +++ b/app/(auth)/Login.js @@ -18,26 +18,10 @@ export default function Login() { const [id, setId] = useState(""); const [pw, setPw] = useState(""); const [showPw, setShowPw] = useState(false); - const [keepLogin, setKeepLogin] = useState(true); const [idError, setIdError] = useState(""); const [pwError, setPwError] = useState(""); - useEffect(() => { - const checkKeepLogin = async () => { - const keep = await AsyncStorage.getItem("keepLogin"); - const token = await AsyncStorage.getItem("token"); - - const storedUserId = await AsyncStorage.getItem("userId"); - const userId = storedUserId ? Number(storedUserId) : null; - - if (keep === "true" && token && userId) { - router.replace("/(tabs)/home"); - } - }; - checkKeepLogin(); - }, []); - const handleLogin = async () => { setIdError(""); setPwError(""); @@ -61,11 +45,6 @@ export default function Login() { console.log(res.data.userId); AsyncStorage.setItem("userId", String(res.data.userId)); AsyncStorage.setItem("NickName", res.data.nickname); - if (keepLogin) { - await AsyncStorage.setItem("keepLogin", "true"); - } else { - await AsyncStorage.removeItem("keepLogin"); - } router.replace("/(tabs)/home"); }) @@ -127,43 +106,10 @@ export default function Login() { {pwError !== "" && {pwError}} - setKeepLogin(!keepLogin)} - > - - 로그인 유지 - - 로그인 - - - - - - - - - @@ -233,15 +179,6 @@ const styles = StyleSheet.create({ fontSize: 12, marginTop: 4, }, - keepWrap: { - flexDirection: "row", - alignItems: "center", - marginBottom: 24, - }, - keepText: { - marginLeft: 8, - fontSize: 14, - }, loginBtn: { backgroundColor: "#5900FF", paddingVertical: 16, @@ -254,18 +191,6 @@ const styles = StyleSheet.create({ fontSize: 14, fontWeight: "bold", }, - socialWrap: { - flexDirection: "row", - justifyContent: "space-around", - marginBottom: 24, - }, - icon: { - width: 44, - height: 44, - resizeMode: "contain", - borderRadius: 22, - overflow: "hidden", - }, divider: { height: 1, backgroundColor: "#ddd", diff --git a/app/(auth)/SignUp.js b/app/(auth)/SignUp.js index ac61bb3..15114c3 100644 --- a/app/(auth)/SignUp.js +++ b/app/(auth)/SignUp.js @@ -7,41 +7,17 @@ export default function SignUp() { return ( - 소셜 계정으로 가입하기 - - - - - - + + router.back()}> - - - - - - - - - - 또는 - + 회원가입 - router.push("/(auth)/SignUpForm")} @@ -65,41 +41,25 @@ export default function SignUp() { const styles = StyleSheet.create({ container: { flex: 1, - padding: 30, - justifyContent: "center", - alignItems: "center", - backgroundColor: "white", - }, - title: { - fontSize: 14, - marginBottom: 20, + paddingHorizontal: 24, + paddingTop: 40, + backgroundColor: "#fff", }, - socialRow: { - flexDirection: "row", - justifyContent: "space-between", - width: "70%", - marginBottom: 30, - }, - icon: { - width: 40, - height: 40, - marginHorizontal: 8, - }, - separatorWrap: { + header: { + height: 56, flexDirection: "row", alignItems: "center", - marginVertical: 10, - width: "80%", - }, - separator: { - flex: 1, - height: 1, - backgroundColor: "#ccc", + backgroundColor: "#fff", + marginBottom: 300, }, - or: { - marginHorizontal: 8, - fontSize: 12, - color: "#777", + headerText: { + position: "absolute", + left: 50, + right: 50, + textAlign: "center", + fontSize: 20, + fontWeight: "400", + color: "#191919", }, button: { backgroundColor: "#5900FF", @@ -118,6 +78,7 @@ const styles = StyleSheet.create({ marginTop: 20, fontSize: 12, color: "#777", + textAlign: "center", }, link: { color: "#5900FF", diff --git a/app/(auth)/SignUpForm.js b/app/(auth)/SignUpForm.js index f72f305..e76b935 100644 --- a/app/(auth)/SignUpForm.js +++ b/app/(auth)/SignUpForm.js @@ -15,7 +15,8 @@ import axios from "axios"; export default function SignUpForm() { const router = useRouter(); - + const [showPassword, setShowPassword] = useState(false); + const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [id, setId] = useState(""); const [nickname, setNickname] = useState(""); const [password, setPassword] = useState(""); @@ -199,7 +200,6 @@ export default function SignUpForm() { {idError} - 닉네임 {nicknameError} - 비밀번호 - + + + setShowPassword(!showPassword)} + > + + + {passwordError} - 비밀번호 확인 - + + + setShowConfirmPassword(!showConfirmPassword)} + > + + + {confirmPasswordError} - setAgreeTerms(!agreeTerms)} @@ -260,7 +281,6 @@ export default function SignUpForm() { {termsError} - setAgreePush(!agreePush)} @@ -280,7 +300,6 @@ export default function SignUpForm() { (선택) - 아이디로 로그인 - - - - 소셜 로그인 - - - - - handleSocialLogin("kakao")}> - - - handleSocialLogin("google")}> - - - handleSocialLogin("naver")}> - - - handleSocialLogin("facebook")}> - - - ); } @@ -129,30 +96,4 @@ const styles = StyleSheet.create({ fontSize: 14, fontWeight: "bold", }, - divider: { - flexDirection: "row", - alignItems: "center", - marginBottom: 16, - width: "100%", - }, - line: { - flex: 1, - height: 1, - backgroundColor: "#ccc", - }, - dividerText: { - marginHorizontal: 10, - color: "#808080", - fontSize: 12, - }, - socialIcons: { - flexDirection: "row", - justifyContent: "space-between", - width: "80%", - }, - icon: { - width: 40, - height: 40, - resizeMode: "contain", - }, }); diff --git a/app/(tabs)/_layout.js b/app/(tabs)/_layout.js index 8f80a56..e81a579 100644 --- a/app/(tabs)/_layout.js +++ b/app/(tabs)/_layout.js @@ -74,12 +74,12 @@ export default function TabLayout() { tabBarShowLabel: false, tabBarActiveTintColor: "#5900FF", tabBarStyle: { - height: "8%", // 56/812 비율 + height: 100, // 56/812 비율 backgroundColor: "#ffffff", borderTopWidth: 0, // 상단 경계선 제거 - // elevation: 0, - alignItems: "center", - width: "100%", + paddingTop: 10, + alignItems: "row", + justifyContent: "space-between", }, tabBarButton: (props) => ( @@ -152,7 +152,9 @@ export default function TabLayout() { options={{ headerShown: false, title: "", - tabBarStyle: { display: "none" }, + tabBarStyle: { + display: "none", + }, tabBarIcon: () => null, }} /> @@ -284,7 +286,7 @@ export default function TabLayout() { position: "absolute", left: 0, right: 0, - bottom: 0, + bottom: 20, alignItems: "center", zIndex: 10, }} diff --git a/app/(tabs)/feedback.js b/app/(tabs)/feedback.js index ac0f688..24dc4cc 100644 --- a/app/(tabs)/feedback.js +++ b/app/(tabs)/feedback.js @@ -28,10 +28,10 @@ export default function Feedback() { const [loadingId, setLoadingId] = useState(null); const [searchText, setSearchText] = useState(""); const [usersId, setUsersId] = useState(null); - const [deleteModal, setDeleteModal] = useState(false); - const [memoModal, setMemoModal] = useState(false); const route = useRouter(); const isFocused = useIsFocused(); + const [list, setList] = useState(null); + const [listN, setListN] = useState("모든 피드백 : " + { listN }); useEffect(() => { if (isFocused) { @@ -61,9 +61,12 @@ export default function Feedback() { }), title: item.title, memo: item.memo, + content: item.content, pin: item.pin || "N", })); - + console.log(data.data.length + "개의 피드백을 불러왔습니다."); + setListN(data.data.length); + setListN("모든 피드백 : " + { listN }) setFeedbackList(mappedData); }) .catch((err) => { @@ -82,6 +85,8 @@ export default function Feedback() { const filteredList = useMemo(() => { if (!searchText.trim()) return feedbackList; + + const lowerSearch = searchText.trim().toLowerCase(); const normalize = (str) => (str ?? "").trim().toLowerCase(); @@ -93,6 +98,14 @@ export default function Feedback() { }); }, [searchText, feedbackList]); + useEffect(() => { + setListN("조회된 피드백 : " + filteredList.length + "건"); + }, [filteredList]); + + const label = searchText.trim() + ? `조회된 피드백 : ${filteredList.length}건` + : `모든 피드백`; + const sortedList = useMemo(() => { const listToSort = filteredList; @@ -194,7 +207,7 @@ export default function Feedback() { - 모든 피드백 + {label} setOpen(!open)} style={{ flexDirection: "row", alignItems: "center" }} @@ -204,8 +217,8 @@ export default function Feedback() { style={{ width: 28, height: 14 }} source={ open - ? require("../../assets/icons/arrow_down.png") - : require("../../assets/icons/arrow_up.png") + ? require("../../assets/icons/arrow_up.png") + : require("../../assets/icons/arrow_down.png") } /> @@ -247,6 +260,7 @@ export default function Feedback() { renderItem={({ item }) => { const isModalVisible = openModalItemId === item.id; const isPinned = item.pin === "Y"; + const isContentEmpty = !(item.content ?? "").trim(); return ( {isPinned && ( setOpenModalItemId(isModalVisible ? null : item.id) } - disabled={loadingId === item.id} + disabled={loadingId === item.id || isContentEmpty} > - {item.title} + {item.title?.length > 15 ? item.title.substring(0, 15) + "..." : item.title} - {item.memo ?? "메모 없음"} + {(item.content ?? "").trim() !== "" ? item.content : "분석중..."} diff --git a/app/(tabs)/home.js b/app/(tabs)/home.js index 7a2e8f3..40a9b48 100644 --- a/app/(tabs)/home.js +++ b/app/(tabs)/home.js @@ -62,10 +62,7 @@ export default function Home() { paddingHorizontal: "4%", }} > - router.push("../screens/bell")} - style={{ padding: 6 }} - > + router.push("../screens/bell")}> - - - {/* 내 정보 */} diff --git a/app/screens/Feedback_result.js b/app/screens/Feedback_result.js index 16e822a..84623cf 100644 --- a/app/screens/Feedback_result.js +++ b/app/screens/Feedback_result.js @@ -1,6 +1,6 @@ // Feedback_result.js import { useRouter, useLocalSearchParams } from "expo-router"; -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useMemo } from "react"; import axios from "axios"; import { View, @@ -28,6 +28,15 @@ const formattedDate = today const { width: SCREEN_WIDTH } = Dimensions.get("window"); +const LABELS_KO = { + pose: "자세", + confidence: "자신감", + facial: "표정", + risk_response: "위기 대처능력", + tone: "말투", + understanding: "업무이해도", +}; + export default function FeedbackResult() { const route = useRouter(); @@ -44,18 +53,21 @@ export default function FeedbackResult() { tone: 0, understanding: 0, }); - - + //이전 피드백 육각형 데이터 비교해서 가장 많이 증가한 데이터 + const [mostImproved, setMostImproved] = useState([]); // 로딩/에러 const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [modalVisible, setModalVisible] = useState(false); - // ✅ 추가: 최상단 고정 상태 (UI 변경 없음) const [isPinned, setIsPinned] = useState(false); const params = useLocalSearchParams(); - const userId = Array.isArray(params.userId) ? params.userId[0] : params.userId; - const feedbackId = Array.isArray(params.feedbackId) ? params.feedbackId[0] : params.feedbackId; + const userId = Array.isArray(params.userId) + ? params.userId[0] + : params.userId; + const feedbackId = Array.isArray(params.feedbackId) + ? params.feedbackId[0] + : params.feedbackId; const title = Array.isArray(params.title) ? params.title[0] : params.title; const api = axios.create({ @@ -70,28 +82,30 @@ export default function FeedbackResult() { try { setLoading(true); setError(null); - const res = await api.get( - `/feedback/${encodeURIComponent(userId)}/${encodeURIComponent(feedbackId)}` - ) - .then((r) => { - console.log("가져온 데이터",{ - userId, - feedbackId, - title, - status: r?.status, - }); - return r; - }); + const res = await api + .get( + `/feedback/${encodeURIComponent(userId)}/${encodeURIComponent( + feedbackId + )}` + ) + .then((r) => { + console.log("가져온 데이터", { + userId, + feedbackId, + title, + status: r?.status, + }); + return r; + }); const data = res.data?.data || {}; - if(data.created_at) { - const date = new Date(data.created_at); - const formatted = date.toLocaleDateString("ko-KR", { - year: "numeric", - month: "2-digit", - day: "2-digit", - }).replace(/\. /g, ".").replace(/\.$/,""); - setCreatedAt(formatted); + if (data.created_at) { + const date = new Date(data.created_at); + const y = date.getFullYear(); + const m = String(date.getMonth() + 1).padStart(2, "0"); + const d = String(date.getDate()).padStart(2, "0"); + const formatted = `${y}년 ${m}월 ${d}일`; + setCreatedAt(formatted); } // 응답 값 매핑 @@ -99,7 +113,6 @@ export default function FeedbackResult() { setCons(data.bad || ""); setTip(data.content || ""); setMemo(data.memo || ""); - // ✅ 목록과 동일 규칙: "Y"면 고정 (UI 영향 없음) setIsPinned((data.pin || "N") === "Y"); setScores({ pose: data.pose || 0, @@ -109,6 +122,7 @@ export default function FeedbackResult() { tone: data.tone || 0, understanding: data.understanding || 0, }); + setMostImproved(data.mostImproved || ""); } catch (e) { setError("피드백을 불러오지 못했어요."); console.warn(e?.response?.data || e?.message); @@ -120,6 +134,30 @@ export default function FeedbackResult() { fetchFeedback(); }, [userId, feedbackId]); + const bestAspectKey = useMemo(() => { + const entries = Object.entries(scores); + if (!entries.length) return "pose"; + let maxKey = "pose"; + let maxVal = -Infinity; + for (const [k, v] of entries) { + if (typeof v === "number" && v > maxVal) { + maxVal = v; + maxKey = k; + } + } + return maxKey; + }, [scores]); + + const bestAspectLabel = LABELS_KO[bestAspectKey] || "자세"; + //받아온 데이터 매핑 + const skillNameMap = { + pose: "자세", + confidence: "자신감", + facial: "표정", + risk_response: "위기대처능력", + tone: "말투", + understanding: "업무이해도", + }; return ( @@ -141,9 +179,7 @@ export default function FeedbackResult() { {title || "피드백"} - - {createdAt || "날짜 없음"} - + {createdAt || "날짜 없음"} {isPinned && ( @@ -152,9 +188,11 @@ export default function FeedbackResult() { style={{ position: "absolute", right: 18, - top: 90, + top: 125, width: 50, height: 70, + zIndex: 1, + elevation: 30, }} /> )} @@ -165,12 +203,17 @@ export default function FeedbackResult() { 사용자 분석 그래프 - - - - 저번보다 자세가 더 좋아졌어요! - + + {mostImproved && mostImproved.length > 0 && ( + + 저번보다{" "} + + {mostImproved.map((s) => skillNameMap[s] || s).join(", ")} + + 이(가) 더 좋아졌어요! + + )} 피드백 및 평가 장점 @@ -208,27 +251,15 @@ export default function FeedbackResult() { 메모 - - - route.replace("/home")} - > - 피드백 삭제 - - route.push("/feedback")} - > - 피드백 저장 - - @@ -256,7 +287,9 @@ export default function FeedbackResult() { setOpenModalItemId={() => setModalVisible(false)} isModalVisible={modalVisible} isPinned={isPinned} - onUpdateTitle={(id, newTitle) => route.setParams({ title: newTitle })} + onUpdateTitle={(id, newTitle) => + route.setParams({ title: newTitle }) + } onUpdateMemo={(id, newMemo) => setMemo(newMemo)} onDelete={(id) => route.replace("/feedback")} onPin={(_id, newPin) => setIsPinned(newPin === "Y")} @@ -273,14 +306,15 @@ const styles = StyleSheet.create({ flex: 0, backgroundColor: "#FFFFFF", paddingHorizontal: 32, - paddingTop: 0, + paddingTop: 24, }, topHeader: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", height: 56, - paddingBottom: 12, + paddingTop: 10, + paddingBottom: 22, paddingHorizontal: 0, backgroundColor: "#fff", }, @@ -295,26 +329,28 @@ const styles = StyleSheet.create({ color: "#191919", fontFamily: "Pretendard", }, + // 🔍 점 3개 아이콘 크게 dotsIcon: { - width: 26, - height: 26, + width: 36, + height: 36, resizeMode: "contain", }, headerRow: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", - marginTop: 10, + marginTop: 30, width: "100%", }, fullLine: { - height: 2, + height: 5, backgroundColor: "#DDDDDD", width: "100%", alignSelf: "center", marginTop: 7, - marginBottom: 55, borderRadius: 3, + elevation: 0, + zIndex: 0, }, topTitle: { fontSize: 18, @@ -334,33 +370,9 @@ const styles = StyleSheet.create({ fontWeight: "300", fontFamily: "Pretendard", color: "#191919", - marginBottom: 56, - }, - graphWrapper: { - width: 206, - height: 206, - alignSelf: "center", - position: "relative", - marginBottom: 10, + marginTop: 35, + marginBottom: 16, }, - graphImage: { - width: "100%", - height: "100%", - resizeMode: "contain", - }, - graphLabel: { - position: "absolute", - fontSize: 13, - fontFamily: "Pretendard", - color: "#191919", - fontWeight: "400", - }, - labelTopLeft: { top: -18, left: 38 }, - labelTopRight: { top: -18, right: 30 }, - labelLeft: { top: "42%", left: -40 }, - labelRight: { top: "42%", right: -70, width: 68, textAlign: "center" }, - labelBottomLeft: { bottom: -16, left: 40 }, - labelBottomRight: { bottom: -16, right: 10 }, improvementText: { marginTop: 40, textAlign: "center", @@ -405,7 +417,7 @@ const styles = StyleSheet.create({ marginTop: 6, }, memoTitle: { - marginTop: 32, + marginTop: 61, fontSize: 16, fontWeight: "500", textAlign: "center", @@ -419,46 +431,9 @@ const styles = StyleSheet.create({ fontSize: 14, color: "#333", minHeight: 100, - marginTop: 12, + marginTop: 17, textAlignVertical: "top", fontFamily: "Pretendard", - }, - buttonContainer: { - flexDirection: "row", - justifyContent: "space-between", - marginTop: 24, - marginBottom: 40, - }, - deleteButton: { - flex: 1, - marginRight: 12, - backgroundColor: "#FFFFFF", - borderColor: "#191919", - borderWidth: 0.3, - borderRadius: 10, - paddingVertical: 14, - }, - saveButton: { - flex: 1, - marginLeft: 12, - backgroundColor: "#191919", - borderRadius: 10, - paddingVertical: 14, - }, - deleteButtonText: { - color: "#808080", - textAlign: "center", - fontWeight: "600", - fontSize: 16, - fontFamily: "Inter", - letterSpacing: -0.5, - }, - saveButtonText: { - color: "#FFFFFF", - textAlign: "center", - fontWeight: "600", - fontSize: 16, - fontFamily: "Inter", - letterSpacing: -0.5, + marginBottom: 104, }, }); diff --git a/app/screens/ScheduleAddModal.js b/app/screens/ScheduleAddModal.js index 3d1d46f..00fcb9d 100644 --- a/app/screens/ScheduleAddModal.js +++ b/app/screens/ScheduleAddModal.js @@ -1,4 +1,4 @@ -import React, { useRef, useState, useMemo, useEffect } from "react"; +import React, { useRef, useState, useMemo } from "react"; import { View, Text, @@ -29,17 +29,35 @@ export default function ScheduleAddModal({ onClose, selectedDate, onSave, - isEditing = false, - editData = null, + isEditing, saving, }) { const modalAddRef = useRef(null); + React.useEffect(() => { + if (visible) { + setHour(""); + setMinute(""); + setTempHour("00"); + setTempMinute("00"); + modalAddRef.current?.open(); + } else { + modalAddRef.current?.close(); + } + }, [visible]); + + React.useEffect(() => { + // 시간 또는 분이 변경되면 별도 처리 가능 (필요시) + console.log("시간 변경: ", hour, minute); + }, [hour, minute]); + const [title, setTitle] = useState(""); - const [hour, setHour] = useState("10"); - const [minute, setMinute] = useState("00"); + const [hour, setHour] = useState(""); + const [minute, setMinute] = useState(""); + const [tempHour, setTempHour] = useState("00"); + const [tempMinute, setTempMinute] = useState("00"); // 중요도 초기값 반드시 API value ("S"/"I"/"N") - const [priority, setPriority] = useState("I"); + const [priority, setPriority] = useState(""); const [memo, setMemo] = useState(""); const [showTimePicker, setShowTimePicker] = useState(false); const [titleRequired, setTitleRequired] = useState(false); @@ -56,137 +74,104 @@ export default function ScheduleAddModal({ [] ); - useEffect(() => { - if (visible) { - if (isEditing && editData) { - setTitle(editData.title || ""); - setHour(editData.hour || "10"); - setMinute(editData.minute || "00"); - // priority: "S" | "I" | "N"으로 변환 - setPriority( - editData.priority === "매우 중요" - ? "S" - : editData.priority === "중요하지 않음" - ? "N" - : "I" - ); - setMemo(editData.memo || ""); - } else { - setTitle(""); - setHour("10"); - setMinute("00"); - setPriority("I"); - setMemo(""); - } - setSaveError(""); - } - }, [visible, isEditing, editData]); - - // **Modal open/close - modalize** - useEffect(() => { - if (visible) { - modalAddRef.current?.open(); - } else { - modalAddRef.current?.close(); - } - }, [visible]); + const openPicker = () => { + setTempHour(hour || "00"); + setTempMinute(minute || "00"); + setShowTimePicker(true); + }; // API 요구 포맷으로 반환 (예: 2025-09-13 10:00:00) const getApiDateTime = () => { // selectedDate가 JS Date 객체이어야 함! - let dateObj = new Date(selectedDate); - if (isNaN(dateObj.getTime())) { - dateObj = new Date(); - } + const dateObj = new Date(selectedDate); const yyyy = dateObj.getFullYear(); const mm = pad(dateObj.getMonth() + 1); const dd = pad(dateObj.getDate()); + const h = hour !== "" ? pad(hour) : "00"; + const m = minute !== "" ? pad(minute) : "00"; return `${yyyy}-${mm}-${dd} ${pad(hour)}:${pad(minute)}:00`; }; const handleSave = () => { const isTitleValid = title.trim() !== ""; + if (!isTitleValid) { + setTitleRequired(true); + setPriorityRequired(false); + setTimeRequired(false); + return; + } else { + setTitleRequired(false); + } + const isTimeValid = hour !== "" && minute !== ""; - const isPriorityValid = !!priority; + if (!isTimeValid) { + setTimeRequired(true); + return; + } else { + setTimeRequired(false); + } - setTitleRequired(!isTitleValid); - setTimeRequired(!isTimeValid); - setPriorityRequired(!isPriorityValid); + const isPriorityValid = priority !== ""; + if (!isPriorityValid) { + setPriorityRequired(true); + setTimeRequired(false); + return; + } else { + setPriorityRequired(false); + } - if (!isTitleValid || !isTimeValid || !isPriorityValid || title.length > 7) { - return; - } + if (title.length > 7) { + return; + } AsyncStorage.getItem("userId") .then((userId) => { - if (!userId) throw new Error("사용자 정보가 없습니다."); - - const params = { - userId, - title, - time: getApiDateTime(), - importance: priority, - memo, - }; - - if (isEditing && editData?.id) { - return axios - .get(`${process.env.EXPO_PUBLIC_API_URL}/calendar/update`, { - params: { ...params, calendar_id: editData.id }, - withCredentials: true, - }) - .then((res) => { - console.log("[일정 수정 성공]", res.data); - onSave && onSave(); - handleCancel(); - }) - .catch((err) => { - setSaveError( - "일정 수정 실패: " + - (err?.response?.data?.message || err.message) - ); - console.error("[일정 수정 실패]", err?.response?.data || err); - }); - } else { - return axios - .get(`${process.env.EXPO_PUBLIC_API_URL}/calendar/add`, { - params, - withCredentials: true, - }) - .then((res) => { - console.log("[일정 추가 성공]", res.data); - onSave && onSave(); - handleCancel(); - }) - .catch((err) => { - setSaveError( - "일정 저장 실패: " + - (err?.response?.data?.message || err.message) - ); - console.error("[일정 추가 실패]", err?.response?.data || err); - }); - } + return axios.get(`${process.env.EXPO_PUBLIC_API_URL}/calendar/add`, { + params: { + userId, + title, + time: getApiDateTime(), + importance: priority, // 반드시 "S" | "I" | "N" + memo, + }, + withCredentials: true + }); + }) + .then((res) => { + const calendarId = res.data.calendarId || res.data.id; + if (calendarId) { + AsyncStorage.setItem("calendarId", calendarId.toString()); + } + console.log("[schedule add] Response:", res.data); + onSave && + onSave({ + title, + hour, + minute, + priority, + memo, + calendarId + }); + handleCancel(); }) .catch((err) => { setSaveError( - (isEditing ? "일정 수정 실패: " : "일정 저장 실패: ") + - (err?.response?.data?.message || err.message) + "일정 저장 실패: " + (err?.response?.data?.message || err.message) ); - console.error("[일정 작업 실패]", err?.response?.data || err); + console.error("[schedule add] Error:", err?.response?.data || err); }); - - if(onSave) onSave(); - handleCancel(); }; - const handleCancel = () => { setTitle(""); - setHour("10"); - setMinute("00"); - setPriority("I"); + setHour(""); + setMinute(""); + setPriority(""); setMemo(""); setSaveError(""); + setTitleRequired(false); + setTimeRequired(false); + setPriorityRequired(false); onClose && onClose(); }; @@ -197,13 +182,12 @@ export default function ScheduleAddModal({ return ( { - setTitle(text); - setTitleRequired(false); - setSaveError(""); + if (text.length <= 7) { + setTitle(text); + setTitleRequired(false); + setSaveError(""); + } }} - maxLength={8} placeholder="제목을 입력하세요" + maxLength={7} /> {titleRequired && ( 제목을 입력해주세요 @@ -249,21 +235,28 @@ export default function ScheduleAddModal({ 면접시간 + setShowTimePicker(true)}> - {`${hour}:${minute}`} + + {hour || minute ? `${hour || "00"}:${minute || "00"}` : "시간을 선택하세요."} - {timeRequired && *} + {timeRequired && 시간을 선택해 주세요.} + 중요도 + - {importanceOptions.map((opt) => ( + {importanceOptions.map((opt) => { + const selected = priority === opt.value; + return ( { setPriority(opt.value); @@ -272,17 +265,20 @@ export default function ScheduleAddModal({ > {opt.label} - ))} + ); + })} + + {priorityRequired && ( + 중요도를 선택해 주세요 + )} - {priorityRequired && *} 메모 @@ -290,9 +286,13 @@ export default function ScheduleAddModal({ style={styles.memoInput} multiline value={memo} - onChangeText={setMemo} - maxLength={50} + onChangeText={(text) => { + if (text.length <= 49) { // 50자 미만(49자 이하) 제한 + setMemo(text); + } + }} placeholder="메모를 입력하세요" + maxLength={49} /> {saveError !== "" && {saveError}} @@ -310,19 +310,25 @@ export default function ScheduleAddModal({ {showTimePicker && ( - setShowTimePicker(false)}> + - setShowTimePicker(false)}> + { + setShowTimePicker(false); + // wheel picker의 임시값 초기화 + setTempHour("00"); + setTempMinute("00"); + }}> 취소 - setShowTimePicker(false)}> + { + setShowTimePicker(false); + setHour(tempHour); + setMinute(tempMinute); + }}> 확인 @@ -330,22 +336,22 @@ export default function ScheduleAddModal({ ({ label: `${h}시`, value: h, }))} - onChange={({ item }) => setHour(item.value)} + onChange={({ item }) => setTempHour(item.value)} /> ({ label: `${m}분`, value: m, }))} - onChange={({ item }) => setMinute(item.value)} + onChange={({ item }) => setTempMinute(item.value)} /> @@ -434,6 +440,7 @@ const styles = StyleSheet.create({ backgroundColor: "#fff", paddingBottom: 20, justifyContent: "center", + height: 270 }, errorMsg: { color: "#FF5A5A", diff --git a/app/screens/change-nickname.js b/app/screens/change-nickname.js index 3d51d5f..b22bf0e 100644 --- a/app/screens/change-nickname.js +++ b/app/screens/change-nickname.js @@ -79,7 +79,7 @@ export default function ChangeNicknameScreen() { const handleModalConfirm = () => { setModalVisible(false); - router.replace("/home"); + router.replace("/myPage"); }; return ( diff --git a/components/Modal/EditListModal.js b/components/Modal/EditListModal.js index 99205da..11a05b1 100644 --- a/components/Modal/EditListModal.js +++ b/components/Modal/EditListModal.js @@ -158,41 +158,59 @@ const EditListModal = ({ }; const handleTitleChange = (text) => { - if (text.length >= 20) { + if (text.length > 20) { setOpen(true); - } else { - setOpen(false); + return; } setTitleInputText(text); }; + + const handleMemoChange = (text) => { + const cleanedText = (text ?? "").replace(/\r?\n/g, ""); + if (cleanedText.length > 50) { setOpen(true); - setMemoInputText(text.substring(0, text.length - (cleanedText.length - 50))); - } else { - setOpen(false); - setMemoInputText(text); + return; } + setMemoInputText(text); + setMemoNum(cleanedText.length); }; + useEffect(() => { if (open) { const timer = setTimeout(() => { setOpen(false); - }, 3000); + }, 2000); return () => clearTimeout(timer); } }, [open]); + + const handleSaveMemo = async () => { const memoToSave = (memoInputText ?? "").replace(/\r?\n/g, ""); + + if (memoToSave.length > 50) { + setOpen(true); + return; + } + await changeMemo(selectedId, memoToSave); setMemoModalVisible(false); }; + const handleSaveTitle = async () => { + + if (titleInputText.length > 20) { + setOpen(true); + return; + } + await changeTitle(selectedId, titleInputText); setTitleModalVisible(false); }; @@ -239,7 +257,6 @@ const EditListModal = ({ style={styles.textInput} value={titleInputText} onChangeText={handleTitleChange} - maxLength={20} /> {open ? ( @@ -282,7 +299,7 @@ const EditListModal = ({ {memoNum}/50 setMemoModalVisible(false)}> - + 취소 - + 저장 @@ -422,7 +439,7 @@ const styles = StyleSheet.create({ paddingLeft: 20, paddingRight: 20, paddingTop: 20, - paddingBottom: 20, + // paddingBottom: 10, fontSize: 16, }, buttonContainer: { @@ -438,9 +455,23 @@ const styles = StyleSheet.create({ marginHorizontal: 7.5, }, cancelBtn: { + marginTop: -3, + width: 140, + height: 45, + borderRadius: 10, + justifyContent: "center", + alignItems: "center", + marginHorizontal: 7.5, backgroundColor: "#DDDDDD", }, saveBtn: { + marginTop: -3, + width: 140, + height: 45, + borderRadius: 10, + justifyContent: "center", + alignItems: "center", + marginHorizontal: 7.5, backgroundColor: "#5900FF", }, deleteBtn: { diff --git a/components/Modal/Feedback_resultModal.js b/components/Modal/Feedback_resultModal.js index 6d4174d..c1f2694 100644 --- a/components/Modal/Feedback_resultModal.js +++ b/components/Modal/Feedback_resultModal.js @@ -393,7 +393,7 @@ const styles = StyleSheet.create({ }, memoModalContent: { - height: 380, + paddingBottom:14, }, modalTitle: { fontSize: 18, diff --git a/components/main/MainCalendar.js b/components/main/MainCalendar.js index 9e3dc83..1e82a94 100644 --- a/components/main/MainCalendar.js +++ b/components/main/MainCalendar.js @@ -3,6 +3,7 @@ import { Text, View, TouchableOpacity, StyleSheet } from "react-native"; import axios from "axios"; import AsyncStorage from "@react-native-async-storage/async-storage"; import { useIsFocused } from "@react-navigation/native"; + // 이번 주(일~토) 날짜 배열 반환 function getCurrentWeekDates() { const today = new Date(); @@ -20,7 +21,9 @@ function WeeklyCalendar() { const weekDates = getCurrentWeekDates(); const today = new Date(); const [schedules, setSchedules] = useState({}); + const [schedulesConut, setSchedulesConst] = useState({}); const isFocused = useIsFocused(); + useEffect(() => { if (isFocused) { const fetchData = async () => { @@ -44,14 +47,25 @@ function WeeklyCalendar() { }); const scheduleMap = {}; + const scheduleMapC = {}; // 요일별 총 개수 카운터로 사용 + weeklyEvents.forEach((item) => { const date = new Date(item.time); const dayIndex = date.getDay(); // 0:일,~6:토 + const dayIndexC = date.getDay(); // 0:일,~6:토 if (!scheduleMap[dayIndex]) scheduleMap[dayIndex] = []; - scheduleMap[dayIndex].push(item.title); + // 요일별 총 개수 누적 (숫자) + if (!scheduleMapC[dayIndexC]) scheduleMapC[dayIndexC] = 0; + scheduleMapC[dayIndexC] += 1; + + // 요일별 일정 최대 3개까지만 추가 + if (scheduleMap[dayIndex].length < 3) { + scheduleMap[dayIndex].push(item.title); + } }); setSchedules(scheduleMap); + setSchedulesConst(scheduleMapC); console.log(res.data); }) .catch((err) => { @@ -75,6 +89,8 @@ function WeeklyCalendar() { date.getDate() === today.getDate(); const scheduleText = schedules[date.getDay()]; + // 해당 요일의 총 일정 개수 (없으면 0) + const scheduleCount = schedulesConut[date.getDay()] || 0; return ( ))} + {scheduleCount > 3 && ( + +{scheduleCount - 3} + )} ) : ( <> @@ -105,6 +124,9 @@ function WeeklyCalendar() { {t} ))} + {scheduleCount > 3 && ( + +{scheduleCount - 3} + )} )} @@ -171,7 +193,7 @@ const styles = StyleSheet.create({ width: 4, height: 4, borderRadius: 2, - backgroundColor: "#222", + backgroundColor: "#5900FF", position: "absolute", top: 12, zIndex: 1, @@ -185,4 +207,9 @@ const styles = StyleSheet.create({ lineHeight: 10, alignSelf: "center", }, + countText: { + fontSize: 10, + color: "#888", + marginTop: 4, + }, }); diff --git a/components/main/MainFeedback.js b/components/main/MainFeedback.js index 0d1554b..fd479cf 100644 --- a/components/main/MainFeedback.js +++ b/components/main/MainFeedback.js @@ -11,6 +11,7 @@ import axios from "axios"; import AsyncStorage from "@react-native-async-storage/async-storage"; import RoundedBar from "./RoundedBar"; import { useIsFocused } from "@react-navigation/native"; + function formatDate(dateStr) { if (!dateStr) return ""; const dateObj = new Date(dateStr); @@ -19,7 +20,8 @@ function formatDate(dateStr) { const day = String(dateObj.getDate()).padStart(2, "0"); return `${year}.${month}.${day}`; } -//시간 계산 + +// 시간 계산 const getTimes = (apiDate) => { const now = new Date(); const date = new Date(apiDate); @@ -43,6 +45,7 @@ const getTimes = (apiDate) => { return `${years}년 전`; } }; + function MainFeedback() { const [shouldScroll, setShouldScroll] = useState(false); const scrollRef = useRef(null); @@ -117,12 +120,15 @@ function MainFeedback() { (item) => !TopScoreLabels.has(item.label) ); const BottomScores = [...RestScores].sort((a, b) => a.percent - b.percent); - //메인 피드백 박스 크기 조절 + + // 메인 피드백 박스 크기 조절 const toggleTextHeight = () => { setTextBoxHeight((prev) => (prev === 227 ? 412 : 227)); setShouldScroll(true); }; + const firstfb = feedback?.[0]; + return firstfb ? ( {/* 헤더 영역 */} @@ -153,7 +159,8 @@ function MainFeedback() { {item.percent}% ))} - {/* 화살표 클릭 시 나오는 화면 */} + + {/* 화살표 클릭 시 나오는 화면 */} {textBoxHeight === 412 && ( @@ -189,7 +196,7 @@ function MainFeedback() { - {firstfb?.content ? firstfb.content : "로딩 중"} + {firstfb?.content ? firstfb.content : "분석 중..."} @@ -216,7 +223,6 @@ function MainFeedback() { ) : ( - {/* 화살표 버튼 */} 최근 피드백이 없습니다. @@ -225,6 +231,7 @@ function MainFeedback() { ); } + const styles = StyleSheet.create({ container: { backgroundColor: "#fff", @@ -241,13 +248,13 @@ const styles = StyleSheet.create({ }, headerRow: { flexDirection: "row", - alignItems: "center", - height: 32, - marginTop: "3%", + alignItems: "baseline", + marginTop: 15, + marginBottom: 10, }, titleSection: { flexDirection: "row", - alignItems: "center", + alignItems: "baseline", flex: 1, marginLeft: "4%", }, @@ -257,22 +264,24 @@ const styles = StyleSheet.create({ lineHeight: 20, fontWeight: "600", textAlign: "left", + textAlignVertical: "bottom", }, date: { fontSize: 12, fontWeight: "400", color: "#191919", marginLeft: "3%", - lineHeight: 34, + textAlignVertical: "bottom", }, rightTime: { fontSize: 12, fontWeight: "400", color: "#808080", - lineHeight: 34, textAlign: "right", marginRight: "2%", minWidth: 50, + + textAlignVertical: "bottom", }, barRow: { width: "95%", @@ -343,4 +352,5 @@ const styles = StyleSheet.create({ alignSelf: "center", }, }); + export default MainFeedback; diff --git a/components/main/MainQuestion.js b/components/main/MainQuestion.js index b31f839..ae89ff6 100644 --- a/components/main/MainQuestion.js +++ b/components/main/MainQuestion.js @@ -57,8 +57,7 @@ const styles = StyleSheet.create({ borderWidth: 1, borderColor: "#CCCCCC", borderRadius: 10, - marginTop: "7%", - marginBottom: "5%", + justifyContent: "center", alignItems: "flex-start", padding: 26, diff --git a/components/modal/Logout.js b/components/modal/Logout.js index 4577256..fbc4fff 100644 --- a/components/modal/Logout.js +++ b/components/modal/Logout.js @@ -1,14 +1,16 @@ import React, { useState } from "react"; import { View, Text, Modal, TouchableOpacity } from "react-native"; import { useRouter } from "expo-router"; +import AsyncStorage from "@react-native-async-storage/async-storage"; export default function Logout({ logout, setLogout }) { const router = useRouter(); - const handleLogout = () => { + const handleLogout = async () => { + await AsyncStorage.clear(); setLogout(false); console.log("로그아웃 처리"); - router.replace("/Login"); + router.replace("/Start"); }; return ( diff --git a/package-lock.json b/package-lock.json index eeccd33..2c0155f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,10 +19,10 @@ "expo-blur": "~14.1.5", "expo-font": "~13.3.2", "expo-linking": "~7.1.7", - "expo-router": "~5.1.3", + "expo-router": "~5.1.6", "expo-splash-screen": "~0.30.10", "expo-status-bar": "~2.2.3", - "expo-system-ui": "~5.0.10", + "expo-system-ui": "~5.0.11", "expo-web-browser": "~14.2.0", "i": "^0.3.7", "react": "^19.0.0", @@ -31,7 +31,7 @@ "react-native-calendars": "^1.1313.0", "react-native-dotenv": "^3.4.11", "react-native-dropdown-picker": "^5.4.6", - "react-native-gesture-handler": "^2.27.1", + "react-native-gesture-handler": "~2.24.0", "react-native-modalize": "^2.1.1", "react-native-reanimated": "~3.17.4", "react-native-safe-area-context": "5.4.0", @@ -46,7 +46,7 @@ "@types/react": "~19.0.10", "expo": "^53.0.22", "jest": "^29.2.1", - "jest-expo": "~53.0.9", + "jest-expo": "~53.0.10", "react-native-dotenv": "^3.4.11", "react-test-renderer": "19.0.0", "typescript": "~5.8.3" @@ -3408,7 +3408,9 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "license": "MIT" + "dev": true, + "license": "MIT", + "peer": true }, "node_modules/@types/node": { "version": "24.0.10", @@ -3780,7 +3782,9 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -3796,7 +3800,9 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -3813,7 +3819,9 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -5830,12 +5838,13 @@ } }, "node_modules/expo-router": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-5.1.3.tgz", - "integrity": "sha512-zoAU0clwEj569PpGOzc06wCcxOskHLEyonJhLNPsweJgu+vE010d6XW+yr5ODR6F3ViFJpfcjbe7u3SaTjl24Q==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-5.1.6.tgz", + "integrity": "sha512-Tc7QFurWqLItrHvbL2TB4OLq8WA4y8fCXPkRG+q9zP4Lk4xKIDskO7/8ff3+XAOHJB5Z8GHn5IhXv4Ik89SVUA==", "license": "MIT", "dependencies": { "@expo/metro-runtime": "5.0.4", + "@expo/schema-utils": "^0.1.0", "@expo/server": "^0.6.3", "@radix-ui/react-slot": "1.2.0", "@react-navigation/bottom-tabs": "^7.3.10", @@ -5845,7 +5854,6 @@ "invariant": "^2.2.4", "react-fast-compare": "^3.2.2", "react-native-is-edge-to-edge": "^1.1.6", - "schema-utils": "^4.0.1", "semver": "~7.6.3", "server-only": "^0.0.1", "shallowequal": "^1.1.0" @@ -5910,12 +5918,12 @@ } }, "node_modules/expo-system-ui": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/expo-system-ui/-/expo-system-ui-5.0.10.tgz", - "integrity": "sha512-BTXbSyJr80yuN6VO4XQKZj7BjesZQLHgOYZ0bWyf4VB19GFZq7ZnZOEc/eoKk1B3eIocOMKUfNCrg/Wn8Kfcuw==", + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/expo-system-ui/-/expo-system-ui-5.0.11.tgz", + "integrity": "sha512-PG5VdaG5cwBe1Rj02mJdnsihKl9Iw/w/a6+qh2mH3f2K/IvQ+Hf7aG2kavSADtkGNCNj7CEIg7Rn4DQz/SE5rQ==", "license": "MIT", "dependencies": { - "@react-native/normalize-colors": "0.79.5", + "@react-native/normalize-colors": "0.79.6", "debug": "^4.3.2" }, "peerDependencies": { @@ -5929,6 +5937,12 @@ } } }, + "node_modules/expo-system-ui/node_modules/@react-native/normalize-colors": { + "version": "0.79.6", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.79.6.tgz", + "integrity": "sha512-0v2/ruY7eeKun4BeKu+GcfO+SHBdl0LJn4ZFzTzjHdWES0Cn+ONqKljYaIv8p9MV2Hx/kcdEvbY4lWI34jC/mQ==", + "license": "MIT" + }, "node_modules/expo-web-browser": { "version": "14.2.0", "resolved": "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-14.2.0.tgz", @@ -5961,6 +5975,7 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, "funding": [ { "type": "github", @@ -5971,7 +5986,8 @@ "url": "https://opencollective.com/fastify" } ], - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/fb-watchman": { "version": "2.0.2", @@ -7176,13 +7192,13 @@ } }, "node_modules/jest-expo": { - "version": "53.0.9", - "resolved": "https://registry.npmjs.org/jest-expo/-/jest-expo-53.0.9.tgz", - "integrity": "sha512-RFLc6490yucW0rZ41paMWnprKQJ5EccCQOL9XMzckO0V43e45FA3q0NUtlDGuo4686Ln4oWUeiJJTzBHbsx1og==", + "version": "53.0.10", + "resolved": "https://registry.npmjs.org/jest-expo/-/jest-expo-53.0.10.tgz", + "integrity": "sha512-J6vGCNOImXxUXv0c70J2hMlGSHTIyVwCviezMtnZeg966lzshESJhLxQatuvA8r7nJ2riffQgM3cWvL+/Hdewg==", "dev": true, "license": "MIT", "dependencies": { - "@expo/config": "~11.0.12", + "@expo/config": "~11.0.13", "@expo/json-file": "^9.1.5", "@jest/create-cache-key-function": "^29.2.1", "@jest/globals": "^29.2.1", @@ -7850,7 +7866,9 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" + "dev": true, + "license": "MIT", + "peer": true }, "node_modules/json5": { "version": "2.2.3", @@ -10029,9 +10047,9 @@ } }, "node_modules/react-native-gesture-handler": { - "version": "2.27.1", - "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.27.1.tgz", - "integrity": "sha512-57TUWerhdz589OcDD21e/YlL923Ma4OIpyWsP0hy7gItBCPm5d7qIUW7Yo/cS2wo1qDdOhJaNlvlBD1lDou1fA==", + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.24.0.tgz", + "integrity": "sha512-ZdWyOd1C8axKJHIfYxjJKCcxjWEpUtUWgTOVY2wynbiveSQDm8X/PDyAKXSer/GOtIpjudUbACOndZXCN3vHsw==", "license": "MIT", "dependencies": { "@egjs/hammerjs": "^2.0.17", @@ -10673,7 +10691,9 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", diff --git a/package.json b/package.json index c599d2f..9bb4fa6 100644 --- a/package.json +++ b/package.json @@ -24,10 +24,10 @@ "expo-blur": "~14.1.5", "expo-font": "~13.3.2", "expo-linking": "~7.1.7", - "expo-router": "~5.1.3", + "expo-router": "~5.1.6", "expo-splash-screen": "~0.30.10", "expo-status-bar": "~2.2.3", - "expo-system-ui": "~5.0.10", + "expo-system-ui": "~5.0.11", "expo-web-browser": "~14.2.0", "i": "^0.3.7", "react": "^19.0.0", @@ -36,7 +36,7 @@ "react-native-calendars": "^1.1313.0", "react-native-dotenv": "^3.4.11", "react-native-dropdown-picker": "^5.4.6", - "react-native-gesture-handler": "^2.27.1", + "react-native-gesture-handler": "~2.24.0", "react-native-modalize": "^2.1.1", "react-native-reanimated": "~3.17.4", "react-native-safe-area-context": "5.4.0", @@ -51,7 +51,7 @@ "@types/react": "~19.0.10", "expo": "^53.0.22", "jest": "^29.2.1", - "jest-expo": "~53.0.9", + "jest-expo": "~53.0.10", "react-native-dotenv": "^3.4.11", "react-test-renderer": "19.0.0", "typescript": "~5.8.3" From ac3402496f66096d713ab79ee03eaa2205589a81 Mon Sep 17 00:00:00 2001 From: PJW Date: Sun, 5 Oct 2025 01:29:05 +0900 Subject: [PATCH 7/8] =?UTF-8?q?front-24=20=EC=9D=BC=EC=A0=95=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=B5=9C=3F=EC=A2=85=EB=B3=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/screens/ScheduleAddModal.js | 308 +++++++++++++++++--------------- 1 file changed, 162 insertions(+), 146 deletions(-) diff --git a/app/screens/ScheduleAddModal.js b/app/screens/ScheduleAddModal.js index 90050a5..7a3152e 100644 --- a/app/screens/ScheduleAddModal.js +++ b/app/screens/ScheduleAddModal.js @@ -1,4 +1,4 @@ -import React, { useRef, useState, useMemo, useEffect } from "react"; +import React, { useRef, useState, useEffect, useMemo } from "react"; import { View, Text, @@ -17,7 +17,6 @@ import { pad } from "./ScheduleList"; const SCREEN_HEIGHT = Dimensions.get("window").height; const SCREEN_PAD = 32; -// 중요도 옵션 (API 값: value) const importanceOptions = [ { label: "매우 중요", value: "S", color: "#FFB7B7" }, { label: "보통", value: "I", color: "#FFCB82" }, @@ -29,34 +28,17 @@ export default function ScheduleAddModal({ onClose, selectedDate, onSave, - isEditing, + isEditing = false, + editData = null, saving, }) { const modalAddRef = useRef(null); - React.useEffect(() => { - if (visible) { - setHour(""); - setMinute(""); - setTempHour("00"); - setTempMinute("00"); - modalAddRef.current?.open(); - } else { - modalAddRef.current?.close(); - } - }, [visible]); - - React.useEffect(() => { - // 시간 또는 분이 변경되면 별도 처리 가능 (필요시) - console.log("시간 변경: ", hour, minute); - }, [hour, minute]); - const [title, setTitle] = useState(""); const [hour, setHour] = useState(""); const [minute, setMinute] = useState(""); const [tempHour, setTempHour] = useState("00"); const [tempMinute, setTempMinute] = useState("00"); - // 중요도 초기값 반드시 API value ("S"/"I"/"N") const [priority, setPriority] = useState(""); const [memo, setMemo] = useState(""); const [showTimePicker, setShowTimePicker] = useState(false); @@ -65,103 +47,132 @@ export default function ScheduleAddModal({ const [priorityRequired, setPriorityRequired] = useState(false); const [saveError, setSaveError] = useState(""); - const hourList = useMemo( - () => Array.from({ length: 24 }, (_, i) => pad(i)), - [] - ); - const minuteList = useMemo( - () => Array.from({ length: 60 }, (_, i) => pad(i)), - [] - ); + const hourList = useMemo(() => Array.from({ length: 24 }, (_, i) => pad(i)), []); + const minuteList = useMemo(() => Array.from({ length: 60 }, (_, i) => pad(i)), []); - const openPicker = () => { - setTempHour(hour || "00"); - setTempMinute(minute || "00"); - setShowTimePicker(true); + useEffect(() => { + if (visible) { + if (isEditing && editData) { + setTitle(editData.title || ""); + setHour(editData.hour || ""); + setMinute(editData.minute || ""); + setPriority( + editData.priority === "매우 중요" + ? "S" + : editData.priority === "중요하지 않음" + ? "N" + : "I" + ); + setMemo(editData.memo || ""); + setTempHour(editData.hour || "00"); + setTempMinute(editData.minute || "00"); + } else { + setTitle(""); + setHour(""); + setMinute(""); + setPriority(""); + setMemo(""); + } + setSaveError(""); + modalAddRef.current?.open(); + } else { + modalAddRef.current?.close(); + } + }, [visible, isEditing, editData]); + + const handleModalClosed = () => { + setTempHour("00"); + setTempMinute("00"); + handleCancel(); }; - // API 요구 포맷으로 반환 (예: 2025-09-13 10:00:00) const getApiDateTime = () => { - // selectedDate가 JS Date 객체이어야 함! const dateObj = new Date(selectedDate); const yyyy = dateObj.getFullYear(); const mm = pad(dateObj.getMonth() + 1); const dd = pad(dateObj.getDate()); - const h = hour !== "" ? pad(hour) : "00"; - const m = minute !== "" ? pad(minute) : "00"; return `${yyyy}-${mm}-${dd} ${pad(hour)}:${pad(minute)}:00`; }; const handleSave = () => { - const isTitleValid = title.trim() !== ""; - if (!isTitleValid) { - setTitleRequired(true); - setPriorityRequired(false); - setTimeRequired(false); - return; - } else { - setTitleRequired(false); - } + if (title.trim() === "") { + setTitleRequired(true); + setPriorityRequired(false); + setTimeRequired(false); + return; + } else { + setTitleRequired(false); + } - const isTimeValid = hour !== "" && minute !== ""; - if (!isTimeValid) { - setTimeRequired(true); - return; - } else { - setTimeRequired(false); - } + if (hour === "" || minute === "") { + setTimeRequired(true); + return; + } else { + setTimeRequired(false); + } - const isPriorityValid = priority !== ""; - if (!isPriorityValid) { - setPriorityRequired(true); - setTimeRequired(false); - return; - } else { - setPriorityRequired(false); - } + if (priority === "") { + setPriorityRequired(true); + setTimeRequired(false); + return; + } else { + setPriorityRequired(false); + } - if (title.length > 7) { - return; - } + if (title.length > 7) { + return; + } AsyncStorage.getItem("userId") .then((userId) => { - return axios.get(`${process.env.EXPO_PUBLIC_API_URL}/calendar/add`, { - params: { - userId, - title, - time: getApiDateTime(), - importance: priority, // 반드시 "S" | "I" | "N" - memo, - }, - withCredentials: true - }); + if (!userId) { + throw new Error("사용자 정보가 없습니다."); + } + + const params = { + userId, + title, + time: getApiDateTime(), + importance: priority, + memo, + }; + + if (isEditing && editData?.id) { + return axios.get(`${process.env.EXPO_PUBLIC_API_URL}/calendar/update`, { + params: { ...params, calendar_id: editData.id }, + withCredentials: true, + }); + } else { + return axios.get(`${process.env.EXPO_PUBLIC_API_URL}/calendar/add`, { + params, + withCredentials: true, + }); + } }) .then((res) => { - const calendarId = res.data.calendarId || res.data.id; - if (calendarId) { - AsyncStorage.setItem("calendarId", calendarId.toString()); - } - console.log("[schedule add] Response:", res.data); + const calendarId = isEditing ? editData.id : res.data.calendarId || res.data.id; onSave && onSave({ + id: calendarId, title, hour, minute, priority, memo, - calendarId }); handleCancel(); }) .catch((err) => { - setSaveError( - "일정 저장 실패: " + (err?.response?.data?.message || err.message) - ); - console.error("[schedule add] Error:", err?.response?.data || err); + setSaveError("일정 저장 실패: " + (err?.response?.data?.message || err.message)); }); }; + const openPicker = () => { + setTempHour(hour || "00"); + setTempMinute(minute || "00"); + setShowTimePicker(true); + }; + const handleCancel = () => { setTitle(""); setHour(""); @@ -175,19 +186,15 @@ export default function ScheduleAddModal({ onClose && onClose(); }; - // 한글 라벨 반환 (API 값 → label) - const getPriorityLabel = (val) => - importanceOptions.find((opt) => opt.value === val)?.label ?? ""; - return ( 면접시간 - - setShowTimePicker(true)}> - - {hour || minute ? `${hour || "00"}:${minute || "00"}` : "시간을 선택하세요."} - - {timeRequired && 시간을 선택해 주세요.} - + + + + {hour || minute ? `${hour || "00"}:${minute || "00"}` : "시간을 선택하세요."} + + + {timeRequired && ( + 시간을 선택해 주세요. + )} + 중요도 - - - {importanceOptions.map((opt) => { - const selected = priority === opt.value; - return ( - { - setPriority(opt.value); - setPriorityRequired(false); - }} - > - - {opt.label} - - - ); - })} - - {priorityRequired && ( - 중요도를 선택해 주세요 - )} + + + {importanceOptions.map((opt) => { + const selected = priority === opt.value; + return ( + { + setPriority(opt.value); + setPriorityRequired(false); + }} + > + + {opt.label} + + + ); + })} + + {priorityRequired && ( + 중요도를 선택해 주세요 + )} @@ -287,7 +300,7 @@ export default function ScheduleAddModal({ multiline value={memo} onChangeText={(text) => { - if (text.length <= 49) { // 50자 미만(49자 이하) 제한 + if (text.length <= 49) { setMemo(text); } }} @@ -311,24 +324,27 @@ export default function ScheduleAddModal({ {showTimePicker && ( - + }} + > - { - setShowTimePicker(false); - // wheel picker의 임시값 초기화 - setTempHour("00"); - setTempMinute("00"); - }}> + { + setShowTimePicker(false); + }} + > 취소 - { - setShowTimePicker(false); - setHour(tempHour); - setMinute(tempMinute); - }}> + { + setShowTimePicker(false); + setHour(tempHour); + setMinute(tempMinute); + }} + > 확인 @@ -440,7 +456,7 @@ const styles = StyleSheet.create({ backgroundColor: "#fff", paddingBottom: 20, justifyContent: "center", - height: 270 + height: 270, }, errorMsg: { color: "#FF5A5A", From 259cd394702d74ffed9ea9e9503f96a854f0a455 Mon Sep 17 00:00:00 2001 From: PJW Date: Sun, 5 Oct 2025 03:05:08 +0900 Subject: [PATCH 8/8] =?UTF-8?q?front-24=20=EC=9D=BC=EC=A0=95=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=B5=9C=3F=3F=EC=A2=85=EB=B3=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/screens/ScheduleAddModal.js | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/app/screens/ScheduleAddModal.js b/app/screens/ScheduleAddModal.js index 7a3152e..d7edae6 100644 --- a/app/screens/ScheduleAddModal.js +++ b/app/screens/ScheduleAddModal.js @@ -56,13 +56,15 @@ export default function ScheduleAddModal({ setTitle(editData.title || ""); setHour(editData.hour || ""); setMinute(editData.minute || ""); - setPriority( - editData.priority === "매우 중요" - ? "S" - : editData.priority === "중요하지 않음" - ? "N" - : "I" - ); + if(editData.priority === "S" || editData.priority === "매우 중요") { + setPriority("S"); + } else if(editData.priority === "X" || editData.priority === "중요하지 않음") { + setPriority("N"); + } else if(editData.priority === "I" || editData.priority === "중요") { + setPriority("I"); + } else { + setPriority("I"); // 기본값 + } setMemo(editData.memo || ""); setTempHour(editData.hour || "00"); setTempMinute(editData.minute || "00"); @@ -387,11 +389,16 @@ const styles = StyleSheet.create({ }, inputRow: { flexDirection: "row", - alignItems: "stretch", + alignItems: "center", marginBottom: 10, minHeight: 50, }, - label: { fontWeight: "bold", width: 80, fontSize: 15, marginTop: 4 }, + label: { + fontWeight: "bold", + width: 80, + fontSize: 15, + marginTop: 4 + }, input: { flex: 1, borderWidth: 1, @@ -411,6 +418,7 @@ const styles = StyleSheet.create({ textAlignVertical: "top", }, footerButtons: { + flex:1, flexDirection: "row", justifyContent: "space-between", paddingVertical: 10, @@ -429,7 +437,9 @@ const styles = StyleSheet.create({ borderRadius: 10, }, required: { color: "red", marginLeft: 6, fontSize: 16 }, - priorityBox: { flexDirection: "row", gap: 8 }, + priorityBox: { + flexDirection: "row", + gap: 7 }, priorityBtn: { width: 84, alignItems: "center",