Skip to content

Conversation

@ProtossManse
Copy link
Collaborator

@ProtossManse ProtossManse commented Oct 3, 2025

🔗 관련 이슈

📙 작업 설명

  • POST elders/bulk API 연동
  • POST elders/health-info/bulk 연동

Summary by CodeRabbit

  • New Features
    • 다수 어르신을 한 번에 등록하고 건강정보를 일괄 제출하는 기능이 추가되었습니다.
    • 어르신 선택을 칩 형태로 빠르게 전환·삭제할 수 있는 UI가 도입되었습니다.
  • Refactor
    • 입력 폼이 단일화되어 첫 입력 필드 포커스와 스크롤 동작이 개선되었습니다.
    • 질병·복약·메모 입력 흐름을 외부 상태로 제어하도록 재구성해 예측성이 향상되었습니다.
    • 뷰모델 및 UI 상태 구조가 재정비되어 다중 어르신 흐름이 더 명확해졌습니다.
  • Chores
    • 대량 등록/제출용 백엔드 연동 및 직렬화된 데이터 포맷이 추가되었습니다.

@coderabbitai
Copy link

coderabbitai bot commented Oct 3, 2025

Walkthrough

일괄 등록(elders/bulk, elders/health-info/bulk) API 연동을 위해 DTO·Service·Repository를 추가·분리했고, 로그인(어르신 정보/건강 정보) UI를 StateFlow 기반 ViewModel과 불변 상태+콜백 컴포넌트로 재구성했습니다. 일부 import 경로 및 기본값·nullability가 조정되었습니다.

Changes

Cohort / File(s) Summary of Changes
API 서비스 및 DTO 추가
app/src/main/java/com/konkuk/medicarecall/data/api/elders/ElderRegisterService.kt, app/src/main/java/com/konkuk/medicarecall/data/dto/request/ElderBulkRegisterRequestDto.kt, app/src/main/java/com/konkuk/medicarecall/data/dto/request/ElderBulkHealthInfoRequestDto.kt, app/src/main/java/com/konkuk/medicarecall/data/dto/response/ElderBulkRegisterResponseDto.kt
일괄 등록/건강정보 일괄 등록 DTO 추가 및 직렬화 매핑, postElderBulk/postElderHealthInfoBulk 엔드포인트 추가.
Repository 인터페이스/구현 개편
app/src/main/java/com/konkuk/medicarecall/data/repository/ElderRegisterRepository.kt, app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/ElderRegisterRepositoryImpl.kt
기존 복합 메서드 제거 후 postElderBulkpostElderHealthInfoBulk로 분리; DTO 매핑 및 Bulk 응답/에러 처리(예: HttpException) 로직 추가.
ViewModel/UiState 신규 구성
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/viewmodel/LoginElderViewModel.kt, .../LoginElderUiState.kt, .../LoginElderHealthUiState.kt
StateFlow 기반 LoginElderViewModel 추가(elders/health 리스트, 선택 인덱스, 다수의 업데이트/추가/제거 메서드) 및 bulk API 호출 메서드 구현.
구 ViewModel 제거 및 import 경로 수정
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/LoginElderViewModel.kt (삭제), app/src/main/java/com/konkuk/medicarecall/MainActivity.kt, app/src/main/java/com/konkuk/medicarecall/navigation/NavGraph.kt
기존 ViewModel 파일 삭제 및 참조를 ...senior.viewmodel... 패키지로 변경.
로그인 화면 리팩터링(어르신 정보/건강 정보)
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/screen/LoginElderInfoScreen.kt, .../LoginElderMedInfoScreen.kt
화면을 UiState 구독·콜백 방식으로 재배선(LazyRow ElderChip, 단일 ElderInputForm 바인딩, 검증/CTA 로직 UiState 기반).
UI 컴포넌트: 불변 상태 + 콜백화
app/src/main/java/com/konkuk/medicarecall/ui/common/component/DiseaseNamesItem.kt, .../MedicationItem.kt, .../GenderToggleButton.kt
로컬 mutable state 제거, 불변 파라미터와 onTextChanged/onAdd/onRemove/onSelect 콜백 도입. GenderToggleButton은 nullable 제거 및 기본값 추가.
신규/수정 UI 컴포넌트 및 폼 리팩터링
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/component/ElderChip.kt, .../ElderInputForm.kt
ElderChip 추가(선택·삭제·클릭 콜백, modifier), ElderInputFormElderData+콜백 기반으로 재작성 및 @Preview 추가.
모델 기본값/nullable 정리
app/src/main/java/com/konkuk/medicarecall/ui/model/ElderData.kt, .../ElderHealthData.kt
ElderData.gender에 기본값(true) 추가, ElderHealthData의 컬렉션 필드에 빈 컬렉션 기본값 추가.
기타 nullability/시그니처 조정
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/info/viewmodel/LoginViewModel.kt, .../ui/feature/settings/screen/ElderDetailScreen.kt, .../MyDetailScreen.kt
성별 관련 상태를 nullable → non-nullable Boolean으로 변경 및 관련 메서드/로컬 상태 조정.
경로/패키지 정리
app/src/main/java/com/konkuk/medicarecall/MainActivity.kt, app/src/main/java/com/konkuk/medicarecall/navigation/NavGraph.kt
LoginElderViewModel의 import 경로를 ...senior.viewmodel.LoginElderViewModel로 업데이트.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as 사용자
  participant InfoS as LoginElderInfoScreen
  participant VM as LoginElderViewModel
  participant Repo as ElderRegisterRepository
  participant API as ElderRegisterService
  participant BE as Backend

  U->>InfoS: 어르신 정보 입력/추가/선택
  InfoS->>VM: updateElder*(필드)
  VM->>VM: elderUiState 업데이트 (StateFlow emit)
  U->>InfoS: 다음(일괄 등록)
  InfoS->>VM: postElderBulk()
  VM->>Repo: postElderBulk(elderList)
  Repo->>API: POST /elders/bulk (ElderBulkRegisterRequestDto)
  API->>BE: 요청 전달
  BE-->>API: 200 + 응답 리스트
  API-->>Repo: Response(body)
  Repo-->>VM: Result<ElderBulkRegisterResponseDto>
  VM->>VM: UI state에 id 동기화
  VM-->>InfoS: 상태 업데이트(완료)
