Skip to content

Comments

Style/#76 시술 플로우 화면 구현#83

Merged
wotjs020708 merged 12 commits intodevelopfrom
style/#76-TreatmentView
Jan 18, 2026
Merged

Style/#76 시술 플로우 화면 구현#83
wotjs020708 merged 12 commits intodevelopfrom
style/#76-TreatmentView

Conversation

@wotjs020708
Copy link
Contributor

🔗 연결된 이슈

📄 작업 내용

  • 시술 플로우 화면 구현(NoTreatment와 로직이 거의 동일합니다.)
  • 검색 기능 추가
구현 내용 IPhone 16 pro IPhone 13 mini
GIF
장하는 결과

👀 기타 더 이야기해볼 점

하다보니까 공통 컴포넌트 사용할 때 protocol로 좀 더 코드를 줄일 수 있지 않을까 라는 생각이 들었습니다.
마동석 책임감

@wotjs020708 wotjs020708 requested a review from a team January 16, 2026 18:03
@wotjs020708 wotjs020708 self-assigned this Jan 16, 2026
@wotjs020708 wotjs020708 requested review from soseoyo12, sum130 and y-eonee and removed request for a team January 16, 2026 18:03
@wotjs020708 wotjs020708 added 재선🐻 Feature 새로운 기능 구현 및 API 연결 labels Jan 16, 2026
@wotjs020708 wotjs020708 linked an issue Jan 16, 2026 that may be closed by this pull request
1 task
@coderabbitai
Copy link

coderabbitai bot commented Jan 16, 2026

📝 Walkthrough

Walkthrough

시술(Treatment) 선택 플로우를 구현합니다. 새로운 상태 관리 뷰모델, 검색 및 필터링 기능, 시술 선택 UI 컴포넌트를 추가하고, 기존 SelectedTreatmentView를 프롭 기반으로 리팩토링합니다.

Changes

Cohort / File(s) Summary
Core Treatment Model & Navigation
Treatment.swift
시술 단계를 나타내는 새로운 열거형(targetDdaySetting, treatmentFilter, downTimeSetting) 추가. 네비게이션 헬퍼 메서드(next, previous)와 포지션 표시(isFirst, isLast) 포함.
Treatment View Model & Extensions
TreatmentViewModel.swift, TreatmentViewModel+Filter.swift, TreatmentViewModel+Search.swift
상태 관리, 시술 선택/제거/확인 로직, 검색 및 필터링 기능을 담당하는 새로운 ObservableObject 뷰모델과 확장 추가. Combine 기반 300ms 디바운스 검색 구현.
Treatment UI Views
TreatmentView.swift, TreatmentFilterView.swift, TreatmentSearchBarTextField.swift
시술 선택 UI 흐름을 관리하는 메인 TreatmentView, 필터링 가능한 시술 목록 표시 TreatmentFilterView, 검색 기능이 포함된 TreatmentSearchBarTextField 컴포넌트 신규 추가.
Component Refactoring & Integration
SelectedTreatmentView.swift, NoTreatmentView.swift
SelectedTreatmentView를 @ObservedObject 기반에서 명시적 프롭(selectedTreatments, removeTreatment)으로 변경. NoTreatmentView에 조건부 SelectedTreatmentView 렌더링 추가.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant TreatmentView
    participant TreatmentViewModel
    participant TreatmentFilterView
    participant SelectedTreatmentView

    User->>TreatmentView: View loads (treatmentFilter state)
    TreatmentView->>TreatmentViewModel: Observe state & selectedTreatments
    TreatmentView->>TreatmentFilterView: Render with ViewModel
    User->>TreatmentFilterView: Search for treatment
    TreatmentFilterView->>TreatmentViewModel: Update searchText
    TreatmentViewModel->>TreatmentViewModel: Debounce (300ms) & filter
    TreatmentViewModel->>TreatmentFilterView: Publish filteredTreatments
    TreatmentFilterView->>User: Display filtered list
    User->>TreatmentFilterView: Tap treatment row
    TreatmentFilterView->>TreatmentViewModel: addTreatment(treatment)
    TreatmentViewModel->>TreatmentViewModel: Append to selectedTreatments
    TreatmentView->>SelectedTreatmentView: Render (if selectedTreatments.count > 0)
    SelectedTreatmentView->>User: Display selected treatments
    User->>SelectedTreatmentView: Tap remove button
    SelectedTreatmentView->>TreatmentViewModel: removeTreatment(treatment)
    TreatmentViewModel->>TreatmentViewModel: Remove from selectedTreatments
    User->>TreatmentView: Tap next button
    TreatmentView->>TreatmentViewModel: next()
    TreatmentViewModel->>TreatmentViewModel: Advance state
    TreatmentView->>User: Navigate to next step
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • sum130
  • soseoyo12
  • y-eonee
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 치료(시술) 플로우 화면 구현에 관한 내용으로, 변경 사항의 주요 목표를 명확하게 요약하고 있습니다.
Description check ✅ Passed PR 설명이 시술 플로우 화면 구현 및 검색 기능 추가에 관한 변경 사항을 상세히 설명하고 있으며, 연결된 이슈도 명시되어 있습니다.
Linked Issues check ✅ Passed PR의 모든 코드 변경이 연결된 이슈 #76의 요구 사항을 충족합니다: TreatmentViewModel, TreatmentView, TreatmentFilterView 등 필요한 UI 컴포넌트들이 구현되었고 검색 기능도 추가되었습니다.
Out of Scope Changes check ✅ Passed PR의 모든 변경 사항이 #76 이슈에서 요구하는 시술 플로우 화면 구현 범위 내에 있으며, 범위를 벗어난 변경 사항은 없습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 style/#76-TreatmentView

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.

}

