Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions App.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ import { ThemeProvider, useTheme } from './src/context/ThemeContext';
const Stack = createStackNavigator();
const Tab = createBottomTabNavigator();

/**
* 하단 5대 메인 탭 네비게이터 (홈, 주간시간표, 달력일지, 학생, 설정)
* @param {object} props - 컴포넌트 속성
* @param {object} props.navigation - React Navigation 네비게이션 객체
* @returns {React.JSX.Element} 메인 하단 탭 네비게이터 요소
*/
function MainTabs({ navigation }) {
const { theme } = useTheme();

Expand All @@ -45,6 +51,7 @@ function MainTabs({ navigation }) {
},
tabBarActiveTintColor: theme.colors.primary || '#007AFF',
tabBarInactiveTintColor: 'gray',
tabBarHideOnKeyboard: true,
headerStyle: {
backgroundColor: theme.colors.surface || '#FFFFFF',
elevation: 0,
Expand Down
28 changes: 28 additions & 0 deletions __tests__/Components.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import ConfirmModal from '../src/components/ConfirmModal';
import ActionMenuItem from '../src/components/ActionMenuItem';
import StatusBadge from '../src/components/StatusBadge';
import FloatingActionButton from '../src/components/FloatingActionButton';
import TimePickerModal from '../src/components/modals/TimePickerModal';

describe('공통 컴포넌트 단위 테스트', () => {
test('Separator 컴포넌트가 오류 없이 렌더링되어야 함', () => {
Expand Down Expand Up @@ -96,5 +97,32 @@ describe('공통 컴포넌트 단위 테스트', () => {
expect(extended).toBeDefined();
expect(extended.toJSON()).toBeTruthy();
});

test('TimePickerModal 컴포넌트가 visible=false 및 true일 때 정상 렌더링되어야 함', () => {
let hiddenModal;
let visibleModal;
act(() => {
hiddenModal = renderer.create(
<TimePickerModal
visible={false}
time="10:00"
onSelectTime={jest.fn()}
onClose={jest.fn()}
/>
);
visibleModal = renderer.create(
<TimePickerModal
visible={true}
time="14:30"
title="정규 수업 시작 시간 선택"
onSelectTime={jest.fn()}
onClose={jest.fn()}
/>
);
});
expect(hiddenModal).toBeDefined();
expect(visibleModal).toBeDefined();
expect(visibleModal.toJSON()).toBeTruthy();
});
});

29 changes: 29 additions & 0 deletions log/devlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,35 @@

---

## 2026-11-01: 전체 화면/모달 키보드 가림 방지 및 날짜·시간 피커 / 주간표 다중 학생 터치 UX 개선 (by Antigravity)

* **반영 내역 (What landed)**:
* **공통 시간 선택 모달([src/components/modals/TimePickerModal.js](../src/components/modals/TimePickerModal.js)) 분리 신설**:
* 주간 시간표에 내장되어 있던 시작 시간 GUI 선택 팝업(빠른 프리셋, 오전/오후 탭, 시/분 그리드)을 재사용 가능한 공통 모달로 모듈화하여 [WeeklyPlanScreen.js](file:///d:/development/mydiary/src/screens/WeeklyPlanScreen.js) 라인 수를 3,089줄에서 2,760줄로 329줄 감축.
* **1번 과제 — 키보드 가림 현상 5곳 및 하단 탭바 자동 숨김 완비**:
* [App.js](file:///d:/development/mydiary/App.js): 하단 탭 네비게이터에 `tabBarHideOnKeyboard: true`를 적용하여 키보드 노출 시 하단 탭바가 입력 영역을 침범하지 않도록 개선.
* [src/screens/WeeklyPlanScreen.js](file:///d:/development/mydiary/src/screens/WeeklyPlanScreen.js): 하단 부가 업무(금주 주요사항, 전주 결석, 특이사항) 멀티라인 입력창 및 수업 추가/편집 모달의 하단 입력창(주소, 연락처, 변동 메모) 포커스 시 키보드 높이 연동 패딩 및 자동 스크롤(`scrollToEnd`) 적용.
* [src/screens/StudentDetailScreen.js](file:///d:/development/mydiary/src/screens/StudentDetailScreen.js): 키보드 노출 시 하단 고정 저장 바(`bottomFixedBar`)를 자동 숨김 처리하여 가용 화면을 확보하고, 정규 수업 과목 및 기타 사항(`notesInput`) 포커스 시 자동 스크롤 연동.
* [src/screens/CalendarScreen.js](file:///d:/development/mydiary/src/screens/CalendarScreen.js): 학생 선택 검색 모달에서 키보드 노출 시 모달을 화면 상단으로 배치하고 키보드 위쪽까지 가용 높이를 동적 계산하도록 개선.
* [src/components/modals/CallManageModal.js](file:///d:/development/mydiary/src/components/modals/CallManageModal.js) & [src/components/modals/StudentStatusModal.js](file:///d:/development/mydiary/src/components/modals/StudentStatusModal.js): 중앙 정렬 다이얼로그가 안드로이드 키보드에 가려지지 않도록 키보드 노출 시 상단 배치 전환.
* **2번 과제 — 날짜/시간 GUI 피커 일원화 및 주간 전체 표 다중 학생 터치 개선**:
* [src/screens/StudentDetailScreen.js](file:///d:/development/mydiary/src/screens/StudentDetailScreen.js): 수동 타이핑하던 '최초 수강 시작일(입회일)'에 인라인 달력(`Calendar`) 피커를 적용하고, '정규 수업 일정 시작 시간'에 공통 [`TimePickerModal`](../src/components/modals/TimePickerModal.js)을 연동하여 오타 원천 방지.
* [src/components/modals/StudentStatusModal.js](file:///d:/development/mydiary/src/components/modals/StudentStatusModal.js): 휴회 종료일 및 재수강 시작일 입력란을 인라인 달력(`Calendar`) 피커로 전환.
* [src/screens/WeeklyPlanScreen.js](file:///d:/development/mydiary/src/screens/WeeklyPlanScreen.js): 주간 전체 표(Table 뷰)의 동일 시간대 셀에 2명 이상 학생이 배정된 경우 각 학생 카드(`cellItemBox`)를 개별 터치하여 해당 학생의 일정을 바로 편집할 수 있도록 분리하고, 셀 빈 영역 터치 시 새 수업 추가 모달이 열리도록 개선.
* **단위 테스트 추가**:
* [__tests__/Components.test.js](file:///d:/development/mydiary/__tests__/Components.test.js): [`TimePickerModal`](../src/components/modals/TimePickerModal.js) 렌더링 검증 테스트 추가 (총 31개 테스트).

* **검증 수치 (Numbers)**:
* 올인원 품질 게이트 (`npm run gate`): 100% 통과
* ESLint 무결성: 에러 0건, 경고 0건
* Jest 단위 테스트: 6개 스위트, 31개 테스트 전체 통과 (100% PASS)
* 정기 프로젝트 건전성 정밀 감사: 96점 / 100점 달성

* **미해결 과제 (Still open)**:
* [docs/gaps.md](file:///d:/development/mydiary/docs/gaps.md)의 `[G1]` HWPX 복수 표 서식 레이아웃 프리셋(표준형, 서술형, 결재란 포함형) 다변화 작업 대기 중.

---

## 2026-10-27: Antigravity 결정론적 라이프사이클 훅(hooks.json) 구축 및 토큰 소모 최적화 (by Antigravity)

* **반영 내역 (What landed)**:
Expand Down
35 changes: 31 additions & 4 deletions src/components/modals/CallManageModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,16 @@ import { useTheme } from '../../context/ThemeContext';
import { theme as defaultTheme } from '../../theme';

/**
* 주간 시간표 전화관리 상담 항목 추가 모달
* 주간 시간표 전화관리 상담 항목 추가 모달 컴포넌트
* @param {object} props - 모달 컴포넌트 속성
* @param {boolean} props.visible - 모달 표시 여부
* @param {() => void} props.onClose - 모달 닫기 콜백 함수
* @param {string} props.callName - 상담 대상 회원 이름 입력값
* @param {(text: string) => void} props.onChangeCallName - 회원 이름 변경 핸들러
* @param {string} props.callContent - 통화 요일 및 상담 내용 입력값
* @param {(text: string) => void} props.onChangeCallContent - 상담 내용 변경 핸들러
* @param {() => void} props.onAdd - 상담 항목 추가 확정 콜백 함수
* @returns {React.JSX.Element} 전화관리 상담 추가 모달 요소
*/
export default function CallManageModal({
visible,
Expand All @@ -29,15 +38,24 @@ export default function CallManageModal({
const { theme: activeTheme } = useTheme();
const theme = activeTheme || defaultTheme;
const [isKeyboardVisible, setIsKeyboardVisible] = useState(false);
const [keyboardHeight, setKeyboardHeight] = useState(0);

useEffect(() => {
const showSub = Keyboard.addListener(
Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow',
() => setIsKeyboardVisible(true)
(e) => {
setIsKeyboardVisible(true);
if (e?.endCoordinates?.height) {
setKeyboardHeight(e.endCoordinates.height);
}
}
);
const hideSub = Keyboard.addListener(
Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide',
() => setIsKeyboardVisible(false)
() => {
setIsKeyboardVisible(false);
setKeyboardHeight(0);
}
);
return () => {
showSub.remove();
Expand All @@ -51,13 +69,18 @@ export default function CallManageModal({
transparent={true}
visible={visible}
onRequestClose={onClose}
statusBarTranslucent
>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
style={styles.keyboardAvoid}
>
<TouchableOpacity
style={styles.modalOverlay}
style={[
styles.modalOverlay,
isKeyboardVisible && styles.modalOverlayKeyboard,
isKeyboardVisible && Platform.OS === 'android' && { paddingBottom: Math.max(keyboardHeight * 0.5, 80) },
]}
activeOpacity={1}
onPress={() => {
if (isKeyboardVisible) {
Expand Down Expand Up @@ -134,6 +157,10 @@ const styles = StyleSheet.create({
justifyContent: 'center',
paddingHorizontal: 24,
},
modalOverlayKeyboard: {
justifyContent: 'flex-start',
paddingTop: Platform.OS === 'android' ? 48 : 64,
},
callModalContent: {
borderRadius: 18,
padding: 20,
Expand Down
Loading
Loading