Loading
sequenceDiagram
  autonumber
  actor U as 사용자
  participant MedS as LoginElderMedInfoScreen
  participant VM as LoginElderViewModel
  participant Repo as ElderRegisterRepository
  participant API as ElderRegisterService
  participant BE as Backend

  U->>MedS: 질병/약/메모 입력, 시간 선택
  MedS->>VM: addDisease/addMedication/selectMedicationTime 등
  VM->>VM: elderHealthUiState 업데이트 (StateFlow emit)
  U->>MedS: 다음(건강정보 일괄 등록)
  MedS->>VM: postElderHealthInfoBulk()
  VM->>Repo: postElderHealthInfoBulk(healthList)
  Repo->>API: POST /elders/health-info/bulk (ElderBulkHealthInfoRequestDto)
  API->>BE: 요청 전달
  BE-->>API: 200 OK
  API-->>Repo: Response(OK)
  Repo-->>VM: Result<Unit>
  VM-->>MedS: 완료 신호(상태 갱신)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

feature

Suggested reviewers

  • ikseong00
  • alswlekk

Poem

칩이 반짝, 상태는 흐르고, 콜백이 문을 두드리네,
일괄로 모아 한 번에 쏘니 응답은 조용히 돌아오고,
기본값은 빈 그릇 채우듯 안전히 놓였고,
뷰모델은 StateFlow로 숨결을 불어넣네. ✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning 이 PR에는 API 연동 외에 기존 LoginElderViewModel 삭제, UI 컴포넌트 리팩토링, 새로운 뷰모델과 스크린 추가 등 다양한 변경사항이 포함되어 있어 이슈 #134의 API 연동 범위를 벗어난 항목이 다수 존재합니다. 기능별로 PR을 분리해 API 연동과 UI 리팩토링을 각각 검토하도록 변경범위를 축소하는 것을 권장합니다.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed 제목은 어르신 일괄 등록 API 연동을 명시해 실제 기능 통합 중 첫 번째 엔드포인트는 잘 표현했으나 건강정보 일괄 제출 연동이 누락되어 변경사항을 완전히 요약하지 못하고 있습니다.
Linked Issues Check ✅ Passed 서비스, 레포지토리, 뷰모델 전반에 elders/bulk 및 elders/health-info/bulk 연동을 위한 메소드와 DTO가 모두 추가되어 이슈 #134의 요구사항을 충실히 충족하고 있습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/elder-api-bulk-#134

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9970b36 and 6ba9bee.

