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
82 changes: 82 additions & 0 deletions __tests__/Components.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ 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';
import RecordPrintModal from '../src/components/modals/RecordPrintModal';
import ScheduleEditModal from '../src/components/modals/ScheduleEditModal';

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

test('RecordPrintModal 컴포넌트가 visible=false 및 true일 때 정상 렌더링되어야 함', () => {
let hiddenModal;
let studentModal;
let recordsModal;
const mockStudent = { id: 's1', name: '홍길동', school_grade: '중2' };
const mockRecords = [{ id: 'r1', class_date: '2026-04-10', student_id: 's1' }];
act(() => {
hiddenModal = renderer.create(
<RecordPrintModal
visible={false}
onClose={jest.fn()}
printType="records"
student={mockStudent}
records={mockRecords}
/>
);
studentModal = renderer.create(
<RecordPrintModal
visible={true}
onClose={jest.fn()}
printType="student"
student={mockStudent}
onPrintStudent={jest.fn()}
onShareStudent={jest.fn()}
/>
);
recordsModal = renderer.create(
<RecordPrintModal
visible={true}
onClose={jest.fn()}
printType="records"
student={mockStudent}
records={mockRecords}
onPrintRecords={jest.fn()}
onShareRecords={jest.fn()}
/>
);
});
expect(hiddenModal).toBeDefined();
expect(studentModal).toBeDefined();
expect(studentModal.toJSON()).toBeTruthy();
expect(recordsModal).toBeDefined();
expect(recordsModal.toJSON()).toBeTruthy();
});

test('ScheduleEditModal 컴포넌트가 visible=false 및 true일 때 정상 렌더링되어야 함', () => {
let hiddenModal;
let visibleModal;
const mockStudents = [
{ id: 's1', name: '홍길동', school_grade: '고1', mobile_phone: '010-1234-5678' },
];
act(() => {
hiddenModal = renderer.create(
<ScheduleEditModal
visible={false}
onClose={jest.fn()}
students={mockStudents}
/>
);
visibleModal = renderer.create(
<ScheduleEditModal
visible={true}
onClose={jest.fn()}
selectedItem={{ id: 'item1', studentName: '홍길동', dayOfWeek: 1, startTime: '14:00' }}
students={mockStudents}
formStudentName="홍길동"
formDayOfWeek={1}
formStartTime="14:00"
formSubject="파이썬"
onSave={jest.fn()}
onDelete={jest.fn()}
onGoToClassRecord={jest.fn()}
/>
);
});
expect(hiddenModal).toBeDefined();
expect(visibleModal).toBeDefined();
expect(visibleModal.toJSON()).toBeTruthy();
});
});

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

---

## 2026-11-13: 전체 UI/UX 5단계 순차 개선 (동적 테마 반응성, 시간 피커 통합, 주간시간표·학생상세 UX, 공통 모달 2종 분리) (by Antigravity)

* **반영 내역 (What landed)**:
* **Phase 1 (동적 테마 전환 반응성 복구)**:
* 8개 화면([HomeScreen.js](../src/screens/HomeScreen.js), [WeeklyPlanScreen.js](../src/screens/WeeklyPlanScreen.js), [CalendarScreen.js](../src/screens/CalendarScreen.js), [StudentListScreen.js](../src/screens/StudentListScreen.js), [SettingsScreen.js](../src/screens/SettingsScreen.js), [StudentDetailScreen.js](../src/screens/StudentDetailScreen.js), [ClassRecordScreen.js](../src/screens/ClassRecordScreen.js), [HelpScreen.js](../src/screens/HelpScreen.js)) 및 공용 컴포넌트([Separator.js](../src/components/Separator.js), [ConfirmModal.js](../src/components/ConfirmModal.js))의 정적 `StyleSheet.create`를 `useMemo(() => createStyles(theme), [theme])` 구조로 전환하여 설정 화면 프리셋 전환 시 즉시 반영되도록 개선.
* **Phase 2 (시간 피커 통합 및 오전 12시 버그 수정)**:
* [ClassRecordScreen.js](../src/screens/ClassRecordScreen.js)의 중복 인라인 시간 피커(~180줄)를 제거하고 공용 [TimePickerModal.js](../src/components/modals/TimePickerModal.js)로 통합.
* **Phase 3 (주간시간표 모바일 UX 강화)**:
* [WeeklyPlanScreen.js](../src/screens/WeeklyPlanScreen.js)의 타임라인 `selectedDayOffset` 초기값을 오늘 요일(월=0~일=6)로 자동 포커스하고, 표 헤더 및 타임라인 요일 탭에 오늘 날짜 강조(`dayHeaderCellToday`, `dayTabPillToday`) 적용.
* 주간 표 열 너비(`68 -> 76`, `tableInner.minWidth: 500`) 및 셀 폰트(`cellItemTitle: 10.5`, `cellItemSubject: 9.5`, `cellItemNote: 9`) 확대.
* `handleMakeCall`이 다중 연락처 매칭 시 `Alert.alert('통화할 번호 선택', ...)` 선택창을 표시하도록 개선.
* **Phase 4 (학생 상세 입력 안정성 및 오프라인 배너)**:
* [StudentDetailScreen.js](../src/screens/StudentDetailScreen.js)에 `beforeRemove` + `ConfirmModal` 기반 미저장 변경사항 이탈 방지 가드 추가(저장/삭제 완료 및 휴회/재수강 즉시 저장 시 정상 이탈 허용).
* 외부 URL `ImageBackground` 배너를 오프라인 테마 반응형 배너 카드(`styles.bannerCard`)로 교체하고, 신규 등록 모드에서도 초기 수강 상태(`active`/`paused`) 전환 지원.
* **Phase 5 (공통 모달 분리 및 단위 테스트)**:
* [RecordPrintModal.js](../src/components/modals/RecordPrintModal.js) 신규 분리 후 [StudentListScreen.js](../src/screens/StudentListScreen.js), [ClassRecordScreen.js](../src/screens/ClassRecordScreen.js), [StudentDetailScreen.js](../src/screens/StudentDetailScreen.js) 연동.
* [ScheduleEditModal.js](../src/components/modals/ScheduleEditModal.js) 신규 분리(이름·학교·전화번호 확장 검색 포함) 후 [WeeklyPlanScreen.js](../src/screens/WeeklyPlanScreen.js) 경량화(2,765줄 -> 2,097줄).
* [__tests__/Components.test.js](../__tests__/Components.test.js)에 `RecordPrintModal`, `ScheduleEditModal` 렌더링 단위 테스트 2건 추가.