extension TreatmentEntity {
static let mockData: [TreatmentEntity] = [
Copy link
Contributor

Choose a reason for hiding this comment

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

목데이터는 레포지토리로 빼봅시다 ~~


import SwiftUI

struct SelectedTreatmentView: View {
Copy link
Contributor

Choose a reason for hiding this comment

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

뒤에 sheet 붙이는 네이밍으로 바꾸는건 어떨까요

x: 0,
y: -5
)
ScrollView(.vertical, showsIndicators: false) {
Copy link
Contributor

Choose a reason for hiding this comment

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

스크롤되는것도 gif 한번만 첨부해주세요!

Comment on lines 37 to 40
VStack(alignment: .leading, spacing: 0) {
TypographyText("회복을 계획할 때 고려해야 할", style: .title1_sb_18, color: .gray1000)
TypographyText("중요한 일정이 있나요?", style: .title1_sb_18, color: .gray1000)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

image 패딩값 한번만 봐주시오

Comment on lines +16 to +17
VStack {

Copy link
Contributor

Choose a reason for hiding this comment

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

이거 엔터좀여

Comment on lines +35 to +37
.padding(.vertical, 8.adjustedH)
.padding(.leading, 16.5.adjustedW)
Button{
Copy link
Contributor

Choose a reason for hiding this comment

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

이부분 엔터해주세용

}
ScrollView(.vertical, showsIndicators: false) {
ForEach(treatments, id: \.self) { treatment in
TreatmentRowView(displayMode: .completeBoxView, treatmentEntity: treatment, isSelected: .constant(false), isCompleted: .constant(false), action: {})
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
Contributor

Choose a reason for hiding this comment

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

이 플로우까지 gif에 첨부되지 않은것 같은데 추가해주세요!

var isLast: Bool { self == Self.allCases.last }
}

// MARK: - Navigation
Copy link
Contributor

Choose a reason for hiding this comment

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

마크주석 빼주세영

@ObservedObject var viewModel: TreatmentViewModel
var body: some View {
VStack {
TreatmentSearchBarTextField(text: $viewModel.searchText, enter: { }, isDisabled: !viewModel.searchText.isEmpty)
Copy link
Contributor

Choose a reason for hiding this comment

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

개행 + 엔터좀요

@wotjs020708 wotjs020708 added Style UI 구현 and removed Feature 새로운 기능 구현 및 API 연결 labels Jan 18, 2026
Copy link
Contributor

@sum130 sum130 left a comment

Choose a reason for hiding this comment

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

어푸푸~~!! 잘.. 마무리되기를!!

Copy link
Contributor

@soseoyo12 soseoyo12 left a comment

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: 5

🤖 Fix all issues with AI agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/TreatmentSearchBarTextField.swift`:
- Around line 26-33: The TextField's onSubmit currently fires even when searches
are meant to be disabled; update the component to both disable the TextField
(e.g., via .disabled(isDisabled) or equivalent) and add a guard inside the
onSubmit handler to no-op when isDisabled is true (guard !isDisabled else {
return }), referencing the existing TextField, its onSubmit closure, the enter()
call, and the isDisabled flag so both UI interaction and programmatic submit are
blocked.

In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Treatment/View/TreatmentFilterView.swift`:
- Around line 29-48: The list currently checks filteredTreatments.isEmpty which
makes an empty search show the full list; change the branching to check the
search state instead (use viewModel.searchText.isEmpty or the searchText
binding) so that when searchText is empty you iterate viewModel.treatments,
otherwise you always iterate viewModel.filteredTreatments (even if empty).
Update the conditional around the ForEach (the block that builds
TreatmentRowView using viewModel.isSelected and viewModel.addTreatment) to use
searchText.isEmpty as the condition and keep the same TreatmentRowView
parameters.
- Line 14: The TreatmentSearchBarTextField currently passes an empty enter
closure and sets isDisabled to !viewModel.searchText.isEmpty which disables the
button when there is input; update the call so enter invokes the intended action
on the view model (e.g., viewModel.performSearch() or viewModel.clearSearch())
and fix the isDisabled logic to reflect the intended state (for example use
viewModel.searchText.isEmpty to disable when there is no input, or remove the
isDisabled prop if the button should always be enabled). Ensure you reference
TreatmentSearchBarTextField, the enter closure, and viewModel.searchText when
making the change.

In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Treatment/View/TreatmentView.swift`:
- Line 76: Update the SwiftLint MARK comment formatting for the
TreatmentSelectedCategoryView section: replace the current comment token that
reads "//MARK: - TreatmentSelectedCategoryView" with the correct spaced format
"// MARK: - TreatmentSelectedCategoryView" so it conforms to the SwiftLint rule;
locate the comment near the TreatmentSelectedCategoryView section in
TreatmentView.swift and adjust the spacing around the MARK token.

In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Treatment/ViewModel/TreatmentViewModel.swift`:
- Around line 48-55: The isDateTextFieldNotEmpty() method currently only checks
numeric ranges for year/month/day allowing invalid combinations (e.g., 2/31);
update this function to parse year/month/day into Ints, check the year range
(>=2020) as before, then build DateComponents(year:yearInt, month:monthInt,
day:dayInt) and use Calendar.current.date(from:) to ensure the combination
yields a real Date (non-nil) before returning true; keep the existing guards for
empty strings and numeric parsing but replace the separate day-range guard with
this DateComponents/Calendar validation to reject impossible dates.

Comment on lines +26 to +33
TextField("" ,text: $text)
.foregroundStyle(.gray1000)
.typography(.body1_m_14)
.multilineTextAlignment(.leading)
.tint(.gray1000)
.onSubmit {
enter()
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

비활성 상태에서도 엔터 제출이 실행됩니다

isDisabled를 검색 비활성 의미로 쓰는 거라면, 버튼만 막는 것으론 onSubmit이 그대로 실행됩니다. 의도라면 TextField 비활성 + onSubmit 가드를 같이 두는 편이 안전합니다.

✅ 제안 수정
-                    TextField("" ,text: $text)
+                    TextField("" ,text: $text)
                         .foregroundStyle(.gray1000)
                         .typography(.body1_m_14)
                         .multilineTextAlignment(.leading)
                         .tint(.gray1000)
+                        .disabled(isDisabled)
                         .onSubmit {
-                            enter()
+                            guard !isDisabled else { return }
+                            enter()
                         }

Also applies to: 45-45

🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/TreatmentSearchBarTextField.swift`
around lines 26 - 33, The TextField's onSubmit currently fires even when
searches are meant to be disabled; update the component to both disable the
TextField (e.g., via .disabled(isDisabled) or equivalent) and add a guard inside
the onSubmit handler to no-op when isDisabled is true (guard !isDisabled else {
return }), referencing the existing TextField, its onSubmit closure, the enter()
call, and the isDisabled flag so both UI interaction and programmatic submit are
blocked.

@ObservedObject var viewModel: TreatmentViewModel
var body: some View {
VStack {
TreatmentSearchBarTextField(text: $viewModel.searchText, enter: { }, isDisabled: !viewModel.searchText.isEmpty)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

검색 버튼이 사실상 무동작/비활성입니다

enter가 빈 클로저이고 isDisabled!searchText.isEmpty라 입력 시 버튼이 비활성됩니다. 버튼을 유지할 거라면 실제 동작(즉시 검색/클리어 등)과 비활성 조건을 의도에 맞게 조정해 주세요.

✅ 최소 수정 예시
-        TreatmentSearchBarTextField(text: $viewModel.searchText, enter: { }, isDisabled: !viewModel.searchText.isEmpty)
+        TreatmentSearchBarTextField(
+            text: $viewModel.searchText,
+            enter: { viewModel.filterTreatments(by: viewModel.searchText) },
+            isDisabled: viewModel.searchText.isEmpty
+        )
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Treatment/View/TreatmentFilterView.swift`
at line 14, The TreatmentSearchBarTextField currently passes an empty enter
closure and sets isDisabled to !viewModel.searchText.isEmpty which disables the
button when there is input; update the call so enter invokes the intended action
on the view model (e.g., viewModel.performSearch() or viewModel.clearSearch())
and fix the isDisabled logic to reflect the intended state (for example use
viewModel.searchText.isEmpty to disable when there is no input, or remove the
isDisabled prop if the button should always be enabled). Ensure you reference
TreatmentSearchBarTextField, the enter closure, and viewModel.searchText when
making the change.

Comment on lines +29 to +48
if viewModel.filteredTreatments.isEmpty {
ForEach(viewModel.treatments, id: \.id) { treatment in
TreatmentRowView(
displayMode: .checkBoxView,
treatmentEntity: treatment,
isSelected: .constant(viewModel.isSelected(treatment)),
action: { viewModel.addTreatment(treatment) }
)
}
} else {
ForEach(viewModel.filteredTreatments, id: \.id) {
treatment in
TreatmentRowView(
displayMode: .checkBoxView,
treatmentEntity: treatment,
isSelected: .constant(viewModel.isSelected(treatment)),
action: { viewModel.addTreatment(treatment) }
)
}
}
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

검색 결과 0건이면 전체 리스트로 되돌아가는 버그

현재는 filteredTreatments.isEmpty일 때 전체 목록을 보여서, 검색 결과가 없는 경우에도 필터가 적용되지 않은 것처럼 보입니다. searchText로 분기해서 “검색 중 / 기본 상태”를 구분해야 합니다.

🐛 제안 수정
-                if viewModel.filteredTreatments.isEmpty {
-                    ForEach(viewModel.treatments, id: \.id) { treatment in
+                if viewModel.searchText.isEmpty {
+                    ForEach(viewModel.treatments, id: \.id) { treatment in
                         TreatmentRowView(
                             displayMode: .checkBoxView,
                             treatmentEntity: treatment,
                             isSelected: .constant(viewModel.isSelected(treatment)),
                             action: { viewModel.addTreatment(treatment) }
                         )
                     }
                 } else {
                     ForEach(viewModel.filteredTreatments, id: \.id) {
                         treatment in
                         TreatmentRowView(
                             displayMode: .checkBoxView,
                             treatmentEntity: treatment,
                             isSelected: .constant(viewModel.isSelected(treatment)),
                             action: { viewModel.addTreatment(treatment) }
                         )
                     }
                 }
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Treatment/View/TreatmentFilterView.swift`
around lines 29 - 48, The list currently checks filteredTreatments.isEmpty which
makes an empty search show the full list; change the branching to check the
search state instead (use viewModel.searchText.isEmpty or the searchText
binding) so that when searchText is empty you iterate viewModel.treatments,
otherwise you always iterate viewModel.filteredTreatments (even if empty).
Update the conditional around the ForEach (the block that builds
TreatmentRowView using viewModel.isSelected and viewModel.addTreatment) to use
searchText.isEmpty as the condition and keep the same TreatmentRowView
parameters.

}
}

//MARK: - TreatmentSelectedCategoryView
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

MARK 주석 포맷을 SwiftLint 규칙에 맞게 수정하세요 (Line 76).

//MARK:// MARK: - ... 형태가 규칙입니다.

🧹 제안 수정
-//MARK: - TreatmentSelectedCategoryView
+// MARK: - TreatmentSelectedCategoryView
📝 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
//MARK: - TreatmentSelectedCategoryView
// MARK: - TreatmentSelectedCategoryView
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 76-76: MARK comment should be in valid format. e.g. '// MARK: ...' or '// MARK: - ...'

(mark)

🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Treatment/View/TreatmentView.swift`
at line 76, Update the SwiftLint MARK comment formatting for the
TreatmentSelectedCategoryView section: replace the current comment token that
reads "//MARK: - TreatmentSelectedCategoryView" with the correct spaced format
"// MARK: - TreatmentSelectedCategoryView" so it conforms to the SwiftLint rule;
locate the comment near the TreatmentSelectedCategoryView section in
TreatmentView.swift and adjust the spacing around the MARK token.

Comment on lines +48 to +55
func isDateTextFieldNotEmpty() -> Bool {
guard !year.isEmpty, !month.isEmpty, !day.isEmpty else {
return false
}
guard let yearInt = Int(year), yearInt >= 2020,
let monthInt = Int(month), monthInt >= 1, monthInt <= 12,
let dayInt = Int(day), dayInt >= 1, dayInt <= 31 else {
return false
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

캘린더 유효성 검증 부족으로 잘못된 날짜가 통과됩니다 (Line 48-55).

현재 로직은 월/일 조합 유효성을 확인하지 않아 2/31 같은 날짜도 통과합니다. 실제 날짜 유효성 검사를 추가하세요.

🛠️ 제안 수정
 func isDateTextFieldNotEmpty() -> Bool {
-    guard !year.isEmpty, !month.isEmpty, !day.isEmpty else {
-        return false
-    }
-    guard let yearInt = Int(year), yearInt >= 2020,
-          let monthInt = Int(month), monthInt >= 1, monthInt <= 12,
-          let dayInt = Int(day), dayInt >= 1, dayInt <= 31 else {
-        return false
-    }
-
-    return true
+    guard let yearInt = Int(year), yearInt >= 2020,
+          let monthInt = Int(month),
+          let dayInt = Int(day) else {
+        return false
+    }
+    var comps = DateComponents()
+    comps.year = yearInt
+    comps.month = monthInt
+    comps.day = dayInt
+    return Calendar.current.date(from: comps) != nil
 }
📝 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
func isDateTextFieldNotEmpty() -> Bool {
guard !year.isEmpty, !month.isEmpty, !day.isEmpty else {
return false
}
guard let yearInt = Int(year), yearInt >= 2020,
let monthInt = Int(month), monthInt >= 1, monthInt <= 12,
let dayInt = Int(day), dayInt >= 1, dayInt <= 31 else {
return false
func isDateTextFieldNotEmpty() -> Bool {
guard let yearInt = Int(year), yearInt >= 2020,
let monthInt = Int(month),
let dayInt = Int(day) else {
return false
}
var comps = DateComponents()
comps.year = yearInt
comps.month = monthInt
comps.day = dayInt
return Calendar.current.date(from: comps) != nil
}
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Treatment/ViewModel/TreatmentViewModel.swift`
around lines 48 - 55, The isDateTextFieldNotEmpty() method currently only checks
numeric ranges for year/month/day allowing invalid combinations (e.g., 2/31);
update this function to parse year/month/day into Ints, check the year range
(>=2020) as before, then build DateComponents(year:yearInt, month:monthInt,
day:dayInt) and use Calendar.current.date(from:) to ensure the combination
yields a real Date (non-nil) before returning true; keep the existing guards for
empty strings and numeric parsing but replace the separate day-range guard with
this DateComponents/Calendar validation to reject impossible dates.

@wotjs020708 wotjs020708 merged commit 7af37f7 into develop Jan 18, 2026
1 check passed
@wotjs020708 wotjs020708 deleted the style/#76-TreatmentView branch January 18, 2026 14:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Style] 선택한 시술 화면 구현

4 participants