📒 Files selected for processing (2)
  • app/src/main/java/com/konkuk/medicarecall/data/api/elders/ElderRegisterService.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/ElderRegisterRepositoryImpl.kt (2 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ProtossManse ProtossManse changed the title [Feat] [Feat] 어르신 일괄 등록 API 연동 #134 Oct 3, 2025
@ProtossManse ProtossManse self-assigned this Oct 3, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (11)
app/src/main/java/com/konkuk/medicarecall/ui/feature/settings/screen/MyDetailScreen.kt (1)

47-47: 불필요한 null 체크 제거 가능

isMaleBoolean으로 변경되어 null이 될 수 없으므로, 113번 라인의 isMale != null 체크와 117번 라인의 isMale == true 체크는 이제 불필요합니다. 간소화해도 좋을 것 같아요.

-                    && isMale != null
-                    val gender = if (isMale == true) GenderType.MALE else GenderType.FEMALE
+                    val gender = if (isMale) GenderType.MALE else GenderType.FEMALE

Also applies to: 113-113, 117-117

app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/component/ElderChip.kt (1)

41-45: InteractionSource null 사용 개선 권장

clickable 수정자에서 interactionSource = null을 사용하는 것은 최신 Compose에서 권장되지 않습니다. remember { MutableInteractionSource() }를 사용하는 것이 좋습니다.

+import androidx.compose.foundation.interaction.MutableInteractionSource
             .clickable(
                 indication = null,
-                interactionSource = null,
+                interactionSource = remember { MutableInteractionSource() },
                 onClick = onClick,
             ),
                     .clickable(
                         indication = null,
-                        interactionSource = null,
+                        interactionSource = remember { MutableInteractionSource() },
                         onClick = { onRemove() },
                     ),

Also applies to: 66-69

app/src/main/java/com/konkuk/medicarecall/data/dto/response/ElderBulkRegisterResponseDto.kt (1)

6-28: 응답 DTO 명확성 개선 제안 (이름 충돌/가독성)

response 패키지의 ElderInfo 명칭이 request 쪽의 ElderInfo(동일 개념의 이름)을 연상시켜 혼동 소지가 있습니다. 응답 모델임을 드러내도록 이름을 구체화하면 유지보수성이 좋아집니다. 또한 guardianId/guardianName 등 필드가 항상 존재하는지 백엔드 스펙을 한 번만 확인 부탁드립니다(없을 수 있으면 nullable 권장).

예시:

-typealias ElderBulkRegisterResponseDto = List<ElderInfo>
+typealias ElderBulkRegisterResponseDto = List<ElderInfoResponse>

-@Serializable
-data class ElderInfo(
+@Serializable
+data class ElderInfoResponse(
   ...
)
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/screen/LoginElderMedInfoScreen.kt (2)

60-64: selectedIndex 범위 가드 필요

uiState.elderHealthList[selectedIndex] 직접 접근은 초기 로딩/동기화 지연 시 IndexOutOfBounds 위험이 있습니다. 사용 전 범위 체크 또는 coerceIn 적용을 권장합니다. 또한 collectAsState 대신 collectAsStateWithLifecycle 사용을 고려해 수명주기 안전성을 높이세요.

예시:

val selected = uiState.selectedIndex.coerceIn(0, (uiState.elderHealthList.size - 1).coerceAtLeast(0))
if (uiState.elderHealthList.isEmpty()) return@Box
// 이후 selected 사용

92-96: scrollState 변수가 섀도잉됨(이름 충돌) → 의도치 않은 스크롤 상태 전달 가능

외부의 verticalScroll용 scrollState와 내부 Row의 horizontalScroll용 scrollState가 동일 이름으로 섀도잉됩니다. 아래처럼 명시적으로 분리해 혼동/버그를 방지하고, DefaultDropdown에는 외부 content scrollState를 전달하세요.

- val scrollState = rememberScrollState()
+ val chipRowScrollState = rememberScrollState()

- Row(Modifier.horizontalScroll(scrollState)) {
+ Row(Modifier.horizontalScroll(chipRowScrollState)) {
    ...
 }

- DefaultDropdown(..., scrollState, { ... })
+ DefaultDropdown(..., scrollState /* 바깥 content 스크롤 상태 유지 */, { ... })

Also applies to: 181-189

app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/ElderRegisterRepositoryImpl.kt (3)

65-85: Enum 매핑의 !! 제거(명확한 실패 메시지로 디버깅 용이)

displayName → enum 변환에서 !!는 모模호한 NPE를 야기합니다. requireNotNull로 명시적 메시지를 주면 장애 분석이 쉬워집니다.

- relationship = RelationshipType.entries.find { it.displayName == elderData.relationship }!!.name,
- residenceType = ElderResidenceType.entries.find { it.displayName == elderData.livingType }!!.name,
+ relationship = requireNotNull(
+   RelationshipType.entries.find { it.displayName == elderData.relationship }
+ ) { "Unknown relationship: ${elderData.relationship}" }.name,
+ residenceType = requireNotNull(
+   ElderResidenceType.entries.find { it.displayName == elderData.livingType }
+ ) { "Unknown residenceType: ${elderData.livingType}" }.name,

87-109: elderId/notes 변환의 NPE 위험 제거

bulk health-info 요청 생성 시 elderHealthData.id!! 및 notes 매핑의 !!.name이 실패 지점을 만듭니다. 사전 검증으로 실패를 명확히 하세요.

- elderId = elderHealthData.id!!,
+ elderId = requireNotNull(elderHealthData.id) { "elderHealthData.id is null" },

- notes = elderHealthData.notes.map { note ->
-   HealthIssueType.entries.find { it.displayName == note }!!.name
- },
+ notes = elderHealthData.notes.map { note ->
+   requireNotNull(HealthIssueType.entries.find { it.displayName == note }) {
+     "Unknown health note: $note"
+   }.name
+ },

65-79: 대용량 bulk 요청 최적화: Retrofit 2.12 withStreaming() 고려

일괄 등록/건강정보는 페이로드가 커질 수 있습니다. Retrofit 2.12.0의 withStreaming()이 직렬화 시점을 I/O 쓰레드로 미루어 UI 스레드 부하를 낮춥니다. DI의 Retrofit 빌더에서 사용을 검토해 주세요.

예: Gson 기준

Retrofit.Builder()
  .addConverterFactory(GsonConverterFactory.create().withStreaming())
  .build()

Based on learnings

Also applies to: 87-106

app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/screen/LoginElderInfoScreen.kt (2)

68-75: 수명주기 대응 수집으로 변경 권장

UI 상태 수집은 collectAsStateWithLifecycle이 안전합니다. 화면 전환/백그라운드 시 불필요 수집을 줄입니다.

- val uiState by loginElderViewModel.elderUiState.collectAsState()
+ val uiState by loginElderViewModel.elderUiState.collectAsStateWithLifecycle()

209-236: 휴대폰 번호 검증 강화 제안

startsWith("010")만으로는 유효성 보장이 약합니다. 숫자 11자리 및 패턴 검사까지 포함하세요.

- else if (!uiState.eldersList.filter { it.phoneNumber.isNotEmpty() }
-         .all { it.phoneNumber.startsWith("010") })
+ else if (!uiState.eldersList.filter { it.phoneNumber.isNotEmpty() }
+         .all { it.phoneNumber.length == 11 && it.phoneNumber.all(Char::isDigit) && it.phoneNumber.startsWith("010") })
app/src/main/java/com/konkuk/medicarecall/ui/common/component/MedicationItem.kt (1)

138-153: 추가 후 선택 시간 초기화 누락(주석과 동작 불일치)

주석은 “입력 필드와 선택된 시간 초기화”라고 되어 있으나, 실제로는 입력만 초기화됩니다. 선택 시간도 해제해 UX 일관성을 맞추는 것을 권장합니다.

 selectedList.forEach { time ->
   onAddMedication(time, inputText)
 }
-// 사용성 개선: 약 추가 후 입력 필드와 선택된 시간 초기화
-onTextChange("")
+// 사용성 개선: 약 추가 후 입력 필드와 선택된 시간 초기화
+onTextChange("")
+selectedList.forEach { time ->
+  // onSelectTime이 토글이라면 여러 번 호출하여 선택 해제
+  onSelectTime(time)
+}

또한 동일 약물 중복 추가를 방지하려면 상위 상태에서 시간대별 List에 contains 체크를 추가하는 것도 고려해 주세요.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 28817aa and be2cab0.

📒 Files selected for processing (24)
  • app/src/main/java/com/konkuk/medicarecall/MainActivity.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/api/ElderRegisterService.kt (2 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/dto/request/ElderBulkHealthInfoRequestDto.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/dto/request/ElderBulkRegisterRequestDto.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/dto/response/ElderBulkRegisterResponseDto.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/repository/ElderRegisterRepository.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/ElderRegisterRepositoryImpl.kt (2 hunks)
  • app/src/main/java/com/konkuk/medicarecall/navigation/NavGraph.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/ui/common/component/DiseaseNamesItem.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/ui/common/component/GenderToggleButton.kt (2 hunks)
  • app/src/main/java/com/konkuk/medicarecall/ui/common/component/MedicationItem.kt (5 hunks)
  • app/src/main/java/com/konkuk/medicarecall/ui/feature/login/info/viewmodel/LoginViewModel.kt (6 hunks)
  • app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/LoginElderViewModel.kt (0 hunks)
  • app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/component/ElderChip.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/component/ElderInputForm.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/screen/LoginElderInfoScreen.kt (6 hunks)
  • app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/screen/LoginElderMedInfoScreen.kt (3 hunks)
  • app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/viewmodel/LoginElderHealthUiState.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/viewmodel/LoginElderUiState.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/viewmodel/LoginElderViewModel.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/ui/feature/settings/screen/ElderDetailScreen.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/ui/feature/settings/screen/MyDetailScreen.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/ui/model/ElderData.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/ui/model/ElderHealthData.kt (1 hunks)
💤 Files with no reviewable changes (1)
  • app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/LoginElderViewModel.kt
🧰 Additional context used
🧬 Code graph analysis (6)
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/component/ElderChip.kt (1)
app/src/main/java/com/konkuk/medicarecall/ui/theme/Theme.kt (1)
  • MediCareCallTheme (38-60)
app/src/main/java/com/konkuk/medicarecall/ui/common/component/DiseaseNamesItem.kt (2)
app/src/main/java/com/konkuk/medicarecall/ui/common/component/ChipItem.kt (1)
  • ChipItem (22-52)
app/src/main/java/com/konkuk/medicarecall/ui/common/component/AddTextField.kt (1)
  • AddTextField (20-66)
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/screen/LoginElderMedInfoScreen.kt (6)
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/info/component/LoginBackButton.kt (1)
  • LoginBackButton (17-37)
app/src/main/java/com/konkuk/medicarecall/ui/common/component/DiseaseNamesItem.kt (1)
  • DiseaseNamesItem (17-69)
app/src/main/java/com/konkuk/medicarecall/ui/common/component/MedicationItem.kt (1)
  • MedicationItem (60-155)
app/src/main/java/com/konkuk/medicarecall/ui/common/component/ChipItem.kt (1)
  • ChipItem (22-52)
app/src/main/java/com/konkuk/medicarecall/ui/common/component/CTAButton.kt (1)
  • CTAButton (22-78)
app/src/main/java/com/konkuk/medicarecall/ui/common/component/DefaultSnackBar.kt (1)
  • DefaultSnackBar (17-37)
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/screen/LoginElderInfoScreen.kt (4)
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/info/component/LoginBackButton.kt (1)
  • LoginBackButton (17-37)
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/component/ElderChip.kt (1)
  • ElderChip (24-77)
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/component/ElderInputForm.kt (1)
  • ElderInputForm (27-120)
app/src/main/java/com/konkuk/medicarecall/ui/common/component/DefaultSnackBar.kt (1)
  • DefaultSnackBar (17-37)
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/component/ElderInputForm.kt (3)
app/src/main/java/com/konkuk/medicarecall/ui/common/component/DefaultTextField.kt (1)
  • DefaultTextField (18-64)
app/src/main/java/com/konkuk/medicarecall/ui/common/component/GenderToggleButton.kt (1)
  • GenderToggleButton (21-87)
app/src/main/java/com/konkuk/medicarecall/ui/common/component/DefaultDropDown.kt (1)
  • DefaultDropdown (41-175)
app/src/main/java/com/konkuk/medicarecall/ui/common/component/MedicationItem.kt (1)
app/src/main/java/com/konkuk/medicarecall/ui/common/component/AddTextField.kt (1)
  • AddTextField (20-66)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci-build
🔇 Additional comments (17)
app/src/main/java/com/konkuk/medicarecall/MainActivity.kt (1)

43-43: Import 경로 업데이트 확인됨.

LoginElderViewModel의 패키지 경로가 viewmodel 하위로 변경되었습니다. 코드 구조 개선을 위한 리팩토링으로 보이며, 기능상 문제는 없습니다.

app/src/main/java/com/konkuk/medicarecall/ui/feature/login/info/viewmodel/LoginViewModel.kt (2)

50-50: 성별 상태를 non-nullable로 변경.

isMaleBoolean?에서 Boolean(기본값 true)로 변경되었습니다. UI 컴포넌트와의 일관성을 위한 개선이며, nullable 처리를 제거하여 코드가 단순해졌습니다.


70-72: 콜백 시그니처 업데이트 확인됨.

onGenderChanged가 non-nullable Boolean을 받도록 변경되어 isMale 상태 변경과 일치합니다.

app/src/main/java/com/konkuk/medicarecall/ui/model/ElderHealthData.kt (1)

5-10: 기본값 추가로 인스턴스 생성 간소화.

ElderHealthData의 모든 필드에 기본값이 추가되어 ElderHealthData()로 빈 인스턴스를 생성할 수 있습니다. 일괄 등록 플로우에서 유용하게 활용될 것으로 보입니다.

app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/viewmodel/LoginElderHealthUiState.kt (1)

1-12: 새로운 UI 상태 클래스 추가.

LoginElderHealthUiState는 어르신 건강 정보 일괄 등록 플로우를 위한 UI 상태를 관리합니다. 기본값이 적절하게 설정되어 있으며, elderHealthList의 초기 값으로 빈 ElderHealthData() 인스턴스를 포함하고 있습니다.

app/src/main/java/com/konkuk/medicarecall/data/dto/request/ElderBulkHealthInfoRequestDto.kt (1)

1-30: 일괄 건강 정보 제출을 위한 DTO 추가.

ElderBulkHealthInfoRequestDtoPOST elders/health-info/bulk 엔드포인트를 위한 요청 DTO입니다. 중첩된 HealthInfoMedicationSchedule 클래스가 적절히 정의되어 있으며, @Serializable@SerialName 애노테이션이 올바르게 적용되었습니다.

app/src/main/java/com/konkuk/medicarecall/ui/common/component/GenderToggleButton.kt (2)

22-25: 성별 토글 버튼 시그니처 간소화.

isMale 파라미터가 Boolean?에서 Boolean(기본값 true)로 변경되고, onGenderChange 콜백도 non-nullable Boolean을 받도록 업데이트되었습니다. Nullable 처리 제거로 코드가 단순해졌습니다.


36-60: 조건부 로직 간소화.

isMale이 non-nullable이 되어 조건부 스타일링 로직이 단순해졌습니다. if (isMale)if (!isMale) 체크가 명확하며, null 처리가 불필요해졌습니다.

app/src/main/java/com/konkuk/medicarecall/navigation/NavGraph.kt (1)

43-43: Import 경로 업데이트 확인됨.

LoginElderViewModel의 패키지 경로가 viewmodel 하위로 변경되었습니다. MainActivity.kt와 동일한 리팩토링이며, 기능상 문제는 없습니다.

app/src/main/java/com/konkuk/medicarecall/ui/feature/settings/screen/ElderDetailScreen.kt (1)

65-65: 성별 상태를 non-nullable로 변경.

isMalemutableStateOf<Boolean?>에서 mutableStateOf<Boolean>로 변경되어 nullable 처리가 제거되었습니다. gender 값(이미 Boolean)으로 초기화되며, UI 컴포넌트와의 일관성이 개선되었습니다.

app/src/main/java/com/konkuk/medicarecall/ui/model/ElderData.kt (1)

6-6: 기본값 추가 확인

gender에 기본값 true를 추가하여 ElderData() 생성을 간소화했습니다. 편의성 향상에 도움이 되네요.

app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/viewmodel/LoginElderUiState.kt (1)

1-8: 새로운 UI 상태 클래스 확인

어르신 로그인 플로우를 위한 깔끔한 상태 홀더입니다. 구조와 기본값이 잘 정의되어 있네요.

app/src/main/java/com/konkuk/medicarecall/data/dto/request/ElderBulkRegisterRequestDto.kt (1)

1-26: Bulk 등록 DTO 확인

일괄 등록을 위한 DTO 구조가 명확하고 직렬화 매핑도 잘 되어 있습니다.

app/src/main/java/com/konkuk/medicarecall/data/api/ElderRegisterService.kt (1)

3-7: Bulk 엔드포인트 추가 확인

일괄 등록 및 건강 정보 bulk 엔드포인트가 깔끔하게 추가되었습니다. Retrofit 설정도 적절하네요.

Also applies to: 26-34

app/src/main/java/com/konkuk/medicarecall/data/repository/ElderRegisterRepository.kt (1)

3-3: Bulk 작업으로 마이그레이션 확인

기존 단일 등록 방식에서 bulk 작업으로 깔끔하게 전환되었습니다. 인터페이스 설계가 명확하네요.

Also applies to: 9-10

app/src/main/java/com/konkuk/medicarecall/ui/common/component/DiseaseNamesItem.kt (1)

18-24: 콜백 기반 상태 관리로 리팩터링 확인

내부 상태를 제거하고 불변 props와 콜백으로 외부화한 것이 단방향 데이터 흐름 패턴에 잘 맞습니다. 깔끔한 리팩터링이네요.

Also applies to: 52-67

app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/screen/LoginElderInfoScreen.kt (1)

115-118: 항목 삭제 시 선택 인덱스 일관성 확인 요청

마지막 항목을 삭제할 때만 selectElder(selectedIndex - 1)을 호출합니다. 중간/첫 항목 삭제 시 선택 인덱스가 유효하게 보정되는지 ViewModel 로직을 한 번만 확인 부탁드립니다(경계값 이슈 예방).

Comment on lines 40 to 118
Column(
verticalArrangement = Arrangement.Center,
) {
Column(
verticalArrangement = Arrangement.Center
) {
DefaultTextField(
value = loginElderViewModel.nameList[index],
onValueChange = { loginElderViewModel.onNameChanged(index, it) },
category = "이름",
placeHolder = "이름"
)
Spacer(Modifier.height(20.dp))
DefaultTextField(
loginElderViewModel.dateOfBirthList[index],
{ input ->
val filtered = input.filter { it.isDigit() }.take(8)
loginElderViewModel.onDOBChanged(index, filtered)
},
category = "생년월일",
placeHolder = "YYYY / MM / DD",
keyboardType = KeyboardType.Number,
visualTransformation = DateOfBirthVisualTransformation(),
maxLength = 8
DefaultTextField(
value = elderData.name,
onValueChange = { input ->
onNameChanged(input)
},
category = "이름",
placeHolder = "이름",
textFieldModifier = if (nameFocusRequester != null)
Modifier.focusRequester(nameFocusRequester)
else Modifier,
)
Spacer(Modifier.height(20.dp))
DefaultTextField(
elderData.birthDate,
{ input ->
val filtered = input.filter { it.isDigit() }.take(8)
onBirthDateChanged(filtered)
},
category = "생년월일",
placeHolder = "YYYY / MM / DD",
keyboardType = KeyboardType.Number,
visualTransformation = DateOfBirthVisualTransformation(),
maxLength = 8,
)
Spacer(Modifier.height(20.dp))
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
Text(
"성별",
color = MediCareCallTheme.colors.gray7,
style = MediCareCallTheme.typography.M_17,
)
Spacer(Modifier.height(20.dp))
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
Text(
"성별",
color = MediCareCallTheme.colors.gray7,
style = MediCareCallTheme.typography.M_17
)

GenderToggleButton(loginElderViewModel.isMaleBoolList[index]) {
loginElderViewModel.onGenderChanged(
index, it
)
}
GenderToggleButton(elderData.gender) {
onGenderChanged(it)
}
Spacer(Modifier.height(20.dp))
DefaultTextField(
loginElderViewModel.phoneNumberList[index],
{ input ->
val filtered = input.filter { it.isDigit() }.take(11)
loginElderViewModel.onPhoneNumberChanged(index, filtered)
},
category = "휴대폰 번호",
placeHolder = "010-1234-5678",
keyboardType = KeyboardType.Number,
visualTransformation = PhoneNumberVisualTransformation(),
maxLength = 11
)
Spacer(Modifier.height(20.dp))
}
Spacer(Modifier.height(20.dp))
DefaultTextField(
elderData.phoneNumber,
{ input ->
val filtered = input.filter { it.isDigit() }.take(11)
onPhoneNumberChanged(filtered)
},
category = "휴대폰 번호",
placeHolder = "010-1234-5678",
keyboardType = KeyboardType.Number,
visualTransformation = PhoneNumberVisualTransformation(),
maxLength = 11,
)
Spacer(Modifier.height(20.dp))


DefaultDropdown(
enumList = RelationshipType.entries.map { it.displayName }
.toList(),
placeHolder = "관계 선택하기",
category = "어르신과의 관계",
scrollState,
{ loginElderViewModel.onRelationshipChanged(index, it) },
loginElderViewModel.relationshipList[index]
)
DefaultDropdown(
enumList = RelationshipType.entries.map { it.displayName }
.toList(),
placeHolder = "관계 선택하기",
category = "어르신과의 관계",
scrollState,
{ onRelationshipChange(it) },
elderData.relationship,
)


Spacer(Modifier.height(20.dp))
Spacer(Modifier.height(20.dp))

DefaultDropdown(
enumList = ElderResidenceType.entries.map { it.displayName }
.toList(),
placeHolder = "거주방식을 선택해주세요",
category = "어르신 거주 방식",
scrollState,
{ loginElderViewModel.onLivingTypeChanged(index, it) },
loginElderViewModel.livingTypeList[index]
)
DefaultDropdown(
enumList = ElderResidenceType.entries.map { it.displayName }
.toList(),
placeHolder = "거주방식을 선택해주세요",
category = "어르신 거주 방식",
scrollState,
{ onLivingTypeChanged(it) },
elderData.livingType,
)

Spacer(Modifier.height(20.dp))
}
}
if (!isExpanded) {
Box(
Modifier
.fillMaxWidth()
.figmaShadow(MediCareCallTheme.shadow.shadow03)
.clip(
RoundedCornerShape(14.dp)
)
.background(MediCareCallTheme.colors.white)
.clickable { loginElderViewModel.expandedFormIndex = index }
) {
Column(
Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
"${loginElderViewModel.nameList[index]}",
color = MediCareCallTheme.colors.gray8,
style = MediCareCallTheme.typography.SB_18
)
Text(
"삭제하기",
color = MediCareCallTheme.colors.negative,
style = MediCareCallTheme.typography.R_14,
modifier = Modifier.clickable(
indication = null,
interactionSource = null,
onClick = {
loginElderViewModel.elders--
loginElderViewModel.expandedFormIndex = 0
}
)
)
}
Text(
loginElderViewModel.phoneNumberList[index].replaceFirst(
Regex("(\\d{3})(\\d{4})(\\d{4})"),
"$1-$2-$3"
),
color = MediCareCallTheme.colors.gray4,
style = MediCareCallTheme.typography.R_14
)
}
}
Spacer(Modifier.height(30.dp))
Spacer(Modifier.height(20.dp))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

modifier 파라미터를 실제 레이아웃에 적용해주세요.

modifier를 기본값과 함께 공개 API로 받지만 Column에 전달하지 않아 외부에서 주입한 수정자가 전혀 반영되지 않습니다. 이로 인해 레이아웃 배치/패딩 조정이 불가능해집니다. 루트 Column 혹은 적절한 래퍼에 modifier = modifier를 적용하도록 반영해주세요.

-    Column(
-        verticalArrangement = Arrangement.Center,
-    ) {
+    Column(
+        modifier = modifier,
+        verticalArrangement = Arrangement.Center,
+    ) {
🤖 Prompt for AI Agents
In
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/component/ElderInputForm.kt
around lines 40 to 118, the composable accepts a modifier parameter but never
applies it to the UI, so external layout/padding adjustments are ignored; update
the root Column (or an appropriate wrapper Column) to include modifier =
modifier (or combine with existing modifiers using modifier.then(...)) so the
passed-in modifier is applied to the layout.

Comment on lines 95 to 116
DefaultDropdown(
enumList = RelationshipType.entries.map { it.displayName }
.toList(),
placeHolder = "관계 선택하기",
category = "어르신과의 관계",
scrollState,
{ onRelationshipChange(it) },
elderData.relationship,
)


Spacer(Modifier.height(20.dp))
Spacer(Modifier.height(20.dp))

DefaultDropdown(
enumList = ElderResidenceType.entries.map { it.displayName }
.toList(),
placeHolder = "거주방식을 선택해주세요",
category = "어르신 거주 방식",
scrollState,
{ loginElderViewModel.onLivingTypeChanged(index, it) },
loginElderViewModel.livingTypeList[index]
)
DefaultDropdown(
enumList = ElderResidenceType.entries.map { it.displayName }
.toList(),
placeHolder = "거주방식을 선택해주세요",
category = "어르신 거주 방식",
scrollState,
{ onLivingTypeChanged(it) },
elderData.livingType,
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

named 인자 뒤에 위치 인자 사용으로 컴파일 실패합니다.

DefaultDropdown 호출부에서 category = ... 이후에 scrollState, { ... }, elderData.relationship을 위치 인자로 넘기고 있는데, Kotlin에서는 named 인자를 사용한 뒤에 positional 인자를 섞을 수 없습니다. 현재 코드는 빌드 단계에서 Mixing named and positioned arguments is not allowed 컴파일 오류가 발생합니다. 모든 인자를 명시적으로 이름 붙여 호출하도록 수정해주세요.

-        DefaultDropdown(
-            enumList = RelationshipType.entries.map { it.displayName }
-                .toList(),
-            placeHolder = "관계 선택하기",
-            category = "어르신과의 관계",
-            scrollState,
-            { onRelationshipChange(it) },
-            elderData.relationship,
-        )
+        DefaultDropdown(
+            enumList = RelationshipType.entries.map { it.displayName }.toList(),
+            placeHolder = "관계 선택하기",
+            category = "어르신과의 관계",
+            scrollState = scrollState,
+            onOptionSelect = { onRelationshipChange(it) },
+            value = elderData.relationship,
+        )

아래 거주 방식 드롭다운도 동일하게 수정이 필요합니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
DefaultDropdown(
enumList = RelationshipType.entries.map { it.displayName }
.toList(),
placeHolder = "관계 선택하기",
category = "어르신과의 관계",
scrollState,
{ onRelationshipChange(it) },
elderData.relationship,
)
Spacer(Modifier.height(20.dp))
Spacer(Modifier.height(20.dp))
DefaultDropdown(
enumList = ElderResidenceType.entries.map { it.displayName }
.toList(),
placeHolder = "거주방식을 선택해주세요",
category = "어르신 거주 방식",
scrollState,
{ loginElderViewModel.onLivingTypeChanged(index, it) },
loginElderViewModel.livingTypeList[index]
)
DefaultDropdown(
enumList = ElderResidenceType.entries.map { it.displayName }
.toList(),
placeHolder = "거주방식을 선택해주세요",
category = "어르신 거주 방식",
scrollState,
{ onLivingTypeChanged(it) },
elderData.livingType,
)
DefaultDropdown(
enumList = RelationshipType.entries.map { it.displayName }.toList(),
placeHolder = "관계 선택하기",
category = "어르신과의 관계",
scrollState = scrollState,
onOptionSelect = { onRelationshipChange(it) },
value = elderData.relationship,
)
Spacer(Modifier.height(20.dp))
DefaultDropdown(
enumList = ElderResidenceType.entries.map { it.displayName }
.toList(),
placeHolder = "거주방식을 선택해주세요",
category = "어르신 거주 방식",
scrollState,
{ onLivingTypeChanged(it) },
elderData.livingType,
)
🤖 Prompt for AI Agents
In
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/component/ElderInputForm.kt
around lines 95–116, the DefaultDropdown calls mix named and positional
arguments which causes the compiler error; change the remaining positional
arguments after category to named arguments as well (e.g., pass scrollState =
scrollState, the change lambda as its named parameter, and the selected value as
its named parameter) for both the relationship and living-type dropdowns so all
arguments are consistently named.

Comment on lines 239 to 245
loginElderViewModel.initElderHealthData()
loginElderViewModel.postElderBulk()
navController.navigate(Route.LoginElderMedInfoScreen.route)
}
},
modifier.padding(bottom = 20.dp)
modifier.padding(bottom = 20.dp),
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

일괄 등록 네트워크 결과 처리 및 네비게이션 순서

postElderBulk() 결과를 확인하지 않고 즉시 이동하면 실패 시 UX가 깨집니다. 비동기 실행으로 성공 시에만 이동하고, 실패는 스낵바로 안내하는 패턴을 권장합니다. 또한 진행 중 중복 클릭 방지(loading state)도 고려해 주세요.

간단 예시:

coroutineScope.launch {
  runCatching { loginElderViewModel.postElderBulk() }
    .onSuccess { navController.navigate(Route.LoginElderMedInfoScreen.route) }
    .onFailure { snackBarState.showSnackbar("등록에 실패했어요. 잠시 후 다시 시도해 주세요.") }
}
🤖 Prompt for AI Agents
In
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/screen/LoginElderInfoScreen.kt
around lines 239-245, the code calls loginElderViewModel.postElderBulk() and
immediately navigates away, which ignores the async network result; change this
to launch a coroutine, set a loading flag to prevent duplicate clicks, await
postElderBulk() and only navigate on success, on failure show a snackbar with a
friendly message and clear/reset the loading state; ensure exceptions from
postElderBulk are caught (e.g., runCatching or try/catch) so failures are
handled and UI restored.

Comment on lines 191 to 204
CTAButtonType.GREEN,
"다음",
{
coroutineScope.launch {
loginElderViewModel.createElderHealthDataList()
loginElderViewModel.updateAllElders()
loginElderViewModel.updateAllEldersHealthInfo()
loginElderViewModel.postElderAndHealth()
delay(200L)
loginElderViewModel.postElderHealthInfoBulk()
navController.navigate(Route.SetCall.route) {
popUpTo(Route.LoginElderInfoScreen.route) {
inclusive = true
}
}
}
},
Modifier.padding(top = 30.dp, bottom = 20.dp)
Modifier.padding(top = 30.dp, bottom = 20.dp),
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

일괄 건강정보 전송 실패 처리 및 중복 클릭 방지

postElderHealthInfoBulk() 호출 후 실패 케이스가 처리되지 않아 실패해도 다음 화면으로 이동할 수 있습니다. runCatching/try-catch로 오류를 스낵바로 안내하고, 진행 중에는 버튼 비활성화(loading state)로 중복 전송도 막아주세요.

간단 예시:

coroutineScope.launch {
  runCatching { loginElderViewModel.postElderHealthInfoBulk() }
    .onSuccess {
      navController.navigate(Route.SetCall.route) { popUpTo(Route.LoginElderInfoScreen.route) { inclusive = true } }
    }
    .onFailure {
      snackBarState.showSnackbar("저장에 실패했어요. 잠시 후 다시 시도해 주세요.")
    }
}
🤖 Prompt for AI Agents
In
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/screen/LoginElderMedInfoScreen.kt
around lines 191–204, the call to loginElderViewModel.postElderHealthInfoBulk()
does not handle failures and allows immediate navigation (and duplicate
submissions) on click; wrap the network call in runCatching or try/catch, only
call navController.navigate(...) on success, show a snackbar with a friendly
error message on failure, and prevent duplicate clicks by disabling the CTA
button while a ViewModel-backed loading flag is true (or set a local loading
state) so the button is disabled and shows a loading indicator until the call
completes. Ensure the ViewModel exposes or updates a loading state around
postElderHealthInfoBulk() (or return a Result) so the composable can observe it
and toggle the button enabled/disabled state accordingly.

Comment on lines 111 to 117
fun removeElder(index: Int) {
_elderUiState.update { state ->
state.copy(
eldersList = state.eldersList.filterIndexed { i, _ -> i != index },
)
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

삭제 이후 selectedIndex 범위가 무너집니다.

removeElder가 리스트에서 항목만 제거하고 selectedIndex를 갱신하지 않아, 마지막 항목을 지운 직후 selectedIndex가 리스트 크기 이상을 가리키게 됩니다. 이후 다른 업데이트 함수에서 eldersList[state.selectedIndex]처럼 접근하면 IndexOutOfBoundsException이 발생합니다. 삭제 후에는 selectedIndexmin(삭제된 인덱스, 새 리스트 마지막 인덱스) 형태로 재조정하거나, 리스트가 비면 0으로 초기화하는 처리가 필요합니다.

     fun removeElder(index: Int) {
         _elderUiState.update { state ->
-            state.copy(
-                eldersList = state.eldersList.filterIndexed { i, _ -> i != index },
-            )
+            val updatedList = state.eldersList.filterIndexed { i, _ -> i != index }
+            state.copy(
+                eldersList = updatedList,
+                selectedIndex = updatedList.indices.lastOrNull()?.coerceAtMost(state.selectedIndex.coerceAtMost(updatedList.lastIndex)) ?: 0,
+            )
         }
     }
🤖 Prompt for AI Agents
In
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/viewmodel/LoginElderViewModel.kt
around lines 111 to 117, removeElder currently only removes the item and leaves
selectedIndex unchanged which can point past the end of the new list; after
filtering the eldersList compute the newSelected = if (newList.isEmpty()) 0 else
min(index, newList.lastIndex) (ensure non-negative) and include selectedIndex =
newSelected in the state.copy so selectedIndex is clamped to the valid range
after deletion.

Copy link
Collaborator

@librawish808 librawish808 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@ikseong00 ikseong00 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#139 PR 이랑 겹치는 부분이 많이 있군요 잘 짜신 것 같습니다

Comment on lines 25 to 30
fun ElderChip(
name: String,
selected: Boolean,
onRemove: () -> Unit,
onClick: () -> Unit,
) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

재사용성을 위해서 Modifier 넘겨줘야 할 것 같습니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영했습니다 !

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

9970b36 사진에 있는 오른쪽 커밋 해시 긁어서 복사해서 넣는 방법도 있어요!
나중에 참고하면 좋을 것 같습니당

Comment on lines 108 to 123
LazyRow(horizontalArrangement = Arrangement.spacedBy(10.dp)) {

itemsIndexed(uiState.eldersList) { index, it ->
ElderChip(
name = it.name,
selected = index == selectedIndex,
onRemove = {
if (selectedIndex == uiState.eldersList.size - 1)
loginElderViewModel.selectElder(selectedIndex - 1)
loginElderViewModel.removeElder(index)
},
onClick = { loginElderViewModel.selectElder(index) },
)

}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be2cab0 and 9970b36.

📒 Files selected for processing (1)
  • app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/component/ElderChip.kt (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/component/ElderChip.kt (1)
app/src/main/java/com/konkuk/medicarecall/ui/theme/Theme.kt (1)
  • MediCareCallTheme (38-60)

Comment on lines 63 to 71
painter = painterResource(id = R.drawable.ic_close),
contentDescription = "remove",
modifier = Modifier
.size(16.dp)
.clickable(
indication = null,
interactionSource = null,
onClick = { onRemove() },
),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

삭제 아이콘 터치 영역이 너무 좁습니다

현재 Icon 에 Modifier.size(16.dp) 뒤에 clickable 을 바로 붙여서 실제 터치 영역이 16dp에 불과합니다. 최소 44~48dp 권장 최소 터치 사이즈보다 크게 부족해 고령 사용자가 버튼을 누르기 매우 어렵고 접근성도 저해됩니다. Box/IconButton 등을 활용해 여유 패딩을 두거나, clip + padding 순서를 조정해서 터치 영역을 확장해주세요. 동시에 stringResource(R.string.xxx_remove) 형태로 콘텐츠 설명도 지역화해주면 좋겠습니다.

🤖 Prompt for AI Agents
In
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/component/ElderChip.kt
around lines 63 to 71, the remove icon's clickable area is constrained to 16.dp
because clickable is applied directly after Modifier.size(16.dp); expand the
touch target to at least 44.dp by moving the clickable to a parent container
(e.g., wrap the Icon in a Box or use IconButton) or apply a larger size/padding
before clickable (ensure clip is applied after padding if needed), add
sufficient padding so the visible icon remains 16.dp but the touchable area
meets accessibility guidelines, and replace the hardcoded contentDescription
with a localized stringResource(R.string.xxx_remove).

@ProtossManse
Copy link
Collaborator Author

@librawish808 @alswlekk
리뷰 부탁드립니다 !

@librawish808
Copy link
Collaborator

확인하였습니다 좋습니다!

Copy link
Collaborator

@alswlekk alswlekk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM !

Comment on lines +271 to +295
fun postElderBulk() {
viewModelScope.launch {
elderRegisterRepository.registerElderAndHealth(
elders = elderUiState.value.eldersList.size,
elderInfoList = elderUiState.value.eldersList,
elderHealthInfo = elderHealthUiState.value.elderHealthList,
)
.onSuccess {
Log.d("httplog", "어르신 및 건강정보 전부 등록 성공!")
elderRegisterRepository.postElderBulk(elderUiState.value.eldersList)
.onSuccess { response ->
_elderUiState.update { state ->
state.copy(
eldersList = state.eldersList.mapIndexed { index, elderData ->
elderData.copy(id = response[index].id)
},
)
}
_elderHealthUiState.update { state ->
state.copy(
elderHealthList = state.elderHealthList.mapIndexed { index, healthData ->
healthData.copy(id = response[index].id)
},
)
}
}
.onFailure { exception ->
Log.e("httplog", "어르신 정보 or 건강정보 등록 실패: ${exception.message}")
when (exception) {
is HttpException -> {
Log.e("httplog", "어르신 일괄등록 실패: ${exception.code()}, ${exception.message()}")
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

일괄처리 코드 깔끔하게 잘 작성하신 거 같아요 !

@ProtossManse ProtossManse merged commit daa35b9 into develop Oct 8, 2025
2 checks passed
@ProtossManse ProtossManse deleted the feat/elder-api-bulk-#134 branch October 8, 2025 16:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 어르신 일괄 등록 API 연동

5 participants