diff --git a/frontend/ongi/android/app/src/main/AndroidManifest.xml b/frontend/ongi/android/app/src/main/AndroidManifest.xml index 59f3017..95fea19 100644 --- a/frontend/ongi/android/app/src/main/AndroidManifest.xml +++ b/frontend/ongi/android/app/src/main/AndroidManifest.xml @@ -52,4 +52,4 @@ - + \ No newline at end of file diff --git a/frontend/ongi/lib/screens/health/health_home_screen.dart b/frontend/ongi/lib/screens/health/health_home_screen.dart index cbca5f9..8072938 100644 --- a/frontend/ongi/lib/screens/health/health_home_screen.dart +++ b/frontend/ongi/lib/screens/health/health_home_screen.dart @@ -317,21 +317,33 @@ class _HealthHomeScreenState extends State { String _convertPainAreaToKorean(String painArea) { final painAreaMap = { - 'head': '머리', - 'neck': '목', - 'shoulder': '어깨', - 'chest': '가슴', - 'back': '등', - 'arm': '팔', - 'hand': '손', - 'abdomen': '복부', - 'waist': '허리', - 'leg': '다리', - 'knee': '무릎', - 'foot': '발', - 'none': '없음', + 'HEAD': '머리', + 'NECK': '목', + 'LEFT_SHOULDER': '왼쪽 어깨', + 'RIGHT_SHOULDER': '오른쪽 어깨', + 'CHEST': '가슴', + 'BACK': '등', + 'LEFT_UPPER_ARM': '왼쪽 윗팔', + 'RIGHT_UPPER_ARM': '오른쪽 윗팔', + 'LEFT_FOREARM': '왼쪽 아랫팔', + 'RIGHT_FOREARM': '오른쪽 아랫팔', + 'LEFT_HAND': '왼쪽 손', + 'RIGHT_HAND': '오른쪽 손', + 'ABDOMEN': '복부', + 'WAIST': '허리', + 'PELVIS': '골반', + 'HIP': '엉덩이', + 'LEFT_THIGH': '왼쪽 허벅지', + 'RIGHT_THIGH': '오른쪽 허벅지', + 'LEFT_CALF': '왼쪽 종아리', + 'RIGHT_CALF': '오른쪽 종아리', + 'LEFT_KNEE': '왼쪽 무릎', + 'RIGHT_KNEE': '오른쪽 무릎', + 'LEFT_FOOT': '왼쪽 발', + 'RIGHT_FOOT': '오른쪽 발', + 'NONE': '없음', }; - return painAreaMap[painArea.toLowerCase()] ?? painArea; + return painAreaMap[painArea] ?? painArea; } Widget _buildPainText() { @@ -362,10 +374,24 @@ class _HealthHomeScreenState extends State { } if (_todayPainRecords.isNotEmpty) { + // 디버깅: 실제 painArea 값들 출력 + for (var record in _todayPainRecords) { + print('home_screen 디버깅 - painArea 원본: ${record['painArea']}'); + print('home_screen 디버깅 - painArea 타입: ${record['painArea'].runtimeType}'); + print('home_screen 디버깅 - 한글 변환: ${_convertPainAreaToKorean(record['painArea'].toString())}'); + } + final koreanAreas = _todayPainRecords - .map( - (record) => _convertPainAreaToKorean(record['painArea'].toString()), - ) + .expand((record) { + final painArea = record['painArea']; + if (painArea is List) { + // painArea가 List인 경우 각 항목을 변환 + return painArea.map((area) => _convertPainAreaToKorean(area.toString())); + } else { + // painArea가 단일 값인 경우 + return [_convertPainAreaToKorean(painArea.toString())]; + } + }) .toSet() .join(', '); diff --git a/frontend/ongi/lib/screens/health/health_status_input_screen.dart b/frontend/ongi/lib/screens/health/health_status_input_screen.dart index 1389944..dd87b56 100644 --- a/frontend/ongi/lib/screens/health/health_status_input_screen.dart +++ b/frontend/ongi/lib/screens/health/health_status_input_screen.dart @@ -29,6 +29,10 @@ class _HealthStatusInputScreenState extends State { DateTime selectedDate = DateTime.now(); bool isFrontView = true; + // 가슴과 등만 별도로 관리하는 상태 + bool _chestSelected = false; + bool _backSelected = false; + // 통증 기록 조회 관련 상태 List> _painRecords = []; bool _isLoadingPainRecords = false; @@ -193,6 +197,9 @@ class _HealthStatusInputScreenState extends State { if (_painRecords.isEmpty) { setState(() { _bodyParts = const BodyParts(); + // 가슴/등 상태 초기화 + _chestSelected = false; + _backSelected = false; // 자녀인 경우 항상 읽기 전용 _isInputMode = !_isChild; }); @@ -204,9 +211,12 @@ class _HealthStatusInputScreenState extends State { _isInputMode = false; }); - // 통증 기록을 BodyParts로 변환 BodyParts newBodyParts = const BodyParts(); + // 가슴/등 상태 초기화 + _chestSelected = false; + _backSelected = false; + for (final record in _painRecords) { final painArea = record['painArea']?.toString().toLowerCase() ?? ''; @@ -217,17 +227,19 @@ class _HealthStatusInputScreenState extends State { case 'neck': newBodyParts = newBodyParts.copyWith(neck: true); break; - case 'shoulder': - newBodyParts = newBodyParts.copyWith( - leftShoulder: true, - rightShoulder: true, - ); + case 'left_shoulder': + newBodyParts = newBodyParts.copyWith(leftShoulder: true); + break; + case 'right_shoulder': + newBodyParts = newBodyParts.copyWith(rightShoulder: true); break; case 'chest': - newBodyParts = newBodyParts.copyWith(upperBody: true); + // 가슴은 별도 상태로 관리 + _chestSelected = true; break; case 'back': - newBodyParts = newBodyParts.copyWith(lowerBody: true); + // 등은 별도 상태로 관리 + _backSelected = true; break; case 'arm': newBodyParts = newBodyParts.copyWith( @@ -277,6 +289,22 @@ class _HealthStatusInputScreenState extends State { setState(() { _bodyParts = newBodyParts; }); + + // 현재 뷰에 따라 가슴/등 표시 + _updateBodyPartsDisplay(); + } + + // 현재 뷰에 따라 가슴/등 표시 업데이트 + void _updateBodyPartsDisplay() { + setState(() { + if (isFrontView) { + // 앞면: 가슴 선택 상태만 반영 + _bodyParts = _bodyParts.copyWith(upperBody: _chestSelected); + } else { + // 뒷면: 등 선택 상태만 반영 + _bodyParts = _bodyParts.copyWith(upperBody: _backSelected); + } + }); } // 날짜 변경 처리 @@ -289,27 +317,54 @@ class _HealthStatusInputScreenState extends State { String _convertPainAreaToKorean(String painArea) { final painAreaMap = { - 'head': '머리', - 'neck': '목', - 'shoulder': '어깨', - 'chest': '가슴', - 'back': '등', - 'arm': '팔', - 'hand': '손', - 'abdomen': '복부', - 'waist': '허리', - 'leg': '다리', - 'knee': '무릎', - 'foot': '발', - 'none': '없음', + 'HEAD': '머리', + 'NECK': '목', + 'LEFT_SHOULDER': '왼쪽 어깨', + 'RIGHT_SHOULDER': '오른쪽 어깨', + 'CHEST': '가슴', + 'BACK': '등', + 'LEFT_UPPER_ARM': '왼쪽 윗팔', + 'RIGHT_UPPER_ARM': '오른쪽 윗팔', + 'LEFT_FOREARM': '왼쪽 아랫팔', + 'RIGHT_FOREARM': '오른쪽 아랫팔', + 'LEFT_HAND': '왼쪽 손', + 'RIGHT_HAND': '오른쪽 손', + 'ABDOMEN': '복부', + 'WAIST': '허리', + 'PELVIS': '골반', + 'HIP': '엉덩이', + 'LEFT_THIGH': '왼쪽 허벅지', + 'RIGHT_THIGH': '오른쪽 허벅지', + 'LEFT_CALF': '왼쪽 종아리', + 'RIGHT_CALF': '오른쪽 종아리', + 'LEFT_KNEE': '왼쪽 무릎', + 'RIGHT_KNEE': '오른쪽 무릎', + 'LEFT_FOOT': '왼쪽 발', + 'RIGHT_FOOT': '오른쪽 발', + 'NONE': '없음', }; - return painAreaMap[painArea.toLowerCase()] ?? painArea; + return painAreaMap[painArea.toUpperCase()] ?? painArea; } Widget _buildTitleText() { if (_painRecords.isNotEmpty) { + for (var record in _painRecords) { + print('디버깅 - painArea 원본: ${record['painArea']}'); + print('디버깅 - painArea 타입: ${record['painArea'].runtimeType}'); + print('디버깅 - 한글 변환: ${_convertPainAreaToKorean(record['painArea'].toString())}'); + } + final koreanAreas = _painRecords - .map((record) => _convertPainAreaToKorean(record['painArea'].toString())) + .expand((record) { + final painArea = record['painArea']; + if (painArea is List) { + // painArea가 List인 경우 각 항목을 변환 + return painArea.map((area) => _convertPainAreaToKorean(area.toString())); + } else { + // painArea가 단일 값인 경우 + return [_convertPainAreaToKorean(painArea.toString())]; + } + }) .where((area) => area != '없음') .toSet() .join(', '); @@ -400,14 +455,34 @@ class _HealthStatusInputScreenState extends State { _isInputMode = true; }); } + + // 가슴/등 선택 상태 업데이트 (앞/뒤 구분) + final partsString = parts.toString(); + final upperBodySelected = partsString.contains('upperBody: true'); + + if (isFrontView) { + // 앞면에서는 가슴만 선택/해제 + _chestSelected = upperBodySelected; + // 뒷면 선택은 유지 + } else { + // 뒷면에서는 등만 선택/해제 + _backSelected = upperBodySelected; + // 앞면 선택은 유지 + } + + // upperBody 제외하고 다른 부위들만 업데이트 + BodyParts updatedParts = parts.copyWith(upperBody: false); + setState(() { - _bodyParts = parts; + _bodyParts = updatedParts; + // 현재 뷰에 맞는 가슴/등 표시 업데이트 + _updateBodyPartsDisplay(); }); } // 선택된 부위가 있는지 확인 bool get hasSelectedParts { - return _bodyParts.toString().contains('true'); + return _bodyParts.toString().contains('true') || _chestSelected || _backSelected; } // 앞/뒤 전환 메서드 @@ -415,48 +490,79 @@ class _HealthStatusInputScreenState extends State { setState(() { isFrontView = !isFrontView; }); + // 뷰 전환 시 가슴/등 표시 업데이트 + _updateBodyPartsDisplay(); } List _getSelectedPainAreas() { List painAreas = []; - // BodyParts를 API enum으로 매핑 final bodyPartsString = _bodyParts.toString(); + + if (bodyPartsString.contains('head: true')) + painAreas.add(PainArea.head); + if (bodyPartsString.contains('neck: true')) + painAreas.add(PainArea.neck); + + // 어깨 + if (bodyPartsString.contains('leftShoulder: true')) + painAreas.add(PainArea.rightShoulder); + if (bodyPartsString.contains('rightShoulder: true')) + painAreas.add(PainArea.leftShoulder); - if (bodyPartsString.contains('head: true')) painAreas.add(PainArea.head); - if (bodyPartsString.contains('neck: true')) painAreas.add(PainArea.neck); - if (bodyPartsString.contains('leftShoulder: true') || - bodyPartsString.contains('rightShoulder: true')) - painAreas.add(PainArea.shoulder); - if (bodyPartsString.contains('upperBody: true')) + // 가슴과 등을 별도 상태로 확인 + if (_chestSelected) painAreas.add(PainArea.chest); - if (bodyPartsString.contains('lowerBody: true')) + if (_backSelected) painAreas.add(PainArea.back); - if (bodyPartsString.contains('leftUpperArm: true') || - bodyPartsString.contains('rightUpperArm: true') || - bodyPartsString.contains('leftElbow: true') || - bodyPartsString.contains('rightElbow: true') || - bodyPartsString.contains('leftLowerArm: true') || - bodyPartsString.contains('rightLowerArm: true')) - painAreas.add(PainArea.arm); - if (bodyPartsString.contains('leftHand: true') || - bodyPartsString.contains('rightHand: true')) - painAreas.add(PainArea.hand); + + // 팔 + if (bodyPartsString.contains('leftUpperArm: true')) + painAreas.add(PainArea.rightUpperArm); + if (bodyPartsString.contains('rightUpperArm: true')) + painAreas.add(PainArea.leftUpperArm); + if (bodyPartsString.contains('leftLowerArm: true')) + painAreas.add(PainArea.rightForearm); + if (bodyPartsString.contains('rightLowerArm: true')) + painAreas.add(PainArea.leftForearm); + + // 손 + if (bodyPartsString.contains('leftHand: true')) + painAreas.add(PainArea.rightHand); + if (bodyPartsString.contains('rightHand: true')) + painAreas.add(PainArea.leftHand); + if (bodyPartsString.contains('abdomen: true')) painAreas.add(PainArea.abdomen); - if (bodyPartsString.contains('vestibular: true')) - painAreas.add(PainArea.waist); // vestibular를 waist로 매핑 - if (bodyPartsString.contains('leftUpperLeg: true') || - bodyPartsString.contains('rightUpperLeg: true') || - bodyPartsString.contains('leftLowerLeg: true') || - bodyPartsString.contains('rightLowerLeg: true')) - painAreas.add(PainArea.leg); - if (bodyPartsString.contains('leftKnee: true') || - bodyPartsString.contains('rightKnee: true')) - painAreas.add(PainArea.knee); - if (bodyPartsString.contains('leftFoot: true') || - bodyPartsString.contains('rightFoot: true')) - painAreas.add(PainArea.foot); + + // 허리/골반/엉덩이 (vestibular 속성) + if (bodyPartsString.contains('vestibular: true')) { + painAreas.add(PainArea.waist); + painAreas.add(PainArea.pelvis); + painAreas.add(PainArea.hip); + } + + // 다리 + if (bodyPartsString.contains('leftUpperLeg: true')) + painAreas.add(PainArea.rightThigh); + if (bodyPartsString.contains('rightUpperLeg: true')) + painAreas.add(PainArea.leftThigh); + if (bodyPartsString.contains('leftLowerLeg: true')) + painAreas.add(PainArea.rightCalf); + if (bodyPartsString.contains('rightLowerLeg: true')) + painAreas.add(PainArea.leftCalf); + + // 무릎 + if (bodyPartsString.contains('leftKnee: true')) + painAreas.add(PainArea.rightKnee); + if (bodyPartsString.contains('rightKnee: true')) + painAreas.add(PainArea.leftKnee); + + // 발 + if (bodyPartsString.contains('leftFoot: true')) + painAreas.add(PainArea.rightFoot); + if (bodyPartsString.contains('rightFoot: true')) + painAreas.add(PainArea.leftFoot); // 중복 제거 return painAreas.toSet().toList(); @@ -469,26 +575,50 @@ class _HealthStatusInputScreenState extends State { return '머리'; case PainArea.neck: return '목'; - case PainArea.shoulder: - return '어깨'; + case PainArea.leftShoulder: + return '왼쪽 어깨'; + case PainArea.rightShoulder: + return '오른쪽 어깨'; case PainArea.chest: return '가슴'; case PainArea.back: return '등'; - case PainArea.arm: - return '팔'; - case PainArea.hand: - return '손'; + case PainArea.leftUpperArm: + return '왼쪽 윗팔'; + case PainArea.rightUpperArm: + return '오른쪽 윗팔'; + case PainArea.leftForearm: + return '왼쪽 아랫팔'; + case PainArea.rightForearm: + return '오른쪽 아랫팔'; + case PainArea.leftHand: + return '왼쪽 손'; + case PainArea.rightHand: + return '오른쪽 손'; case PainArea.abdomen: return '복부'; case PainArea.waist: return '허리'; - case PainArea.leg: - return '다리'; - case PainArea.knee: - return '무릎'; - case PainArea.foot: - return '발'; + case PainArea.pelvis: + return '골반'; + case PainArea.hip: + return '엉덩이'; + case PainArea.leftThigh: + return '왼쪽 허벅지'; + case PainArea.rightThigh: + return '오른쪽 허벅지'; + case PainArea.leftCalf: + return '왼쪽 종아리'; + case PainArea.rightCalf: + return '오른쪽 종아리'; + case PainArea.leftKnee: + return '왼쪽 무릎'; + case PainArea.rightKnee: + return '오른쪽 무릎'; + case PainArea.leftFoot: + return '왼쪽 발'; + case PainArea.rightFoot: + return '오른쪽 발'; case PainArea.none: return '없음'; } @@ -715,17 +845,19 @@ class _HealthStatusInputScreenState extends State { try { final dateString = DateFormat('yyyy-MM-dd').format(selectedDate); - // 여러 부위가 선택된 경우 각각 개별 API 호출 - for (final painArea in painAreas) { - final result = await PainService.addPainRecord( - date: dateString, - painArea: painArea.value, - painLevel: PainLevel.midWeak.value, - ); + // 선택된 모든 부위를 한 번에 API 호출 (List로 전송) + final painAreaValues = painAreas.map((area) => area.value).toList(); + print('통증 기록 저장 시도: 날짜=$dateString, 부위들=$painAreaValues'); + + final result = await PainService.addPainRecord( + date: dateString, + painAreas: painAreaValues, // List로 전송, painLevel 제거 + ); - if (result == null) { - throw Exception('통증 기록 저장에 실패했습니다.'); - } + print('통증 기록 저장 결과: $result'); + + if (result == null) { + throw Exception('통증 기록 저장에 실패했습니다. 부위들: $painAreaValues'); } // 로딩 다이얼로그 닫기 @@ -736,7 +868,7 @@ class _HealthStatusInputScreenState extends State { SnackBar( content: Text( '통증 기록이 저장되었습니다. (${painAreas.length}개 부위)', - style: TextStyle(color: AppColors.ongiOrange), + style: const TextStyle(color: AppColors.ongiOrange), ), backgroundColor: Colors.white, behavior: SnackBarBehavior.floating, @@ -750,14 +882,24 @@ class _HealthStatusInputScreenState extends State { // 기록 다시 로드 await _loadPainRecords(); } catch (e) { + print('통증 기록 저장 중 오류 발생: $e'); + // 로딩 다이얼로그 닫기 Navigator.of(context).pop(); // 에러 메시지 ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('오류가 발생했습니다: ${e.toString()}'), + content: Text( + '통증 기록 저장 실패: ${e.toString()}', + style: const TextStyle(color: Colors.white), + ), backgroundColor: Colors.red, + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + duration: const Duration(seconds: 5), ), ); } diff --git a/frontend/ongi/lib/services/health_service.dart b/frontend/ongi/lib/services/health_service.dart index 126e594..7f9119a 100644 --- a/frontend/ongi/lib/services/health_service.dart +++ b/frontend/ongi/lib/services/health_service.dart @@ -32,4 +32,4 @@ class HealthService { throw Exception('통증 기록을 불러오는 중 오류가 발생했습니다: $e'); } } -} +} \ No newline at end of file diff --git a/frontend/ongi/lib/services/pain_service.dart b/frontend/ongi/lib/services/pain_service.dart index 817cec1..2f8ee39 100644 --- a/frontend/ongi/lib/services/pain_service.dart +++ b/frontend/ongi/lib/services/pain_service.dart @@ -9,8 +9,7 @@ class PainService { // 통증 기록 추가 static Future?> addPainRecord({ required String date, - required String painArea, - required String painLevel, + required List painAreas, // List로 변경 }) async { try { final token = await PrefsManager.getAccessToken(); @@ -27,49 +26,51 @@ class PainService { }, body: jsonEncode({ 'date': date, - 'painArea': painArea, - 'painLevel': painLevel, + 'painArea': painAreas, // painLevel 제거, painArea는 List }), ); if (response.statusCode == 200 || response.statusCode == 201) { return jsonDecode(response.body); } else { - throw Exception('Failed to add pain record: ${response.statusCode}'); + print('통증 기록 API 오류: ${response.statusCode}, 응답: ${response.body}'); + throw Exception('Failed to add pain record: ${response.statusCode}, ${response.body}'); } } catch (e) { + print('통증 기록 서비스 오류: $e'); return null; } } } -// 통증 부위 enum 매핑 +// 통증 부위 enum 매핑 (백엔드와 일치) enum PainArea { head('HEAD'), neck('NECK'), - shoulder('SHOULDER'), + leftShoulder('LEFT_SHOULDER'), + rightShoulder('RIGHT_SHOULDER'), chest('CHEST'), back('BACK'), - arm('ARM'), - hand('HAND'), + leftUpperArm('LEFT_UPPER_ARM'), + rightUpperArm('RIGHT_UPPER_ARM'), + leftForearm('LEFT_FOREARM'), + rightForearm('RIGHT_FOREARM'), + leftHand('LEFT_HAND'), + rightHand('RIGHT_HAND'), abdomen('ABDOMEN'), waist('WAIST'), - leg('LEG'), - knee('KNEE'), - foot('FOOT'), + pelvis('PELVIS'), + hip('HIP'), + leftThigh('LEFT_THIGH'), + rightThigh('RIGHT_THIGH'), + leftCalf('LEFT_CALF'), + rightCalf('RIGHT_CALF'), + leftKnee('LEFT_KNEE'), + rightKnee('RIGHT_KNEE'), + leftFoot('LEFT_FOOT'), + rightFoot('RIGHT_FOOT'), none('NONE'); const PainArea(this.value); final String value; } - -// 통증 강도 enum -enum PainLevel { - strong('STRONG'), - midStrong('MID_STRONG'), - midWeak('MID_WEAK'), - weak('WEAK'); - - const PainLevel(this.value); - final String value; -}