* **검증 수치 (Numbers)**:
* 올인원 품질 게이트 (`cmd /c npm run gate`): 100% 통과
* ESLint 무결성: 에러 0건, 경고 0건
* Jest 단위 테스트: 6개 스위트, 33개 테스트 전체 통과 (100% PASS)
* 정기 프로젝트 건전성 정밀 감사: **100점 / 100점** 달성 (초거대 위험 파일 0건, 공통 컴포넌트 11개)

* **미해결 과제 (Still open)**:
* [docs/gaps.md](file:///d:/development/mydiary/docs/gaps.md)의 `[G1]` HWPX 복수 표 서식 레이아웃 프리셋(표준형, 서술형, 결재란 포함형) 다변화 작업 대기 중.
* [StudentDetailScreen.js](../src/screens/StudentDetailScreen.js)(1,587줄) 및 [WeeklyPlanScreen.js](../src/screens/WeeklyPlanScreen.js)(2,097줄) 추가 세부 섹션 분리 선택 사항.

---

## 2026-11-02: v2.5.0 공식 마이너 릴리즈 마일스톤 버전 5개 파일 동기화 및 릴리즈 태깅 (by Antigravity)

* **반영 내역 (What landed)**:
Expand Down
11 changes: 8 additions & 3 deletions src/components/ConfirmModal.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';
import {
Modal,
View,
Expand All @@ -8,7 +8,8 @@ import {
ActivityIndicator,
} from 'react-native';
import { Feather } from '@expo/vector-icons';
import { theme } from '../theme';
import { useTheme } from '../context/ThemeContext';
import { theme as defaultTheme } from '../theme';

/**
* 앱 전반에서 일관된 UX를 제공하는 공통 확인/삭제 모달 컴포넌트
Expand All @@ -25,6 +26,10 @@ const ConfirmModal = ({
loading = false,
iconName = isDestructive ? 'alert-triangle' : 'help-circle',
}) => {
const { theme: activeTheme } = useTheme();
const theme = activeTheme || defaultTheme;
const styles = useMemo(() => createStyles(theme), [theme]);

return (
<Modal
transparent
Expand Down Expand Up @@ -91,7 +96,7 @@ const ConfirmModal = ({
);
};

const styles = StyleSheet.create({
const createStyles = (theme) => StyleSheet.create({
overlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
Expand Down
17 changes: 14 additions & 3 deletions src/components/Separator.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { theme } from '../theme';
import { useTheme } from '../context/ThemeContext';
import { theme as defaultTheme } from '../theme';

/**
* 리스트 아이템 사이 또는 섹션 구분을 위한 공통 구분선 컴포넌트
*/
const Separator = React.memo(({ style }) => {
return <View style={[styles.separator, style]} />;
const { theme: activeTheme } = useTheme();
const theme = activeTheme || defaultTheme;

return (
<View
style={[
styles.separator,
{ backgroundColor: theme.colors.outline || '#E2E8F0' },
style,
]}
/>
);
});

Separator.displayName = 'Separator';

const styles = StyleSheet.create({
separator: {
height: 1,
backgroundColor: theme.colors.outline || '#E2E8F0',
},
});

Expand Down
Loading
Loading